You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by gv...@apache.org on 2020/04/30 14:26:46 UTC

[ignite] branch ignite-12248 updated: IGNITE-12972: Calcite integration. Serialization refactoring. This closes #7764

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 6e306e4  IGNITE-12972: Calcite integration. Serialization refactoring. This closes #7764
6e306e4 is described below

commit 6e306e416e715de454240e93e275afa083ae5bba
Author: Igor Seliverstov <gv...@gmail.com>
AuthorDate: Thu Apr 30 17:26:26 2020 +0300

    IGNITE-12972: Calcite integration. Serialization refactoring. This closes #7764
---
 .../query/calcite/exec/ExchangeServiceImpl.java    |   4 +-
 .../query/calcite/exec/ExecutionContext.java       |  47 +-
 .../query/calcite/exec/ExecutionServiceImpl.java   |  77 +-
 .../query/calcite/exec/LogicalRelImplementor.java  |  46 +-
 .../query/calcite/exec/PhysicalRelImplementor.java | 251 ------
 .../processors/query/calcite/exec/exp/Call.java    | 118 ---
 .../query/calcite/exec/exp/CallOperation.java      |  27 -
 .../query/calcite/exec/exp/DynamicParam.java       |  63 --
 .../query/calcite/exec/exp/ExpToRexTranslator.java | 116 ---
 .../query/calcite/exec/exp/Expression.java         |  58 --
 .../query/calcite/exec/exp/ExpressionFactory.java  | 224 +-----
 .../query/calcite/exec/exp/ExpressionVisitor.java  |  51 --
 .../query/calcite/exec/exp/InputRef.java           |  63 --
 .../processors/query/calcite/exec/exp/Literal.java |  63 --
 .../query/calcite/exec/exp/RexToExpTranslator.java | 151 ----
 .../query/calcite/exec/exp/agg/AggCallExp.java     | 178 -----
 .../calcite/exec/exp/agg/WrappersFactoryImpl.java  |   4 +-
 .../query/calcite/exec/exp/type/BasicType.java     | 152 ----
 .../calcite/exec/exp/type/CharacterDataType.java   |  32 -
 .../query/calcite/exec/exp/type/CharacterType.java | 142 ----
 .../query/calcite/exec/exp/type/DataType.java      |  89 ---
 .../query/calcite/exec/exp/type/JavaType.java      |  84 --
 .../query/calcite/exec/exp/type/StructType.java    | 100 ---
 .../query/calcite/externalize/RelJson.java         | 871 +++++++++++++++++++++
 .../query/calcite/externalize/RelJsonReader.java   | 310 ++++++++
 .../query/calcite/externalize/RelJsonWriter.java   | 161 ++++
 .../query/calcite/message/MessageType.java         |  10 +-
 .../query/calcite/message/QueryStartRequest.java   |  80 +-
 .../query/calcite/metadata/FragmentInfo.java       | 141 ----
 ...FragmentInfo.java => IgniteMdNodesMapping.java} |  96 ++-
 .../query/calcite/metadata/IgniteMetadata.java     |  14 +-
 .../query/calcite/metadata/NodesMapping.java       | 137 +++-
 .../query/calcite/metadata/RelMetadataQueryEx.java |  14 +-
 .../calcite/prepare/AbstractMultiStepPlan.java     |  76 +-
 .../processors/query/calcite/prepare/Cloner.java   |  67 +-
 .../processors/query/calcite/prepare/Fragment.java | 102 +--
 .../FragmentDescription.java}                      | 196 ++---
 .../query/calcite/prepare/FragmentSplitter.java    | 336 +++-----
 .../query/calcite/prepare/MultiStepDmlPlan.java    |   2 +-
 .../query/calcite/prepare/MultiStepPlan.java       |  15 +
 .../query/calcite/prepare/MultiStepQueryPlan.java  |   2 +-
 .../query/calcite/prepare/RelTarget.java           |  42 -
 .../query/calcite/prepare/RelTargetAware.java      |  28 -
 .../query/calcite/prepare/RelTargetImpl.java       |  50 --
 .../processors/query/calcite/prepare/Splitter.java | 112 ++-
 .../query/calcite/rel/IgniteAggregate.java         |   7 +
 .../query/calcite/rel/IgniteExchange.java          |  12 +
 .../processors/query/calcite/rel/IgniteFilter.java |   6 +
 .../processors/query/calcite/rel/IgniteJoin.java   |  22 +
 .../query/calcite/rel/IgniteMapAggregate.java      |   6 +
 .../query/calcite/rel/IgniteProject.java           |   6 +
 .../query/calcite/rel/IgniteReceiver.java          |  61 +-
 .../query/calcite/rel/IgniteReduceAggregate.java   |  15 +
 .../processors/query/calcite/rel/IgniteSender.java |  93 ++-
 .../query/calcite/rel/IgniteTableModify.java       |  33 +-
 .../query/calcite/rel/IgniteTableScan.java         |   7 +
 .../query/calcite/rel/IgniteTrimExchange.java      |  44 +-
 .../query/calcite/rel/IgniteUnionAll.java          |   9 +
 .../processors/query/calcite/rel/IgniteValues.java |   7 +
 .../calcite/rule/TableModifyConverterRule.java     |   2 +-
 .../calcite/serialize/AggregatePhysicalRel.java    | 108 ---
 .../query/calcite/serialize/FilterPhysicalRel.java |  84 --
 .../query/calcite/serialize/JoinPhysicalRel.java   |  94 ---
 .../query/calcite/serialize/PhysicalRel.java       |  33 -
 .../calcite/serialize/PhysicalRelVisitor.java      |  86 --
 .../calcite/serialize/ProjectPhysicalRel.java      |  99 ---
 .../calcite/serialize/ReceiverPhysicalRel.java     | 154 ----
 .../calcite/serialize/RelToPhysicalConverter.java  | 203 -----
 .../query/calcite/serialize/SenderPhysicalRel.java |  98 ---
 .../calcite/serialize/TableModifyPhysicalRel.java  | 117 ---
 .../calcite/serialize/TableScanPhysicalRel.java    |  73 --
 .../calcite/serialize/TrimExchangePhysicalRel.java |  94 ---
 .../calcite/serialize/UnionAllPhysicalRel.java     |  67 --
 .../query/calcite/serialize/ValuesPhysicalRel.java |  79 --
 .../query/calcite/trait/DistributionFunction.java  |   5 +
 .../query/calcite/trait/DistributionTraitDef.java  |   2 +-
 .../processors/query/calcite/util/Commons.java     | 182 ++++-
 .../query/calcite/util/IgniteMethod.java           |   6 +-
 .../processors/query/calcite/PlannerTest.java      | 169 +++-
 .../calcite/exec/ExpressionEvaluationTest.java     |  73 --
 .../calcite/exec/rel/AbstractExecutionTest.java    |   4 +-
 .../query/calcite/jdbc/JdbcQueryTest.java          |   2 +
 82 files changed, 2699 insertions(+), 4465 deletions(-)

diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExchangeServiceImpl.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExchangeServiceImpl.java
index bac38b0..40e2ebb 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExchangeServiceImpl.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExchangeServiceImpl.java
@@ -31,6 +31,7 @@ import org.apache.ignite.internal.processors.query.calcite.message.MessageServic
 import org.apache.ignite.internal.processors.query.calcite.message.MessageType;
 import org.apache.ignite.internal.processors.query.calcite.message.QueryBatchAcknowledgeMessage;
 import org.apache.ignite.internal.processors.query.calcite.message.QueryBatchMessage;
+import org.apache.ignite.internal.processors.query.calcite.prepare.FragmentDescription;
 import org.apache.ignite.internal.processors.query.calcite.prepare.PlanningContext;
 import org.apache.ignite.internal.processors.query.calcite.util.AbstractService;
 import org.apache.ignite.internal.processors.query.calcite.util.Commons;
@@ -188,6 +189,7 @@ public class ExchangeServiceImpl extends AbstractService implements ExchangeServ
      */
     private ExecutionContext baseInboxContext(UUID queryId, long fragmentId) {
         PlanningContext ctx = PlanningContext.builder().logger(log).build();
-        return new ExecutionContext(taskExecutor(), ctx, queryId, fragmentId, null, ImmutableMap.of());
+        FragmentDescription fragmentDescription = new FragmentDescription(fragmentId, null, -1, null, null);
+        return new ExecutionContext(taskExecutor(), ctx, queryId, fragmentDescription, ImmutableMap.of());
     }
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionContext.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionContext.java
index 6381d7f..9733bd3 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionContext.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionContext.java
@@ -17,12 +17,15 @@
 
 package org.apache.ignite.internal.processors.query.calcite.exec;
 
+import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 import org.apache.calcite.DataContext;
 import org.apache.calcite.linq4j.QueryProvider;
 import org.apache.calcite.schema.SchemaPlus;
 import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
+import org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping;
+import org.apache.ignite.internal.processors.query.calcite.prepare.FragmentDescription;
 import org.apache.ignite.internal.processors.query.calcite.prepare.PlanningContext;
 import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
 
@@ -34,13 +37,10 @@ public class ExecutionContext implements DataContext {
     private final UUID queryId;
 
     /** */
-    private final long fragmentId;
-
-    /** */
     private final PlanningContext ctx;
 
     /** */
-    private final int[] parts;
+    private final FragmentDescription fragmentDescription;
 
     /** */
     private final Map<String, Object> params;
@@ -54,17 +54,16 @@ public class ExecutionContext implements DataContext {
     /**
      * @param ctx Parent context.
      * @param queryId Query ID.
-     * @param fragmentId Fragment ID.
-     * @param parts Partitions.
+     * @param fragmentDescription Partitions information.
      * @param params Parameters.
      */
-    public ExecutionContext(QueryTaskExecutor executor, PlanningContext ctx, UUID queryId, long fragmentId, int[] parts, Map<String, Object> params) {
+    public ExecutionContext(QueryTaskExecutor executor, PlanningContext ctx, UUID queryId,
+        FragmentDescription fragmentDescription, Map<String, Object> params) {
         this.executor = executor;
+        this.ctx = ctx;
         this.queryId = queryId;
-        this.fragmentId = fragmentId;
-        this.parts = parts;
+        this.fragmentDescription = fragmentDescription;
         this.params = params;
-        this.ctx = ctx;
     }
 
     /**
@@ -85,14 +84,36 @@ public class ExecutionContext implements DataContext {
      * @return Fragment ID.
      */
     public long fragmentId() {
-        return fragmentId;
+        return fragmentDescription.fragmentId();
     }
 
     /**
      * @return Interested partitions.
      */
     public int[] partitions() {
-        return parts;
+        return fragmentDescription.partitions();
+    }
+
+    /** */
+    public int partitionsCount () {
+        return fragmentDescription.partitionsCount();
+    }
+
+    /**
+     * @return Target mapping.
+     */
+    public NodesMapping targetMapping() {
+        return fragmentDescription.targetMapping();
+    }
+
+    /** */
+    public List<UUID> remoteSources(long exchangeId) {
+        return fragmentDescription.remoteSources().get(exchangeId);
+    }
+
+    /** */
+    public FragmentDescription fragmentDescription() {
+        return fragmentDescription;
     }
 
     /**
@@ -157,6 +178,6 @@ public class ExecutionContext implements DataContext {
      * @param task Query task.
      */
     public void execute(Runnable task) {
-        executor.execute(queryId, fragmentId, task);
+        executor.execute(queryId, fragmentId(), task);
     }
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionServiceImpl.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionServiceImpl.java
index 00c39c4..6d14305 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionServiceImpl.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionServiceImpl.java
@@ -74,6 +74,7 @@ import org.apache.ignite.internal.processors.query.calcite.metadata.PartitionSer
 import org.apache.ignite.internal.processors.query.calcite.prepare.CacheKey;
 import org.apache.ignite.internal.processors.query.calcite.prepare.CalciteQueryFieldMetadata;
 import org.apache.ignite.internal.processors.query.calcite.prepare.Fragment;
+import org.apache.ignite.internal.processors.query.calcite.prepare.FragmentDescription;
 import org.apache.ignite.internal.processors.query.calcite.prepare.IgnitePlanner;
 import org.apache.ignite.internal.processors.query.calcite.prepare.MultiStepDmlPlan;
 import org.apache.ignite.internal.processors.query.calcite.prepare.MultiStepPlan;
@@ -87,8 +88,6 @@ import org.apache.ignite.internal.processors.query.calcite.prepare.ValidationRes
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
 import org.apache.ignite.internal.processors.query.calcite.schema.SchemaHolder;
-import org.apache.ignite.internal.processors.query.calcite.serialize.RelToPhysicalConverter;
-import org.apache.ignite.internal.processors.query.calcite.serialize.SenderPhysicalRel;
 import org.apache.ignite.internal.processors.query.calcite.trait.DistributionTraitDef;
 import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
 import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
@@ -591,13 +590,11 @@ public class ExecutionServiceImpl extends AbstractService implements ExecutionSe
         List<Fragment> fragments = plan.fragments();
 
         // Local execution
-        Fragment local = F.first(fragments);
+        Fragment fragment = F.first(fragments);
+        NodesMapping mapping = plan.fragmentMapping(fragment);
 
         if (U.assertionsEnabled()) {
-            assert local != null;
-
-            NodesMapping mapping = local.mapping();
-
+            assert fragment != null;
             assert mapping != null;
 
             List<UUID> nodes = mapping.nodes();
@@ -605,45 +602,61 @@ public class ExecutionServiceImpl extends AbstractService implements ExecutionSe
             assert nodes != null && nodes.size() == 1 && F.first(nodes).equals(pctx.localNodeId());
         }
 
-        ExecutionContext ectx = new ExecutionContext(taskExecutor(), pctx, queryId, local.fragmentId(),
-            local.mapping().partitions(pctx.localNodeId()), Commons.parametersMap(pctx.parameters()));
+        FragmentDescription fragmentDescription = new FragmentDescription(
+            fragment.fragmentId(),
+            mapping.partitions(pctx.localNodeId()),
+            mapping.assignments().size(),
+            plan.targetMapping(fragment),
+            plan.remoteSources(fragment)
+        );
 
-        Node<Object[]> node = new LogicalRelImplementor(ectx, partitionService(), mailboxRegistry(), exchangeService(), failureProcessor()).go(local.root());
+        ExecutionContext ectx = new ExecutionContext(
+            taskExecutor(),
+            pctx,
+            queryId,
+            fragmentDescription,
+            Commons.parametersMap(pctx.parameters()));
 
-        assert !(node instanceof SenderPhysicalRel);
+        Node<Object[]> node = new LogicalRelImplementor(ectx, partitionService(), mailboxRegistry(), exchangeService(), failureProcessor()).go(fragment.root());
 
-        QueryInfo info = new QueryInfo(ectx, fragments, node);
+        QueryInfo info = new QueryInfo(ectx, plan, node);
 
         // register query
         register(info);
 
         // start remote execution
         if (fragments.size() > 1) {
-            RelToPhysicalConverter converter = new RelToPhysicalConverter(ectx.getTypeFactory());
-
             for (int i = 1; i < fragments.size(); i++) {
-                Fragment fragment = fragments.get(i);
+                Fragment fragment0 = fragments.get(i);
+                NodesMapping mapping0 = plan.fragmentMapping(fragment0);
 
                 boolean error = false;
 
-                for (UUID nodeId : fragment.mapping().nodes()) {
+                for (UUID nodeId : mapping0.nodes()) {
                     if (error)
-                        info.onResponse(nodeId, fragment.fragmentId(), new QueryCancelledException());
+                        info.onResponse(nodeId, fragment0.fragmentId(), new QueryCancelledException());
                     else {
                         try {
+                            FragmentDescription fragmentDescription0 = new FragmentDescription(
+                                fragment0.fragmentId(),
+                                mapping0.partitions(nodeId),
+                                mapping0.assignments().size(),
+                                plan.targetMapping(fragment0),
+                                plan.remoteSources(fragment0)
+                            );
+
                             QueryStartRequest req = new QueryStartRequest(
                                 queryId,
-                                fragment.fragmentId(),
                                 pctx.schemaName(),
-                                converter.go(fragment.root()),
+                                Commons.toJson(fragment0.root()),
                                 pctx.topologyVersion(),
-                                fragment.mapping().partitions(nodeId),
+                                fragmentDescription0,
                                 pctx.parameters());
 
                             messageService().send(nodeId, req);
                         }
                         catch (Exception e) {
-                            info.onResponse(nodeId, fragment.fragmentId(), e);
+                            info.onResponse(nodeId, fragment0.fragmentId(), e);
                             error = true;
                         }
                     }
@@ -725,19 +738,18 @@ public class ExecutionServiceImpl extends AbstractService implements ExecutionSe
                 taskExecutor(),
                 ctx,
                 msg.queryId(),
-                msg.fragmentId(),
-                msg.partitions(),
+                msg.fragmentDescription(),
                 Commons.parametersMap(msg.parameters())
             );
 
-            Node<Object[]> node = new PhysicalRelImplementor(execCtx, partitionService(),
-                mailboxRegistry(), exchangeService(), failureProcessor()).go(msg.root());
+            Node<Object[]> node = new LogicalRelImplementor(execCtx, partitionService(),
+                mailboxRegistry(), exchangeService(), failureProcessor()).go(Commons.fromJson(ctx.createCluster(), msg.root()));
 
             assert node instanceof Outbox : node;
 
             node.context().execute(((Outbox<Object[]>) node)::init);
 
-            messageService().send(nodeId, new QueryStartResponse(msg.queryId(), msg.fragmentId()));
+            messageService().send(nodeId, new QueryStartResponse(msg.queryId(), msg.fragmentDescription().fragmentId()));
         }
         catch (Throwable ex) { // TODO don't catch errors!
             cancelQuery(msg.queryId());
@@ -748,7 +760,7 @@ public class ExecutionServiceImpl extends AbstractService implements ExecutionSe
             U.warn(log, "Failed to start query. [nodeId=" + nodeId + ']', ex);
 
             try {
-                messageService().send(nodeId, new QueryStartResponse(msg.queryId(), msg.fragmentId(), ex));
+                messageService().send(nodeId, new QueryStartResponse(msg.queryId(), msg.fragmentDescription().fragmentId(), ex));
             }
             catch (IgniteCheckedException e) {
                 e.addSuppressed(ex);
@@ -874,7 +886,7 @@ public class ExecutionServiceImpl extends AbstractService implements ExecutionSe
         private Throwable error;
 
         /** */
-        private QueryInfo(ExecutionContext ctx, List<Fragment> fragments, Node<Object[]> root) {
+        private QueryInfo(ExecutionContext ctx, MultiStepPlan plan, Node<Object[]> root) {
             this.ctx = ctx;
 
             RootNode rootNode = new RootNode(ctx, ExecutionServiceImpl.this::onCursorClose);
@@ -885,15 +897,14 @@ public class ExecutionServiceImpl extends AbstractService implements ExecutionSe
             remotes = new HashSet<>();
             waiting = new HashSet<>();
 
-            for (int i = 1; i < fragments.size(); i++) {
-                Fragment fragment = fragments.get(i);
-                long id = fragment.fragmentId();
-                List<UUID> nodes = fragment.mapping().nodes();
+            for (int i = 1; i < plan.fragments().size(); i++) {
+                Fragment fragment = plan.fragments().get(i);
+                List<UUID> nodes = plan.fragmentMapping(fragment).nodes();
 
                 remotes.addAll(nodes);
 
                 for (UUID node : nodes)
-                    waiting.add(new RemoteFragmentKey(node, id));
+                    waiting.add(new RemoteFragmentKey(node, fragment.fragmentId()));
             }
 
             state = QueryState.RUNNING;
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 8845b16..d32644c 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
@@ -26,8 +26,8 @@ import org.apache.calcite.rel.RelDistribution;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.schema.ScannableTable;
-import org.apache.calcite.sql.SqlOperatorTable;
 import org.apache.calcite.sql.validate.SqlConformance;
+import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.ImmutableIntList;
 import org.apache.ignite.internal.processors.failure.FailureProcessor;
 import org.apache.ignite.internal.processors.query.calcite.exec.exp.ExpressionFactory;
@@ -43,8 +43,6 @@ import org.apache.ignite.internal.processors.query.calcite.exec.rel.ProjectNode;
 import org.apache.ignite.internal.processors.query.calcite.exec.rel.ScanNode;
 import org.apache.ignite.internal.processors.query.calcite.exec.rel.UnionAllNode;
 import org.apache.ignite.internal.processors.query.calcite.metadata.PartitionService;
-import org.apache.ignite.internal.processors.query.calcite.prepare.Fragment;
-import org.apache.ignite.internal.processors.query.calcite.prepare.RelTarget;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteAggregate;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
@@ -103,19 +101,17 @@ public class LogicalRelImplementor implements IgniteRelVisitor<Node<Object[]>> {
 
         final IgniteTypeFactory typeFactory = ctx.getTypeFactory();
         final SqlConformance conformance = ctx.parent().conformance();
-        final SqlOperatorTable opTable = ctx.parent().opTable();
 
-        expressionFactory = new ExpressionFactory(typeFactory, conformance, opTable);
+        expressionFactory = new ExpressionFactory(typeFactory, conformance);
     }
 
     /** {@inheritDoc} */
     @Override public Node<Object[]> visit(IgniteSender rel) {
-        RelTarget target = rel.target();
         IgniteDistribution distribution = rel.distribution();
-        Destination destination = distribution.function().destination(partitionService, target.mapping(), distribution.getKeys());
+        Destination destination = distribution.function().destination(partitionService, ctx.targetMapping(), distribution.getKeys());
 
         // Outbox fragment ID is used as exchange ID as well.
-        Outbox<Object[]> outbox = new Outbox<>(ctx, exchangeService, mailboxRegistry, ctx.fragmentId(), target.fragmentId(), destination);
+        Outbox<Object[]> outbox = new Outbox<>(ctx, exchangeService, mailboxRegistry, rel.exchangeId(), rel.targetFragmentId(), destination);
         outbox.register(visit(rel.getInput()));
 
         mailboxRegistry.register(outbox);
@@ -134,13 +130,7 @@ public class LogicalRelImplementor implements IgniteRelVisitor<Node<Object[]>> {
 
     /** {@inheritDoc} */
     @Override public Node<Object[]> visit(IgniteTrimExchange rel) {
-        RelTarget target = rel.target();
-
-        assert target != null && target.mapping() != null && !F.isEmpty(target.mapping().assignments());
-
-        Predicate<Object[]> predicate = partitionFilter(rel.distribution(), target.mapping().assignments().size());
-
-        FilterNode node = new FilterNode(ctx, predicate);
+        FilterNode node = new FilterNode(ctx, partitionFilter(rel.distribution()));
         node.register(visit(rel.getInput()));
 
         return node;
@@ -172,7 +162,7 @@ public class LogicalRelImplementor implements IgniteRelVisitor<Node<Object[]>> {
 
     /** {@inheritDoc} */
     @Override public Node<Object[]> visit(IgniteValues rel) {
-        return new ScanNode(ctx, expressionFactory.valuesRex(ctx, Commons.flat(Commons.cast(rel.getTuples())), rel.getRowType().getFieldCount()));
+        return new ScanNode(ctx, expressionFactory.values(ctx, Commons.flat(Commons.cast(rel.getTuples())), rel.getRowType().getFieldCount()));
     }
 
     /** {@inheritDoc} */
@@ -201,16 +191,14 @@ public class LogicalRelImplementor implements IgniteRelVisitor<Node<Object[]>> {
 
     /** {@inheritDoc} */
     @Override public Node<Object[]> visit(IgniteReceiver rel) {
-        Fragment source = rel.source();
-
-        // Corresponding outbox fragment ID is used as exchange ID as well.
-        Inbox<Object[]> inbox = (Inbox<Object[]>) mailboxRegistry.register(new Inbox<>(ctx, exchangeService, mailboxRegistry, source.fragmentId(), source.fragmentId()));
+        Inbox<?> inbox = mailboxRegistry.register(
+            new Inbox<>(ctx, exchangeService, mailboxRegistry, rel.exchangeId(), rel.sourceFragmentId()));
 
         // here may be an already created (to consume rows from remote nodes) inbox
         // without proper context, we need to init it with a right one.
-        inbox.init(ctx, source.mapping().nodes(), expressionFactory.comparator(ctx, rel.collations(), rel.getRowType()));
+        inbox.init(ctx, ctx.remoteSources(rel.exchangeId()), expressionFactory.comparator(ctx, rel.collations(), rel.getRowType()));
 
-        return inbox;
+        return (Node<Object[]>)inbox;
     }
 
     /** {@inheritDoc} */
@@ -271,21 +259,15 @@ public class LogicalRelImplementor implements IgniteRelVisitor<Node<Object[]>> {
     }
 
     /** */
-    private Predicate<Object[]> partitionFilter(IgniteDistribution distr, int partitions) {
+    private Predicate<Object[]> partitionFilter(IgniteDistribution distr) {
         assert distr.getType() == RelDistribution.Type.HASH_DISTRIBUTED;
-        assert !F.isEmpty(ctx.partitions());
-
-        boolean[] filter = new boolean[partitions];
-
-        for (int part : ctx.partitions())
-            filter[part] = true;
 
+        ImmutableBitSet filter = ImmutableBitSet.of(ctx.partitions());
         DistributionFunction function = distr.function();
         ImmutableIntList keys = distr.getKeys();
+        ToIntFunction<Object> partFunction = function.partitionFunction(partitionService, ctx.partitionsCount(), keys);
 
-        ToIntFunction<Object> partFunction = function.partitionFunction(partitionService, partitions, keys);
-
-        return o -> filter[partFunction.applyAsInt(o)];
+        return o -> filter.get(partFunction.applyAsInt(o));
     }
 
     /** */
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/PhysicalRelImplementor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/PhysicalRelImplementor.java
deleted file mode 100644
index e090b38..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/PhysicalRelImplementor.java
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.exec;
-
-import java.util.List;
-import java.util.function.Predicate;
-import java.util.function.Supplier;
-import java.util.function.ToIntFunction;
-import org.apache.calcite.rel.RelDistribution;
-import org.apache.calcite.schema.ScannableTable;
-import org.apache.calcite.sql.SqlOperatorTable;
-import org.apache.calcite.sql.validate.SqlConformance;
-import org.apache.calcite.util.ImmutableIntList;
-import org.apache.ignite.internal.processors.failure.FailureProcessor;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.ExpressionFactory;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AccumulatorWrapper;
-import org.apache.ignite.internal.processors.query.calcite.exec.rel.AggregateNode;
-import org.apache.ignite.internal.processors.query.calcite.exec.rel.FilterNode;
-import org.apache.ignite.internal.processors.query.calcite.exec.rel.Inbox;
-import org.apache.ignite.internal.processors.query.calcite.exec.rel.JoinNode;
-import org.apache.ignite.internal.processors.query.calcite.exec.rel.ModifyNode;
-import org.apache.ignite.internal.processors.query.calcite.exec.rel.Node;
-import org.apache.ignite.internal.processors.query.calcite.exec.rel.Outbox;
-import org.apache.ignite.internal.processors.query.calcite.exec.rel.ProjectNode;
-import org.apache.ignite.internal.processors.query.calcite.exec.rel.ScanNode;
-import org.apache.ignite.internal.processors.query.calcite.exec.rel.UnionAllNode;
-import org.apache.ignite.internal.processors.query.calcite.metadata.PartitionService;
-import org.apache.ignite.internal.processors.query.calcite.schema.TableDescriptor;
-import org.apache.ignite.internal.processors.query.calcite.serialize.AggregatePhysicalRel;
-import org.apache.ignite.internal.processors.query.calcite.serialize.FilterPhysicalRel;
-import org.apache.ignite.internal.processors.query.calcite.serialize.JoinPhysicalRel;
-import org.apache.ignite.internal.processors.query.calcite.serialize.PhysicalRel;
-import org.apache.ignite.internal.processors.query.calcite.serialize.PhysicalRelVisitor;
-import org.apache.ignite.internal.processors.query.calcite.serialize.ProjectPhysicalRel;
-import org.apache.ignite.internal.processors.query.calcite.serialize.ReceiverPhysicalRel;
-import org.apache.ignite.internal.processors.query.calcite.serialize.SenderPhysicalRel;
-import org.apache.ignite.internal.processors.query.calcite.serialize.TableModifyPhysicalRel;
-import org.apache.ignite.internal.processors.query.calcite.serialize.TableScanPhysicalRel;
-import org.apache.ignite.internal.processors.query.calcite.serialize.TrimExchangePhysicalRel;
-import org.apache.ignite.internal.processors.query.calcite.serialize.UnionAllPhysicalRel;
-import org.apache.ignite.internal.processors.query.calcite.serialize.ValuesPhysicalRel;
-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.type.IgniteTypeFactory;
-import org.apache.ignite.internal.processors.query.calcite.util.Commons;
-import org.apache.ignite.internal.util.typedef.F;
-
-/**
- * Converts RelGraph to logical or physical tree.
- */
-public class PhysicalRelImplementor implements PhysicalRelVisitor<Node<Object[]>> {
-    /** */
-    private final ExecutionContext ctx;
-
-    /** */
-    private final PartitionService partitionService;
-
-    /** */
-    private final ExchangeService exchangeService;
-
-    /** */
-    private final MailboxRegistry mailboxRegistry;
-
-    /** */
-    private final ExpressionFactory expressionFactory;
-
-    /** */
-    public PhysicalRelImplementor(ExecutionContext ctx, PartitionService partitionService, MailboxRegistry mailboxRegistry, ExchangeService exchangeService, FailureProcessor failure) {
-        this.ctx = ctx;
-        this.partitionService = partitionService;
-        this.mailboxRegistry = mailboxRegistry;
-        this.exchangeService = exchangeService;
-
-        IgniteTypeFactory typeFactory = ctx.getTypeFactory();
-        SqlOperatorTable opTable = ctx.parent().opTable();
-        SqlConformance conformance = ctx.parent().conformance();
-
-        expressionFactory = new ExpressionFactory(typeFactory, conformance, opTable);
-    }
-
-    /** {@inheritDoc} */
-    @Override public Node<Object[]> visit(TableScanPhysicalRel rel) {
-        Iterable<Object[]> source = ctx
-            .parent()
-            .catalogReader()
-            .getTable(rel.tableName())
-            .unwrap(ScannableTable.class)
-            .scan(ctx);
-
-        return new ScanNode(ctx, source);
-    }
-
-    /** {@inheritDoc} */
-    @Override public Node<Object[]> visit(FilterPhysicalRel rel) {
-        FilterNode node = new FilterNode(ctx, expressionFactory.predicate(ctx, rel.condition(), rel.rowType()));
-        node.register(visit(rel.input()));
-
-        return node;
-    }
-
-    /** {@inheritDoc} */
-    @Override public Node<Object[]> visit(TrimExchangePhysicalRel rel) {
-        Predicate<Object[]> predicate = partitionFilter(rel.function(), rel.distributionKeys(), rel.partitions());
-
-        FilterNode node = new FilterNode(ctx, predicate);
-        node.register(visit(rel.input()));
-
-        return node;
-    }
-
-    /** {@inheritDoc} */
-    @Override public Node<Object[]> visit(ProjectPhysicalRel rel) {
-        ProjectNode node = new ProjectNode(ctx, expressionFactory.project(ctx, rel.projects(), rel.rowType()));
-        node.register(visit(rel.input()));
-
-        return node;
-    }
-
-    /** {@inheritDoc} */
-    @Override public Node<Object[]> visit(JoinPhysicalRel rel) {
-        JoinNode node = new JoinNode(ctx, expressionFactory.predicate(ctx, rel.condition(), rel.rowType()));
-        node.register(F.asList(visit(rel.left()), visit(rel.right())));
-
-        return node;
-    }
-
-    /** {@inheritDoc} */
-    @Override public Node<Object[]> visit(SenderPhysicalRel rel) {
-        Destination destination = rel.distributionFunction()
-            .destination(partitionService, rel.mapping(), rel.distributionKeys());
-
-        Outbox<Object[]> outbox = new Outbox<>(ctx, exchangeService, mailboxRegistry,
-            ctx.fragmentId(), rel.targetFragmentId(), destination);
-        outbox.register(visit(rel.input()));
-
-        mailboxRegistry.register(outbox);
-
-        return outbox;
-    }
-
-    /** {@inheritDoc} */
-    @Override public Node<Object[]> visit(ReceiverPhysicalRel rel) {
-        Inbox<?> inbox = mailboxRegistry.register(new Inbox<>(ctx, exchangeService, mailboxRegistry, rel.sourceFragmentId(), rel.sourceFragmentId()));
-
-        inbox.init(ctx, rel.sources(), expressionFactory.comparator(ctx, rel.collations(), rel.rowType()));
-
-        return (Node<Object[]>) inbox;
-    }
-
-    /** {@inheritDoc} */
-    @Override public Node<Object[]> visit(ValuesPhysicalRel rel) {
-        Iterable<Object[]> source = expressionFactory.valuesExp(ctx, rel.values(), rel.rowLength());
-
-        return new ScanNode(ctx, source);
-    }
-
-    /** {@inheritDoc} */
-    @Override public Node<Object[]> visit(TableModifyPhysicalRel rel) {
-        switch (rel.operation()){
-            case INSERT:
-            case UPDATE:
-            case DELETE:
-                TableDescriptor desc = ctx.parent().catalogReader().getTable(rel.tableName()).unwrap(TableDescriptor.class);
-                ModifyNode node = new ModifyNode(ctx, desc, rel.operation(), rel.updateColumnList());
-                node.register(visit(rel.input()));
-
-                return node;
-            case MERGE:
-                throw new UnsupportedOperationException();
-            default:
-                throw new AssertionError();
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override public Node<Object[]> visit(UnionAllPhysicalRel rel) {
-        UnionAllNode<Object[]> node = new UnionAllNode<>(ctx);
-        node.register(Commons.transform(rel.inputs(), this::visit));
-        return node;
-    }
-
-    /** {@inheritDoc} */
-    @Override public Node<Object[]> visit(AggregatePhysicalRel rel) {
-        AggregateNode.AggregateType type;
-
-        switch (rel.type()) {
-            case AggregatePhysicalRel.MAP:
-                type = AggregateNode.AggregateType.MAP;
-                break;
-            case AggregatePhysicalRel.REDUCE:
-                type = AggregateNode.AggregateType.REDUCE;
-                break;
-            case AggregatePhysicalRel.SINGLE:
-                type = AggregateNode.AggregateType.SINGLE;
-                break;
-            default:
-                throw new AssertionError();
-        }
-
-        RowHandler<Object[]> rowHandler = ArrayRowHandler.INSTANCE;
-
-        Supplier<List<AccumulatorWrapper>> factory = expressionFactory.wrappersFactory(ctx,
-            rowHandler, type, rel.calls(), rel.inputRowType());
-
-        AggregateNode<Object[]> node = new AggregateNode<>(ctx, type, rel.groupSets(), factory, rowHandler);
-
-        node.register(visit(rel.input()));
-
-        return node;
-    }
-
-    /** {@inheritDoc} */
-    @Override public Node<Object[]> visit(PhysicalRel rel) {
-        return rel.accept(this);
-    }
-
-    /** */
-    private Predicate<Object[]> partitionFilter(DistributionFunction function, ImmutableIntList keys, int partitions) {
-        assert function.type() == RelDistribution.Type.HASH_DISTRIBUTED;
-        assert !F.isEmpty(ctx.partitions());
-
-        boolean[] filter = new boolean[partitions];
-
-        for (int part : ctx.partitions())
-            filter[part] = true;
-
-        ToIntFunction<Object> partFunction = function.partitionFunction(partitionService, partitions, keys);
-
-        return o -> filter[partFunction.applyAsInt(o)];
-    }
-
-    /** */
-    public Node<Object[]> go(PhysicalRel root) {
-        return visit(root);
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/Call.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/Call.java
deleted file mode 100644
index 91573d3..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/Call.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.exec.exp;
-
-import java.util.List;
-import java.util.Objects;
-import org.apache.calcite.sql.SqlIdentifier;
-import org.apache.calcite.sql.SqlOperator;
-import org.apache.calcite.sql.SqlOperatorTable;
-import org.apache.calcite.sql.SqlSyntax;
-import org.apache.calcite.sql.parser.SqlParserPos;
-import org.apache.calcite.sql.validate.SqlConformance;
-import org.apache.calcite.sql.validate.SqlNameMatchers;
-import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.type.DataType;
-import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
-import org.apache.ignite.internal.util.MutableSingletonList;
-import org.apache.ignite.internal.util.typedef.F;
-
-/**
- * Describes {@link org.apache.calcite.rex.RexCall}.
- */
-public class Call implements Expression {
-    /** */
-    private DataType type;
-
-    /** */
-    private String opName;
-
-    /** */
-    private SqlSyntax syntax;
-
-    /** */
-    private List<Expression> operands;
-
-    /** */
-    private transient CallOperation opImpl;
-
-    /**
-     * @param type Result type.
-     * @param operands Operands.
-     */
-    public Call(DataType type, String opName, SqlSyntax syntax, List<Expression> operands) {
-        this.type = type;
-        this.opName = opName;
-        this.syntax = syntax;
-        this.operands = operands;
-    }
-
-    /** {@inheritDoc} */
-    @Override public DataType resultType() {
-        return type;
-    }
-
-    /**
-     * @param opTable Operators table.
-     * @return Sql logical operator.
-     */
-    public SqlOperator sqlOperator(SqlOperatorTable opTable) {
-        List<SqlOperator> bag = new MutableSingletonList<>();
-
-        opTable.lookupOperatorOverloads(
-            new SqlIdentifier(opName, SqlParserPos.ZERO), null,
-            syntax, bag, SqlNameMatchers.withCaseSensitive(true));
-
-        return Objects.requireNonNull(F.first(bag));
-    }
-
-    /** */
-    public String opName() {
-        return opName;
-    }
-
-    /** */
-    public SqlSyntax syntax() {
-        return syntax;
-    }
-
-    /**
-     * @return Operands.
-     */
-    public List<Expression> operands() {
-        return operands;
-    }
-
-    /** {@inheritDoc} */
-    @Override public <T> T accept(ExpressionVisitor<T> visitor) {
-        return visitor.visit(this);
-    }
-
-    /** {@inheritDoc} */
-    @Override public <T> T evaluate(ExecutionContext ctx, Object... args) {
-        if (opImpl == null) {
-            IgniteTypeFactory typeFactory = ctx.parent().typeFactory();
-            SqlConformance conformance = ctx.parent().conformance();
-            SqlOperatorTable opTable = ctx.parent().opTable();
-
-            opImpl = new ExpressionFactory(typeFactory, conformance, opTable).implement(this);
-        }
-
-        return (T) opImpl.apply(operands.stream().map(o -> o.evaluate(ctx, args)).toArray());
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/CallOperation.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/CallOperation.java
deleted file mode 100644
index 3150bc6..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/CallOperation.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.exec.exp;
-
-import java.util.function.Function;
-
-/**
- *
- */
-public interface CallOperation extends Function<Object[], Object> {
-    @Override Object apply(Object... args);
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/DynamicParam.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/DynamicParam.java
deleted file mode 100644
index 256609b..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/DynamicParam.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.exec.exp;
-
-import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.type.DataType;
-
-/**
- * Describes {@link org.apache.calcite.rex.RexDynamicParam}.
- */
-public class DynamicParam implements Expression {
-    /** */
-    private final DataType type;
-
-    /** */
-    private final int index;
-
-    /**
-     * @param type Data type.
-     * @param index Index.
-     */
-    public DynamicParam(DataType type, int index) {
-        this.type = type;
-        this.index = index;
-    }
-
-    /** {@inheritDoc} */
-    @Override public DataType resultType() {
-        return type;
-    }
-
-    /**
-     * @return Index.
-     */
-    public int index() {
-        return index;
-    }
-
-    /** {@inheritDoc} */
-    @Override public <T> T accept(ExpressionVisitor<T> visitor) {
-        return visitor.visit(this);
-    }
-
-    /** {@inheritDoc} */
-    @Override public <T> T evaluate(ExecutionContext ctx, Object... args) {
-        return (T) ctx.get("?" + index);
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpToRexTranslator.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpToRexTranslator.java
deleted file mode 100644
index d02c449..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpToRexTranslator.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.exec.exp;
-
-import java.util.List;
-import org.apache.calcite.rel.RelCollations;
-import org.apache.calcite.rel.core.AggregateCall;
-import org.apache.calcite.rex.RexBuilder;
-import org.apache.calcite.rex.RexNode;
-import org.apache.calcite.sql.SqlOperatorTable;
-import org.apache.calcite.util.ImmutableIntList;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AggCallExp;
-import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
-import org.apache.ignite.internal.processors.query.calcite.util.Commons;
-
-/**
- * A translator of Expressions into Rex nodes.
- */
-public class ExpToRexTranslator implements ExpressionVisitor<RexNode> {
-    /** */
-    private final IgniteTypeFactory typeFactory;
-
-    /** */
-    private final SqlOperatorTable opTable;
-
-    /** */
-    private final RexBuilder builder;
-
-    /**
-     * Creates a Translator.
-     *
-     * @param typeFactory Ignite type factory.
-     * @param opTable Operators table.
-     */
-    public ExpToRexTranslator(IgniteTypeFactory typeFactory, SqlOperatorTable opTable) {
-        this.typeFactory = typeFactory;
-        this.opTable = opTable;
-
-        builder = new RexBuilder(typeFactory);
-    }
-
-    /**
-     * Translates a list of expressions into a list of Rex nodes.
-     *
-     * @param exps List of expressions.
-     * @return List of Rex nodes.
-     */
-    public List<RexNode> translate(List<Expression> exps) {
-        return Commons.transform(exps, this::translate);
-    }
-
-    /**
-     * Translates an expression into a RexNode.
-     *
-     * @param exp Expression.
-     * @return RexNode.
-     */
-    public RexNode translate(Expression exp) {
-        return exp.accept(this);
-    }
-
-    /** */
-    public AggregateCall translate(AggCallExp desc) {
-        return AggregateCall.create(
-            desc.sqlOperator(opTable),
-            desc.distinct(),
-            desc.approximate(),
-            desc.ignoreNulls(),
-            ImmutableIntList.of(desc.args()),
-            desc.filterArg(),
-            RelCollations.EMPTY,
-            desc.resultType().logicalType(typeFactory),
-            desc.name()
-        );
-    }
-
-    /** {@inheritDoc} */
-    @Override public RexNode visit(InputRef exp) {
-        return builder.makeInputRef(exp.logicalType(typeFactory), exp.index());
-    }
-
-    /** {@inheritDoc} */
-    @Override public RexNode visit(Literal exp) {
-        return builder.makeLiteral(exp.value(), exp.logicalType(typeFactory),false);
-    }
-
-    /** {@inheritDoc} */
-    @Override public RexNode visit(DynamicParam exp) {
-        return builder.makeDynamicParam(exp.logicalType(typeFactory), exp.index());
-    }
-
-    /** {@inheritDoc} */
-    @Override public RexNode visit(Call exp) {
-        return builder.makeCall(exp.logicalType(typeFactory), exp.sqlOperator(opTable), translate(exp.operands()));
-    }
-
-    /** {@inheritDoc} */
-    @Override public RexNode visit(Expression exp) {
-        return exp.accept(this);
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/Expression.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/Expression.java
deleted file mode 100644
index ef44a80..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/Expression.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.exec.exp;
-
-import java.io.Serializable;
-import org.apache.calcite.rel.type.RelDataType;
-import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.type.DataType;
-import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
-
-/**
- * Describes {@link org.apache.calcite.rex.RexNode}
- */
-public interface Expression extends Serializable {
-    /**
-     * Accepts a visit from a visitor.
-     *
-     * @param visitor Expression visitor.
-     * @return Visit result.
-     */
-    <T> T accept(ExpressionVisitor<T> visitor);
-
-    /** */
-    DataType resultType();
-
-    /** */
-    default RelDataType logicalType(IgniteTypeFactory typeFactory) {
-        return resultType().logicalType(typeFactory);
-    }
-
-    /** */
-    default Class<?> javaType(IgniteTypeFactory typeFactory) {
-        return resultType().javaType(typeFactory);
-    }
-
-    /**
-     * Evaluates expression.
-     *
-     * @param ctx Execution context.
-     * @return Evaluated value.
-     */
-    <T> T evaluate(ExecutionContext ctx, Object... args);
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactory.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactory.java
index d3e30ff..1ecae37 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactory.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactory.java
@@ -18,9 +18,8 @@
 package org.apache.ignite.internal.processors.query.calcite.exec.exp;
 
 import com.google.common.collect.ImmutableList;
-import java.io.StringReader;
+import com.google.common.primitives.Primitives;
 import java.lang.reflect.Modifier;
-import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
@@ -31,9 +30,7 @@ import java.util.function.Predicate;
 import java.util.function.Supplier;
 import org.apache.calcite.adapter.enumerable.JavaRowFormat;
 import org.apache.calcite.adapter.enumerable.PhysTypeImpl;
-import org.apache.calcite.adapter.enumerable.RexImpTable;
 import org.apache.calcite.adapter.enumerable.RexToLixTranslator;
-import org.apache.calcite.config.CalciteSystemProperty;
 import org.apache.calcite.linq4j.tree.BlockBuilder;
 import org.apache.calcite.linq4j.tree.Expressions;
 import org.apache.calcite.linq4j.tree.MethodDeclaration;
@@ -43,31 +40,22 @@ import org.apache.calcite.rel.core.AggregateCall;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rex.RexBuilder;
-import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexProgram;
 import org.apache.calcite.rex.RexProgramBuilder;
-import org.apache.calcite.sql.SqlOperatorTable;
-import org.apache.calcite.sql.SqlSyntax;
 import org.apache.calcite.sql.validate.SqlConformance;
 import org.apache.calcite.util.Pair;
-import org.apache.calcite.util.Util;
-import org.apache.ignite.IgniteException;
 import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
 import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler;
 import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AccumulatorWrapper;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AggCallExp;
 import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.WrappersFactoryImpl;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.type.DataType;
 import org.apache.ignite.internal.processors.query.calcite.exec.rel.AggregateNode;
 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.processors.query.calcite.util.IgniteMethod;
 import org.apache.ignite.internal.util.GridBoundedConcurrentLinkedHashMap;
 import org.apache.ignite.internal.util.typedef.F;
-import org.codehaus.commons.compiler.CompilerFactoryFactory;
-import org.codehaus.commons.compiler.IClassBodyEvaluator;
-import org.codehaus.commons.compiler.ICompilerFactory;
 
 /**
  * Implements rex expression into a function object. Uses JaninoRexCompiler under the hood.
@@ -79,38 +67,23 @@ public class ExpressionFactory {
     private static final Map<String, Scalar> SCALAR_CACHE = new GridBoundedConcurrentLinkedHashMap<>(1024);
 
     /** */
-    private static final Map<String, CallOperation> CALL_OP_CACHE = new GridBoundedConcurrentLinkedHashMap<>(1024);
-
-    /** */
     private final IgniteTypeFactory typeFactory;
 
     /** */
     private final SqlConformance conformance;
 
     /** */
-    private final SqlOperatorTable opTable;
-
-    /** */
     private final RexBuilder rexBuilder;
 
     /** */
-    private final ExpToRexTranslator expToRexTranslator;
-
-    /** */
-    private final RexToExpTranslator rexToExpTranslator;
-
-    /** */
     private final RelDataType emptyType;
 
     /** */
-    public ExpressionFactory(IgniteTypeFactory typeFactory, SqlConformance conformance, SqlOperatorTable opTable) {
+    public ExpressionFactory(IgniteTypeFactory typeFactory, SqlConformance conformance) {
         this.typeFactory = typeFactory;
         this.conformance = conformance;
-        this.opTable = opTable;
 
         rexBuilder = new RexBuilder(typeFactory);
-        expToRexTranslator = new ExpToRexTranslator(typeFactory, opTable);
-        rexToExpTranslator = new RexToExpTranslator(typeFactory);
         emptyType = new RelDataTypeFactory.Builder(typeFactory).build();
     }
 
@@ -125,28 +98,10 @@ public class ExpressionFactory {
     }
 
     /** */
-    public ExpToRexTranslator expToRexTranslator() {
-        return expToRexTranslator;
-    }
-
-    /** */
-    public RexToExpTranslator rexToExpTranslator() {
-        return rexToExpTranslator;
-    }
-
-    /** */
     public Supplier<List<AccumulatorWrapper>> wrappersFactory(ExecutionContext root, RowHandler handler, AggregateNode.AggregateType type, List<AggregateCall> calls, RelDataType rowType) {
         return new WrappersFactoryImpl(root, type, handler, calls, rowType);
     }
 
-    /** */
-    public Supplier<List<AccumulatorWrapper>> wrappersFactory(ExecutionContext root, RowHandler handler, AggregateNode.AggregateType type, List<AggCallExp> calls, DataType rowType) {
-        List<AggregateCall> calls0 = Commons.transform(calls, expToRexTranslator::translate);
-        RelDataType rowType0 = rowType == null ? null : rowType.logicalType(typeFactory);
-
-        return wrappersFactory(root, handler, type, calls0, rowType0);
-    }
-
     /**
      * Creates a comparator for given data type and collations. Mainly used for sorted exchange.
      *
@@ -161,31 +116,6 @@ public class ExpressionFactory {
     }
 
     /**
-     * Creates a comparator for given data type and collations. Mainly used for sorted exchange.
-     *
-     * @param root Execution context, holds a planner context, query and its parameters,
-     *             execution specific variables (like queryId, current user, session, etc).
-     * @param collations Collations.
-     * @param rowType Input row type.
-     * @return Row comparator.
-     */
-    public <T> Comparator<T> comparator(ExecutionContext root, List<RelCollation> collations, DataType rowType) {
-        return null; // TODO
-    }
-
-    /**
-     * Creates a Filter predicate.
-     * @param ctx Execution context, holds a planner context, query and its parameters,
-     *             execution specific variables (like queryId, current user, session, etc).
-     * @param filter Filter expression.
-     * @param rowType Input row type.
-     * @return Filter predicate.
-     */
-    public <T> Predicate<T> predicate(ExecutionContext ctx, Expression filter, DataType rowType) {
-        return predicate(ctx, expToRexTranslator.translate(filter), rowType.logicalType(typeFactory));
-    }
-
-    /**
      * Creates a Filter predicate.
      * @param ctx Execution context, holds a planner context, query and its parameters,
      *             execution specific variables (like queryId, current user, session, etc).
@@ -206,19 +136,6 @@ public class ExpressionFactory {
      * @param rowType Input row type.
      * @return Project function.
      */
-    public <T> Function<T, T> project(ExecutionContext ctx, List<Expression> projects, DataType rowType) {
-        return project(ctx, expToRexTranslator.translate(projects), rowType.logicalType(typeFactory));
-    }
-
-    /**
-     * Creates a Project function. Resulting function returns a row with different fields,
-     * fields order, fields types, etc.
-     * @param ctx Execution context, holds a planner context, query and its parameters,
-     *             execution specific variables (like queryId, current user, session, etc).
-     * @param projects Projection expressions.
-     * @param rowType Input row type.
-     * @return Project function.
-     */
     public <T> Function<T, T> project(ExecutionContext ctx, List<RexNode> projects, RelDataType rowType) {
         return new ProjectImpl<>(ctx, scalar(projects, rowType), projects.size());
     }
@@ -232,34 +149,17 @@ public class ExpressionFactory {
      * @param rowLen Row length.
      * @return Values relational node rows source.
      */
-    public <T> Iterable<T> valuesExp(ExecutionContext ctx, List<Expression> values, int rowLen) {
+    public <T> Iterable<T> values(ExecutionContext ctx, List<RexLiteral> values, int rowLen) {
         Object[] out = new Object[values.size()];
 
-        for (int i = 0; i < values.size(); i++)
-            out[i] = values.get(i).evaluate(ctx);
+        for (int i = 0; i < values.size(); i++) {
+            RexLiteral literal = values.get(i);
+            out[i] = literal.getValueAs(Primitives.wrap((Class<?>)typeFactory.getJavaClass(literal.getType())));
+        }
 
         return () -> new ValuesIterator<>(out, rowLen);
     }
 
-    /**
-     * Creates a Values relational node rows source.
-     *
-     * @param ctx Execution context, holds a planner context, query and its parameters,
-     *             execution specific variables (like queryId, current user, session, etc).
-     * @param values Values.
-     * @param rowLen Row length.
-     * @return Values relational node rows source.
-     */
-    public <T> Iterable<T> valuesRex(ExecutionContext ctx, List<RexNode> values, int rowLen) {
-        // it's safe to just interpret literals
-        return valuesExp(ctx, rexToExpTranslator.translate(values), rowLen);
-    }
-
-    /** */
-    public CallOperation implement(Call call) {
-        return CALL_OP_CACHE.computeIfAbsent(digest(call), k -> compile(call));
-    }
-
     /** */
     public Scalar scalar(RexNode node, RelDataType type) {
         return scalar(ImmutableList.of(node), type);
@@ -318,81 +218,7 @@ public class ExpressionFactory {
             Modifier.PUBLIC, Object[].class, IgniteMethod.SCALAR_EXECUTE.method().getName(),
             ImmutableList.of(context_, inputValues_, outputValues_), builder.toBlock());
 
-        return compile(Scalar.class, Expressions.toString(F.asList(declaration), "\n", false));
-    }
-
-    /** */
-    private CallOperation compile(Call call) {
-        RexCall rexCall = rexCall(call);
-
-        BlockBuilder builder = new BlockBuilder();
-
-        ParameterExpression params_ =
-            Expressions.parameter(Object[].class, "in");
-
-        RexToLixTranslator.InputGetter inputGetter =
-            new RexToLixTranslator.InputGetterImpl(
-                ImmutableList.of(
-                    Pair.of(params_,
-                        PhysTypeImpl.of(typeFactory, inputType(rexCall),
-                            JavaRowFormat.ARRAY, false))));
-
-        RexToLixTranslator translator = RexToLixTranslator.forAggregation(typeFactory,
-            builder, inputGetter, conformance);
-
-        builder.add(
-            RexImpTable.INSTANCE.get(rexCall.op)
-                .implement(translator,
-                    rexCall, RexImpTable.NullAs.NULL));
-
-        MethodDeclaration declaration = Expressions.methodDecl(
-            Modifier.PUBLIC, Object.class, IgniteMethod.CALL_APPLY.method().getName(), ImmutableList.of(params_), builder.toBlock());
-
-        return compile(CallOperation.class, Expressions.toString(F.asList(declaration), "\n", true));
-    }
-
-    /** */
-    private RexCall rexCall(Call call) {
-        List<Expression> operands = call.operands();
-        List<RexNode> refs = new ArrayList<>(operands.size());
-
-        for (int i = 0; i < operands.size(); i++)
-            refs.add(rexBuilder.makeInputRef(operands.get(i).logicalType(typeFactory), i));
-
-        return (RexCall) rexBuilder.makeCall(call.logicalType(typeFactory), call.sqlOperator(opTable), refs);
-    }
-
-    /** */
-    private RelDataType inputType(RexCall rexCall) {
-        RelDataTypeFactory.Builder typeBuilder = new RelDataTypeFactory.Builder(typeFactory);
-        ImmutableList<RexNode> rexNodes = rexCall.operands;
-        for (int i = 0; i < rexNodes.size(); i++)
-            typeBuilder.add("EXP$" + i, rexNodes.get(i).getType());
-        return typeBuilder.build();
-    }
-
-    /** */
-    private String digest(Call call) {
-        List<Expression> operands = call.operands();
-
-        final StringBuilder sb = new StringBuilder(call.opName());
-
-        if (!operands.isEmpty() || call.syntax() != SqlSyntax.FUNCTION_ID) {
-            sb.append("(");
-
-            for (int i = 0; i < operands.size(); i++) {
-                if (i != 0)
-                    sb.append(", ");
-
-                sb.append("$").append(i).append(":").append(operands.get(i).resultType());
-            }
-
-            sb.append(")");
-        }
-
-        sb.append(":").append(call.resultType());
-
-        return sb.toString();
+        return Commons.compile(Scalar.class, Expressions.toString(F.asList(declaration), "\n", false));
     }
 
     /** */
@@ -417,38 +243,6 @@ public class ExpressionFactory {
     }
 
     /** */
-    private static <T> T compile(Class<T> interfaceType, String body) {
-        final boolean debug = CalciteSystemProperty.DEBUG.value();
-
-        if (debug)
-            Util.debugCode(System.out, body);
-
-        try {
-            final ICompilerFactory compilerFactory;
-
-            try {
-                compilerFactory = CompilerFactoryFactory.getDefaultCompilerFactory();
-            } catch (Exception e) {
-                throw new IllegalStateException(
-                    "Unable to instantiate java compiler", e);
-            }
-
-            IClassBodyEvaluator cbe = compilerFactory.newClassBodyEvaluator();
-
-            cbe.setImplementedInterfaces(new Class[]{ interfaceType });
-            cbe.setParentClassLoader(ExpressionFactory.class.getClassLoader());
-
-            if (debug)
-                // Add line numbers to the generated janino class
-                cbe.setDebuggingInformation(true, true, true);
-
-            return (T) cbe.createInstance(new StringReader(body));
-        } catch (Exception e) {
-            throw new IgniteException(e);
-        }
-    }
-
-    /** */
     private static class PredicateImpl<T> implements Predicate<T> {
         /** */
         private final ExecutionContext ctx;
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionVisitor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionVisitor.java
deleted file mode 100644
index e3d74bf..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionVisitor.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.exec.exp;
-
-/**
- * Visitor pattern for traversing a tree of {@link Expression} objects.
- */
-public interface ExpressionVisitor<T> {
-    /**
-     * See {@link ExpressionVisitor#visit(Expression)}
-     */
-    T visit(InputRef exp);
-
-    /**
-     * See {@link ExpressionVisitor#visit(Expression)}
-     */
-    T visit(Literal exp);
-
-    /**
-     * See {@link ExpressionVisitor#visit(Expression)}
-     */
-    T visit(DynamicParam exp);
-
-    /**
-     * See {@link ExpressionVisitor#visit(Expression)}
-     */
-    T visit(Call exp);
-
-    /**
-     * Implements given expression.
-     *
-     * @param exp Expression.
-     * @return Implementation result.
-     */
-    T visit(Expression exp);
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/InputRef.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/InputRef.java
deleted file mode 100644
index dfbfb18..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/InputRef.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.exec.exp;
-
-import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.type.DataType;
-
-/**
- * Describes {@link org.apache.calcite.rex.RexInputRef}.
- */
-public class InputRef implements Expression {
-    /** */
-    private final DataType type;
-
-    /** */
-    private final int index;
-
-    /**
-     * @param type Data type.
-     * @param index Index.
-     */
-    public InputRef(DataType type, int index) {
-        this.type = type;
-        this.index = index;
-    }
-
-    /** {@inheritDoc} */
-    @Override public DataType resultType() {
-        return type;
-    }
-
-    /**
-     * @return Index.
-     */
-    public int index() {
-        return index;
-    }
-
-    /** {@inheritDoc} */
-    @Override public <T> T accept(ExpressionVisitor<T> visitor) {
-        return visitor.visit(this);
-    }
-
-    /** {@inheritDoc} */
-    @Override public <T> T evaluate(ExecutionContext ctx, Object... args) {
-        return (T) args[index];
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/Literal.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/Literal.java
deleted file mode 100644
index 2108537..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/Literal.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.exec.exp;
-
-import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.type.DataType;
-
-/**
- * Describes {@link org.apache.calcite.rex.RexLiteral}.
- */
-public class Literal implements Expression {
-    /** */
-    private final DataType type;
-
-    /** */
-    private final Object value;
-
-    /**
-     * @param type Data type.
-     * @param value Value.
-     */
-    public Literal(DataType type, Object value) {
-        this.type = type;
-        this.value = value;
-    }
-
-    /** {@inheritDoc} */
-    @Override public DataType resultType() {
-        return type;
-    }
-
-    /**
-     * @return Value.
-     */
-    public Object value() {
-        return value;
-    }
-
-    /** {@inheritDoc} */
-    @Override public <T> T accept(ExpressionVisitor<T> visitor) {
-        return visitor.visit(this);
-    }
-
-    /** {@inheritDoc} */
-    @Override public <T> T evaluate(ExecutionContext ctx, Object... args) {
-        return (T) value;
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RexToExpTranslator.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RexToExpTranslator.java
deleted file mode 100644
index 104bb50..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RexToExpTranslator.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.exec.exp;
-
-import java.util.List;
-import org.apache.calcite.rel.core.AggregateCall;
-import org.apache.calcite.rex.RexCall;
-import org.apache.calcite.rex.RexCorrelVariable;
-import org.apache.calcite.rex.RexDynamicParam;
-import org.apache.calcite.rex.RexFieldAccess;
-import org.apache.calcite.rex.RexInputRef;
-import org.apache.calcite.rex.RexLiteral;
-import org.apache.calcite.rex.RexLocalRef;
-import org.apache.calcite.rex.RexNode;
-import org.apache.calcite.rex.RexOver;
-import org.apache.calcite.rex.RexPatternFieldRef;
-import org.apache.calcite.rex.RexRangeRef;
-import org.apache.calcite.rex.RexSubQuery;
-import org.apache.calcite.rex.RexTableInputRef;
-import org.apache.calcite.rex.RexVisitor;
-import org.apache.calcite.sql.SqlSyntax;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AggCallExp;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.type.DataType;
-import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
-import org.apache.ignite.internal.processors.query.calcite.util.Commons;
-
-/**
- * A translator of Rex nodes into Expressions.
- */
-public class RexToExpTranslator implements RexVisitor<Expression> {
-    /** */
-    private final IgniteTypeFactory typeFactory;
-
-    public RexToExpTranslator(IgniteTypeFactory typeFactory) {
-        this.typeFactory = typeFactory;
-    }
-
-    /**
-     * Translates a list of Rex nodes into a list of expressions.
-     *
-     * @param operands List of Rex nodes.
-     * @return List of expressions.
-     */
-    public List<Expression> translate(List<RexNode> operands) {
-        return Commons.transform(operands, this::translate);
-    }
-
-    /**
-     * Translates a RexNode into an expression.
-     *
-     * @param rex RexNode.
-     * @return Expression.
-     */
-    public Expression translate(RexNode rex) {
-        return rex.accept(this);
-    }
-
-    /** */
-    public AggCallExp translate(AggregateCall call) {
-        DataType type = DataType.fromType(call.type);
-        SqlSyntax syntax = call.getAggregation().getSyntax();
-        String function = call.getAggregation().getName();
-        String name = call.name;
-        boolean distinct = call.isDistinct();
-        boolean approximate = call.isApproximate();
-        boolean ignoreNulls = call.ignoreNulls();
-        int[] args = call.getArgList().stream()
-            .mapToInt(Integer::intValue)
-            .toArray();
-        int filterArg = call.filterArg;
-
-        return new AggCallExp(type, syntax, function,
-            name, distinct, approximate, ignoreNulls, args, filterArg);
-    }
-
-    /** {@inheritDoc} */
-     @Override public Expression visitInputRef(RexInputRef rex) {
-        return new InputRef(DataType.fromType(rex.getType()), rex.getIndex());
-    }
-
-    /** {@inheritDoc} */
-    @Override public Expression visitLiteral(RexLiteral rex) {
-        final DataType type = DataType.fromType(rex.getType());
-        return new Literal(type, rex.getValueAs(Commons.boxType(type.javaType(typeFactory))));
-    }
-
-    /** {@inheritDoc} */
-    @Override public Expression visitDynamicParam(RexDynamicParam rex) {
-        return new DynamicParam(DataType.fromType(rex.getType()), rex.getIndex());
-    }
-
-    /** {@inheritDoc} */
-    @Override public Expression visitCall(RexCall rex) {
-        return new Call(DataType.fromType(rex.getType()), rex.op.getName(), rex.op.getSyntax(), translate(rex.getOperands()));
-    }
-
-    /** {@inheritDoc} */
-    @Override public Expression visitLocalRef(RexLocalRef rex) {
-        throw new UnsupportedOperationException();
-    }
-
-    /** {@inheritDoc} */
-    @Override public Expression visitOver(RexOver rex) {
-        throw new UnsupportedOperationException();
-    }
-
-    /** {@inheritDoc} */
-    @Override public Expression visitCorrelVariable(RexCorrelVariable rex) {
-        throw new UnsupportedOperationException();
-    }
-
-    /** {@inheritDoc} */
-    @Override public Expression visitRangeRef(RexRangeRef rex) {
-        throw new UnsupportedOperationException();
-    }
-
-    /** {@inheritDoc} */
-    @Override public Expression visitFieldAccess(RexFieldAccess rex) {
-        throw new UnsupportedOperationException();
-    }
-
-    /** {@inheritDoc} */
-    @Override public Expression visitSubQuery(RexSubQuery rex) {
-        throw new UnsupportedOperationException();
-    }
-
-    /** {@inheritDoc} */
-    @Override public Expression visitTableInputRef(RexTableInputRef rex) {
-        throw new UnsupportedOperationException();
-    }
-
-    /** {@inheritDoc} */
-    @Override public Expression visitPatternFieldRef(RexPatternFieldRef rex) {
-        throw new UnsupportedOperationException();
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/AggCallExp.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/AggCallExp.java
deleted file mode 100644
index c9227bc..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/AggCallExp.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.exec.exp.agg;
-
-import java.io.Externalizable;
-import java.io.IOException;
-import java.io.ObjectInput;
-import java.io.ObjectOutput;
-import java.util.List;
-import java.util.Objects;
-import org.apache.calcite.sql.SqlAggFunction;
-import org.apache.calcite.sql.SqlIdentifier;
-import org.apache.calcite.sql.SqlOperator;
-import org.apache.calcite.sql.SqlOperatorTable;
-import org.apache.calcite.sql.SqlSyntax;
-import org.apache.calcite.sql.parser.SqlParserPos;
-import org.apache.calcite.sql.validate.SqlNameMatchers;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.type.DataType;
-import org.apache.ignite.internal.util.MutableSingletonList;
-import org.apache.ignite.internal.util.typedef.F;
-
-/**
- *
- */
-public class AggCallExp implements Externalizable {
-    /** */
-    private static final byte DISTINCT     = 1;
-
-    /** */
-    private static final byte APPROXIMATE  = 1 << 1;
-
-    /** */
-    private static final byte IGNORE_NULLS = 1 << 2;
-
-    /** */
-    private static final byte HAS_NAME     = 1 << 3;
-
-    /** */
-    private DataType type;
-
-    /** */
-    private SqlSyntax syntax;
-
-    /** */
-    private String function;
-
-    /** */
-    private String name;
-
-    /** */
-    private int[] args;
-
-    /** */
-    private int filterArg;
-
-    /** */
-    private byte flags;
-
-    /** */
-    public AggCallExp(DataType type, SqlSyntax syntax, String function, String name,
-        boolean distinct, boolean approximate, boolean ignoreNulls, int[] args, int filterArg) {
-        this.type = type;
-        this.syntax = syntax;
-        this.function = function;
-        this.name = name;
-        this.args = args;
-        this.filterArg = filterArg;
-
-        byte flags = 0;
-
-        if (distinct)
-            flags |= DISTINCT;
-        if (approximate)
-            flags |= APPROXIMATE;
-        if (ignoreNulls)
-            flags |= IGNORE_NULLS;
-
-        this.flags = flags;
-    }
-
-    public AggCallExp() {
-    }
-
-    /** */
-    public DataType resultType() {
-        return type;
-    }
-
-    /** */
-    public SqlSyntax syntax() {
-        return syntax;
-    }
-
-    /** */
-    public String function() {
-        return function;
-    }
-
-    /** */
-    public SqlAggFunction sqlOperator(SqlOperatorTable opTable) {
-        List<SqlOperator> bag = new MutableSingletonList<>();
-
-        opTable.lookupOperatorOverloads(
-            new SqlIdentifier(function, SqlParserPos.ZERO), null,
-            syntax, bag, SqlNameMatchers.withCaseSensitive(true));
-
-        return (SqlAggFunction) Objects.requireNonNull(F.first(bag));
-    }
-
-    /** */
-    public String name() {
-        return name;
-    }
-
-    /** */
-    public boolean distinct() {
-        return (flags & DISTINCT) == DISTINCT;
-    }
-
-    /** */
-    public boolean approximate() {
-        return (flags & APPROXIMATE) == APPROXIMATE;
-    }
-
-    /** */
-    public boolean ignoreNulls() {
-        return (flags & IGNORE_NULLS) == IGNORE_NULLS;
-    }
-
-    /** */
-    public int[] args() {
-        return args;
-    }
-
-    /** */
-    public int filterArg() {
-        return filterArg;
-    }
-
-    /** {@inheritDoc} */
-    @Override public void writeExternal(ObjectOutput out) throws IOException {
-        out.writeByte(flags);
-        out.writeByte(syntax.ordinal());
-        out.writeUTF(function);
-        if ((flags & HAS_NAME) == HAS_NAME)
-            out.writeUTF(name);
-        out.writeObject(type);
-        out.writeObject(args);
-        out.writeInt(filterArg);
-    }
-
-    /** {@inheritDoc} */
-    @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
-        flags = in.readByte();
-        syntax = SqlSyntax.values()[in.readByte()];
-        function = in.readUTF();
-        if ((flags & HAS_NAME) == HAS_NAME)
-            name = in.readUTF();
-        type = (DataType) in.readObject();
-        args = (int[]) in.readObject();
-        filterArg = in.readInt();
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/WrappersFactoryImpl.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/WrappersFactoryImpl.java
index c6e7687..d2c353e 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/WrappersFactoryImpl.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/WrappersFactoryImpl.java
@@ -29,7 +29,6 @@ import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rel.type.RelDataTypeField;
 import org.apache.calcite.rex.RexBuilder;
 import org.apache.calcite.rex.RexNode;
-import org.apache.calcite.sql.SqlOperatorTable;
 import org.apache.calcite.sql.validate.SqlConformance;
 import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
 import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler;
@@ -295,9 +294,8 @@ public class WrappersFactoryImpl implements Supplier<List<AccumulatorWrapper>> {
 
             IgniteTypeFactory typeFactory = ctx.parent().typeFactory();
             SqlConformance conformance = ctx.parent().conformance();
-            SqlOperatorTable opTable = ctx.parent().opTable();
 
-            ExpressionFactory factory = new ExpressionFactory(typeFactory, conformance, opTable);
+            ExpressionFactory factory = new ExpressionFactory(typeFactory, conformance);
 
             if (type != AggregateType.REDUCE && !F.isEmpty(call.getArgList()))
                 inAdapter = createInAdapter(accumulator, factory);
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/type/BasicType.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/type/BasicType.java
deleted file mode 100644
index e4b6aa6..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/type/BasicType.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.exec.exp.type;
-
-import java.io.Externalizable;
-import java.io.IOException;
-import java.io.ObjectInput;
-import java.io.ObjectOutput;
-import org.apache.calcite.rel.type.RelDataType;
-import org.apache.calcite.sql.type.BasicSqlType;
-import org.apache.calcite.sql.type.SqlTypeName;
-import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
-
-import static org.apache.calcite.rel.type.RelDataType.PRECISION_NOT_SPECIFIED;
-import static org.apache.calcite.rel.type.RelDataType.SCALE_NOT_SPECIFIED;
-
-/** */
-public class BasicType implements DataType, Externalizable {
-    /** */
-    private SqlTypeName typeName;
-
-    /** */
-    private int precision = PRECISION_NOT_SPECIFIED;
-
-    /** */
-    private int scale = SCALE_NOT_SPECIFIED;
-
-    /** */
-    private boolean nullable;
-
-    /** */
-    public BasicType() {
-    }
-
-    /**
-     * @param type Source type.
-     */
-    public BasicType(BasicSqlType type) {
-        typeName = type.getSqlTypeName();
-        precision = type.getPrecision();
-        scale = type.getScale();
-        nullable = type.isNullable();
-    }
-
-    /** {@inheritDoc} */
-    @Override public SqlTypeName typeName() {
-        return typeName;
-    }
-
-    /** {@inheritDoc} */
-    @Override public int precision() {
-        return precision;
-    }
-
-    /** {@inheritDoc} */
-    @Override public int scale() {
-        return scale;
-    }
-
-    /** */
-    @Override public boolean nullable() {
-        return nullable;
-    }
-
-    /** {@inheritDoc} */
-    @Override public RelDataType logicalType(IgniteTypeFactory factory) {
-        RelDataType type;
-
-        if (!typeName.allowsPrec())
-            type = factory.createSqlType(typeName);
-        else if (!typeName.allowsScale())
-            type = factory.createSqlType(typeName, precision);
-        else
-            type = factory.createSqlType(typeName, precision, scale);
-
-        return nullable ? factory.createTypeWithNullability(type, nullable) : type;
-    }
-
-    /** {@inheritDoc} */
-    @Override public Class<?> javaType(IgniteTypeFactory typeFactory) {
-        return (Class<?>) typeFactory.getJavaClass(logicalType(typeFactory));
-    }
-
-    /** {@inheritDoc} */
-    @Override public void writeExternal(ObjectOutput out) throws IOException {
-        out.writeByte(typeName.ordinal());
-
-        if (typeName.allowsScale())
-            out.writeInt(scale);
-
-        if (typeName.allowsPrec())
-            out.writeInt(precision);
-
-        out.writeBoolean(nullable);
-    }
-
-    /** {@inheritDoc} */
-    @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
-        typeName = SqlTypeName.values()[in.readByte()];
-
-        if (typeName.allowsScale())
-            scale = in.readInt();
-
-        if (typeName.allowsPrec())
-            precision = in.readInt();
-
-        nullable = in.readBoolean();
-    }
-
-    /** {@inheritDoc} */
-    @Override public String toString() {
-        return toString(true);
-    }
-
-    /** */
-    protected String toString(boolean withNullability) {
-        StringBuilder sb = new StringBuilder(typeName.name());
-
-        boolean printPrecision = precision != PRECISION_NOT_SPECIFIED;
-        boolean printScale = scale != SCALE_NOT_SPECIFIED;
-
-        if (printPrecision) {
-            sb.append('(');
-            sb.append(precision);
-            if (printScale) {
-                sb.append(", ");
-                sb.append(scale);
-            }
-            sb.append(')');
-        }
-
-        if (withNullability && !nullable)
-            sb.append(" NOT NULL");
-
-        return sb.toString();
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/type/CharacterDataType.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/type/CharacterDataType.java
deleted file mode 100644
index 9c43998..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/type/CharacterDataType.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.exec.exp.type;
-
-import java.nio.charset.Charset;
-import org.apache.calcite.sql.SqlCollation;
-
-/**
- *
- */
-public interface CharacterDataType extends DataType {
-    /** */
-    Charset charset();
-
-    /** */
-    SqlCollation collation();
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/type/CharacterType.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/type/CharacterType.java
deleted file mode 100644
index bb935ee..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/type/CharacterType.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.exec.exp.type;
-
-import java.io.IOException;
-import java.io.ObjectInput;
-import java.io.ObjectOutput;
-import java.nio.charset.Charset;
-import org.apache.calcite.rel.type.RelDataType;
-import org.apache.calcite.sql.SqlCollation;
-import org.apache.calcite.sql.type.BasicSqlType;
-import org.apache.commons.codec.Charsets;
-import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
-
-/** */
-public class CharacterType extends BasicType implements CharacterDataType {
-    /** */
-    private static final int COERCIBLE_COLLATION = 0;
-
-    /** */
-    private static final int IMPLICIT_COLLATION = 1;
-
-    /** */
-    private static final int CUSTOM_COLLATION = 2;
-
-    /** */
-    private Charset charset;
-
-    /** */
-    private SqlCollation collation;
-
-    /** */
-    public CharacterType() {
-    }
-
-    /**
-     * @param type Source type.
-     */
-    public CharacterType(BasicSqlType type) {
-        super(type);
-
-        charset = type.getCharset();
-        collation = type.getCollation();
-    }
-
-    /** {@inheritDoc} */
-    @Override public Charset charset() {
-        return charset;
-    }
-
-    /** {@inheritDoc} */
-    @Override public SqlCollation collation() {
-        return collation;
-    }
-
-    /** {@inheritDoc} */
-    @Override public RelDataType logicalType(IgniteTypeFactory factory) {
-        return factory.createTypeWithCharsetAndCollation(super.logicalType(factory), charset, collation);
-    }
-
-    /** {@inheritDoc} */
-    @Override public Class<?> javaType(IgniteTypeFactory typeFactory) {
-        return (Class<?>)typeFactory.getJavaClass(logicalType(typeFactory));
-    }
-
-    /** {@inheritDoc} */
-    @Override public void writeExternal(ObjectOutput out) throws IOException {
-        super.writeExternal(out);
-
-        if (charset == null)
-            out.writeBoolean(false);
-        else {
-            out.writeBoolean(true);
-            out.writeUTF(charset.name());
-        }
-
-        if (collation == SqlCollation.COERCIBLE)
-            out.writeByte(COERCIBLE_COLLATION);
-        else if (collation == SqlCollation.IMPLICIT)
-            out.writeByte(IMPLICIT_COLLATION);
-        else {
-            out.writeByte(CUSTOM_COLLATION);
-            out.writeUTF(collation.getCollationName());
-            out.writeByte(collation.getCoercibility().ordinal());
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
-        super.readExternal(in);
-
-        if (in.readBoolean())
-            charset = Charsets.toCharset(in.readUTF());
-
-        byte collationType = in.readByte();
-
-        if (collationType == COERCIBLE_COLLATION)
-            collation = SqlCollation.COERCIBLE;
-        else if (collationType == IMPLICIT_COLLATION)
-            collation = SqlCollation.IMPLICIT;
-        else if (collationType == CUSTOM_COLLATION)
-            collation = new SqlCollation(in.readUTF(), SqlCollation.Coercibility.values()[in.readByte()]);
-        else
-            throw new AssertionError();
-    }
-
-    /** {@inheritDoc} */
-    @Override public String toString() {
-        StringBuilder sb = new StringBuilder(toString(false));
-
-        if (charset != null)
-            sb.append(" CHARACTER SET \"")
-                .append(charset.name())
-                .append("\"");
-        if (collation != null
-            && collation != SqlCollation.IMPLICIT
-            && collation != SqlCollation.COERCIBLE)
-            sb.append(" COLLATE \"")
-                .append(collation.getCollationName())
-                .append("\"");
-
-        if (!nullable())
-            sb.append(" NOT NULL");
-
-        return sb.toString();
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/type/DataType.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/type/DataType.java
deleted file mode 100644
index 21c0c9a..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/type/DataType.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.exec.exp.type;
-
-import java.io.Serializable;
-import java.util.LinkedHashMap;
-import org.apache.calcite.rel.type.RelDataType;
-import org.apache.calcite.rel.type.RelDataTypeFactoryImpl;
-import org.apache.calcite.rel.type.RelDataTypeField;
-import org.apache.calcite.sql.type.BasicSqlType;
-import org.apache.calcite.sql.type.SqlTypeName;
-import org.apache.calcite.sql.type.SqlTypeUtil;
-import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
-
-/**
- * Serializable RelDataType representation.
- */
-public interface DataType extends Serializable {
-    /**
-     * Factory method to construct data type representation from RelDataType.
-     * @param type RelDataType.
-     * @return DataType.
-     */
-    static DataType fromType(RelDataType type) {
-        if (type.isStruct()) {
-            assert type.isStruct();
-
-            LinkedHashMap<String, DataType> fields = new LinkedHashMap<>();
-
-            for (RelDataTypeField field : type.getFieldList())
-                fields.put(field.getName(), fromType(field.getType()));
-
-            return new StructType(fields);
-        }
-
-        if (type instanceof RelDataTypeFactoryImpl.JavaType)
-            return new JavaType(((RelDataTypeFactoryImpl.JavaType) type).getJavaClass(), type.isNullable());
-
-        assert type instanceof BasicSqlType : type;
-
-        if (SqlTypeUtil.inCharFamily(type))
-            return new CharacterType((BasicSqlType) type);
-
-        return new BasicType((BasicSqlType) type);
-    }
-
-    /** */
-    SqlTypeName typeName();
-
-    /** */
-    int precision();
-
-    /** */
-    int scale();
-
-    /** */
-    boolean nullable();
-
-    /**
-     * Returns logical type.
-     *
-     * @param factory Type factory.
-     * @return Logical type.
-     */
-    RelDataType logicalType(IgniteTypeFactory factory);
-
-    /**
-     * Returns java type.
-     *
-     * @param factory Type factory.
-     * @return Java type.
-     */
-    Class<?> javaType(IgniteTypeFactory factory);
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/type/JavaType.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/type/JavaType.java
deleted file mode 100644
index 9b9af2e..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/type/JavaType.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.exec.exp.type;
-
-import org.apache.calcite.rel.type.RelDataType;
-import org.apache.calcite.sql.type.JavaToSqlTypeConversionRules;
-import org.apache.calcite.sql.type.SqlTypeName;
-import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
-
-import static org.apache.calcite.rel.type.RelDataType.PRECISION_NOT_SPECIFIED;
-import static org.apache.calcite.rel.type.RelDataType.SCALE_NOT_SPECIFIED;
-
-/** */
-public class JavaType implements DataType {
-    /** */
-    private final Class<?> clazz;
-
-    /** */
-    private final boolean nullable;
-
-    /** {@inheritDoc} */
-    @Override public SqlTypeName typeName() {
-        final SqlTypeName typeName =
-            JavaToSqlTypeConversionRules.instance().lookup(clazz);
-
-        return typeName == null ? SqlTypeName.OTHER : typeName;
-    }
-
-    /** {@inheritDoc} */
-    @Override public int precision() {
-        return PRECISION_NOT_SPECIFIED;
-    }
-
-    /** {@inheritDoc} */
-    @Override public int scale() {
-        return SCALE_NOT_SPECIFIED;
-    }
-
-    /** {@inheritDoc} */
-    @Override public boolean nullable() {
-        return nullable;
-    }
-
-    /**
-     * @param clazz Value class.
-     * @param nullable nullable flag.
-     */
-    public JavaType(Class<?> clazz, boolean nullable) {
-        assert !nullable || !clazz.isPrimitive();
-
-        this.clazz = clazz;
-        this.nullable = nullable;
-    }
-
-    /** {@inheritDoc} */
-    @Override public RelDataType logicalType(IgniteTypeFactory factory) {
-        return factory.createJavaType(clazz);
-    }
-
-    /** {@inheritDoc} */
-    @Override public Class<?> javaType(IgniteTypeFactory factory) {
-        return clazz;
-    }
-
-    /** {@inheritDoc} */
-    @Override public String toString() {
-        return String.valueOf(clazz);
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/type/StructType.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/type/StructType.java
deleted file mode 100644
index 97aa7c3..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/type/StructType.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.exec.exp.type;
-
-import java.util.LinkedHashMap;
-import java.util.Map;
-import org.apache.calcite.rel.type.RelDataType;
-import org.apache.calcite.rel.type.RelDataTypeFactory;
-import org.apache.calcite.sql.type.SqlTypeName;
-import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
-
-import static org.apache.calcite.rel.type.RelDataType.PRECISION_NOT_SPECIFIED;
-import static org.apache.calcite.rel.type.RelDataType.SCALE_NOT_SPECIFIED;
-
-/** */
-public class StructType implements DataType {
-    /** */
-    private final LinkedHashMap<String, DataType> fields;
-
-    /**
-     * @param fields Fields.
-     */
-    public StructType(LinkedHashMap<String, DataType> fields) {
-        this.fields = fields;
-    }
-
-    public Map<String, DataType> fields() {
-        return fields;
-    }
-
-    /** {@inheritDoc} */
-    @Override public SqlTypeName typeName() {
-        return SqlTypeName.ROW;
-    }
-
-    /** {@inheritDoc} */
-    @Override public int precision() {
-        return PRECISION_NOT_SPECIFIED;
-    }
-
-    /** {@inheritDoc} */
-    @Override public int scale() {
-        return SCALE_NOT_SPECIFIED;
-    }
-
-    /** {@inheritDoc} */
-    @Override public boolean nullable() {
-        return false;
-    }
-
-    /** {@inheritDoc} */
-    @Override public RelDataType logicalType(IgniteTypeFactory factory) {
-        RelDataTypeFactory.Builder builder = new RelDataTypeFactory.Builder(factory);
-        fields.forEach((n,f) -> builder.add(n,f.logicalType(factory)));
-        return builder.build();
-    }
-
-    /** {@inheritDoc} */
-    @Override public Class<?> javaType(IgniteTypeFactory typeFactory) {
-        return Object[].class; // TODO currently a row is mapped to an object array, will be changed in future.
-    }
-
-    /** {@inheritDoc} */
-    @Override public String toString() {
-        StringBuilder sb = new StringBuilder("Row(");
-
-        boolean first = true;
-
-        for (Map.Entry<String, DataType> field : fields.entrySet()) {
-            if(first)
-                first = false;
-            else
-                sb.append(", ");
-
-            sb.append("\"")
-                .append(field.getKey())
-                .append("\":")
-                .append(field.getValue());
-        }
-
-        sb.append(")");
-
-        return sb.toString();
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelJson.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelJson.java
new file mode 100644
index 0000000..5bad195
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelJson.java
@@ -0,0 +1,871 @@
+/*
+ * 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.externalize;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import org.apache.calcite.avatica.AvaticaUtils;
+import org.apache.calcite.avatica.util.TimeUnit;
+import org.apache.calcite.avatica.util.TimeUnitRange;
+import org.apache.calcite.linq4j.tree.BlockBuilder;
+import org.apache.calcite.linq4j.tree.Expressions;
+import org.apache.calcite.linq4j.tree.MethodDeclaration;
+import org.apache.calcite.linq4j.tree.ParameterExpression;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.RelCollationImpl;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.RelDistribution.Type;
+import org.apache.calcite.rel.RelFieldCollation;
+import org.apache.calcite.rel.RelFieldCollation.Direction;
+import org.apache.calcite.rel.RelFieldCollation.NullDirection;
+import org.apache.calcite.rel.RelInput;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.core.CorrelationId;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.rel.type.RelDataTypeFactoryImpl.JavaType;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexDynamicParam;
+import org.apache.calcite.rex.RexFieldAccess;
+import org.apache.calcite.rex.RexFieldCollation;
+import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexOver;
+import org.apache.calcite.rex.RexSlot;
+import org.apache.calcite.rex.RexVariable;
+import org.apache.calcite.rex.RexWindow;
+import org.apache.calcite.rex.RexWindowBound;
+import org.apache.calcite.sql.JoinConditionType;
+import org.apache.calcite.sql.JoinType;
+import org.apache.calcite.sql.SqlAggFunction;
+import org.apache.calcite.sql.SqlExplain;
+import org.apache.calcite.sql.SqlExplainFormat;
+import org.apache.calcite.sql.SqlExplainLevel;
+import org.apache.calcite.sql.SqlFunction;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlInsertKeyword;
+import org.apache.calcite.sql.SqlIntervalQualifier;
+import org.apache.calcite.sql.SqlJsonConstructorNullClause;
+import org.apache.calcite.sql.SqlJsonQueryWrapperBehavior;
+import org.apache.calcite.sql.SqlJsonValueEmptyOrErrorBehavior;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlMatchRecognize;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.SqlSelectKeyword;
+import org.apache.calcite.sql.SqlSyntax;
+import org.apache.calcite.sql.SqlWindow;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.fun.SqlTrimFunction;
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.sql.validate.SqlNameMatchers;
+import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.calcite.util.Util;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
+import org.apache.ignite.internal.processors.query.calcite.trait.DistributionFunction;
+import org.apache.ignite.internal.processors.query.calcite.trait.DistributionFunction.AffinityDistribution;
+import org.apache.ignite.internal.processors.query.calcite.trait.DistributionTrait;
+import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
+import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.apache.ignite.internal.util.typedef.F;
+
+/**
+ * Utilities for converting {@link RelNode} into JSON format.
+ */
+@SuppressWarnings({"rawtypes", "unchecked"})
+class RelJson {
+    /** */
+    @FunctionalInterface
+    public static interface RelFactory extends Function<RelInput, RelNode> {
+        /** {@inheritDoc} */
+        @Override RelNode apply(RelInput input);
+    }
+
+    /** */
+    private static final LoadingCache<String, RelFactory> FACTORIES_CACHE = CacheBuilder.newBuilder()
+        .build(CacheLoader.from(RelJson::relFactory));
+
+    /** */
+    private static RelFactory relFactory(String typeName) {
+        Class<?> clazz = null;
+
+        if (!typeName.contains(".")) {
+            for (String package_ : PACKAGES) {
+                if ((clazz = classForName(package_ + typeName, true)) != null)
+                    break;
+            }
+        }
+
+        if (clazz == null)
+            clazz = classForName(typeName, false);
+
+        assert RelNode.class.isAssignableFrom(clazz);
+
+        Constructor<RelNode> constructor;
+
+        try {
+            constructor = (Constructor<RelNode>)clazz.getConstructor(RelInput.class);
+        }
+        catch (NoSuchMethodException e) {
+            throw new RuntimeException("class does not have required constructor, "
+                + clazz + "(RelInput)");
+        }
+
+        ParameterExpression input_ = Expressions.parameter(RelInput.class);
+        BlockBuilder builder = new BlockBuilder();
+        builder.add(Expressions.new_(constructor, input_));
+        MethodDeclaration declaration = Expressions.methodDecl(
+            Modifier.PUBLIC, RelNode.class, "apply", F.asList(input_), builder.toBlock());
+        return Commons.compile(RelFactory.class, Expressions.toString(F.asList(declaration), "\n", true));
+    }
+
+    /** */
+    private static final ImmutableMap<String, Enum<?>> ENUM_BY_NAME;
+
+    /** */
+    static {
+        // Build a mapping from enum constants (e.g. LEADING) to the enum
+        // that contains them (e.g. SqlTrimFunction.Flag). If there two
+        // enum constants have the same name, the builder will throw.
+        final ImmutableMap.Builder<String, Enum<?>> enumByName =
+            ImmutableMap.builder();
+
+        register(enumByName, JoinConditionType.class);
+        register(enumByName, JoinType.class);
+        register(enumByName, Direction.class);
+        register(enumByName, NullDirection.class);
+        register(enumByName, SqlTypeName.class);
+        register(enumByName, SqlKind.class);
+        register(enumByName, SqlSyntax.class);
+        register(enumByName, SqlExplain.Depth.class);
+        register(enumByName, SqlExplainFormat.class);
+        register(enumByName, SqlExplainLevel.class);
+        register(enumByName, SqlInsertKeyword.class);
+        register(enumByName, SqlJsonConstructorNullClause.class);
+        register(enumByName, SqlJsonQueryWrapperBehavior.class);
+        register(enumByName, SqlJsonValueEmptyOrErrorBehavior.class);
+        register(enumByName, SqlMatchRecognize.AfterOption.class);
+        register(enumByName, SqlSelectKeyword.class);
+        register(enumByName, SqlTrimFunction.Flag.class);
+        register(enumByName, TimeUnitRange.class);
+        ENUM_BY_NAME = enumByName.build();
+    }
+
+    /** */
+    private static void register(ImmutableMap.Builder<String, Enum<?>> builder, Class<? extends Enum> aClass) {
+        String preffix = aClass.getSimpleName() + "#";
+        for (Enum enumConstant : aClass.getEnumConstants())
+            builder.put(preffix + enumConstant.name(), enumConstant);
+    }
+
+    /** */
+    private static Class<?> classForName(String typeName, boolean skipNotFound) {
+        try {
+            return Class.forName(typeName);
+        }
+        catch (ClassNotFoundException e) {
+            if (skipNotFound)
+                return null;
+
+            throw new RuntimeException("unknown type " + typeName);
+        }
+    }
+
+    /** */
+    private static final List<String> PACKAGES =
+        ImmutableList.of(
+            "org.apache.ignite.internal.processors.query.calcite.rel.",
+            "org.apache.calcite.rel.",
+            "org.apache.calcite.rel.core.",
+            "org.apache.calcite.rel.logical.",
+            "org.apache.calcite.adapter.jdbc.",
+            "org.apache.calcite.adapter.jdbc.JdbcRules$");
+
+    /** */
+    Function<RelInput, RelNode> factory(String type) {
+        return FACTORIES_CACHE.getUnchecked(type);
+    }
+
+    /** */
+    String classToTypeName(Class<? extends RelNode> class_) {
+        if (IgniteRel.class.isAssignableFrom(class_))
+            return class_.getSimpleName();
+
+        String canonicalName = class_.getName();
+        for (String package_ : PACKAGES) {
+            if (canonicalName.startsWith(package_)) {
+                String remaining = canonicalName.substring(package_.length());
+                if (remaining.indexOf('.') < 0 && remaining.indexOf('$') < 0)
+                    return remaining;
+            }
+        }
+        return canonicalName;
+    }
+
+    /** */
+    Object toJson(Object value) {
+        if (value == null
+            || value instanceof Number
+            || value instanceof String
+            || value instanceof Boolean)
+            return value;
+        else if (value instanceof Enum)
+            return toJson((Enum)value);
+        else if (value instanceof RexNode)
+            return toJson((RexNode)value);
+        else if (value instanceof RexWindow)
+            return toJson((RexWindow)value);
+        else if (value instanceof RexFieldCollation)
+            return toJson((RexFieldCollation)value);
+        else if (value instanceof RexWindowBound)
+            return toJson((RexWindowBound)value);
+        else if (value instanceof CorrelationId)
+            return toJson((CorrelationId)value);
+        else if (value instanceof List) {
+            List<Object> list = list();
+            for (Object o : (List)value)
+                list.add(toJson(o));
+            return list;
+        }
+        else if (value instanceof ImmutableBitSet) {
+            List<Object> list = list();
+            for (Integer integer : (ImmutableBitSet)value)
+                list.add(toJson(integer));
+            return list;
+        }
+        else if (value instanceof DistributionTrait)
+            return toJson((DistributionTrait)value);
+        else if (value instanceof AggregateCall)
+            return toJson((AggregateCall)value);
+        else if (value instanceof RelCollationImpl)
+            return toJson((RelCollationImpl)value);
+        else if (value instanceof RelDataType)
+            return toJson((RelDataType)value);
+        else if (value instanceof RelDataTypeField)
+            return toJson((RelDataTypeField)value);
+        else
+            throw new UnsupportedOperationException("type not serializable: "
+                + value + " (type " + value.getClass().getCanonicalName() + ")");
+    }
+
+    /** */
+    RelCollation toCollation(List<Map<String, Object>> jsonFieldCollations) {
+        List<RelFieldCollation> fieldCollations = new ArrayList<>();
+        for (Map<String, Object> map : jsonFieldCollations)
+            fieldCollations.add(toFieldCollation(map));
+        return RelCollations.of(fieldCollations);
+    }
+
+    /** */
+    IgniteDistribution toDistribution(Object distribution) {
+        if (distribution instanceof String) {
+            switch ((String)distribution) {
+                case "single":
+                    return IgniteDistributions.single();
+                case "any":
+                    return IgniteDistributions.any();
+                case "broadcast":
+                    return IgniteDistributions.broadcast();
+                case "random":
+                    return IgniteDistributions.random();
+            }
+        }
+
+        Map<String, Object> map = (Map<String, Object>)distribution;
+
+        assert "hash".equals(map.get("type"));
+
+        DistributionFunction function = DistributionFunction.HashDistribution.INSTANCE;
+
+        Number cacheId = (Number)map.get("cacheId");
+
+        if (cacheId != null)
+            function = new AffinityDistribution(cacheId.intValue(), cacheId);
+
+        return IgniteDistributions.hash((List<Integer>)map.get("keys"), function);
+    }
+
+    /** */
+    RelDataType toType(RelDataTypeFactory typeFactory, Object o) {
+        if (o instanceof List) {
+            List<Map<String, Object>> jsonList = (List<Map<String, Object>>)o;
+            RelDataTypeFactory.Builder builder = typeFactory.builder();
+            for (Map<String, Object> jsonMap : jsonList)
+                builder.add((String)jsonMap.get("name"), toType(typeFactory, jsonMap));
+            return builder.build();
+        }
+        else if (o instanceof Map) {
+            Map<String, Object> map = (Map<String, Object>)o;
+            String clazz = (String)map.get("class");
+
+            if (clazz != null) {
+                RelDataType type = typeFactory.createJavaType(classForName(clazz, false));
+
+                if (Boolean.TRUE == map.get("nullable"))
+                    type = typeFactory.createTypeWithNullability(type, true);
+
+                return type;
+            }
+
+            Object fields = map.get("fields");
+
+            if (fields != null)
+                return toType(typeFactory, fields);
+            else {
+                SqlTypeName sqlTypeName = toEnum(map.get("type"));
+                Integer precision = (Integer)map.get("precision");
+                Integer scale = (Integer)map.get("scale");
+                if (SqlTypeName.INTERVAL_TYPES.contains(sqlTypeName)) {
+                    TimeUnit startUnit = sqlTypeName.getStartUnit();
+                    TimeUnit endUnit = sqlTypeName.getEndUnit();
+                    return typeFactory.createSqlIntervalType(
+                        new SqlIntervalQualifier(startUnit, endUnit, SqlParserPos.ZERO));
+                }
+                RelDataType type;
+                if (precision == null)
+                    type = typeFactory.createSqlType(sqlTypeName);
+                else if (scale == null)
+                    type = typeFactory.createSqlType(sqlTypeName, precision);
+                else
+                    type = typeFactory.createSqlType(sqlTypeName, precision, scale);
+
+                if (Boolean.TRUE == map.get("nullable"))
+                    type = typeFactory.createTypeWithNullability(type, true);
+
+                return type;
+            }
+        }
+        else {
+            SqlTypeName sqlTypeName = toEnum(o);
+            return typeFactory.createSqlType(sqlTypeName);
+        }
+    }
+
+    /** */
+    RexNode toRex(RelInput relInput, Object o) {
+        RelOptCluster cluster = relInput.getCluster();
+        RexBuilder rexBuilder = cluster.getRexBuilder();
+        if (o == null)
+            return null;
+        else if (o instanceof Map) {
+            Map map = (Map)o;
+            Map<String, Object> opMap = (Map)map.get("op");
+            RelDataTypeFactory typeFactory = cluster.getTypeFactory();
+            if (opMap != null) {
+                if (map.containsKey("class"))
+                    opMap.put("class", map.get("class"));
+                List operands = (List)map.get("operands");
+                List<RexNode> rexOperands = toRexList(relInput, operands);
+                Object jsonType = map.get("type");
+                Map window = (Map)map.get("window");
+                if (window != null) {
+                    SqlAggFunction operator = (SqlAggFunction)toOp(opMap);
+                    RelDataType type = toType(typeFactory, jsonType);
+                    List<RexNode> partitionKeys = new ArrayList<>();
+                    if (window.containsKey("partition"))
+                        partitionKeys = toRexList(relInput, (List)window.get("partition"));
+                    List<RexFieldCollation> orderKeys = new ArrayList<>();
+                    if (window.containsKey("order"))
+                        orderKeys = toRexFieldCollationList(relInput, (List)window.get("order"));
+                    RexWindowBound lowerBound;
+                    RexWindowBound upperBound;
+                    boolean physical;
+                    if (window.get("rows-lower") != null) {
+                        lowerBound = toRexWindowBound(relInput, (Map)window.get("rows-lower"));
+                        upperBound = toRexWindowBound(relInput, (Map)window.get("rows-upper"));
+                        physical = true;
+                    }
+                    else if (window.get("range-lower") != null) {
+                        lowerBound = toRexWindowBound(relInput, (Map)window.get("range-lower"));
+                        upperBound = toRexWindowBound(relInput, (Map)window.get("range-upper"));
+                        physical = false;
+                    }
+                    else {
+                        // No ROWS or RANGE clause
+                        lowerBound = null;
+                        upperBound = null;
+                        physical = false;
+                    }
+                    boolean distinct = (Boolean)map.get("distinct");
+                    return rexBuilder.makeOver(type, operator, rexOperands, partitionKeys,
+                        ImmutableList.copyOf(orderKeys), lowerBound, upperBound, physical,
+                        true, false, distinct, false);
+                }
+                else {
+                    SqlOperator operator = toOp(opMap);
+                    RelDataType type;
+                    if (jsonType != null)
+                        type = toType(typeFactory, jsonType);
+                    else
+                        type = rexBuilder.deriveReturnType(operator, rexOperands);
+                    return rexBuilder.makeCall(type, operator, rexOperands);
+                }
+            }
+            Integer input = (Integer)map.get("input");
+            if (input != null) {
+                // Check if it is a local ref.
+                if (map.containsKey("type")) {
+                    RelDataType type = toType(typeFactory, map.get("type"));
+                    return map.get("dynamic") == Boolean.TRUE
+                        ? rexBuilder.makeDynamicParam(type, input)
+                        : rexBuilder.makeLocalRef(type, input);
+                }
+
+                List<RelNode> inputNodes = relInput.getInputs();
+                int i = input;
+                for (RelNode inputNode : inputNodes) {
+                    RelDataType rowType = inputNode.getRowType();
+                    if (i < rowType.getFieldCount()) {
+                        RelDataTypeField field = rowType.getFieldList().get(i);
+                        return rexBuilder.makeInputRef(field.getType(), input);
+                    }
+                    i -= rowType.getFieldCount();
+                }
+                throw new RuntimeException("input field " + input + " is out of range");
+            }
+
+            String field = (String)map.get("field");
+            if (field != null) {
+                Object jsonExpr = map.get("expr");
+                RexNode expr = toRex(relInput, jsonExpr);
+                return rexBuilder.makeFieldAccess(expr, field, true);
+            }
+
+            String correl = (String)map.get("correl");
+            if (correl != null) {
+                RelDataType type = toType(typeFactory, map.get("type"));
+                return rexBuilder.makeCorrel(type, new CorrelationId(correl));
+            }
+
+            if (map.containsKey("literal")) {
+                Object literal = map.get("literal");
+                RelDataType type = toType(typeFactory, map.get("type"));
+
+                if (literal == null)
+                    return rexBuilder.makeNullLiteral(type);
+
+                if (type.getSqlTypeName() == SqlTypeName.SYMBOL)
+                    literal = toEnum(literal);
+
+                return rexBuilder.makeLiteral(literal, type, false);
+            }
+            
+            throw new UnsupportedOperationException("cannot convert to rex " + o);
+        }
+        else if (o instanceof Boolean)
+            return rexBuilder.makeLiteral((Boolean)o);
+        else if (o instanceof String)
+            return rexBuilder.makeLiteral((String)o);
+        else if (o instanceof Number) {
+            Number number = (Number)o;
+            if (number instanceof Double || number instanceof Float)
+                return rexBuilder.makeApproxLiteral(
+                    BigDecimal.valueOf(number.doubleValue()));
+            else
+                return rexBuilder.makeExactLiteral(
+                    BigDecimal.valueOf(number.longValue()));
+        }
+        else
+            throw new UnsupportedOperationException("cannot convert to rex " + o);
+    }
+
+    /** */
+    SqlOperator toOp(Map<String, Object> map) {
+        // in case different operator has the same kind, check with both name and kind.
+        String name = map.get("name").toString();
+        SqlKind sqlKind = toEnum(map.get("kind"));
+        SqlSyntax sqlSyntax = toEnum(map.get("syntax"));
+        List<SqlOperator> operators = new ArrayList<>();
+        SqlStdOperatorTable.instance().lookupOperatorOverloads(
+            new SqlIdentifier(name, new SqlParserPos(0, 0)),
+            null,
+            sqlSyntax,
+            operators,
+            SqlNameMatchers.liberal());
+        for (SqlOperator operator : operators)
+            if (operator.kind == sqlKind)
+                return operator;
+        String class_ = (String)map.get("class");
+        if (class_ != null)
+            return AvaticaUtils.instantiatePlugin(SqlOperator.class, class_);
+        return null;
+    }
+
+    /** */
+    <T> List<T> list() {
+        return new ArrayList<>();
+    }
+
+    /** */
+    <T> Map<String, T> map() {
+        return new LinkedHashMap<>();
+    }
+
+    /** */
+    private <T extends Enum<T>> T toEnum(Object o) {
+        if (o instanceof Map) {
+            Map<String, Object> map = (Map<String, Object>)o;
+            String class_ = (String)map.get("class");
+            String name = map.get("name").toString();
+            return Util.enumVal((Class<T>)classForName(class_, false), name);
+        }
+
+        assert o instanceof String && ENUM_BY_NAME.containsKey(o);
+
+        String name = (String)o;
+        return (T)ENUM_BY_NAME.get(name);
+    }
+
+    /** */
+    private RelFieldCollation toFieldCollation(Map<String, Object> map) {
+        Integer field = (Integer)map.get("field");
+        Direction direction = toEnum(map.get("direction"));
+        NullDirection nullDirection = toEnum(map.get("nulls"));
+        return new RelFieldCollation(field, direction, nullDirection);
+    }
+
+    /** */
+    private List<RexFieldCollation> toRexFieldCollationList(RelInput relInput, List<Map<String, Object>> order) {
+        if (order == null)
+            return null;
+
+        List<RexFieldCollation> list = new ArrayList<>();
+        for (Map<String, Object> o : order) {
+            RexNode expr = toRex(relInput, o.get("expr"));
+            Set<SqlKind> directions = new HashSet<>();
+            if (toEnum(o.get("direction")) == Direction.DESCENDING)
+                directions.add(SqlKind.DESCENDING);
+            if (toEnum(o.get("null-direction")) == NullDirection.FIRST)
+                directions.add(SqlKind.NULLS_FIRST);
+            else
+                directions.add(SqlKind.NULLS_LAST);
+            list.add(new RexFieldCollation(expr, directions));
+        }
+        return list;
+    }
+
+    /** */
+    private RexWindowBound toRexWindowBound(RelInput input, Map<String, Object> map) {
+        if (map == null)
+            return null;
+
+        String type = (String)map.get("type");
+        switch (type) {
+            case "CURRENT_ROW":
+                return RexWindowBound.create(
+                    SqlWindow.createCurrentRow(SqlParserPos.ZERO), null);
+            case "UNBOUNDED_PRECEDING":
+                return RexWindowBound.create(
+                    SqlWindow.createUnboundedPreceding(SqlParserPos.ZERO), null);
+            case "UNBOUNDED_FOLLOWING":
+                return RexWindowBound.create(
+                    SqlWindow.createUnboundedFollowing(SqlParserPos.ZERO), null);
+            case "PRECEDING":
+                RexNode precedingOffset = toRex(input, map.get("offset"));
+                return RexWindowBound.create(null,
+                    input.getCluster().getRexBuilder().makeCall(
+                        SqlWindow.PRECEDING_OPERATOR, precedingOffset));
+            case "FOLLOWING":
+                RexNode followingOffset = toRex(input, map.get("offset"));
+                return RexWindowBound.create(null,
+                    input.getCluster().getRexBuilder().makeCall(
+                        SqlWindow.FOLLOWING_OPERATOR, followingOffset));
+            default:
+                throw new UnsupportedOperationException("cannot convert type to rex window bound " + type);
+        }
+    }
+
+    /** */
+    private List<RexNode> toRexList(RelInput relInput, List<?> operands) {
+        List<RexNode> list = new ArrayList<>();
+        for (Object operand : operands)
+            list.add(toRex(relInput, operand));
+        return list;
+    }
+
+    /** */
+    private Object toJson(Enum<?> enum0) {
+        String key = enum0.getDeclaringClass().getSimpleName() + "#" + enum0.name();
+
+        if (ENUM_BY_NAME.get(key) == enum0)
+            return key;
+
+        Map<String, Object> map = map();
+        map.put("class", enum0.getDeclaringClass().getName());
+        map.put("name", enum0.name());
+        return map;
+    }
+
+    /** */
+    private Object toJson(AggregateCall node) {
+        Map<String, Object> map = map();
+        map.put("agg", toJson(node.getAggregation()));
+        map.put("type", toJson(node.getType()));
+        map.put("distinct", node.isDistinct());
+        map.put("operands", node.getArgList());
+        map.put("name", node.getName());
+        return map;
+    }
+
+    /** */
+    private Object toJson(RelDataType node) {
+        if (node instanceof JavaType) {
+            Map<String, Object> map = map();
+            map.put("class", ((JavaType)node).getJavaClass().getName());
+            if (node.isNullable())
+                map.put("nullable", true);
+
+            return map;
+        }
+        if (node.isStruct()) {
+            List<Object> list = list();
+            for (RelDataTypeField field : node.getFieldList())
+                list.add(toJson(field));
+            return list;
+        }
+        else {
+            Map<String, Object> map = map();
+            map.put("type", toJson(node.getSqlTypeName()));
+            if (node.isNullable())
+                map.put("nullable", true);
+            if (node.getSqlTypeName().allowsPrec())
+                map.put("precision", node.getPrecision());
+            if (node.getSqlTypeName().allowsScale())
+                map.put("scale", node.getScale());
+            return map;
+        }
+    }
+
+    /** */
+    private Object toJson(RelDataTypeField node) {
+        Map<String, Object> map;
+        if (node.getType().isStruct()) {
+            map = map();
+            map.put("fields", toJson(node.getType()));
+        }
+        else
+            map = (Map<String, Object>)toJson(node.getType());
+        map.put("name", node.getName());
+        return map;
+    }
+
+    /** */
+    private Object toJson(CorrelationId node) {
+        return node.getId();
+    }
+
+    /** */
+    private Object toJson(RexNode node) {
+        Map<String, Object> map;
+        switch (node.getKind()) {
+            case FIELD_ACCESS:
+                map = map();
+                RexFieldAccess fieldAccess = (RexFieldAccess)node;
+                map.put("field", fieldAccess.getField().getName());
+                map.put("expr", toJson(fieldAccess.getReferenceExpr()));
+
+                return map;
+            case LITERAL:
+                RexLiteral literal = (RexLiteral)node;
+                Object value = literal.getValue3();
+                map = map();
+                map.put("literal", toJson(value));
+                map.put("type", toJson(node.getType()));
+
+                return map;
+            case INPUT_REF:
+                map = map();
+                map.put("input", ((RexSlot)node).getIndex());
+                map.put("name", ((RexVariable)node).getName());
+
+                return map;
+            case DYNAMIC_PARAM:
+                map = map();
+                map.put("input", ((RexDynamicParam)node).getIndex());
+                map.put("name", ((RexVariable)node).getName());
+                map.put("type", toJson(node.getType()));
+                map.put("dynamic", true);
+
+                return map;
+            case LOCAL_REF:
+                map = map();
+                map.put("input", ((RexSlot)node).getIndex());
+                map.put("name", ((RexVariable)node).getName());
+                map.put("type", toJson(node.getType()));
+
+                return map;
+            case CORREL_VARIABLE:
+                map = map();
+                map.put("correl", ((RexVariable)node).getName());
+                map.put("type", toJson(node.getType()));
+
+                return map;
+            default:
+                if (node instanceof RexCall) {
+                    RexCall call = (RexCall)node;
+                    map = map();
+                    map.put("op", toJson(call.getOperator()));
+                    List<Object> list = list();
+
+                    for (RexNode operand : call.getOperands())
+                        list.add(toJson(operand));
+
+                    map.put("operands", list);
+
+                    if (node.getKind() == SqlKind.CAST)
+                        map.put("type", toJson(node.getType()));
+
+                    if (call.getOperator() instanceof SqlFunction)
+                        if (((SqlFunction)call.getOperator()).getFunctionType().isUserDefined()) {
+                            SqlOperator op = call.getOperator();
+                            map.put("class", op.getClass().getName());
+                            map.put("type", toJson(node.getType()));
+                            map.put("deterministic", op.isDeterministic());
+                            map.put("dynamic", op.isDynamicFunction());
+                        }
+
+                    if (call instanceof RexOver) {
+                        RexOver over = (RexOver)call;
+                        map.put("distinct", over.isDistinct());
+                        map.put("type", toJson(node.getType()));
+                        map.put("window", toJson(over.getWindow()));
+                    }
+
+                    return map;
+                }
+                throw new UnsupportedOperationException("unknown rex " + node);
+        }
+    }
+
+    /** */
+    private Object toJson(RexWindow window) {
+        Map<String, Object> map = map();
+        if (!window.partitionKeys.isEmpty())
+            map.put("partition", toJson(window.partitionKeys));
+        if (!window.orderKeys.isEmpty())
+            map.put("order", toJson(window.orderKeys));
+        if (window.getLowerBound() == null) {
+            // No ROWS or RANGE clause
+        }
+        else if (window.getUpperBound() == null)
+            if (window.isRows())
+                map.put("rows-lower", toJson(window.getLowerBound()));
+            else
+                map.put("range-lower", toJson(window.getLowerBound()));
+        else if (window.isRows()) {
+            map.put("rows-lower", toJson(window.getLowerBound()));
+            map.put("rows-upper", toJson(window.getUpperBound()));
+        }
+        else {
+            map.put("range-lower", toJson(window.getLowerBound()));
+            map.put("range-upper", toJson(window.getUpperBound()));
+        }
+        return map;
+    }
+
+    /** */
+    private Object toJson(DistributionTrait distribution) {
+        Type type = distribution.getType();
+
+        switch (type) {
+            case ANY:
+            case BROADCAST_DISTRIBUTED:
+            case RANDOM_DISTRIBUTED:
+            case SINGLETON:
+
+                return type.shortName;
+            case HASH_DISTRIBUTED:
+                Map<String, Object> map = map();
+                List<Object> keys = list();
+                for (Integer key : distribution.getKeys())
+                    keys.add(toJson(key));
+
+                map.put("keys", keys);
+
+                if (distribution.function() instanceof AffinityDistribution)
+                    map.put("cacheId", ((AffinityDistribution)distribution.function()).cacheId());
+
+                return map;
+            default:
+                throw new AssertionError("Unexpected type: " + type);
+        }
+    }
+
+    /** */
+    private Object toJson(RelCollationImpl node) {
+        List<Object> list = list();
+        for (RelFieldCollation fieldCollation : node.getFieldCollations()) {
+            Map<String, Object> map = map();
+            map.put("field", fieldCollation.getFieldIndex());
+            map.put("direction", toJson(fieldCollation.getDirection()));
+            map.put("nulls", toJson(fieldCollation.nullDirection));
+            list.add(map);
+        }
+        return list;
+    }
+
+    /** */
+    private Object toJson(RexFieldCollation collation) {
+        Map<String, Object> map = map();
+        map.put("expr", toJson(collation.left));
+        map.put("direction", toJson(collation.getDirection()));
+        map.put("null-direction", toJson(collation.getNullDirection()));
+        return map;
+    }
+
+    /** */
+    private Object toJson(RexWindowBound windowBound) {
+        Map<String, Object> map = map();
+        if (windowBound.isCurrentRow())
+            map.put("type", "CURRENT_ROW");
+        else if (windowBound.isUnbounded())
+            map.put("type", windowBound.isPreceding() ? "UNBOUNDED_PRECEDING" : "UNBOUNDED_FOLLOWING");
+        else {
+            map.put("type", windowBound.isPreceding() ? "PRECEDING" : "FOLLOWING");
+            map.put("offset", toJson(windowBound.getOffset()));
+        }
+        return map;
+    }
+
+    /** */
+    private Object toJson(SqlOperator operator) {
+        // User-defined operators are not yet handled.
+        Map map = map();
+        map.put("name", operator.getName());
+        map.put("kind", toJson(operator.kind));
+        map.put("syntax", toJson(operator.getSyntax()));
+        return map;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelJsonReader.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelJsonReader.java
new file mode 100644
index 0000000..5c000e0
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelJsonReader.java
@@ -0,0 +1,310 @@
+/*
+ * 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.externalize;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableList;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.function.Function;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelOptSchema;
+import org.apache.calcite.plan.RelOptTable;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.RelDistribution;
+import org.apache.calcite.rel.RelInput;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.sql.SqlAggFunction;
+import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.calcite.util.Pair;
+import org.apache.calcite.util.Util;
+import org.apache.ignite.IgniteException;
+
+/** */
+@SuppressWarnings({"rawtypes", "unchecked"})
+public class RelJsonReader {
+    /** */
+    private static final TypeReference<LinkedHashMap<String, Object>> TYPE_REF =
+        new TypeReference<LinkedHashMap<String, Object>>() {};
+
+    /** */
+    private final RelOptCluster cluster;
+
+    /** */
+    private final RelOptSchema relOptSchema;
+
+    /** */
+    private final RelJson relJson = new RelJson();
+
+    /** */
+    private final Map<String, RelNode> relMap = new LinkedHashMap<>();
+
+    /** */
+    private RelNode lastRel;
+
+    /** */
+    public RelJsonReader(RelOptCluster cluster, RelOptSchema relOptSchema) {
+        this.cluster = cluster;
+        this.relOptSchema = relOptSchema;
+    }
+
+    /** */
+    public RelNode read(String s) {
+        try {
+            lastRel = null;
+            Map<String, Object> o = new ObjectMapper().readValue(s, TYPE_REF);
+            List<Map<String, Object>> rels = (List)o.get("rels");
+            readRels(rels);
+            return lastRel;
+        }
+        catch (JsonProcessingException e) {
+            throw new IgniteException(e);
+        }
+    }
+
+    /** */
+    private void readRels(List<Map<String, Object>> jsonRels) {
+        for (Map<String, Object> jsonRel : jsonRels)
+            readRel(jsonRel);
+    }
+
+    /** */
+    private void readRel(Map<String, Object> jsonRel) {
+        String id = (String)jsonRel.get("id");
+        String type = (String)jsonRel.get("relOp");
+        Function<RelInput, RelNode> factory = relJson.factory(type);
+        RelNode rel = factory.apply(new RelInputImpl(jsonRel));
+        relMap.put(id, rel);
+        lastRel = rel;
+    }
+
+    /** */
+    private class RelInputImpl implements RelInput {
+        /** */
+        private final Map<String, Object> jsonRel;
+
+        /** */
+        private RelInputImpl(Map<String, Object> jsonRel) {
+            this.jsonRel = jsonRel;
+        }
+
+        /** {@inheritDoc} */
+        @Override public RelOptCluster getCluster() {
+            return cluster;
+        }
+
+        /** {@inheritDoc} */
+        @Override public RelTraitSet getTraitSet() {
+            return cluster.traitSet();
+        }
+
+        /** {@inheritDoc} */
+        @Override public RelOptTable getTable(String table) {
+            List<String> list = getStringList(table);
+            return relOptSchema.getTableForMember(list);
+        }
+
+        /** {@inheritDoc} */
+        @Override public RelNode getInput() {
+            List<RelNode> inputs = getInputs();
+            assert inputs.size() == 1;
+            return inputs.get(0);
+        }
+
+        /** {@inheritDoc} */
+        @Override public List<RelNode> getInputs() {
+            List<String> jsonInputs = getStringList("inputs");
+            if (jsonInputs == null)
+                return ImmutableList.of(lastRel);
+            List<RelNode> inputs = new ArrayList<>();
+            for (String jsonInput : jsonInputs)
+                inputs.add(lookupInput(jsonInput));
+            return inputs;
+        }
+
+        /** {@inheritDoc} */
+        @Override public RexNode getExpression(String tag) {
+            return relJson.toRex(this, jsonRel.get(tag));
+        }
+
+        /** {@inheritDoc} */
+        @Override public ImmutableBitSet getBitSet(String tag) {
+            return ImmutableBitSet.of(getIntegerList(tag));
+        }
+
+        /** {@inheritDoc} */
+        @Override public List<ImmutableBitSet> getBitSetList(String tag) {
+            List<List<Integer>> list = getIntegerListList(tag);
+            if (list == null)
+                return null;
+            ImmutableList.Builder<ImmutableBitSet> builder =
+                ImmutableList.builder();
+            for (List<Integer> integers : list)
+                builder.add(ImmutableBitSet.of(integers));
+            return builder.build();
+        }
+
+        /** {@inheritDoc} */
+        @Override public List<String> getStringList(String tag) {
+            return (List<String>)jsonRel.get(tag);
+        }
+
+        /** {@inheritDoc} */
+        @Override public List<Integer> getIntegerList(String tag) {
+            return (List<Integer>)jsonRel.get(tag);
+        }
+
+        /** {@inheritDoc} */
+        @Override public List<List<Integer>> getIntegerListList(String tag) {
+            return (List<List<Integer>>)jsonRel.get(tag);
+        }
+
+        /** {@inheritDoc} */
+        @Override public List<AggregateCall> getAggregateCalls(String tag) {
+            List<Map<String, Object>> jsonAggs = (List)jsonRel.get(tag);
+            List<AggregateCall> inputs = new ArrayList<>();
+            for (Map<String, Object> jsonAggCall : jsonAggs)
+                inputs.add(toAggCall(jsonAggCall));
+            return inputs;
+        }
+
+        /** {@inheritDoc} */
+        @Override public Object get(String tag) {
+            return jsonRel.get(tag);
+        }
+
+        /** {@inheritDoc} */
+        @Override public String getString(String tag) {
+            return (String)jsonRel.get(tag);
+        }
+
+        /** {@inheritDoc} */
+        @Override public float getFloat(String tag) {
+            return ((Number)jsonRel.get(tag)).floatValue();
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean getBoolean(String tag, boolean default_) {
+            Boolean b = (Boolean)jsonRel.get(tag);
+            return b != null ? b : default_;
+        }
+
+        /** {@inheritDoc} */
+        @Override public <E extends Enum<E>> E getEnum(String tag, Class<E> enumClass) {
+            return Util.enumVal(enumClass,
+                getString(tag).toUpperCase(Locale.ROOT));
+        }
+
+        /** {@inheritDoc} */
+        @Override public List<RexNode> getExpressionList(String tag) {
+            List<Object> jsonNodes = (List)jsonRel.get(tag);
+            List<RexNode> nodes = new ArrayList<>();
+            for (Object jsonNode : jsonNodes)
+                nodes.add(relJson.toRex(this, jsonNode));
+            return nodes;
+        }
+
+        /** {@inheritDoc} */
+        @Override public RelDataType getRowType(String tag) {
+            Object o = jsonRel.get(tag);
+            return relJson.toType(cluster.getTypeFactory(), o);
+        }
+
+        /** {@inheritDoc} */
+        @Override public RelDataType getRowType(String expressionsTag, String fieldsTag) {
+            List<RexNode> expressionList = getExpressionList(expressionsTag);
+            List<String> names =
+                (List<String>)get(fieldsTag);
+            return cluster.getTypeFactory().createStructType(
+                new AbstractList<Map.Entry<String, RelDataType>>() {
+                    @Override public Map.Entry<String, RelDataType> get(int index) {
+                        return Pair.of(names.get(index),
+                            expressionList.get(index).getType());
+                    }
+
+                    @Override public int size() {
+                        return names.size();
+                    }
+                });
+        }
+
+        /** {@inheritDoc} */
+        @Override public RelCollation getCollation() {
+            return relJson.toCollation((List)get("collation"));
+        }
+
+        /** {@inheritDoc} */
+        @Override public RelDistribution getDistribution() {
+            return relJson.toDistribution(get("distribution"));
+        }
+
+        /** {@inheritDoc} */
+        @Override public ImmutableList<ImmutableList<RexLiteral>> getTuples(String tag) {
+            List<List> jsonTuples = (List)get(tag);
+            ImmutableList.Builder<ImmutableList<RexLiteral>> builder =
+                ImmutableList.builder();
+            for (List jsonTuple : jsonTuples)
+                builder.add(getTuple(jsonTuple));
+            return builder.build();
+        }
+
+        /** */
+        private RelNode lookupInput(String jsonInput) {
+            RelNode node = relMap.get(jsonInput);
+            if (node == null)
+                throw new RuntimeException("unknown id " + jsonInput
+                    + " for relational expression");
+            return node;
+        }
+
+        /** */
+        private ImmutableList<RexLiteral> getTuple(List jsonTuple) {
+            ImmutableList.Builder<RexLiteral> builder =
+                ImmutableList.builder();
+            for (Object jsonValue : jsonTuple)
+                builder.add((RexLiteral)relJson.toRex(this, jsonValue));
+            return builder.build();
+        }
+
+        /** */
+        private AggregateCall toAggCall(Map<String, Object> jsonAggCall) {
+            Map<String, Object> aggMap = (Map)jsonAggCall.get("agg");
+            SqlAggFunction aggregation = (SqlAggFunction)relJson.toOp(aggMap);
+            Boolean distinct = (Boolean)jsonAggCall.get("distinct");
+            List<Integer> operands = (List<Integer>)jsonAggCall.get("operands");
+            Integer filterOperand = (Integer)jsonAggCall.get("filter");
+            RelDataType type = relJson.toType(cluster.getTypeFactory(), jsonAggCall.get("type"));
+            String name = (String)jsonAggCall.get("name");
+            return AggregateCall.create(aggregation, distinct, false, false, operands,
+                filterOperand == null ? -1 : filterOperand,
+                RelCollations.EMPTY,
+                type, name);
+        }
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelJsonWriter.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelJsonWriter.java
new file mode 100644
index 0000000..42b5f32
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelJsonWriter.java
@@ -0,0 +1,161 @@
+/*
+ * 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.externalize;
+
+import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.RelWriter;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.sql.SqlExplainLevel;
+import org.apache.calcite.util.Pair;
+import org.apache.ignite.IgniteException;
+
+/**
+ * Callback for a relational expression to dump itself as JSON.
+ *
+ * @see RelJsonReader
+ */
+public class RelJsonWriter implements RelWriter {
+    /** */
+    private final RelJson relJson = new RelJson();
+
+    /** */
+    private final List<Object> relList = new ArrayList<>();
+
+    /** */
+    private final Map<RelNode, String> relIdMap = new IdentityHashMap<>();
+
+    /** */
+    private final boolean pretty;
+
+    /** */
+    private String previousId;
+
+    /** */
+    private List<Pair<String, Object>> items = new ArrayList<>();
+
+    /** */
+    public RelJsonWriter() {
+        this(false);
+    }
+
+    /** */
+    public RelJsonWriter(boolean pretty) {
+        this.pretty = pretty;
+    }
+
+    /** {@inheritDoc} */
+    @Override public final void explain(RelNode rel, List<Pair<String, Object>> valueList) {
+        try (RexNode.Closeable ignored = withRexNormalize()) {
+            explain_(rel, valueList);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public SqlExplainLevel getDetailLevel() {
+        return SqlExplainLevel.ALL_ATTRIBUTES;
+    }
+
+    /** {@inheritDoc} */
+    @Override public RelWriter item(String term, Object value) {
+        items.add(Pair.of(term, value));
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override public RelWriter done(RelNode node) {
+        List<Pair<String, Object>> current0 = items;
+        items = new ArrayList<>();
+        explain_(node, current0);
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean nest() {
+        return true;
+    }
+
+    /** */
+    public String asString() {
+        try (RexNode.Closeable ignored = withRexNormalize()) {
+            StringWriter writer = new StringWriter();
+            ObjectMapper mapper = new ObjectMapper();
+
+            ObjectWriter writer0 = pretty
+                ? mapper.writer(new DefaultPrettyPrinter())
+                : mapper.writer();
+
+            writer0
+                .withRootName("rels")
+                .writeValue(writer, relList);
+
+            return writer.toString();
+        }
+        catch (IOException e) {
+            throw new IgniteException(e);
+        }
+    }
+
+    /** */
+    private void explain_(RelNode rel, List<Pair<String, Object>> values) {
+        final Map<String, Object> map = relJson.map();
+
+        map.put("id", null); // ensure that id is the first attribute
+        map.put("relOp", relJson.classToTypeName(rel.getClass()));
+
+        for (Pair<String, Object> value : values) {
+            if (value.right instanceof RelNode)
+                continue;
+
+            map.put(value.left, relJson.toJson(value.right));
+        }
+        // omit 'inputs: ["3"]' if "3" is the preceding rel
+        final List<Object> list = explainInputs(rel.getInputs());
+        if (list.size() != 1 || !list.get(0).equals(previousId)) {
+            map.put("inputs", list);
+        }
+
+        final String id = Integer.toString(relIdMap.size());
+        relIdMap.put(rel, id);
+        map.put("id", id);
+
+        relList.add(map);
+        previousId = id;
+    }
+
+    /** */
+    private List<Object> explainInputs(List<RelNode> inputs) {
+        final List<Object> list = relJson.list();
+        for (RelNode input : inputs) {
+            String id = relIdMap.get(input);
+            if (id == null) {
+                input.explain(this);
+                id = previousId;
+            }
+            list.add(id);
+        }
+        return list;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/message/MessageType.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/message/MessageType.java
index 24ee972..0f90bfd 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/message/MessageType.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/message/MessageType.java
@@ -18,6 +18,8 @@
 package org.apache.ignite.internal.processors.query.calcite.message;
 
 import java.util.function.Supplier;
+import org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping;
+import org.apache.ignite.internal.processors.query.calcite.prepare.FragmentDescription;
 
 /**
  *
@@ -29,7 +31,9 @@ public enum MessageType {
     QUERY_BATCH_MESSAGE(303, QueryBatchMessage::new),
     QUERY_ACKNOWLEDGE_MESSAGE(304, QueryBatchAcknowledgeMessage::new),
     QUERY_INBOX_CANCEL_MESSAGE(305, InboxCancelMessage::new),
-    GENERIC_ROW_MESSAGE(306, GenericRowMessage::new);
+    GENERIC_ROW_MESSAGE(306, GenericRowMessage::new),
+    NODES_MAPPING(350, NodesMapping::new),
+    FRAGMENT_DESCRIPTION(351, FragmentDescription::new);
 
     /** */
     private final int directType;
@@ -83,6 +87,10 @@ public enum MessageType {
                 return QUERY_INBOX_CANCEL_MESSAGE.newMessage();
             case 306:
                 return GENERIC_ROW_MESSAGE.newMessage();
+            case 350:
+                return NODES_MAPPING.newMessage();
+            case 351:
+                return FRAGMENT_DESCRIPTION.newMessage();
             default:
                 return null;
         }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/message/QueryStartRequest.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/message/QueryStartRequest.java
index 5d7b675..7c71cca 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/message/QueryStartRequest.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/message/QueryStartRequest.java
@@ -22,7 +22,7 @@ import java.util.UUID;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.GridDirectTransient;
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
-import org.apache.ignite.internal.processors.query.calcite.serialize.PhysicalRel;
+import org.apache.ignite.internal.processors.query.calcite.prepare.FragmentDescription;
 import org.apache.ignite.marshaller.Marshaller;
 import org.apache.ignite.plugin.extensions.communication.MessageReader;
 import org.apache.ignite.plugin.extensions.communication.MessageWriter;
@@ -38,20 +38,13 @@ public class QueryStartRequest implements MarshalableMessage {
     private UUID queryId;
 
     /** */
-    private long fragmentId;
-
-    /** */
-    private int[] partitions;
-
-    /** */
     private AffinityTopologyVersion version;
 
     /** */
-    @GridDirectTransient
-    private PhysicalRel root;
+    private FragmentDescription fragmentDescription;
 
     /** */
-    private byte[] rootBytes;
+    private String root;
 
     /** */
     @GridDirectTransient
@@ -61,20 +54,18 @@ public class QueryStartRequest implements MarshalableMessage {
     private byte[] paramsBytes;
 
     /** */
-    public QueryStartRequest(UUID queryId, long fragmentId, String schema, PhysicalRel root, AffinityTopologyVersion version, int[] partitions, Object[] params) {
+    public QueryStartRequest(UUID queryId, String schema, String root, AffinityTopologyVersion version,
+        FragmentDescription fragmentDescription, Object[] params) {
         this.schema = schema;
         this.queryId = queryId;
-        this.fragmentId = fragmentId;
-        this.partitions = partitions;
+        this.fragmentDescription = fragmentDescription;
         this.version = version;
         this.root = root;
         this.params = params;
     }
 
     /** */
-    QueryStartRequest() {
-
-    }
+    QueryStartRequest() {}
 
     /**
      * @return Schema name.
@@ -91,17 +82,10 @@ public class QueryStartRequest implements MarshalableMessage {
     }
 
     /**
-     * @return Fragment ID.
+     * @return Fragment description.
      */
-    public long fragmentId() {
-        return fragmentId;
-    }
-
-    /**
-     * @return Partitions.
-     */
-    public int[] partitions() {
-        return partitions;
+    public FragmentDescription fragmentDescription() {
+        return fragmentDescription;
     }
 
     /**
@@ -114,7 +98,7 @@ public class QueryStartRequest implements MarshalableMessage {
     /**
      * @return Fragment plan.
      */
-    public PhysicalRel root() {
+    public String root() {
         return root;
     }
 
@@ -127,20 +111,18 @@ public class QueryStartRequest implements MarshalableMessage {
 
     /** {@inheritDoc} */
     @Override public void prepareMarshal(Marshaller marshaller) throws IgniteCheckedException {
-        if (rootBytes == null && root != null)
-            rootBytes = marshaller.marshal(root);
-
         if (paramsBytes == null && params != null)
             paramsBytes = marshaller.marshal(params);
+
+        fragmentDescription.prepareMarshal(marshaller);
     }
 
     /** {@inheritDoc} */
     @Override public void prepareUnmarshal(Marshaller marshaller, ClassLoader loader) throws IgniteCheckedException {
-        if (root == null && rootBytes != null)
-            root = marshaller.unmarshal(rootBytes, loader);
-
         if (params == null && paramsBytes != null)
             params = marshaller.unmarshal(paramsBytes, loader);
+
+        fragmentDescription.prepareUnmarshal(marshaller, loader);
     }
 
     /** {@inheritDoc} */
@@ -156,7 +138,7 @@ public class QueryStartRequest implements MarshalableMessage {
 
         switch (writer.state()) {
             case 0:
-                if (!writer.writeLong("fragmentId", fragmentId))
+                if (!writer.writeMessage("fragmentDescription", fragmentDescription))
                     return false;
 
                 writer.incrementState();
@@ -168,30 +150,24 @@ public class QueryStartRequest implements MarshalableMessage {
                 writer.incrementState();
 
             case 2:
-                if (!writer.writeIntArray("partitions", partitions))
+                if (!writer.writeUuid("queryId", queryId))
                     return false;
 
                 writer.incrementState();
 
             case 3:
-                if (!writer.writeByteArray("rootBytes", rootBytes))
+                if (!writer.writeString("root", root))
                     return false;
 
                 writer.incrementState();
 
             case 4:
-                if (!writer.writeUuid("queryId", queryId))
-                    return false;
-
-                writer.incrementState();
-
-            case 5:
                 if (!writer.writeString("schema", schema))
                     return false;
 
                 writer.incrementState();
 
-            case 6:
+            case 5:
                 if (!writer.writeAffinityTopologyVersion("version", version))
                     return false;
 
@@ -211,7 +187,7 @@ public class QueryStartRequest implements MarshalableMessage {
 
         switch (reader.state()) {
             case 0:
-                fragmentId = reader.readLong("fragmentId");
+                fragmentDescription = reader.readMessage("fragmentDescription");
 
                 if (!reader.isLastRead())
                     return false;
@@ -227,7 +203,7 @@ public class QueryStartRequest implements MarshalableMessage {
                 reader.incrementState();
 
             case 2:
-                partitions = reader.readIntArray("partitions");
+                queryId = reader.readUuid("queryId");
 
                 if (!reader.isLastRead())
                     return false;
@@ -235,7 +211,7 @@ public class QueryStartRequest implements MarshalableMessage {
                 reader.incrementState();
 
             case 3:
-                rootBytes = reader.readByteArray("rootBytes");
+                root = reader.readString("root");
 
                 if (!reader.isLastRead())
                     return false;
@@ -243,14 +219,6 @@ public class QueryStartRequest implements MarshalableMessage {
                 reader.incrementState();
 
             case 4:
-                queryId = reader.readUuid("queryId");
-
-                if (!reader.isLastRead())
-                    return false;
-
-                reader.incrementState();
-
-            case 5:
                 schema = reader.readString("schema");
 
                 if (!reader.isLastRead())
@@ -258,7 +226,7 @@ public class QueryStartRequest implements MarshalableMessage {
 
                 reader.incrementState();
 
-            case 6:
+            case 5:
                 version = reader.readAffinityTopologyVersion("version");
 
                 if (!reader.isLastRead())
@@ -278,6 +246,6 @@ public class QueryStartRequest implements MarshalableMessage {
 
     /** {@inheritDoc} */
     @Override public byte fieldsCount() {
-        return 7;
+        return 6;
     }
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/FragmentInfo.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/FragmentInfo.java
deleted file mode 100644
index c9bc75a..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/FragmentInfo.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.metadata;
-
-import com.google.common.collect.ImmutableList;
-import org.apache.ignite.internal.processors.query.calcite.prepare.RelTargetAware;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
-import org.apache.ignite.internal.util.typedef.F;
-
-/**
- * Collects meta information about a query fragment, mainly it is data
- * location and a list of nodes, capable to execute the fragment on.
- */
-public class FragmentInfo {
-    /** */
-    private final NodesMapping mapping;
-
-    /** */
-    private final ImmutableList<RelTargetAware> targetAwareList;
-
-    /**
-     * Constructs Values leaf fragment info.
-     */
-    public FragmentInfo() {
-        this(ImmutableList.of(), null);
-    }
-
-    /**
-     * Constructs Receiver leaf fragment info.
-     *
-     * @param targetAware A node that needs target information.
-     */
-    public FragmentInfo(RelTargetAware targetAware) {
-        this(ImmutableList.of(targetAware), null);
-    }
-
-    /**
-     * Constructs Scan leaf fragment info.
-     *
-     * @param mapping Nodes mapping, describing where interested data placed.
-     */
-    public FragmentInfo(NodesMapping mapping) {
-        this(null, mapping);
-    }
-
-    /**
-     * Used on merge of two relational node tree edges.
-     *
-     * @param targetAwareList nodes that need target information.
-     * @param mapping Nodes mapping, describing where interested data placed.
-     */
-    public FragmentInfo(ImmutableList<RelTargetAware> targetAwareList, NodesMapping mapping) {
-        this.targetAwareList = targetAwareList;
-        this.mapping = mapping;
-    }
-
-    /**
-     * @return Nodes mapping, describing where interested data placed.
-     */
-    public NodesMapping mapping() {
-        return mapping;
-    }
-
-    /**
-     * @return {@code True} if the fragment has nodes mapping.
-     */
-    public boolean mapped() {
-        return mapping != null;
-    }
-
-    /**
-     * @return Nodes that need target information..
-     */
-    public ImmutableList<RelTargetAware> targetAwareList() {
-        return targetAwareList;
-    }
-
-    /**
-     * Merges two FragmentInfo objects.
-     * @param other FragmentInfo to merge with.
-     * @return Resulting FragmentInfo.
-     * @throws LocationMappingException in case there is no nodes capable to execute a query fragment.
-     */
-    public FragmentInfo merge(FragmentInfo other) throws LocationMappingException {
-        return new FragmentInfo(
-            merge(targetAwareList(), other.targetAwareList()),
-            merge(mapping(), other.mapping()));
-    }
-
-    /**
-     * Prunes involved partitions (hence nodes, involved in query execution) on the basis of filter,
-     * its distribution, query parameters and original nodes mapping.
-     * @param filter Filter.
-     * @return Resulting fragment info.
-     */
-    public FragmentInfo prune(IgniteFilter filter) {
-        if (mapping != null) {
-            NodesMapping newMapping = mapping.prune(filter);
-
-            if (newMapping != mapping)
-                return new FragmentInfo(targetAwareList, newMapping);
-        }
-
-        return this;
-    }
-
-    /** */
-    private static NodesMapping merge(NodesMapping left, NodesMapping right) throws LocationMappingException {
-        if (left == null)
-            return right;
-        if (right == null)
-            return left;
-
-        return left.mergeWith(right);
-    }
-
-    /** */
-    private static <T> ImmutableList<T> merge(ImmutableList<T> left, ImmutableList<T> right) {
-        if (F.isEmpty(left))
-            return right;
-        if (F.isEmpty(right))
-            return left;
-
-        return ImmutableList.<T>builder().addAll(left).addAll(right).build();
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdFragmentInfo.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdNodesMapping.java
similarity index 64%
rename from modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdFragmentInfo.java
rename to modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdNodesMapping.java
index b860711..087daf8 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdFragmentInfo.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdNodesMapping.java
@@ -29,7 +29,7 @@ import org.apache.calcite.rel.metadata.MetadataHandler;
 import org.apache.calcite.rel.metadata.ReflectiveRelMetadataProvider;
 import org.apache.calcite.rel.metadata.RelMetadataProvider;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
-import org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMetadata.FragmentMetadata;
+import org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMetadata.NodesMappingMetadata;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteReceiver;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
@@ -39,49 +39,48 @@ import org.apache.ignite.internal.processors.query.calcite.util.Commons;
 import org.apache.ignite.internal.processors.query.calcite.util.IgniteMethod;
 
 /**
- * Implementation class for {@link RelMetadataQueryEx#getFragmentInfo(RelNode)} method call.
+ * Implementation class for {@link RelMetadataQueryEx#nodesMapping(RelNode)} method call.
  */
-public class IgniteMdFragmentInfo implements MetadataHandler<FragmentMetadata> {
+public class IgniteMdNodesMapping implements MetadataHandler<NodesMappingMetadata> {
     /**
-     * Metadata provider, responsible for fragment meta information request. It uses this implementation class under the hood.
+     * Metadata provider, responsible for nodes mapping request. It uses this implementation class under the hood.
      */
     public static final RelMetadataProvider SOURCE =
         ReflectiveRelMetadataProvider.reflectiveSource(
-            IgniteMethod.FRAGMENT_INFO.method(), new IgniteMdFragmentInfo());
+            IgniteMethod.NODES_MAPPING.method(), new IgniteMdNodesMapping());
 
     /** {@inheritDoc} */
-    @Override public MetadataDef<FragmentMetadata> getDef() {
-        return FragmentMetadata.DEF;
+    @Override public MetadataDef<NodesMappingMetadata> getDef() {
+        return NodesMappingMetadata.DEF;
     }
 
     /**
-     * Requests meta information about a fragment with the given relation node at the head of the fragment, mainly it is data
-     * location and a list of nodes, capable to execute the fragment on.
+     * Requests meta information about nodes capable to execute a query over particular partitions.
      *
      * @param rel Relational node.
      * @param mq Metadata query instance. Used to request appropriate metadata from node children.
-     * @return Fragment meta information.
+     * @return Nodes mapping, representing a list of nodes capable to execute a query over particular partitions.
      */
-    public FragmentInfo fragmentInfo(RelNode rel, RelMetadataQuery mq) {
+    public NodesMapping nodesMapping(RelNode rel, RelMetadataQuery mq) {
         throw new AssertionError();
     }
 
     /**
-     * See {@link IgniteMdFragmentInfo#fragmentInfo(RelNode, RelMetadataQuery)}
+     * See {@link IgniteMdNodesMapping#nodesMapping(RelNode, RelMetadataQuery)}
      */
-    public FragmentInfo fragmentInfo(RelSubset rel, RelMetadataQuery mq) {
+    public NodesMapping nodesMapping(RelSubset rel, RelMetadataQuery mq) {
         throw new AssertionError();
     }
 
     /**
-     * See {@link IgniteMdFragmentInfo#fragmentInfo(RelNode, RelMetadataQuery)}
+     * See {@link IgniteMdNodesMapping#nodesMapping(RelNode, RelMetadataQuery)}
      */
-    public FragmentInfo fragmentInfo(SingleRel rel, RelMetadataQuery mq) {
-        return _fragmentInfo(rel.getInput(), mq);
+    public NodesMapping nodesMapping(SingleRel rel, RelMetadataQuery mq) {
+        return _nodesMapping(rel.getInput(), mq);
     }
 
     /**
-     * See {@link IgniteMdFragmentInfo#fragmentInfo(RelNode, RelMetadataQuery)}
+     * See {@link IgniteMdNodesMapping#nodesMapping(RelNode, RelMetadataQuery)}
      *
      * {@link LocationMappingException} may be thrown on two children nodes locations merge. This means
      * that the fragment (which part the parent node is) cannot be executed on any node and additional exchange
@@ -89,20 +88,20 @@ public class IgniteMdFragmentInfo implements MetadataHandler<FragmentMetadata> {
      * exchange. After the exchange is put into the fragment and the fragment is split into two ones, fragment meta
      * information will be recalculated for all fragments.
      */
-    public FragmentInfo fragmentInfo(BiRel rel, RelMetadataQuery mq) {
-        FragmentInfo left = _fragmentInfo(rel.getLeft(), mq);
-        FragmentInfo right = _fragmentInfo(rel.getRight(), mq);
+    public NodesMapping nodesMapping(BiRel rel, RelMetadataQuery mq) {
+        NodesMapping left = _nodesMapping(rel.getLeft(), mq);
+        NodesMapping right = _nodesMapping(rel.getRight(), mq);
 
         try {
-            return left.merge(right);
+            return merge(left, right);
         }
         catch (LocationMappingException e) {
             String msg = "Failed to calculate physical distribution";
 
             // a replicated cache is cheaper to redistribute
-            if (!left.mapping().hasPartitionedCaches())
+            if (!left.hasPartitionedCaches())
                 throw new OptimisticPlanningException(msg, rel.getLeft(), e);
-            else if (!right.mapping().hasPartitionedCaches())
+            else if (!right.hasPartitionedCaches())
                 throw new OptimisticPlanningException(msg, rel.getRight(), e);
             else {
                 // both sub-trees have partitioned sources, less cost is better
@@ -117,7 +116,7 @@ public class IgniteMdFragmentInfo implements MetadataHandler<FragmentMetadata> {
     }
 
     /**
-     * See {@link IgniteMdFragmentInfo#fragmentInfo(RelNode, RelMetadataQuery)}
+     * See {@link IgniteMdNodesMapping#nodesMapping(RelNode, RelMetadataQuery)}
      *
      * {@link LocationMappingException} may be thrown on two children nodes locations merge. This means
      * that the fragment (which part the parent node is) cannot be executed on any node and additional exchange
@@ -125,14 +124,14 @@ public class IgniteMdFragmentInfo implements MetadataHandler<FragmentMetadata> {
      * exchange. After the exchange is put into the fragment and the fragment is split into two ones, fragment meta
      * information will be recalculated for all fragments.
      */
-    public FragmentInfo fragmentInfo(SetOp rel, RelMetadataQuery mq) {
-        FragmentInfo res = new FragmentInfo();
+    public NodesMapping nodesMapping(SetOp rel, RelMetadataQuery mq) {
+        NodesMapping res = null;
 
         for (RelNode input : rel.getInputs()) {
-            FragmentInfo inputInfo = _fragmentInfo(input, mq);
+            NodesMapping inputMapping = _nodesMapping(input, mq);
 
             try {
-                res = res.merge(inputInfo);
+                res = merge(res, inputMapping);
             }
             catch (LocationMappingException e) {
                 throw new OptimisticPlanningException("Failed to calculate physical distribution", input, e);
@@ -143,33 +142,35 @@ public class IgniteMdFragmentInfo implements MetadataHandler<FragmentMetadata> {
     }
 
     /**
-     * See {@link IgniteMdFragmentInfo#fragmentInfo(RelNode, RelMetadataQuery)}
+     * See {@link IgniteMdNodesMapping#nodesMapping(RelNode, RelMetadataQuery)}
      *
      * Prunes involved partitions (hence nodes, involved in query execution) if possible.
      */
-    public FragmentInfo fragmentInfo(IgniteFilter rel, RelMetadataQuery mq) {
-        return _fragmentInfo(rel.getInput(), mq).prune(rel);
+    public NodesMapping nodesMapping(IgniteFilter rel, RelMetadataQuery mq) {
+        NodesMapping mapping = _nodesMapping(rel.getInput(), mq);
+
+        return mapping == null ? null : mapping.prune(rel);
     }
 
     /**
-     * See {@link IgniteMdFragmentInfo#fragmentInfo(RelNode, RelMetadataQuery)}
+     * See {@link IgniteMdNodesMapping#nodesMapping(RelNode, RelMetadataQuery)}
      */
-    public FragmentInfo fragmentInfo(IgniteReceiver rel, RelMetadataQuery mq) {
-        return new FragmentInfo(rel.source());
+    public NodesMapping nodesMapping(IgniteReceiver rel, RelMetadataQuery mq) {
+        return null;
     }
 
     /**
-     * See {@link IgniteMdFragmentInfo#fragmentInfo(RelNode, RelMetadataQuery)}
+     * See {@link IgniteMdNodesMapping#nodesMapping(RelNode, RelMetadataQuery)}
      */
-    public FragmentInfo fragmentInfo(IgniteTableScan rel, RelMetadataQuery mq) {
-        return new FragmentInfo(rel.getTable().unwrap(DistributedTable.class).mapping(Commons.context(rel)));
+    public NodesMapping nodesMapping(IgniteTableScan rel, RelMetadataQuery mq) {
+        return rel.getTable().unwrap(DistributedTable.class).mapping(Commons.context(rel));
     }
 
     /**
-     * See {@link IgniteMdFragmentInfo#fragmentInfo(RelNode, RelMetadataQuery)}
+     * See {@link IgniteMdNodesMapping#nodesMapping(RelNode, RelMetadataQuery)}
      */
-    public FragmentInfo fragmentInfo(IgniteValues rel, RelMetadataQuery mq) {
-        return new FragmentInfo();
+    public NodesMapping nodesMapping(IgniteValues rel, RelMetadataQuery mq) {
+        return null;
     }
 
     /**
@@ -178,9 +179,20 @@ public class IgniteMdFragmentInfo implements MetadataHandler<FragmentMetadata> {
      * @param mq Metadata query instance.
      * @return Fragment meta information.
      */
-    public static FragmentInfo _fragmentInfo(RelNode rel, RelMetadataQuery mq) {
+    public static NodesMapping _nodesMapping(RelNode rel, RelMetadataQuery mq) {
         assert mq instanceof RelMetadataQueryEx;
 
-        return ((RelMetadataQueryEx) mq).getFragmentInfo(rel);
+        return ((RelMetadataQueryEx) mq).nodesMapping(rel);
+    }
+
+    /** */
+    private static NodesMapping merge(NodesMapping mapping1, NodesMapping mapping2) throws LocationMappingException {
+        if (mapping1 == null)
+            return mapping2;
+
+        if (mapping2 == null)
+            return mapping1;
+
+        return mapping1.mergeWith(mapping2);
     }
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMetadata.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMetadata.java
index 8437f0b..87431d3 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMetadata.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMetadata.java
@@ -39,19 +39,19 @@ public class IgniteMetadata {
             ImmutableList.of(
                 IgniteMdDerivedDistribution.SOURCE,
                 IgniteMdDistribution.SOURCE,
-                IgniteMdFragmentInfo.SOURCE,
+                IgniteMdNodesMapping.SOURCE,
                 DefaultRelMetadataProvider.INSTANCE));
 
-    public interface FragmentMetadata extends Metadata {
-        MetadataDef<FragmentMetadata> DEF = MetadataDef.of(FragmentMetadata.class,
-            FragmentMetadata.Handler.class, IgniteMethod.FRAGMENT_INFO.method());
+    public interface NodesMappingMetadata extends Metadata {
+        MetadataDef<NodesMappingMetadata> DEF = MetadataDef.of(NodesMappingMetadata.class,
+            NodesMappingMetadata.Handler.class, IgniteMethod.NODES_MAPPING.method());
 
         /** Determines how the rows are distributed. */
-        FragmentInfo fragmentInfo();
+        NodesMapping nodesMapping();
 
         /** Handler API. */
-        interface Handler extends MetadataHandler<FragmentMetadata> {
-            FragmentInfo fragmentInfo(RelNode r, RelMetadataQuery mq);
+        interface Handler extends MetadataHandler<NodesMappingMetadata> {
+            NodesMapping nodesMapping(RelNode r, RelMetadataQuery mq);
         }
     }
 
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/NodesMapping.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/NodesMapping.java
index 3bb89b0..0af3792 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/NodesMapping.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/NodesMapping.java
@@ -17,24 +17,34 @@
 
 package org.apache.ignite.internal.processors.query.calcite.metadata;
 
-import java.io.Serializable;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.UUID;
 import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.internal.GridDirectCollection;
+import org.apache.ignite.internal.GridDirectTransient;
 import org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionState;
+import org.apache.ignite.internal.processors.query.calcite.message.MarshalableMessage;
+import org.apache.ignite.internal.processors.query.calcite.message.MessageType;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
 import org.apache.ignite.internal.processors.query.calcite.util.Commons;
 import org.apache.ignite.internal.util.GridIntList;
+import org.apache.ignite.internal.util.UUIDCollectionMessage;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.marshaller.Marshaller;
+import org.apache.ignite.plugin.extensions.communication.Message;
+import org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType;
+import org.apache.ignite.plugin.extensions.communication.MessageReader;
+import org.apache.ignite.plugin.extensions.communication.MessageWriter;
 
 /**
  * Represents a list of nodes capable to execute a query over particular partitions.
  */
-public class NodesMapping implements Serializable {
+public class NodesMapping implements MarshalableMessage {
     /** */
     public static final byte HAS_MOVING_PARTITIONS  = 1;
 
@@ -54,13 +64,23 @@ public class NodesMapping implements Serializable {
     public static final byte CLIENT                 = 1 << 5;
 
     /** */
-    private final List<UUID> nodes;
+    @GridDirectCollection(UUID.class)
+    private List<UUID> nodes;
 
     /** */
-    private final List<List<UUID>> assignments;
+    @GridDirectTransient
+    private List<List<UUID>> assignments;
 
     /** */
-    private final byte flags;
+    @GridDirectCollection(Message.class)
+    private List<UUIDCollectionMessage> assignments0;
+
+    /** */
+    private byte flags;
+
+    /** */
+    public NodesMapping() {
+    }
 
     /** */
     public NodesMapping(List<UUID> nodes, List<List<UUID>> assignments, byte flags) {
@@ -104,6 +124,9 @@ public class NodesMapping implements Serializable {
     public NodesMapping mergeWith(NodesMapping other) throws LocationMappingException {
         byte flags = (byte) (this.flags | other.flags);
 
+        // reset posiible set deduplicated flag
+        flags &= ~DEDUPLICATED;
+
         List<UUID> nodes = intersectReplicated(this.nodes, other.nodes);
 
         // if there is no moving partitions both assignments are identical.
@@ -259,4 +282,108 @@ public class NodesMapping implements Serializable {
 
         return res;
     }
+
+    /** {@inheritDoc} */
+    @Override public MessageType type() {
+        return MessageType.NODES_MAPPING;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean writeTo(ByteBuffer buf, MessageWriter writer) {
+        writer.setBuffer(buf);
+
+        if (!writer.isHeaderWritten()) {
+            if (!writer.writeHeader(directType(), fieldsCount()))
+                return false;
+
+            writer.onHeaderWritten();
+        }
+
+        switch (writer.state()) {
+            case 0:
+                if (!writer.writeCollection("assignments0", assignments0, MessageCollectionItemType.MSG))
+                    return false;
+
+                writer.incrementState();
+
+            case 1:
+                if (!writer.writeByte("flags", flags))
+                    return false;
+
+                writer.incrementState();
+
+            case 2:
+                if (!writer.writeCollection("nodes", nodes, MessageCollectionItemType.UUID))
+                    return false;
+
+                writer.incrementState();
+
+        }
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean readFrom(ByteBuffer buf, MessageReader reader) {
+        reader.setBuffer(buf);
+
+        if (!reader.beforeMessageRead())
+            return false;
+
+        switch (reader.state()) {
+            case 0:
+                assignments0 = reader.readCollection("assignments0", MessageCollectionItemType.MSG);
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 1:
+                flags = reader.readByte("flags");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 2:
+                nodes = reader.readCollection("nodes", MessageCollectionItemType.UUID);
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+        }
+
+        return reader.afterMessageRead(NodesMapping.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public byte fieldsCount() {
+        return 3;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void prepareMarshal(Marshaller marshaller) {
+        if (assignments != null && assignments0 == null)
+            assignments0 = Commons.transform(assignments, this::transform);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void prepareUnmarshal(Marshaller marshaller, ClassLoader loader) {
+        if (assignments0 != null && assignments == null)
+            assignments = Commons.transform(assignments0, this::transform);
+    }
+
+    /** */
+    private List<UUID> transform(UUIDCollectionMessage message) {
+        return message.uuids() instanceof List ? (List<UUID>)message.uuids() : new ArrayList<>(message.uuids());
+    }
+
+    /** */
+    private UUIDCollectionMessage transform(List<UUID> uuids) {
+        return new UUIDCollectionMessage(uuids);
+    }
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/RelMetadataQueryEx.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/RelMetadataQueryEx.java
index 4b4bc29..fc6e4fc 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/RelMetadataQueryEx.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/RelMetadataQueryEx.java
@@ -53,15 +53,15 @@ public class RelMetadataQueryEx extends RelMetadataQuery {
     }
 
     /** */
-    private static final IgniteMetadata.FragmentMetadata.Handler SOURCE_DISTRIBUTION_INITIAL_HANDLER =
-        initialHandler(IgniteMetadata.FragmentMetadata.Handler.class);
+    private static final IgniteMetadata.NodesMappingMetadata.Handler SOURCE_DISTRIBUTION_INITIAL_HANDLER =
+        initialHandler(IgniteMetadata.NodesMappingMetadata.Handler.class);
 
     /** */
     private static final IgniteMetadata.DerivedDistribution.Handler DERIVED_DISTRIBUTIONS_INITIAL_HANDLER =
         initialHandler(IgniteMetadata.DerivedDistribution.Handler.class);
 
     /** */
-    private IgniteMetadata.FragmentMetadata.Handler sourceDistributionHandler;
+    private IgniteMetadata.NodesMappingMetadata.Handler sourceDistributionHandler;
 
     /** */
     private IgniteMetadata.DerivedDistribution.Handler derivedDistributionsHandler;
@@ -97,17 +97,17 @@ public class RelMetadataQueryEx extends RelMetadataQuery {
     }
 
     /**
-     * Calculates fragment meta information, the given relation node is a root of.
+     * Calculates data location mapping for a query fragment the given relation node is a root of.
      *
      * @param rel Relational node.
      * @return Fragment meta information.
      */
-    public FragmentInfo getFragmentInfo(RelNode rel) {
+    public NodesMapping nodesMapping(RelNode rel) {
         for (;;) {
             try {
-                return sourceDistributionHandler.fragmentInfo(rel, this);
+                return sourceDistributionHandler.nodesMapping(rel, this);
             } catch (JaninoRelMetadataProvider.NoHandler e) {
-                sourceDistributionHandler = revise(e.relClass, IgniteMetadata.FragmentMetadata.DEF);
+                sourceDistributionHandler = revise(e.relClass, IgniteMetadata.NodesMappingMetadata.DEF);
             }
         }
     }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/AbstractMultiStepPlan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/AbstractMultiStepPlan.java
index c759379..cc1e6e8 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/AbstractMultiStepPlan.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/AbstractMultiStepPlan.java
@@ -17,13 +17,20 @@
 
 package org.apache.ignite.internal.processors.query.calcite.prepare;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.UUID;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.ignite.internal.processors.query.GridQueryFieldMetadata;
 import org.apache.ignite.internal.processors.query.IgniteSQLException;
 import org.apache.ignite.internal.processors.query.calcite.metadata.MappingService;
+import org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping;
 import org.apache.ignite.internal.processors.query.calcite.metadata.OptimisticPlanningException;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteReceiver;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSender;
 import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.U;
 
 /**
  *
@@ -35,6 +42,10 @@ public abstract class AbstractMultiStepPlan implements MultiStepPlan {
     /** */
     protected final List<GridQueryFieldMetadata> fieldsMeta;
 
+    /** */
+    protected Map<Long, NodesMapping> mappings;
+
+    /** */
     protected AbstractMultiStepPlan(List<Fragment> fragments, List<GridQueryFieldMetadata> fieldsMeta) {
         this.fragments = fragments;
         this.fieldsMeta = fieldsMeta;
@@ -51,14 +62,44 @@ public abstract class AbstractMultiStepPlan implements MultiStepPlan {
     }
 
     /** {@inheritDoc} */
+    @Override public NodesMapping fragmentMapping(Fragment fragment) {
+        return fragmentMapping(fragment.fragmentId());
+    }
+
+    /** {@inheritDoc} */
+    @Override public NodesMapping targetMapping(Fragment fragment) {
+        if (fragment.local())
+            return null;
+
+        return fragmentMapping(((IgniteSender)fragment.root()).targetFragmentId());
+    }
+
+    /** {@inheritDoc} */
+    @Override public Map<Long, List<UUID>> remoteSources(Fragment fragment) {
+        List<IgniteReceiver> remotes = fragment.remotes();
+
+        if (F.isEmpty(remotes))
+            return null;
+
+        HashMap<Long, List<UUID>> res = U.newHashMap(remotes.size());
+
+        for (IgniteReceiver remote : remotes)
+            res.put(remote.exchangeId(), fragmentMapping(remote.sourceFragmentId()).nodes());
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
     @Override public void init(MappingService mappingService, PlanningContext ctx) {
+        mappings = U.newHashMap(fragments.size());
+
         RelMetadataQuery mq = F.first(fragments).root().getCluster().getMetadataQuery();
 
         for (int i = 0, j = 0; i < fragments.size();) {
             Fragment fragment = fragments.get(i);
 
             try {
-                fragment.init(mappingService, ctx, mq);
+                mappings.put(fragment.fragmentId(), fragment.map(mappingService, ctx, mq));
 
                 i++;
             }
@@ -66,18 +107,45 @@ public abstract class AbstractMultiStepPlan implements MultiStepPlan {
                 if (++j > 3)
                     throw new IgniteSQLException("Failed to map query.", e);
 
-                replace(fragment, new FragmentSplitter().go(fragment, e.node(), mq));
+                replace(fragment, new FragmentSplitter(e.node()).go(fragment));
 
-                i = 0; // restart init routine.
+                // restart init routine.
+                mappings.clear();
+                i = 0;
             }
         }
     }
 
     /** */
+    private NodesMapping fragmentMapping(long fragmentId) {
+        return mappings == null ? null : mappings.get(fragmentId);
+    }
+
+    /** */
     private void replace(Fragment fragment, List<Fragment> replacement) {
         assert !F.isEmpty(replacement);
 
-        fragments.set(fragments.indexOf(fragment), F.first(replacement));
+        Map<Long, Long> newTargets = new HashMap<>();
+
+        for (Fragment fragment0 : replacement) {
+            for (IgniteReceiver remote : fragment0.remotes())
+                newTargets.put(remote.exchangeId(), fragment0.fragmentId());
+        }
+
+        for (int i = 0; i < fragments.size(); i++) {
+            Fragment fragment0 = fragments.get(i);
+
+            if (fragment0 == fragment)
+                fragments.set(i, F.first(replacement));
+            else if (!fragment0.local()) {
+                IgniteSender sender = (IgniteSender)fragment0.root();
+                Long newTargetId = newTargets.get(sender.exchangeId());
+
+                if (newTargetId != null)
+                    sender.targetFragmentId(newTargetId);
+            }
+        }
+
         fragments.addAll(replacement.subList(1, replacement.size()));
     }
 }
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 b13f99a..9d2bacd 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
@@ -17,11 +17,10 @@
 
 package org.apache.ignite.internal.processors.query.calcite.prepare;
 
+import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import org.apache.calcite.plan.RelOptCluster;
-import org.apache.calcite.prepare.Prepare;
 import org.apache.calcite.rel.RelNode;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteAggregate;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange;
@@ -48,17 +47,10 @@ class Cloner implements IgniteRelVisitor<IgniteRel> {
     private final RelOptCluster cluster;
 
     /** */
-    private final Prepare.CatalogReader catalogReader;
+    private FragmentProto curr;
 
-    /** */
-    private List<Fragment> fragments;
-
-    /**
-     * @param ctx Planner context.
-     */
-    Cloner(PlanningContext ctx) {
-        cluster = ctx.createCluster();
-        catalogReader = ctx.catalogReader();
+    Cloner(RelOptCluster cluster) {
+        this.cluster = cluster;
     }
 
     /**
@@ -70,15 +62,14 @@ class Cloner implements IgniteRelVisitor<IgniteRel> {
     List<Fragment> go(List<Fragment> src) {
         assert !F.isEmpty(src);
 
-        fragments = new ArrayList<>(src.size());
+        List<Fragment> fragments = new ArrayList<>(src.size());
 
-        Fragment first = F.first(src);
-
-        fragments.add(new Fragment(first.fragmentId(), visit(first.root())));
-
-        assert fragments.size() == src.size();
-
-        Collections.reverse(fragments);
+        for (Fragment fragment : src) {
+            curr = new FragmentProto(fragment.fragmentId(), fragment.root());
+            curr.root = visit(curr.root);
+            fragments.add(curr.build());
+            curr = null;
+        }
 
         return fragments;
     }
@@ -87,7 +78,7 @@ class Cloner implements IgniteRelVisitor<IgniteRel> {
     @Override public IgniteRel visit(IgniteSender rel) {
         IgniteRel input = visit((IgniteRel) rel.getInput());
 
-        return new IgniteSender(cluster, rel.getTraitSet(), input);
+        return new IgniteSender(cluster, rel.getTraitSet(), input, rel.exchangeId(), rel.targetFragmentId(), rel.distribution());
     }
 
     /** {@inheritDoc} */
@@ -101,7 +92,7 @@ class Cloner implements IgniteRelVisitor<IgniteRel> {
     @Override public IgniteRel visit(IgniteTrimExchange rel) {
         RelNode input = visit((IgniteRel) rel.getInput());
 
-        return new IgniteTrimExchange(cluster, rel.getTraitSet(), input);
+        return new IgniteTrimExchange(cluster, rel.getTraitSet(), input, rel.distribution());
     }
 
     /** {@inheritDoc} */
@@ -115,7 +106,7 @@ class Cloner implements IgniteRelVisitor<IgniteRel> {
     @Override public IgniteRel visit(IgniteTableModify rel) {
         RelNode input = visit((IgniteRel) rel.getInput());
 
-        return new IgniteTableModify(cluster, rel.getTraitSet(), rel.getTable(), catalogReader, input,
+        return new IgniteTableModify(cluster, rel.getTraitSet(), rel.getTable(), input,
             rel.getOperation(), rel.getUpdateColumnList(), rel.getSourceExpressionList(), rel.isFlattened());
     }
 
@@ -146,10 +137,12 @@ class Cloner implements IgniteRelVisitor<IgniteRel> {
 
     /** {@inheritDoc} */
     @Override public IgniteRel visit(IgniteReceiver rel) {
-        Fragment fragment = rel.source();
-        fragments.add(fragment = new Fragment(fragment.fragmentId(), visit(fragment.root())));
+        IgniteReceiver receiver = new IgniteReceiver(cluster, rel.getTraitSet(), rel.getRowType(),
+            rel.exchangeId(), rel.sourceFragmentId());
+
+        curr.remotes.add(receiver);
 
-        return new IgniteReceiver(cluster, rel.getTraitSet(), fragment);
+        return receiver;
     }
 
     /** {@inheritDoc} */
@@ -187,4 +180,26 @@ class Cloner implements IgniteRelVisitor<IgniteRel> {
     @Override public IgniteRel visit(IgniteRel rel) {
         return rel.accept(this);
     }
+
+    /** */
+    private static class FragmentProto {
+        /** */
+        private final long id;
+
+        /** */
+        private IgniteRel root;
+
+        /** */
+        private final ImmutableList.Builder<IgniteReceiver> remotes = ImmutableList.builder();
+
+        /** */
+        private FragmentProto(long id, IgniteRel root) {
+            this.id = id;
+            this.root = root;
+        }
+
+        Fragment build() {
+            return new Fragment(id, root, remotes.build());
+        }
+    }
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/Fragment.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/Fragment.java
index 43251b1..9680f31 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/Fragment.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/Fragment.java
@@ -17,19 +17,20 @@
 
 package org.apache.ignite.internal.processors.query.calcite.prepare;
 
+import com.google.common.collect.ImmutableList;
 import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicLong;
 import org.apache.calcite.rel.RelDistribution;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
-import org.apache.ignite.internal.processors.query.calcite.metadata.FragmentInfo;
-import org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMdFragmentInfo;
+import org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMdNodesMapping;
 import org.apache.ignite.internal.processors.query.calcite.metadata.LocationMappingException;
 import org.apache.ignite.internal.processors.query.calcite.metadata.MappingService;
 import org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping;
 import org.apache.ignite.internal.processors.query.calcite.metadata.OptimisticPlanningException;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteReceiver;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSender;
-import org.apache.ignite.internal.util.typedef.F;
 
 import static org.apache.calcite.rel.RelDistribution.Type.BROADCAST_DISTRIBUTED;
 import static org.apache.calcite.rel.RelDistribution.Type.SINGLETON;
@@ -37,9 +38,9 @@ import static org.apache.calcite.rel.RelDistribution.Type.SINGLETON;
 /**
  * Fragment of distributed query
  */
-public class Fragment implements RelTargetAware {
+public class Fragment {
     /** */
-    private static final AtomicLong ID_GEN = new AtomicLong();
+    static final AtomicLong ID_GEN = new AtomicLong();
 
     /** */
     private final long id;
@@ -48,50 +49,48 @@ public class Fragment implements RelTargetAware {
     private final IgniteRel root;
 
     /** */
-    private NodesMapping mapping;
-
-    /**
-     * @param root Root node of the fragment.
-     */
-    public Fragment(IgniteRel root) {
-        this(ID_GEN.getAndIncrement(), root);
-    }
+    private final ImmutableList<IgniteReceiver> remotes;
 
     /**
      * @param id Fragment id.
      * @param root Root node of the fragment.
+     * @param remotes Remote sources of the fragment.
      */
-    public Fragment(long id, IgniteRel root){
+    public Fragment(long id, IgniteRel root, List<IgniteReceiver> remotes){
         this.id = id;
         this.root = root;
+        this.remotes = ImmutableList.copyOf(remotes);
     }
 
     /**
-     * Inits fragment and its dependencies. Mainly init process consists of data location calculation.
+     * Mapps the fragment to its data location.
      *
      * @param mappingService Mapping service.
      * @param ctx Planner context.
-     * @param mq Metadata query used for data location calculation.
+     * @param mq Metadata query.
      */
-    public void init(MappingService mappingService, PlanningContext ctx, RelMetadataQuery mq) throws OptimisticPlanningException {
-        FragmentInfo info = IgniteMdFragmentInfo._fragmentInfo(root, mq);
-
-        mapping = fragmentMapping(mappingService, ctx, info);
+    NodesMapping map(MappingService mappingService, PlanningContext ctx, RelMetadataQuery mq) throws OptimisticPlanningException {
+        NodesMapping mapping = IgniteMdNodesMapping._nodesMapping(root, mq);
 
-        if (F.isEmpty(info.targetAwareList()))
-            return;
+        try {
+            if (mapping != null)
+                mapping = local() ? localMapping(ctx).mergeWith(mapping) : mapping;
+            else if (local())
+                mapping = localMapping(ctx);
+            else {
+                RelDistribution.Type type = ((IgniteSender)root).sourceDistribution().getType();
 
-        RelTargetImpl target = new RelTargetImpl(id, mapping);
+                boolean single = type == SINGLETON || type == BROADCAST_DISTRIBUTED;
 
-        for (RelTargetAware aware : info.targetAwareList())
-            aware.target(target);
-    }
+                // TODO selection strategy.
+                mapping = mappingService.mapBalanced(ctx.topologyVersion(), single ? 1 : 0, null);
+            }
+        }
+        catch (LocationMappingException e) {
+            throw new OptimisticPlanningException("Failed to calculate physical distribution", root, e);
+        }
 
-    /**
-     * @return Root node.
-     */
-    public IgniteRel root() {
-        return root;
+        return mapping.deduplicate();
     }
 
     /**
@@ -102,17 +101,17 @@ public class Fragment implements RelTargetAware {
     }
 
     /**
-     * @return Fragment mapping.
+     * @return Root node.
      */
-    public NodesMapping mapping() {
-        return mapping;
+    public IgniteRel root() {
+        return root;
     }
 
-    /** {@inheritDoc} */
-    @Override public void target(RelTarget target) {
-        assert root instanceof RelTargetAware;
-
-        ((RelTargetAware) root).target(target);
+    /**
+     * @return Fragment remote sources.
+     */
+    public List<IgniteReceiver> remotes() {
+        return remotes;
     }
 
     /** */
@@ -121,31 +120,6 @@ public class Fragment implements RelTargetAware {
     }
 
     /** */
-    private NodesMapping fragmentMapping(MappingService mappingService, PlanningContext ctx, FragmentInfo info) throws OptimisticPlanningException {
-        NodesMapping mapping;
-
-        try {
-            if (info.mapped())
-                mapping = local() ? localMapping(ctx).mergeWith(info.mapping()) : info.mapping();
-            else if (local())
-                mapping = localMapping(ctx);
-            else {
-                RelDistribution.Type type = ((IgniteSender)root).sourceDistribution().getType();
-
-                boolean single = type == SINGLETON || type == BROADCAST_DISTRIBUTED;
-
-                // TODO selection strategy.
-                mapping = mappingService.mapBalanced(ctx.topologyVersion(), single ? 1 : 0, null);
-            }
-        }
-        catch (LocationMappingException e) {
-            throw new OptimisticPlanningException("Failed to calculate physical distribution", root, e);
-        }
-
-        return mapping.deduplicate();
-    }
-
-    /** */
     private NodesMapping localMapping(PlanningContext ctx) {
         return new NodesMapping(Collections.singletonList(ctx.localNodeId()), null, NodesMapping.CLIENT);
     }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/message/QueryStartRequest.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/FragmentDescription.java
similarity index 54%
copy from modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/message/QueryStartRequest.java
copy to modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/FragmentDescription.java
index 5d7b675..ae9c92e 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/message/QueryStartRequest.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/FragmentDescription.java
@@ -15,28 +15,28 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.processors.query.calcite.message;
+package org.apache.ignite.internal.processors.query.calcite.prepare;
 
 import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
 import java.util.UUID;
-import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.GridDirectMap;
 import org.apache.ignite.internal.GridDirectTransient;
-import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
-import org.apache.ignite.internal.processors.query.calcite.serialize.PhysicalRel;
+import org.apache.ignite.internal.processors.query.calcite.message.MarshalableMessage;
+import org.apache.ignite.internal.processors.query.calcite.message.MessageType;
+import org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping;
+import org.apache.ignite.internal.util.UUIDCollectionMessage;
+import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.marshaller.Marshaller;
+import org.apache.ignite.plugin.extensions.communication.Message;
+import org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType;
 import org.apache.ignite.plugin.extensions.communication.MessageReader;
 import org.apache.ignite.plugin.extensions.communication.MessageWriter;
 
-/**
- *
- */
-public class QueryStartRequest implements MarshalableMessage {
-    /** */
-    private String schema;
-
-    /** */
-    private UUID queryId;
-
+/** */
+public class FragmentDescription implements MarshalableMessage {
     /** */
     private long fragmentId;
 
@@ -44,103 +44,62 @@ public class QueryStartRequest implements MarshalableMessage {
     private int[] partitions;
 
     /** */
-    private AffinityTopologyVersion version;
+    private int partitionsCount;
 
     /** */
-    @GridDirectTransient
-    private PhysicalRel root;
+    private NodesMapping targetMapping;
 
     /** */
-    private byte[] rootBytes;
+    @GridDirectTransient
+    private Map<Long, List<UUID>> remoteSources;
 
     /** */
-    @GridDirectTransient
-    private Object[] params;
+    @GridDirectMap(keyType = Long.class, valueType = Message.class)
+    private Map<Long, UUIDCollectionMessage> remoteSources0;
 
     /** */
-    private byte[] paramsBytes;
+    public FragmentDescription() {
+    }
 
     /** */
-    public QueryStartRequest(UUID queryId, long fragmentId, String schema, PhysicalRel root, AffinityTopologyVersion version, int[] partitions, Object[] params) {
-        this.schema = schema;
-        this.queryId = queryId;
+    public FragmentDescription(long fragmentId, int[] partitions, int partitionsCount,
+        NodesMapping targetMapping,
+        Map<Long, List<UUID>> remoteSources) {
         this.fragmentId = fragmentId;
         this.partitions = partitions;
-        this.version = version;
-        this.root = root;
-        this.params = params;
+        this.partitionsCount = partitionsCount;
+        this.targetMapping = targetMapping;
+        this.remoteSources = remoteSources;
     }
 
     /** */
-    QueryStartRequest() {
-
-    }
-
-    /**
-     * @return Schema name.
-     */
-    public String schema() {
-        return schema;
-    }
-
-    /**
-     * @return Query ID.
-     */
-    public UUID queryId() {
-        return queryId;
-    }
-
-    /**
-     * @return Fragment ID.
-     */
     public long fragmentId() {
         return fragmentId;
     }
 
-    /**
-     * @return Partitions.
-     */
-    public int[] partitions() {
-        return partitions;
-    }
-
-    /**
-     * @return Topology version.
-     */
-    public AffinityTopologyVersion topologyVersion() {
-        return version;
+    /** */
+    public NodesMapping targetMapping() {
+        return targetMapping;
     }
 
-    /**
-     * @return Fragment plan.
-     */
-    public PhysicalRel root() {
-        return root;
+    /** */
+    public Map<Long, List<UUID>> remoteSources() {
+        return remoteSources;
     }
 
-    /**
-     * @return Query parameters.
-     */
-    public Object[] parameters() {
-        return params;
+    /** */
+    public int[] partitions() {
+        return partitions;
     }
 
-    /** {@inheritDoc} */
-    @Override public void prepareMarshal(Marshaller marshaller) throws IgniteCheckedException {
-        if (rootBytes == null && root != null)
-            rootBytes = marshaller.marshal(root);
-
-        if (paramsBytes == null && params != null)
-            paramsBytes = marshaller.marshal(params);
+    /** */
+    public int partitionsCount() {
+        return partitionsCount;
     }
 
     /** {@inheritDoc} */
-    @Override public void prepareUnmarshal(Marshaller marshaller, ClassLoader loader) throws IgniteCheckedException {
-        if (root == null && rootBytes != null)
-            root = marshaller.unmarshal(rootBytes, loader);
-
-        if (params == null && paramsBytes != null)
-            params = marshaller.unmarshal(paramsBytes, loader);
+    @Override public MessageType type() {
+        return MessageType.FRAGMENT_DESCRIPTION;
     }
 
     /** {@inheritDoc} */
@@ -162,37 +121,25 @@ public class QueryStartRequest implements MarshalableMessage {
                 writer.incrementState();
 
             case 1:
-                if (!writer.writeByteArray("paramsBytes", paramsBytes))
+                if (!writer.writeIntArray("partitions", partitions))
                     return false;
 
                 writer.incrementState();
 
             case 2:
-                if (!writer.writeIntArray("partitions", partitions))
+                if (!writer.writeInt("partitionsCount", partitionsCount))
                     return false;
 
                 writer.incrementState();
 
             case 3:
-                if (!writer.writeByteArray("rootBytes", rootBytes))
+                if (!writer.writeMap("remoteSources0", remoteSources0, MessageCollectionItemType.LONG, MessageCollectionItemType.MSG))
                     return false;
 
                 writer.incrementState();
 
             case 4:
-                if (!writer.writeUuid("queryId", queryId))
-                    return false;
-
-                writer.incrementState();
-
-            case 5:
-                if (!writer.writeString("schema", schema))
-                    return false;
-
-                writer.incrementState();
-
-            case 6:
-                if (!writer.writeAffinityTopologyVersion("version", version))
+                if (!writer.writeMessage("targetMapping", targetMapping))
                     return false;
 
                 writer.incrementState();
@@ -219,7 +166,7 @@ public class QueryStartRequest implements MarshalableMessage {
                 reader.incrementState();
 
             case 1:
-                paramsBytes = reader.readByteArray("paramsBytes");
+                partitions = reader.readIntArray("partitions");
 
                 if (!reader.isLastRead())
                     return false;
@@ -227,7 +174,7 @@ public class QueryStartRequest implements MarshalableMessage {
                 reader.incrementState();
 
             case 2:
-                partitions = reader.readIntArray("partitions");
+                partitionsCount = reader.readInt("partitionsCount");
 
                 if (!reader.isLastRead())
                     return false;
@@ -235,7 +182,7 @@ public class QueryStartRequest implements MarshalableMessage {
                 reader.incrementState();
 
             case 3:
-                rootBytes = reader.readByteArray("rootBytes");
+                remoteSources0 = reader.readMap("remoteSources0", MessageCollectionItemType.LONG, MessageCollectionItemType.MSG, false);
 
                 if (!reader.isLastRead())
                     return false;
@@ -243,41 +190,46 @@ public class QueryStartRequest implements MarshalableMessage {
                 reader.incrementState();
 
             case 4:
-                queryId = reader.readUuid("queryId");
+                targetMapping = reader.readMessage("targetMapping");
 
                 if (!reader.isLastRead())
                     return false;
 
                 reader.incrementState();
 
-            case 5:
-                schema = reader.readString("schema");
-
-                if (!reader.isLastRead())
-                    return false;
-
-                reader.incrementState();
+        }
 
-            case 6:
-                version = reader.readAffinityTopologyVersion("version");
+        return reader.afterMessageRead(FragmentDescription.class);
+    }
 
-                if (!reader.isLastRead())
-                    return false;
+    /** {@inheritDoc} */
+    @Override public byte fieldsCount() {
+        return 5;
+    }
 
-                reader.incrementState();
+    /** {@inheritDoc} */
+    @Override public void prepareMarshal(Marshaller marshaller) {
+        if (remoteSources0 == null && remoteSources != null) {
+            remoteSources0 = U.newHashMap(remoteSources.size());
 
+            for (Map.Entry<Long, List<UUID>> e : remoteSources.entrySet())
+                remoteSources0.put(e.getKey(), new UUIDCollectionMessage(e.getValue()));
         }
 
-        return reader.afterMessageRead(QueryStartRequest.class);
+        if (targetMapping != null)
+            targetMapping.prepareMarshal(marshaller);
     }
 
     /** {@inheritDoc} */
-    @Override public MessageType type() {
-        return MessageType.QUERY_START_REQUEST;
-    }
+    @Override public void prepareUnmarshal(Marshaller marshaller, ClassLoader loader) {
+        if (remoteSources == null && remoteSources0 != null) {
+            remoteSources = U.newHashMap(remoteSources0.size());
 
-    /** {@inheritDoc} */
-    @Override public byte fieldsCount() {
-        return 7;
+            for (Map.Entry<Long, UUIDCollectionMessage> e : remoteSources0.entrySet())
+                remoteSources.put(e.getKey(), new ArrayList<>(e.getValue().uuids()));
+        }
+
+        if (targetMapping != null)
+            targetMapping.prepareUnmarshal(marshaller, loader);
     }
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/FragmentSplitter.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/FragmentSplitter.java
index 3083feb..6ac560a 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/FragmentSplitter.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/FragmentSplitter.java
@@ -19,13 +19,13 @@ package org.apache.ignite.internal.processors.query.calcite.prepare;
 
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Deque;
+import java.util.LinkedList;
 import java.util.List;
 import org.apache.calcite.plan.RelOptCluster;
-import org.apache.calcite.plan.RelTrait;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelNode;
-import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.type.RelDataType;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteAggregate;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
@@ -42,274 +42,114 @@ import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTrimExchange;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteUnionAll;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteValues;
-import org.apache.ignite.internal.processors.query.calcite.trait.DistributionTrait;
-import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
-import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
 import org.apache.ignite.internal.processors.query.calcite.util.Commons;
-import org.apache.ignite.internal.util.typedef.F;
-import org.apache.ignite.internal.util.typedef.internal.U;
 
 /**
  *
  */
 public class FragmentSplitter implements IgniteRelVisitor<IgniteRel> {
     /** */
-    private RelMetadataQuery mq;
-
-    /** */
-    private List<Fragment> fragments;
+    private final Deque<FragmentProto> stack = new LinkedList<>();
 
     /** */
     private RelNode cutPoint;
 
     /** */
-    public List<Fragment> go(Fragment fragment, RelNode cutPoint, RelMetadataQuery mq) {
-        this.cutPoint = cutPoint;
-        this.mq = mq;
-
-        fragments = new ArrayList<>();
-
-        try {
-            fragments.add(new Fragment(visit(fragment.root())));
-
-            Collections.reverse(fragments);
-
-            return fragments;
-        }
-        finally {
-            this.cutPoint = null;
-            this.mq = null;
+    private FragmentProto curr;
 
-            fragments = null;
-        }
+    public FragmentSplitter(RelNode cutPoint) {
+        this.cutPoint = cutPoint;
     }
 
-    /** {@inheritDoc} */
-    @Override public IgniteRel visit(IgniteSender rel) {
-        assert cutPoint != null;
-        assert rel != cutPoint;
-
-        IgniteRel input = (IgniteRel) rel.getInput();
-        IgniteRel newInput = visit(input);
-
-        assert input != newInput;
+    /** */
+    public List<Fragment> go(Fragment fragment) {
+        ArrayList<Fragment> res = new ArrayList<>();
 
-        checkTraitsEqual(input, newInput);
+        stack.push(new FragmentProto(Fragment.ID_GEN.getAndIncrement(), fragment.root()));
 
-        RelTraitSet traits = rel.getTraitSet();
+        while (!stack.isEmpty()) {
+            curr = stack.pop();
+            curr.root = visit(curr.root);
+            res.add(curr.build());
+            curr = null;
+        }
 
-        return (IgniteRel) rel.copy(traits, F.asList(newInput));
+        return res;
     }
 
     /** {@inheritDoc} */
     @Override public IgniteRel visit(IgniteFilter rel) {
-        if (cutPoint == rel)
-            return split(rel);
-
-        IgniteRel input = (IgniteRel) rel.getInput();
-        IgniteRel newInput = visit(input);
-
-        if (input == newInput)
-            return rel;
-
-        checkTraits(input, newInput);
-
-        RelTraitSet traits = rel.getTraitSet()
-            .replace(newInput.distribution());
-
-        return (IgniteRel) rel.copy(traits, F.asList(newInput));
+        return processNode(rel);
     }
 
     /** {@inheritDoc} */
     @Override public IgniteRel visit(IgniteTrimExchange rel) {
-        if (cutPoint == rel)
-            return split(rel);
-
-        if (cutPoint == null)
-            return (IgniteRel) rel.getInput();
-
-        IgniteRel input = (IgniteRel) rel.getInput();
-        IgniteRel newInput = visit(input);
-
-        if (input == newInput)
-            return rel;
-
-        checkTraitsEqual(input, newInput);
-
-        RelTraitSet traits = rel.getTraitSet();
-
-        return (IgniteRel) rel.copy(traits, F.asList(newInput));
+        return processNode(rel);
     }
 
     /** {@inheritDoc} */
     @Override public IgniteRel visit(IgniteProject rel) {
-        if (cutPoint == rel)
-            return split(rel);
-
-        IgniteRel input = (IgniteRel) rel.getInput();
-        IgniteRel newInput = visit(input);
-
-        if (input == newInput)
-            return rel;
-
-        checkTraits(input, newInput);
-
-        RelTraitSet traits = rel.getTraitSet()
-            .replace(IgniteDistributions.project(mq, newInput, rel.getProjects()));
-
-        return (IgniteRel) rel.copy(traits, F.asList(newInput));
+        return processNode(rel);
     }
 
     /** {@inheritDoc} */
     @Override public IgniteRel visit(IgniteJoin rel) {
-        if (cutPoint == null)
-            return rel; // No need to check deeper
-
-        if (cutPoint == rel)
-            return split(rel);
-
-        IgniteRel left = (IgniteRel) rel.getLeft();
-        IgniteRel newLeft = visit(left);
-        IgniteRel right = (IgniteRel) rel.getRight();
-        IgniteRel newRight = visit(right);
-
-        if (left == newLeft && right == newRight)
-            return rel;
-
-        checkTraitsEqual(left, newLeft);
-        checkTraitsEqual(right, newRight);
-
-        RelTraitSet traits = rel.getTraitSet();
-
-        return (IgniteRel) rel.copy(traits, F.asList(newLeft, newRight));
+        return processNode(rel);
     }
 
     /** {@inheritDoc} */
-    @Override public IgniteRel visit(IgniteTableScan rel) {
-        if (rel == cutPoint)
-            return split(rel);
-
-        assert cutPoint == null;
-
-        return rel;
+    @Override public IgniteRel visit(IgniteTableModify rel) {
+        return processNode(rel);
     }
 
     /** {@inheritDoc} */
-    @Override public IgniteRel visit(IgniteReceiver rel) {
-        assert cutPoint == null;
-
-        return rel;
+    @Override public IgniteRel visit(IgniteAggregate rel) {
+        return processNode(rel);
     }
 
     /** {@inheritDoc} */
-    @Override public IgniteRel visit(IgniteExchange rel) {
-        throw new AssertionError();
+    @Override public IgniteRel visit(IgniteMapAggregate rel) {
+        return processNode(rel);
     }
 
     /** {@inheritDoc} */
-    @Override public IgniteRel visit(IgniteAggregate rel) {
-        if (rel == cutPoint)
-            return split(rel);
-
-        IgniteRel input = (IgniteRel) rel.getInput();
-        IgniteRel newInput = visit(input);
-
-        if (input == newInput)
-            return rel;
-
-        checkTraits(input, newInput);
-
-        IgniteDistribution distr = IgniteDistributions.aggregate(mq,
-            newInput, rel.getGroupSet(), rel.getGroupSets(), rel.getAggCallList());
-
-        assert distr != null;
-
-        RelTraitSet traits = rel.getTraitSet().replace(distr);
+    @Override public IgniteRel visit(IgniteReduceAggregate rel) {
+        assert cutPoint != rel;
 
-        return (IgniteRel) rel.copy(traits, F.asList(newInput));
+        return processNode(rel);
     }
 
     /** {@inheritDoc} */
-    @Override public IgniteRel visit(IgniteMapAggregate rel) {
-        assert rel != cutPoint;
-
-        IgniteRel input = (IgniteRel) rel.getInput();
-        IgniteRel newInput = visit(input);
-
-        if (input == newInput)
-            return rel;
-
-        checkTraitsEqual(input, newInput);
-
-        RelTraitSet traits = rel.getTraitSet();
-
-        return (IgniteRel) rel.copy(traits, F.asList(newInput));
+    @Override public IgniteRel visit(IgniteUnionAll rel) {
+        return processNode(rel);
     }
 
     /** {@inheritDoc} */
-    @Override public IgniteRel visit(IgniteReduceAggregate rel) {
-        assert cutPoint == null;
+    @Override public IgniteRel visit(IgniteSender rel) {
+        assert cutPoint != rel;
 
-        return rel;
+        return processNode(rel);
     }
 
     /** {@inheritDoc} */
-    @Override public IgniteRel visit(IgniteTableModify rel) {
-        if (cutPoint == null)
-            return rel; // No need to check deeper
-
-        if (rel == cutPoint)
-            return split(rel);
-
-        IgniteRel input = (IgniteRel) rel.getInput();
-        IgniteRel newInput = visit(input);
-
-        if (input == newInput)
-            return rel;
-
-        checkTraitsEqual(input, newInput);
-
-        RelTraitSet traits = rel.getTraitSet();
-
-        return (IgniteRel) rel.copy(traits, F.asList(newInput));
+    @Override public IgniteRel visit(IgniteTableScan rel) {
+        return processNode(rel);
     }
 
     /** {@inheritDoc} */
     @Override public IgniteRel visit(IgniteValues rel) {
-        assert cutPoint == null;
+        assert cutPoint != rel;
 
         return rel;
     }
 
     /** {@inheritDoc} */
-    @Override public IgniteRel visit(IgniteUnionAll rel) {
-        if (cutPoint == null)
-            return rel; // No need to check deeper
-
-        if (rel == cutPoint)
-            return split(rel);
-
-        boolean inputChanged = false;
-
-        List<IgniteRel> inputs = Commons.cast(rel.getInputs());
-        List<IgniteRel> newInputs = new ArrayList<>(inputs.size());
-
-        for (RelNode input : inputs) {
-            IgniteRel newInput = visit(input);
-
-            if (input != newInput) {
-                inputChanged = true;
-
-                checkTraitsEqual((IgniteRel) input, newInput);
-            }
-
-            newInputs.add(newInput);
-        }
+    @Override public IgniteRel visit(IgniteReceiver rel) {
+        assert cutPoint != rel;
 
-        if (!inputChanged)
-            return rel;
+        curr.remotes.add(rel);
 
-        return (IgniteRel) rel.copy(rel.getTraitSet(), Commons.cast(newInputs));
+        return rel;
     }
 
     /** {@inheritDoc} */
@@ -317,61 +157,79 @@ public class FragmentSplitter implements IgniteRelVisitor<IgniteRel> {
         return rel.accept(this);
     }
 
-    /** */
-    private IgniteRel visit(RelNode rel) {
-        return visit((IgniteRel) rel);
+    /** {@inheritDoc} */
+    @Override public IgniteRel visit(IgniteExchange rel) {
+        throw new AssertionError();
     }
 
-    /** */
-    private void checkTraits(IgniteRel orig, IgniteRel newRel) {
-        if (!U.assertionsEnabled() || orig == newRel)
-            return;
+    /**
+     * Visits all children of a parent.
+     */
+    private IgniteRel processNode(IgniteRel rel) {
+        if (rel == cutPoint) {
+            cutPoint = null;
 
-        RelTraitSet origTraits = orig.getTraitSet();
-        RelTraitSet newTraits = newRel.getTraitSet();
+            return split(rel);
+        }
 
-        ImmutableList<RelTrait> difference = origTraits.difference(newTraits);
+        List<IgniteRel> inputs = Commons.cast(rel.getInputs());
 
-        // Only distribution trait may be changed after split
-        if (difference.isEmpty() || difference.size() == 1 && F.first(difference) instanceof DistributionTrait)
-            return;
+        for (int i = 0; i < inputs.size(); i++)
+            visitChild(rel, i, inputs.get(i));
 
-        assert false : difference;
+        return rel;
     }
 
-    /** */
-    private void checkTraitsEqual(IgniteRel orig, IgniteRel newRel) {
-        if (!U.assertionsEnabled() || orig == newRel)
-            return;
-
-        RelTraitSet origTraits = orig.getTraitSet();
-        RelTraitSet newTraits = newRel.getTraitSet();
-
-        ImmutableList<RelTrait> difference = origTraits.difference(newTraits);
-
-        if (difference.isEmpty())
-            return;
+    /**
+     * Visits a particular child of a parent and replaces the child if it was changed.
+     */
+    private void visitChild(IgniteRel parent, int i, IgniteRel child) {
+        IgniteRel newChild = visit(child);
 
-        assert false : difference;
+        if (newChild != child)
+            parent.replaceInput(i, newChild);
     }
 
     /** */
     private IgniteRel split(IgniteRel rel) {
-        cutPoint = null;
+        RelOptCluster cluster = rel.getCluster();
+        RelTraitSet traits = rel.getTraitSet();
+        RelDataType rowType = rel.getRowType();
 
-        IgniteRel newRel = visit(rel);
+        RelNode input = rel instanceof IgniteTrimExchange ? rel.getInput(0) : rel;
 
-        checkTraits(rel, newRel);
+        long targetFragmentId = curr.id;
+        long sourceFragmentId = Fragment.ID_GEN.getAndIncrement();
+        long exchangeId = sourceFragmentId;
 
-        RelOptCluster cluster = rel.getCluster();
-        RelTraitSet traits = rel.getTraitSet();
+        IgniteReceiver receiver = new IgniteReceiver(cluster, traits, rowType, exchangeId, sourceFragmentId);
+        IgniteSender sender = new IgniteSender(cluster, traits, input, exchangeId, targetFragmentId, rel.distribution());
 
-        IgniteSender sender = new IgniteSender(cluster, traits, newRel);
+        curr.remotes.add(receiver);
+        stack.push(new FragmentProto(sourceFragmentId, sender));
 
-        Fragment fragment = new Fragment(sender);
+        return receiver;
+    }
+
+    /** */
+    private static class FragmentProto {
+        /** */
+        private final long id;
+
+        /** */
+        private IgniteRel root;
 
-        fragments.add(fragment);
+        /** */
+        private final ImmutableList.Builder<IgniteReceiver> remotes = ImmutableList.builder();
 
-        return new IgniteReceiver(cluster, traits, fragment);
+        /** */
+        private FragmentProto(long id, IgniteRel root) {
+            this.id = id;
+            this.root = root;
+        }
+
+        Fragment build() {
+            return new Fragment(id, root, remotes.build());
+        }
     }
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/MultiStepDmlPlan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/MultiStepDmlPlan.java
index 553bb5f..548400b 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/MultiStepDmlPlan.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/MultiStepDmlPlan.java
@@ -48,6 +48,6 @@ public class MultiStepDmlPlan extends AbstractMultiStepPlan {
     /** {@inheritDoc}
      * @param ctx*/
     @Override public QueryPlan clone(PlanningContext ctx) {
-        return new MultiStepDmlPlan(new Cloner(ctx).go(fragments), fieldsMetadata());
+        return new MultiStepDmlPlan(new Cloner(ctx.createCluster()).go(fragments), fieldsMetadata());
     }
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/MultiStepPlan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/MultiStepPlan.java
index 9c3fd75..1d09db0 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/MultiStepPlan.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/MultiStepPlan.java
@@ -18,8 +18,11 @@
 package org.apache.ignite.internal.processors.query.calcite.prepare;
 
 import java.util.List;
+import java.util.Map;
+import java.util.UUID;
 import org.apache.ignite.internal.processors.query.GridQueryFieldMetadata;
 import org.apache.ignite.internal.processors.query.calcite.metadata.MappingService;
+import org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping;
 
 /**
  * Regular query or DML
@@ -36,6 +39,18 @@ public interface MultiStepPlan extends QueryPlan {
     List<GridQueryFieldMetadata> fieldsMetadata();
 
     /**
+     * @param fragment Fragment.
+     * @return Mapping for a given fragment.
+     */
+    NodesMapping fragmentMapping(Fragment fragment);
+
+    /** */
+    NodesMapping targetMapping(Fragment fragment);
+
+    /** */
+    Map<Long, List<UUID>> remoteSources(Fragment fragment);
+
+    /**
      * Inits query fragments.
      *
      * @param mappingService Mapping service.
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/MultiStepQueryPlan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/MultiStepQueryPlan.java
index 9535abb..fe1f837 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/MultiStepQueryPlan.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/MultiStepQueryPlan.java
@@ -49,6 +49,6 @@ public class MultiStepQueryPlan extends AbstractMultiStepPlan {
     /** {@inheritDoc}
      * @param ctx*/
     @Override public QueryPlan clone(PlanningContext ctx) {
-        return new MultiStepQueryPlan(new Cloner(ctx).go(fragments), fieldsMetadata());
+        return new MultiStepQueryPlan(new Cloner(ctx.createCluster()).go(fragments), fieldsMetadata());
     }
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/RelTarget.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/RelTarget.java
deleted file mode 100644
index 904b468..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/RelTarget.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.prepare;
-
-import java.io.Serializable;
-import org.apache.calcite.util.ImmutableIntList;
-import org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping;
-import org.apache.ignite.internal.processors.query.calcite.metadata.PartitionService;
-import org.apache.ignite.internal.processors.query.calcite.trait.DistributionFunction;
-
-/**
- *
- */
-public interface RelTarget extends Serializable {
-    /**
-     * @return Target fragment ID.
-     */
-    long fragmentId();
-
-    /**
-     * Returns target mapping. It's used in calculation a nodes list, where to send a particular row to.
-     * See {@link DistributionFunction#destination(PartitionService, NodesMapping, ImmutableIntList)}
-     *
-     * @return Target mapping.
-     */
-    NodesMapping mapping();
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/RelTargetAware.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/RelTargetAware.java
deleted file mode 100644
index a879b30..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/RelTargetAware.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.prepare;
-
-/**
- * Describes an object, that needs information about physical target distribution.
- */
-public interface RelTargetAware {
-    /**
-     * @param target Remote target information.
-     */
-    void target(RelTarget target);
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/RelTargetImpl.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/RelTargetImpl.java
deleted file mode 100644
index 1c27bde..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/RelTargetImpl.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.prepare;
-
-import org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping;
-
-/**
- *
- */
-public class RelTargetImpl implements RelTarget {
-    /** */
-    private final long fragmentId;
-
-    /** */
-    private final NodesMapping mapping;
-
-    /**
-     * @param fragmentId Target fragment ID.
-     * @param mapping Target mapping.
-     */
-    public RelTargetImpl(long fragmentId, NodesMapping mapping) {
-        this.fragmentId = fragmentId;
-        this.mapping = mapping;
-    }
-
-    /** {@inheritDoc} */
-    @Override public long fragmentId() {
-        return fragmentId;
-    }
-
-    /** {@inheritDoc} */
-    @Override public NodesMapping mapping() {
-        return mapping;
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/Splitter.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/Splitter.java
index 4cdf985..0c3344e 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/Splitter.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/Splitter.java
@@ -17,11 +17,12 @@
 
 package org.apache.ignite.internal.processors.query.calcite.prepare;
 
+import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Deque;
+import java.util.LinkedList;
 import java.util.List;
 import org.apache.calcite.plan.RelOptCluster;
-import org.apache.calcite.rel.RelNode;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteAggregate;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
@@ -38,131 +39,162 @@ import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTrimExchange;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteUnionAll;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteValues;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
 
 /**
  * Splits a query into a list of query fragments.
  */
 public class Splitter implements IgniteRelVisitor<IgniteRel> {
     /** */
-    private List<Fragment> fragments;
+    private final Deque<FragmentProto> stack = new LinkedList<>();
 
     /** */
-    public List<Fragment> go(IgniteRel root) {
-        fragments = new ArrayList<>();
+    private FragmentProto curr;
 
-        try {
-            fragments.add(new Fragment(visit(root)));
+    /** */
+    public List<Fragment> go(IgniteRel root) {
+        ArrayList<Fragment> res = new ArrayList<>();
 
-            Collections.reverse(fragments);
+        stack.push(new FragmentProto(Fragment.ID_GEN.getAndIncrement(), root));
 
-            return fragments;
-        }
-        finally {
-            fragments = null;
+        while (!stack.isEmpty()) {
+            curr = stack.pop();
+            curr.root = visit(curr.root);
+            res.add(curr.build());
+            curr = null;
         }
+
+        return res;
     }
 
     /** {@inheritDoc} */
     @Override public IgniteRel visit(IgniteFilter rel) {
-        return visitChildren(rel);
+        return processNode(rel);
     }
 
     /** {@inheritDoc} */
     @Override public IgniteRel visit(IgniteTrimExchange rel) {
-        return visitChildren(rel);
+        return processNode(rel);
     }
 
     /** {@inheritDoc} */
     @Override public IgniteRel visit(IgniteProject rel) {
-        return visitChildren(rel);
+        return processNode(rel);
     }
 
     /** {@inheritDoc} */
     @Override public IgniteRel visit(IgniteJoin rel) {
-        return visitChildren(rel);
+        return processNode(rel);
     }
 
     /** {@inheritDoc} */
     @Override public IgniteRel visit(IgniteTableModify rel) {
-        return visitChildren(rel);
+        return processNode(rel);
     }
 
     /** {@inheritDoc} */
     @Override public IgniteRel visit(IgniteAggregate rel) {
-        return visitChildren(rel);
+        return processNode(rel);
     }
 
     /** {@inheritDoc} */
     @Override public IgniteRel visit(IgniteMapAggregate rel) {
-        return visitChildren(rel);
+        return processNode(rel);
     }
 
     /** {@inheritDoc} */
     @Override public IgniteRel visit(IgniteReduceAggregate rel) {
-        return visitChildren(rel);
+        return processNode(rel);
     }
 
     /** {@inheritDoc} */
-    @Override public IgniteRel visit(IgniteTableScan rel) {
-        return rel;
+    @Override public IgniteRel visit(IgniteUnionAll rel) {
+        return processNode(rel);
     }
 
     /** {@inheritDoc} */
-    @Override public IgniteRel visit(IgniteValues rel) {
-        return rel;
+    @Override public IgniteRel visit(IgniteSender rel) {
+        return processNode(rel);
     }
 
     /** {@inheritDoc} */
-    @Override public IgniteRel visit(IgniteUnionAll rel) {
-        return visitChildren(rel);
+    @Override public IgniteRel visit(IgniteTableScan rel) {
+        return rel;
     }
 
     /** {@inheritDoc} */
-    @Override public IgniteRel visit(IgniteRel rel) {
-        return rel.accept(this);
+    @Override public IgniteRel visit(IgniteValues rel) {
+        return rel;
     }
 
     /** {@inheritDoc} */
     @Override public IgniteRel visit(IgniteReceiver rel) {
-        throw new AssertionError("An attempt to split an already split task.");
+        throw new AssertionError();
     }
 
     /** {@inheritDoc} */
-    @Override public IgniteRel visit(IgniteSender rel) {
-        throw new AssertionError("An attempt to split an already split task.");
+    @Override public IgniteRel visit(IgniteRel rel) {
+        return rel.accept(this);
     }
 
     /** {@inheritDoc} */
     @Override public IgniteRel visit(IgniteExchange rel) {
         RelOptCluster cluster = rel.getCluster();
-        IgniteRel input = visit((IgniteRel) rel.getInput());
 
-        Fragment fragment = new Fragment(new IgniteSender(cluster, rel.getTraitSet(), input));
+        long targetFragmentId = curr.id;
+        long sourceFragmentId = Fragment.ID_GEN.getAndIncrement();
+        long exchangeId = sourceFragmentId;
+
+        IgniteReceiver receiver = new IgniteReceiver(cluster, rel.getTraitSet(), rel.getRowType(), exchangeId, sourceFragmentId);
+        IgniteSender sender = new IgniteSender(cluster, rel.getTraitSet(), rel.getInput(), exchangeId, targetFragmentId, rel.distribution());
 
-        fragments.add(fragment);
+        curr.remotes.add(receiver);
+        stack.push(new FragmentProto(sourceFragmentId, sender));
 
-        return new IgniteReceiver(cluster, rel.getTraitSet(), fragment);
+        return receiver;
     }
 
     /**
      * Visits all children of a parent.
      */
-    private <T extends RelNode> T visitChildren(RelNode rel) {
-        List<RelNode> inputs = rel.getInputs();
+    private IgniteRel processNode(IgniteRel rel) {
+        List<IgniteRel> inputs = Commons.cast(rel.getInputs());
 
         for (int i = 0; i < inputs.size(); i++)
             visitChild(rel, i, inputs.get(i));
 
-        return (T)rel;
+        return rel;
     }
 
     /**
      * Visits a particular child of a parent and replaces the child if it was changed.
      */
-    private void visitChild(RelNode parent, int i, RelNode child) {
-        IgniteRel newChild = visit((IgniteRel) child);
+    private void visitChild(IgniteRel parent, int i, IgniteRel child) {
+        IgniteRel newChild = visit(child);
 
         if (newChild != child)
             parent.replaceInput(i, newChild);
     }
+
+    /** */
+    private static class FragmentProto {
+        /** */
+        private final long id;
+
+        /** */
+        private IgniteRel root;
+
+        /** */
+        private final ImmutableList.Builder<IgniteReceiver> remotes = ImmutableList.builder();
+
+        /** */
+        private FragmentProto(long id, IgniteRel root) {
+            this.id = id;
+            this.root = root;
+        }
+
+        Fragment build() {
+            return new Fragment(id, root, remotes.build());
+        }
+    }
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteAggregate.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteAggregate.java
index c4b507b..2380553 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteAggregate.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteAggregate.java
@@ -24,6 +24,7 @@ import org.apache.calcite.plan.RelOptCost;
 import org.apache.calcite.plan.RelOptPlanner;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelDistribution;
+import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.core.AggregateCall;
@@ -31,6 +32,7 @@ import org.apache.calcite.rel.metadata.RelMetadataQuery;
 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.util.Commons;
 
 /**
  *
@@ -42,6 +44,11 @@ public class IgniteAggregate extends Aggregate implements IgniteRel {
     }
 
     /** {@inheritDoc} */
+    public IgniteAggregate(RelInput input) {
+        super(Commons.changeTraits(input, IgniteConvention.INSTANCE));
+    }
+
+    /** {@inheritDoc} */
     @Override public Aggregate copy(RelTraitSet traitSet, RelNode input, ImmutableBitSet groupSet, List<ImmutableBitSet> groupSets, List<AggregateCall> aggCalls) {
         return new IgniteAggregate(getCluster(), traitSet, input, groupSet, groupSets, aggCalls);
     }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteExchange.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteExchange.java
index ba03867..243cd88 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteExchange.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteExchange.java
@@ -20,8 +20,11 @@ package org.apache.ignite.internal.processors.query.calcite.rel;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelDistribution;
+import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Exchange;
+import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
 
 /**
  * Relational expression that imposes a particular distribution on its input
@@ -40,6 +43,15 @@ public class IgniteExchange extends Exchange implements IgniteRel {
         super(cluster, traitSet, input, distribution);
     }
 
+    public IgniteExchange(RelInput input) {
+        super(Commons.changeTraits(input, IgniteConvention.INSTANCE));
+    }
+
+    /** {@inheritDoc} */
+    @Override public IgniteDistribution distribution() {
+        return (IgniteDistribution)distribution;
+    }
+
     /** {@inheritDoc} */
     @Override public Exchange copy(RelTraitSet traitSet, RelNode newInput, RelDistribution newDistribution) {
         return new IgniteExchange(getCluster(), traitSet, newInput, newDistribution);
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteFilter.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteFilter.java
index cc5e148..40ef435 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteFilter.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteFilter.java
@@ -19,9 +19,11 @@ package org.apache.ignite.internal.processors.query.calcite.rel;
 
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Filter;
 import org.apache.calcite.rex.RexNode;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
 
 /**
  * Relational expression that iterates over its input
@@ -45,6 +47,10 @@ public class IgniteFilter extends Filter implements IgniteRel {
         super(cluster, traits, input, condition);
     }
 
+    public IgniteFilter(RelInput input) {
+        super(Commons.changeTraits(input, IgniteConvention.INSTANCE));
+    }
+
     /** {@inheritDoc} */
     @Override public Filter copy(RelTraitSet traitSet, RelNode input, RexNode condition) {
         return new IgniteFilter(getCluster(), traitSet, input, condition);
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteJoin.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteJoin.java
index 2c0ff3a..2d25ee4 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteJoin.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteJoin.java
@@ -17,18 +17,23 @@
 
 package org.apache.ignite.internal.processors.query.calcite.rel;
 
+import com.google.common.collect.ImmutableSet;
+import java.util.List;
 import java.util.Set;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptCost;
 import org.apache.calcite.plan.RelOptPlanner;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelDistribution;
+import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.RelWriter;
 import org.apache.calcite.rel.core.CorrelationId;
 import org.apache.calcite.rel.core.Join;
 import org.apache.calcite.rel.core.JoinRelType;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.sql.SqlExplainLevel;
 
 /**
  * Relational expression that combines two relational expressions according to
@@ -56,6 +61,17 @@ public class IgniteJoin extends Join implements IgniteRel {
         super(cluster, traitSet, left, right, condition, variablesSet, joinType);
     }
 
+    /** */
+    public IgniteJoin(RelInput input) {
+        this(input.getCluster(),
+            input.getTraitSet().replace(IgniteConvention.INSTANCE),
+            input.getInputs().get(0),
+            input.getInputs().get(1),
+            input.getExpression("condition"),
+            ImmutableSet.copyOf((List<CorrelationId>)input.get("variablesSet")),
+            input.getEnum("joinType", JoinRelType.class));
+    }
+
     /** {@inheritDoc} */
     @Override public Join copy(RelTraitSet traitSet, RexNode condition, RelNode left, RelNode right, JoinRelType joinType, boolean semiJoinDone) {
         return new IgniteJoin(getCluster(), traitSet, left, right, condition, variablesSet, joinType);
@@ -73,4 +89,10 @@ public class IgniteJoin extends Join implements IgniteRel {
 
         return super.computeSelfCost(planner, mq);
     }
+
+    /** {@inheritDoc} */
+    @Override public RelWriter explainTerms(RelWriter pw) {
+        return super.explainTerms(pw)
+            .itemIf("variablesSet", variablesSet.asList(), pw.getDetailLevel() == SqlExplainLevel.ALL_ATTRIBUTES);
+    }
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMapAggregate.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMapAggregate.java
index 39439c2..6d66aa2 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMapAggregate.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMapAggregate.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.processors.query.calcite.rel;
 import java.util.List;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.core.AggregateCall;
@@ -35,6 +36,11 @@ public class IgniteMapAggregate extends Aggregate implements IgniteRel {
         super(cluster, traitSet, input, groupSet, groupSets, aggCalls);
     }
 
+    /** */
+    public IgniteMapAggregate(RelInput input) {
+        super(Commons.changeTraits(input, IgniteConvention.INSTANCE));
+    }
+
     /** {@inheritDoc} */
     @Override public Aggregate copy(RelTraitSet traitSet, RelNode input, ImmutableBitSet groupSet, List<ImmutableBitSet> groupSets, List<AggregateCall> aggCalls) {
         return new IgniteMapAggregate(getCluster(), traitSet, input, groupSet, groupSets, aggCalls);
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 06cee71..64b8343 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
@@ -20,10 +20,12 @@ package org.apache.ignite.internal.processors.query.calcite.rel;
 import java.util.List;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Project;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rex.RexNode;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
 
 /**
  * Relational expression that computes a set of
@@ -43,6 +45,10 @@ public class IgniteProject extends Project implements IgniteRel {
         super(cluster, traits, input, projects, rowType);
     }
 
+    public IgniteProject(RelInput input) {
+        super(Commons.changeTraits(input, IgniteConvention.INSTANCE));
+    }
+
     /** {@inheritDoc} */
     @Override public Project copy(RelTraitSet traitSet, RelNode input, List<RexNode> projects, RelDataType rowType) {
         return new IgniteProject(getCluster(), traitSet, input, projects, rowType);
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteReceiver.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteReceiver.java
index 46cad1d..7f6497b 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteReceiver.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteReceiver.java
@@ -21,34 +21,58 @@ import java.util.List;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.AbstractRelNode;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
-import org.apache.ignite.internal.processors.query.calcite.prepare.Fragment;
+import org.apache.calcite.rel.RelWriter;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.sql.SqlExplainLevel;
 
 /**
  * Relational expression that receives elements from remote {@link IgniteSender}
  */
 public class IgniteReceiver extends AbstractRelNode implements IgniteRel {
     /** */
-    private final Fragment source;
+    private final long exchangeId;
+
+    /** */
+    private final long sourceFragmentId;
 
     /**
      * Creates a Receiver
-     *
-     * @param cluster  Cluster that this relational expression belongs to
-     * @param traits   Traits of this relational expression
-     * @param source   Source fragment.
      */
-    public IgniteReceiver(RelOptCluster cluster, RelTraitSet traits, Fragment source) {
+    public IgniteReceiver(RelOptCluster cluster, RelTraitSet traits, RelDataType rowType, long exchangeId,
+        long sourceFragmentId) {
         super(cluster, traits);
 
-        this.source = source;
+        this.exchangeId = exchangeId;
+        this.sourceFragmentId = sourceFragmentId;
+        this.rowType = rowType;
+    }
+
+    /** */
+    public IgniteReceiver(RelInput input) {
+        this(
+            input.getCluster(),
+            input.getTraitSet().replace(IgniteConvention.INSTANCE),
+            input.getRowType("rowType"),
+            ((Number)input.get("exchangeId")).longValue(),
+            ((Number)input.get("sourceFragmentId")).longValue());
+    }
 
-        rowType = source.root().getRowType();
+    /** */
+    public long exchangeId() {
+        return exchangeId;
+    }
+
+    /** */
+    public long sourceFragmentId() {
+        return sourceFragmentId;
     }
 
     /** {@inheritDoc} */
     @Override public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) {
-        return new IgniteReceiver(getCluster(), traitSet, source);
+        return new IgniteReceiver(getCluster(), traitSet, rowType, exchangeId, sourceFragmentId);
     }
 
     /** {@inheritDoc} */
@@ -56,10 +80,17 @@ public class IgniteReceiver extends AbstractRelNode implements IgniteRel {
         return visitor.visit(this);
     }
 
-    /**
-     * @return Source fragment.
-     */
-    public Fragment source() {
-        return source;
+    /** {@inheritDoc} */
+    @Override public RelWriter explainTerms(RelWriter pw) {
+        RelWriter writer = super.explainTerms(pw);
+
+        if (pw.getDetailLevel() != SqlExplainLevel.ALL_ATTRIBUTES)
+            return writer;
+
+        return writer
+            .item("rowType", rowType)
+            .item("exchangeId", exchangeId)
+            .item("sourceFragmentId", sourceFragmentId)
+            .itemIf("collations", collations(), collations() != null && collations() != RelCollations.EMPTY);
     }
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteReduceAggregate.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteReduceAggregate.java
index 4c9a4ab..58b0824 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteReduceAggregate.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteReduceAggregate.java
@@ -22,12 +22,14 @@ import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptUtil;
 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.SingleRel;
 import org.apache.calcite.rel.core.Aggregate.Group;
 import org.apache.calcite.rel.core.AggregateCall;
 import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.sql.SqlExplainLevel;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.Util;
 import org.apache.ignite.internal.processors.query.calcite.trait.DistributionTraitDef;
@@ -62,6 +64,18 @@ public class IgniteReduceAggregate extends SingleRel implements IgniteRel {
         this.rowType = rowType;
     }
 
+    /** */
+    public IgniteReduceAggregate(RelInput input) {
+        this(
+            input.getCluster(),
+            input.getTraitSet().replace(IgniteConvention.INSTANCE),
+            input.getInput(),
+            input.getBitSet("group"),
+            input.getBitSetList("groups"),
+            input.getAggregateCalls("aggs"),
+            input.getRowType("rowType"));
+    }
+
     /** {@inheritDoc} */
     @Override protected RelDataType deriveRowType() {
         throw new UnsupportedOperationException();
@@ -80,6 +94,7 @@ public class IgniteReduceAggregate extends SingleRel implements IgniteRel {
     /** {@inheritDoc} */
     @Override public RelWriter explainTerms(RelWriter pw) {
         super.explainTerms(pw)
+            .itemIf("rowType", rowType, pw.getDetailLevel() == SqlExplainLevel.ALL_ATTRIBUTES)
             .item("group", groupSet)
             .itemIf("groups", groupSets, Group.induce(groupSet, groupSets) != Group.SIMPLE)
             .itemIf("aggs", aggCalls, pw.nest());
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteSender.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteSender.java
index 4e4d84e..1b0e4b0 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteSender.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteSender.java
@@ -20,10 +20,12 @@ package org.apache.ignite.internal.processors.query.calcite.rel;
 import java.util.List;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelDistributions;
+import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.RelWriter;
 import org.apache.calcite.rel.SingleRel;
-import org.apache.ignite.internal.processors.query.calcite.prepare.RelTarget;
-import org.apache.ignite.internal.processors.query.calcite.prepare.RelTargetAware;
+import org.apache.calcite.sql.SqlExplainLevel;
 import org.apache.ignite.internal.processors.query.calcite.trait.DistributionTraitDef;
 import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
 
@@ -31,38 +33,67 @@ import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribut
  * Relational expression that iterates over its input
  * and sends elements to remote {@link IgniteReceiver}
  */
-public class IgniteSender extends SingleRel implements IgniteRel, RelTargetAware {
+public class IgniteSender extends SingleRel implements IgniteRel {
     /** */
-    private RelTarget target;
+    private final long exchangeId;
 
-    /**
-     * Creates a Sender.
-     *
-     * @param cluster  Cluster that this relational expression belongs to
-     * @param traits   Traits of this relational expression
-     * @param input    input relational expression
-     */
-    public IgniteSender(RelOptCluster cluster, RelTraitSet traits, RelNode input) {
-        this(cluster, traits, input, null);
-    }
+    /** */
+    private long targetFragmentId;
+
+    /** */
+    private final IgniteDistribution distribution;
 
     /**
      * Creates a Sender.
-     *
      * @param cluster  Cluster that this relational expression belongs to
      * @param traits   Traits of this relational expression
      * @param input    input relational expression
-     * @param target   Remote targets information
+     * @param exchangeId Exchange ID.
+     * @param targetFragmentId Target fragment ID.
      */
-    private IgniteSender(RelOptCluster cluster, RelTraitSet traits, RelNode input, RelTarget target) {
+    public IgniteSender(RelOptCluster cluster, RelTraitSet traits, RelNode input, long exchangeId,
+        long targetFragmentId, IgniteDistribution distribution) {
         super(cluster, traits, input);
 
-        this.target = target;
+        assert traitSet.containsIfApplicable(distribution)
+            : "traits=" + traitSet + ", distribution" + distribution;
+        assert distribution != RelDistributions.ANY;
+
+        this.exchangeId = exchangeId;
+        this.targetFragmentId = targetFragmentId;
+        this.distribution = distribution;
+    }
+
+    public IgniteSender(RelInput input) {
+        this(
+            input.getCluster(),
+            input.getTraitSet()
+                .replace(input.getDistribution())
+                .replace(IgniteConvention.INSTANCE),
+            input.getInput(),
+            ((Number)input.get("exchangeId")).longValue(),
+            ((Number)input.get("targetFragmentId")).longValue(),
+            (IgniteDistribution)input.getDistribution());
+    }
+
+    /** */
+    public long exchangeId() {
+        return exchangeId;
+    }
+
+    /** */
+    public long targetFragmentId() {
+        return targetFragmentId;
+    }
+
+    /** */
+    public void targetFragmentId(long targetFragmentId) {
+        this.targetFragmentId = targetFragmentId;
     }
 
     /** {@inheritDoc} */
     @Override public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) {
-        return new IgniteSender(getCluster(), traitSet, sole(inputs), target);
+        return new IgniteSender(getCluster(), traitSet, sole(inputs), exchangeId, targetFragmentId, distribution);
     }
 
     /** {@inheritDoc} */
@@ -71,15 +102,8 @@ public class IgniteSender extends SingleRel implements IgniteRel, RelTargetAware
     }
 
     /** {@inheritDoc} */
-    @Override public void target(RelTarget target) {
-        this.target = target;
-    }
-
-    /**
-     * @return Remote targets information.
-     */
-    public RelTarget target() {
-        return target;
+    @Override public IgniteDistribution distribution() {
+        return distribution;
     }
 
     /**
@@ -88,4 +112,17 @@ public class IgniteSender extends SingleRel implements IgniteRel, RelTargetAware
     public IgniteDistribution sourceDistribution() {
         return input.getTraitSet().getTrait(DistributionTraitDef.INSTANCE);
     }
+
+    /** {@inheritDoc} */
+    @Override public RelWriter explainTerms(RelWriter pw) {
+        RelWriter writer = super.explainTerms(pw);
+
+        if (pw.getDetailLevel() != SqlExplainLevel.ALL_ATTRIBUTES)
+            return writer;
+
+        return writer
+            .item("exchangeId", exchangeId)
+            .item("targetFragmentId", targetFragmentId)
+            .item("distribution", distribution());
+    }
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTableModify.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTableModify.java
index 817a53e..ae455de 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTableModify.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTableModify.java
@@ -21,10 +21,12 @@ import java.util.List;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.plan.RelTraitSet;
-import org.apache.calcite.prepare.Prepare;
+import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.RelWriter;
 import org.apache.calcite.rel.core.TableModify;
 import org.apache.calcite.rex.RexNode;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
 
 /**
  *
@@ -37,29 +39,39 @@ public class IgniteTableModify extends TableModify implements IgniteRel {
      * <blockquote>
      * <pre>UPDATE table SET iden1 = exp1, ident2 = exp2  WHERE condition</pre>
      * </blockquote>
-     *
-     * @param cluster              Cluster this relational expression belongs to
+     *  @param cluster              Cluster this relational expression belongs to
      * @param traitSet             Traits of this relational expression
      * @param table                Target table to modify
-     * @param catalogReader        accessor to the table metadata.
      * @param input                Sub-query or filter condition
      * @param operation            Modify operation (INSERT, UPDATE, DELETE)
      * @param updateColumnList     List of column identifiers to be updated
-     *                             (e.g. ident1, ident2); null if not UPDATE
+*                             (e.g. ident1, ident2); null if not UPDATE
      * @param sourceExpressionList List of value expressions to be set
-     *                             (e.g. exp1, exp2); null if not UPDATE
+*                             (e.g. exp1, exp2); null if not UPDATE
      * @param flattened            Whether set flattens the input row type
      */
     public IgniteTableModify(RelOptCluster cluster,
         RelTraitSet traitSet,
         RelOptTable table,
-        Prepare.CatalogReader catalogReader,
         RelNode input,
         Operation operation,
         List<String> updateColumnList,
         List<RexNode> sourceExpressionList,
         boolean flattened) {
-        super(cluster, traitSet, table, catalogReader, input, operation, updateColumnList, sourceExpressionList, flattened);
+        super(cluster, traitSet, table, Commons.context(cluster).catalogReader(), input, operation, updateColumnList, sourceExpressionList, flattened);
+    }
+
+    public IgniteTableModify(RelInput input) {
+        this(
+            input.getCluster(),
+            input.getTraitSet().replace(IgniteConvention.INSTANCE),
+            input.getTable("table"),
+            input.getInput(),
+            input.getEnum("operation", Operation.class),
+            input.getStringList("updateColumnList"),
+            input.getExpressionList("sourceExpressionList"),
+            input.getBoolean("flattened", true)
+        );
     }
 
     /** {@inheritDoc} */
@@ -68,7 +80,6 @@ public class IgniteTableModify extends TableModify implements IgniteRel {
             getCluster(),
             traitSet,
             getTable(),
-            getCatalogReader(),
             sole(inputs),
             getOperation(),
             getUpdateColumnList(),
@@ -76,6 +87,10 @@ public class IgniteTableModify extends TableModify implements IgniteRel {
             isFlattened());
     }
 
+    @Override public RelWriter explainTerms(RelWriter pw) {
+        return super.explainTerms(pw);
+    }
+
     /** {@inheritDoc} */
     @Override public <T> T accept(IgniteRelVisitor<T> visitor) {
         return visitor.visit(this);
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 3ba3d35..30bb899 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
@@ -21,8 +21,10 @@ import java.util.List;
 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.rel.RelNode;
 import org.apache.calcite.rel.core.TableScan;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
 
 /**
  * Relational operator that returns the contents of a table.
@@ -39,6 +41,11 @@ public class IgniteTableScan extends TableScan implements IgniteRel {
         super(cluster, traits, table);
     }
 
+    /** */
+    public IgniteTableScan(RelInput input) {
+        super(Commons.changeTraits(input, IgniteConvention.INSTANCE));
+    }
+
     /** {@inheritDoc} */
     @Override public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) {
         assert inputs.isEmpty();
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTrimExchange.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTrimExchange.java
index 4126060..fb7ccdb 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTrimExchange.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTrimExchange.java
@@ -17,14 +17,15 @@
 
 package org.apache.ignite.internal.processors.query.calcite.rel;
 
-import java.util.List;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelDistribution;
+import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
-import org.apache.calcite.rel.SingleRel;
-import org.apache.ignite.internal.processors.query.calcite.prepare.RelTarget;
-import org.apache.ignite.internal.processors.query.calcite.prepare.RelTargetAware;
+import org.apache.calcite.rel.core.Exchange;
 import org.apache.ignite.internal.processors.query.calcite.trait.DistributionTraitDef;
+import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
 
 import static org.apache.calcite.rel.RelDistribution.Type.BROADCAST_DISTRIBUTED;
 import static org.apache.calcite.rel.RelDistribution.Type.HASH_DISTRIBUTED;
@@ -32,40 +33,31 @@ import static org.apache.calcite.rel.RelDistribution.Type.HASH_DISTRIBUTED;
 /**
  *
  */
-public class IgniteTrimExchange extends SingleRel implements IgniteRel, RelTargetAware {
+public class IgniteTrimExchange extends Exchange implements IgniteRel {
     /** */
-    private RelTarget target;
-
-    /** */
-    public IgniteTrimExchange(RelOptCluster cluster, RelTraitSet traits, RelNode input) {
-        super(cluster, traits, input);
+    public IgniteTrimExchange(RelOptCluster cluster, RelTraitSet traits, RelNode input, RelDistribution distribution) {
+        super(cluster, traits, input, distribution);
 
+        assert distribution.getType() == HASH_DISTRIBUTED;
         assert input.getTraitSet().getTrait(DistributionTraitDef.INSTANCE).getType() == BROADCAST_DISTRIBUTED;
-        assert traits.getTrait(DistributionTraitDef.INSTANCE).getType() == HASH_DISTRIBUTED;
     }
 
-    /** */
-    @Override public RelNode copy(RelTraitSet traits, List<RelNode> inputs) {
-        RelNode input = sole(inputs);
-
-        assert input.getTraitSet().getTrait(DistributionTraitDef.INSTANCE).getType() == BROADCAST_DISTRIBUTED;
-        assert traits.getTrait(DistributionTraitDef.INSTANCE).getType() == HASH_DISTRIBUTED;
-
-        return new IgniteTrimExchange(getCluster(), traits, input);
+    public IgniteTrimExchange(RelInput input) {
+        super(Commons.changeTraits(input, IgniteConvention.INSTANCE));
     }
 
     /** {@inheritDoc} */
-    @Override public <T> T accept(IgniteRelVisitor<T> visitor) {
-        return visitor.visit(this);
+    @Override public IgniteDistribution distribution() {
+        return (IgniteDistribution)distribution;
     }
 
-    /** */
-    public RelTarget target() {
-        return target;
+    /** {@inheritDoc} */
+    @Override public Exchange copy(RelTraitSet traits, RelNode input, RelDistribution distribution) {
+        return new IgniteTrimExchange(getCluster(), traits, input, distribution);
     }
 
     /** {@inheritDoc} */
-    @Override public void target(RelTarget target) {
-        this.target = target;
+    @Override public <T> T accept(IgniteRelVisitor<T> visitor) {
+        return visitor.visit(this);
     }
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteUnionAll.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteUnionAll.java
index b184493..30991d2 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteUnionAll.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteUnionAll.java
@@ -23,6 +23,7 @@ import org.apache.calcite.plan.RelOptCost;
 import org.apache.calcite.plan.RelOptPlanner;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelDistribution;
+import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.SetOp;
 import org.apache.calcite.rel.core.Union;
@@ -37,6 +38,14 @@ public class IgniteUnionAll extends Union implements IgniteRel {
         super(cluster, traits, inputs, true);
     }
 
+    /** */
+    public IgniteUnionAll(RelInput input) {
+        this(
+            input.getCluster(),
+            input.getTraitSet().replace(IgniteConvention.INSTANCE),
+            input.getInputs());
+    }
+
     /** {@inheritDoc} */
     @Override public SetOp copy(RelTraitSet traitSet, List<RelNode> inputs, boolean all) {
         assert all;
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteValues.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteValues.java
index f631f68..8db88a1 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteValues.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteValues.java
@@ -21,10 +21,12 @@ import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Values;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rex.RexLiteral;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
 
 /**
  *
@@ -47,6 +49,11 @@ public class IgniteValues extends Values implements IgniteRel {
         super(cluster, rowType, tuples, traits);
     }
 
+    /** */
+    public IgniteValues(RelInput input) {
+        super(Commons.changeTraits(input, IgniteConvention.INSTANCE));
+    }
+
     /** {@inheritDoc} */
     @Override public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) {
         assert inputs.isEmpty();
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/TableModifyConverterRule.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/TableModifyConverterRule.java
index dded683..24352d2 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/TableModifyConverterRule.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/TableModifyConverterRule.java
@@ -55,7 +55,7 @@ public class TableModifyConverterRule extends RelOptRule {
             .replace(IgniteDistributions.single()); // TODO move to IgniteMdDistributions
 
         RuleUtils.transformTo(call,
-            new IgniteTableModify(cluster, traits, rel.getTable(), rel.getCatalogReader(), input,
+            new IgniteTableModify(cluster, traits, rel.getTable(), input,
             rel.getOperation(), rel.getUpdateColumnList(), rel.getSourceExpressionList(), rel.isFlattened()));
     }
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/AggregatePhysicalRel.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/AggregatePhysicalRel.java
deleted file mode 100644
index ef0e9ed..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/AggregatePhysicalRel.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.serialize;
-
-import java.io.IOException;
-import java.io.ObjectInput;
-import java.io.ObjectOutput;
-import java.util.List;
-import org.apache.calcite.util.ImmutableBitSet;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AggCallExp;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.type.DataType;
-
-/**
- *
- */
-public class AggregatePhysicalRel implements PhysicalRel {
-    /** */
-    public static final byte SINGLE = 0;
-
-    /** */
-    public static final byte MAP = 1;
-
-    /** */
-    public static final byte REDUCE = 2;
-
-    /** */
-    private byte type;
-
-    /** */
-    private List<ImmutableBitSet> groupSets;
-
-    /** */
-    private List<AggCallExp> calls;
-
-    /** */
-    private PhysicalRel input;
-
-    /** */
-    private DataType inputRowType;
-
-    public AggregatePhysicalRel(byte type, List<ImmutableBitSet> groupSets, List<AggCallExp> calls, PhysicalRel input, DataType inputRowType) {
-        this.type = type;
-        this.groupSets = groupSets;
-        this.calls = calls;
-        this.input = input;
-        this.inputRowType = inputRowType;
-    }
-
-    public AggregatePhysicalRel() {
-    }
-
-    public byte type() {
-        return type;
-    }
-
-    public List<ImmutableBitSet> groupSets() {
-        return groupSets;
-    }
-
-    public List<AggCallExp> calls() {
-        return calls;
-    }
-
-    public PhysicalRel input() {
-        return input;
-    }
-
-    public DataType inputRowType() {
-        return inputRowType;
-    }
-
-    @Override public <T> T accept(PhysicalRelVisitor<T> visitor) {
-        return visitor.visit(this);
-    }
-
-    @Override public void writeExternal(ObjectOutput out) throws IOException {
-        out.writeByte(type);
-        out.writeObject(groupSets);
-        out.writeObject(calls);
-        out.writeObject(input);
-        if (type != REDUCE)
-            out.writeObject(inputRowType);
-    }
-
-    @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
-        type = in.readByte();
-        groupSets = (List<ImmutableBitSet>) in.readObject();
-        calls = (List<AggCallExp>) in.readObject();
-        input = (PhysicalRel) in.readObject();
-        if (type != REDUCE)
-            inputRowType = (DataType) in.readObject();
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/FilterPhysicalRel.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/FilterPhysicalRel.java
deleted file mode 100644
index 41346a6..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/FilterPhysicalRel.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.serialize;
-
-import java.io.IOException;
-import java.io.ObjectInput;
-import java.io.ObjectOutput;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.Expression;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.type.DataType;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
-
-/**
- * Describes {@link IgniteFilter}
- */
-public class FilterPhysicalRel implements PhysicalRel {
-    /** */
-    private DataType rowType;
-
-    /** */
-    private Expression condition;
-
-    /** */
-    private PhysicalRel input;
-
-    /** */
-    public FilterPhysicalRel() {
-    }
-
-    /** */
-    public FilterPhysicalRel(DataType rowType, Expression condition, PhysicalRel input) {
-        this.rowType = rowType;
-        this.condition = condition;
-        this.input = input;
-    }
-
-    /** */
-    public DataType rowType() {
-        return rowType;
-    }
-
-    /** */
-    public Expression condition() {
-        return condition;
-    }
-
-    /** */
-    public PhysicalRel input() {
-        return input;
-    }
-
-    /** {@inheritDoc} */
-    @Override public <T> T accept(PhysicalRelVisitor<T> visitor) {
-        return visitor.visit(this);
-    }
-
-    /** {@inheritDoc} */
-    @Override public void writeExternal(ObjectOutput out) throws IOException {
-        out.writeObject(rowType);
-        out.writeObject(condition);
-        out.writeObject(input);
-    }
-
-    /** {@inheritDoc} */
-    @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
-        rowType = (DataType) in.readObject();
-        condition = (Expression) in.readObject();
-        input = (PhysicalRel) in.readObject();
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/JoinPhysicalRel.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/JoinPhysicalRel.java
deleted file mode 100644
index e1dce81..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/JoinPhysicalRel.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.serialize;
-
-import java.io.IOException;
-import java.io.ObjectInput;
-import java.io.ObjectOutput;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.Expression;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.type.DataType;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteJoin;
-
-/**
- * Describes {@link IgniteJoin}.
- */
-public class JoinPhysicalRel implements PhysicalRel {
-    /** */
-    private DataType rowType;
-
-    /** */
-    private PhysicalRel left;
-
-    /** */
-    private PhysicalRel right;
-
-    /** */
-    private Expression condition;
-
-    public JoinPhysicalRel() {
-    }
-
-    /** */
-    public JoinPhysicalRel(DataType rowType, PhysicalRel left, PhysicalRel right, Expression condition) {
-        this.rowType = rowType;
-        this.left = left;
-        this.right = right;
-        this.condition = condition;
-    }
-
-    /** */
-    public DataType rowType() {
-        return rowType;
-    }
-
-    /** */
-    public PhysicalRel left() {
-        return left;
-    }
-
-    /** */
-    public PhysicalRel right() {
-        return right;
-    }
-
-    /** */
-    public Expression condition() {
-        return condition;
-    }
-
-    /** {@inheritDoc} */
-    @Override public <T> T accept(PhysicalRelVisitor<T> visitor) {
-        return visitor.visit(this);
-    }
-
-    /** {@inheritDoc} */
-    @Override public void writeExternal(ObjectOutput out) throws IOException {
-        out.writeObject(rowType);
-        out.writeObject(left);
-        out.writeObject(right);
-        out.writeObject(condition);
-    }
-
-    /** {@inheritDoc} */
-    @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
-        rowType = (DataType) in.readObject();
-        left = (PhysicalRel) in.readObject();
-        right = (PhysicalRel) in.readObject();
-        condition = (Expression) in.readObject();
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/PhysicalRel.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/PhysicalRel.java
deleted file mode 100644
index ca0747d..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/PhysicalRel.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.serialize;
-
-import java.io.Externalizable;
-
-/**
- * A superclass of all relational nodes representations.
- */
-public interface PhysicalRel extends Externalizable {
-    /**
-     * Accepts a visit from a visitor.
-     *
-     * @param visitor Visitor.
-     * @return Visit result.
-     */
-    <T> T accept(PhysicalRelVisitor<T> visitor);
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/PhysicalRelVisitor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/PhysicalRelVisitor.java
deleted file mode 100644
index 0c1aee1..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/PhysicalRelVisitor.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.serialize;
-
-/**
- *
- */
-public interface PhysicalRelVisitor<T> {
-    /**
-     * See {@link PhysicalRelVisitor#visit(PhysicalRel)}
-     */
-    T visit(TableScanPhysicalRel rel);
-
-    /**
-     * See {@link PhysicalRelVisitor#visit(PhysicalRel)}
-     */
-    T visit(FilterPhysicalRel rel);
-
-    /**
-     * See {@link PhysicalRelVisitor#visit(PhysicalRel)}
-     */
-    T visit(TrimExchangePhysicalRel rel);
-
-    /**
-     * See {@link PhysicalRelVisitor#visit(PhysicalRel)}
-     */
-    T visit(ProjectPhysicalRel rel);
-
-    /**
-     * See {@link PhysicalRelVisitor#visit(PhysicalRel)}
-     */
-    T visit(JoinPhysicalRel rel);
-
-    /**
-     * See {@link PhysicalRelVisitor#visit(PhysicalRel)}
-     */
-    T visit(ReceiverPhysicalRel rel);
-
-    /**
-     * See {@link PhysicalRelVisitor#visit(PhysicalRel)}
-     */
-    T visit(ValuesPhysicalRel rel);
-
-    /**
-     * See {@link PhysicalRelVisitor#visit(PhysicalRel)}
-     */
-    T visit(SenderPhysicalRel rel);
-
-    /**
-     * See {@link PhysicalRelVisitor#visit(PhysicalRel)}
-     */
-    T visit(AggregatePhysicalRel rel);
-
-    /**
-     * See {@link PhysicalRelVisitor#visit(PhysicalRel)}
-     */
-    T visit(TableModifyPhysicalRel rel);
-
-    /**
-     * See {@link PhysicalRelVisitor#visit(PhysicalRel)}
-     */
-    T visit(UnionAllPhysicalRel rel);
-
-    /**
-     * Processes given physical rel.
-     *
-     * @param rel Physical rel.
-     * @return Visit result.
-     */
-    T visit(PhysicalRel rel);
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/ProjectPhysicalRel.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/ProjectPhysicalRel.java
deleted file mode 100644
index cb0b1fb..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/ProjectPhysicalRel.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.serialize;
-
-import java.io.IOException;
-import java.io.ObjectInput;
-import java.io.ObjectOutput;
-import java.util.ArrayList;
-import java.util.List;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.Expression;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.type.DataType;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteProject;
-
-/**
- * Describes {@link IgniteProject}.
- */
-public class ProjectPhysicalRel implements PhysicalRel {
-    /** */
-    private DataType rowType;
-
-    /** */
-    private List<Expression> projects;
-
-    /** */
-    private PhysicalRel input;
-
-    public ProjectPhysicalRel() {
-    }
-
-    /** */
-    public ProjectPhysicalRel(DataType rowType, List<Expression> projects, PhysicalRel input) {
-        this.rowType = rowType;
-        this.projects = projects;
-        this.input = input;
-    }
-
-    /** */
-    public DataType rowType() {
-        return rowType;
-    }
-
-    /** */
-    public List<Expression> projects() {
-        return projects;
-    }
-
-    /** */
-    public PhysicalRel input() {
-        return input;
-    }
-
-    /** {@inheritDoc} */
-    @Override public <T> T accept(PhysicalRelVisitor<T> visitor) {
-        return visitor.visit(this);
-    }
-
-    /** {@inheritDoc} */
-    @Override public void writeExternal(ObjectOutput out) throws IOException {
-        out.writeObject(rowType);
-
-        out.writeInt(projects.size());
-
-        for (Expression project : projects)
-            out.writeObject(project);
-
-        out.writeObject(input);
-    }
-
-    /** {@inheritDoc} */
-    @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
-        rowType = (DataType) in.readObject();
-
-        int projectsSize = in.readInt();
-
-        List<Expression> projects = new ArrayList<>(projectsSize);
-
-        for (int i = 0; i < projectsSize; i++)
-            projects.add((Expression) in.readObject());
-
-        this.projects = projects;
-
-        input = (PhysicalRel) in.readObject();
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/ReceiverPhysicalRel.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/ReceiverPhysicalRel.java
deleted file mode 100644
index b2d4894..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/ReceiverPhysicalRel.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.serialize;
-
-import java.io.IOException;
-import java.io.ObjectInput;
-import java.io.ObjectOutput;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.UUID;
-import org.apache.calcite.rel.RelCollation;
-import org.apache.calcite.rel.RelCollationTraitDef;
-import org.apache.calcite.rel.RelCollations;
-import org.apache.calcite.rel.RelFieldCollation;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.type.DataType;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteReceiver;
-
-/**
- * Describes {@link IgniteReceiver}.
- */
-public class ReceiverPhysicalRel implements PhysicalRel {
-    /** */
-    private DataType rowType;
-
-    /** */
-    private long sourceFragmentId;
-
-    /** */
-    private List<UUID> sources;
-
-    /** */
-    private List<RelCollation> collations;
-
-    /** */
-    public ReceiverPhysicalRel() {
-    }
-
-    /** */
-    public ReceiverPhysicalRel(DataType rowType, long sourceFragmentId, List<UUID> sources, List<RelCollation> collations) {
-        this.rowType = rowType;
-        this.sourceFragmentId = sourceFragmentId;
-        this.sources = sources;
-        this.collations = collations;
-    }
-
-    /** */
-    public DataType rowType() {
-        return rowType;
-    }
-
-    /** */
-    public long sourceFragmentId() {
-        return sourceFragmentId;
-    }
-
-    /** */
-    public Collection<UUID> sources() {
-        return sources;
-    }
-
-    /** */
-    public List<RelCollation> collations() {
-        return collations;
-    }
-
-    /** {@inheritDoc} */
-    @Override public <T> T accept(PhysicalRelVisitor<T> visitor) {
-        return visitor.visit(this);
-    }
-
-    /** {@inheritDoc} */
-    @Override public void writeExternal(ObjectOutput out) throws IOException {
-        out.writeObject(rowType);
-        out.writeLong(sourceFragmentId);
-
-        out.writeInt(sources.size());
-
-        for (UUID source : sources)
-            out.writeObject(source);
-
-        if (collations == null)
-            out.writeInt(-1);
-        else {
-            out.writeInt(collations.size());
-
-            for (RelCollation collation : collations) {
-                List<RelFieldCollation> fields = collation.getFieldCollations();
-
-                out.writeInt(fields.size());
-
-                for (RelFieldCollation field : fields) {
-                    out.writeInt(field.getFieldIndex());
-                    out.writeByte(field.direction.ordinal());
-                    out.writeByte(field.nullDirection.ordinal());
-                }
-            }
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
-        rowType = (DataType) in.readObject();
-        sourceFragmentId = in.readLong();
-
-        final int sourcesSize = in.readInt();
-
-        List<UUID> sources = new ArrayList<>(sourcesSize);
-
-        for (int i = 0; i < sourcesSize; i++)
-            sources.add((UUID) in.readObject());
-
-        this.sources = sources;
-
-        int collationsSize = in.readInt();
-
-        if (collationsSize == -1)
-            return;
-
-        List<RelCollation> collations = new ArrayList<>(collationsSize);
-
-        for (int i = 0; i < collationsSize; i++) {
-            int fieldsSize = in.readInt();
-
-            List<RelFieldCollation> fields = new ArrayList<>(fieldsSize);
-
-            for (int j = 0; j < fieldsSize; j++) {
-                int fieldIndex = in.readInt();
-                RelFieldCollation.Direction direction = RelFieldCollation.Direction.values()[in.readByte()];
-                RelFieldCollation.NullDirection nullDirection = RelFieldCollation.NullDirection.values()[in.readByte()];
-
-                fields.add(new RelFieldCollation(fieldIndex, direction, nullDirection));
-            }
-            collations.add(RelCollationTraitDef.INSTANCE.canonize(RelCollations.of(fields)));
-        }
-
-        this.collations = collations;
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/RelToPhysicalConverter.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/RelToPhysicalConverter.java
deleted file mode 100644
index b07453f..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/RelToPhysicalConverter.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.serialize;
-
-import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.List;
-import org.apache.calcite.rel.RelDistribution;
-import org.apache.calcite.rel.RelNode;
-import org.apache.calcite.rex.RexLiteral;
-import org.apache.calcite.util.ImmutableBitSet;
-import org.apache.calcite.util.ImmutableIntList;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.Expression;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.RexToExpTranslator;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AggCallExp;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.type.DataType;
-import org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping;
-import org.apache.ignite.internal.processors.query.calcite.prepare.RelTarget;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteAggregate;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteJoin;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteMapAggregate;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteProject;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteReceiver;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteReduceAggregate;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRelVisitor;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSender;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableModify;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTrimExchange;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteUnionAll;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteValues;
-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;
-
-/**
- * Converts RelNode tree to physical rel tree.
- */
-public class RelToPhysicalConverter implements IgniteRelVisitor<PhysicalRel> {
-    /** */
-    private final RexToExpTranslator rexTranslator;
-
-    /** */
-    private final IgniteTypeFactory typeFactory;
-
-    /** */
-    public RelToPhysicalConverter(IgniteTypeFactory typeFactory) {
-        this.typeFactory = typeFactory;
-
-        rexTranslator = new RexToExpTranslator(typeFactory);
-    }
-
-    /** */
-    public PhysicalRel go(IgniteRel root) {
-        return visit(root);
-    }
-
-    /** {@inheritDoc} */
-    @Override public PhysicalRel visit(IgniteSender rel) {
-        long fragmentId = rel.target().fragmentId();
-        NodesMapping mapping = rel.target().mapping();
-        DistributionFunction fun = rel.distribution().function();
-        ImmutableIntList keys = rel.distribution().getKeys();
-
-        return new SenderPhysicalRel(fragmentId, mapping, fun, keys, visit((IgniteRel) rel.getInput()));
-    }
-
-    /** {@inheritDoc} */
-    @Override public PhysicalRel visit(IgniteFilter rel) {
-        return new FilterPhysicalRel(DataType.fromType(rel.getRowType()),
-            rexTranslator.translate(rel.getCondition()), visit((IgniteRel) rel.getInput()));
-    }
-
-    /** {@inheritDoc} */
-    @Override public PhysicalRel visit(IgniteTrimExchange rel) {
-        IgniteDistribution distr = rel.distribution();
-        RelTarget target = rel.target();
-
-        assert distr.getType() == RelDistribution.Type.HASH_DISTRIBUTED;
-        assert target != null && target.mapping() != null && !F.isEmpty(target.mapping().assignments());
-
-        int partitions = target.mapping().assignments().size();
-
-        return new TrimExchangePhysicalRel(distr.function(), distr.getKeys(), partitions, visit((IgniteRel) rel.getInput()));
-    }
-
-    /** {@inheritDoc} */
-    @Override public PhysicalRel visit(IgniteProject rel) {
-        return new ProjectPhysicalRel(DataType.fromType(rel.getInput().getRowType()),
-            rexTranslator.translate(rel.getProjects()), visit((IgniteRel) rel.getInput()));
-    }
-
-    /** {@inheritDoc} */
-    @Override public PhysicalRel visit(IgniteJoin rel) {
-        DataType dataType = DataType.fromType(
-            Commons.combinedRowType(
-                typeFactory, rel.getLeft().getRowType(), rel.getRight().getRowType()));
-
-        return new JoinPhysicalRel(dataType, visit((IgniteRel) rel.getLeft()), visit((IgniteRel) rel.getRight()), rexTranslator.translate(rel.getCondition()));
-    }
-
-    /** {@inheritDoc} */
-    @Override public PhysicalRel visit(IgniteAggregate rel) {
-        byte type = AggregatePhysicalRel.SINGLE;
-        List<ImmutableBitSet> groupSets = rel.getGroupSets();
-        List<AggCallExp> calls = Commons.transform(rel.getAggCallList(), rexTranslator::translate);
-        PhysicalRel input = visit((IgniteRel) rel.getInput());
-        DataType inputRowType = DataType.fromType(rel.getInput().getRowType());
-
-        return new AggregatePhysicalRel(type, groupSets, calls, input, inputRowType);
-    }
-
-    /** {@inheritDoc} */
-    @Override public PhysicalRel visit(IgniteMapAggregate rel) {
-        byte type = AggregatePhysicalRel.MAP;
-        List<ImmutableBitSet> groupSets = rel.getGroupSets();
-        List<AggCallExp> calls = Commons.transform(
-            rel.getAggCallList(), rexTranslator::translate);
-        PhysicalRel input = visit((IgniteRel) rel.getInput());
-        DataType inputRowType = DataType.fromType(rel.getInput().getRowType());
-
-        return new AggregatePhysicalRel(type, groupSets, calls, input, inputRowType);
-    }
-
-    /** {@inheritDoc} */
-    @Override public PhysicalRel visit(IgniteReduceAggregate rel) {
-        byte type = AggregatePhysicalRel.REDUCE;
-        List<ImmutableBitSet> groupSets = rel.groupSets();
-        List<AggCallExp> calls = Commons.transform(
-            rel.aggregateCalls(), rexTranslator::translate);
-        PhysicalRel input = visit((IgniteRel) rel.getInput());
-        DataType inputRowType = null;
-
-        return new AggregatePhysicalRel(type, groupSets, calls, input, inputRowType);
-    }
-
-    /** {@inheritDoc} */
-    @Override public PhysicalRel visit(IgniteTableScan rel) {
-        return new TableScanPhysicalRel(rel.getTable().getQualifiedName());
-    }
-
-    /** {@inheritDoc} */
-    @Override public PhysicalRel visit(IgniteReceiver rel) {
-        return new ReceiverPhysicalRel(DataType.fromType(rel.getRowType()),
-            rel.source().fragmentId(), rel.source().mapping().nodes(), rel.collations());
-    }
-
-    /** {@inheritDoc} */
-    @Override public PhysicalRel visit(IgniteTableModify rel) {
-        return new TableModifyPhysicalRel(rel.getTable().getQualifiedName(),
-            rel.getOperation(), rel.getUpdateColumnList(), visit((IgniteRel) rel.getInput()));
-    }
-
-    /** {@inheritDoc} */
-    @Override public PhysicalRel visit(IgniteValues rel) {
-        return new ValuesPhysicalRel(toValues(rel.getTuples()), rel.getRowType().getFieldCount());
-    }
-
-    /** {@inheritDoc} */
-    @Override public PhysicalRel visit(IgniteUnionAll rel) {
-        List<PhysicalRel> inputs = new ArrayList<>(rel.getInputs().size());
-
-        for (RelNode input : rel.getInputs())
-            inputs.add(visit((IgniteRel) input));
-
-        return new UnionAllPhysicalRel(inputs);
-    }
-
-    /** {@inheritDoc} */
-    @Override public PhysicalRel visit(IgniteExchange rel) {
-        throw new AssertionError();
-    }
-
-    /** {@inheritDoc} */
-    @Override public PhysicalRel visit(IgniteRel rel) {
-        return rel.accept(this);
-    }
-
-    /** */
-    private List<Expression> toValues(ImmutableList<ImmutableList<RexLiteral>> tuples) {
-        return rexTranslator.translate(Commons.flat(Commons.cast(tuples)));
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/SenderPhysicalRel.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/SenderPhysicalRel.java
deleted file mode 100644
index 22bc5be..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/SenderPhysicalRel.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.serialize;
-
-import java.io.IOException;
-import java.io.ObjectInput;
-import java.io.ObjectOutput;
-import org.apache.calcite.util.ImmutableIntList;
-import org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSender;
-import org.apache.ignite.internal.processors.query.calcite.trait.DistributionFunction;
-
-/**
- * Describes {@link IgniteSender}.
- */
-public class SenderPhysicalRel implements PhysicalRel {
-    /** */
-    private long targetFragmentId;
-
-    /** */
-    private NodesMapping mapping;
-
-    /** */
-    private DistributionFunction function;
-
-    /** */
-    private ImmutableIntList distributionKeys;
-
-    /** */
-    private PhysicalRel input;
-
-    public SenderPhysicalRel() {
-    }
-
-    /** */
-    public SenderPhysicalRel(long targetFragmentId, NodesMapping mapping, DistributionFunction function, ImmutableIntList distributionKeys, PhysicalRel input) {
-        this.targetFragmentId = targetFragmentId;
-        this.mapping = mapping;
-        this.function = function;
-        this.distributionKeys = distributionKeys;
-        this.input = input;
-    }
-
-    public long targetFragmentId() {
-        return targetFragmentId;
-    }
-
-    public NodesMapping mapping() {
-        return mapping;
-    }
-
-    public DistributionFunction distributionFunction() {
-        return function;
-    }
-
-    public ImmutableIntList distributionKeys() {
-        return distributionKeys;
-    }
-
-    public PhysicalRel input() {
-        return input;
-    }
-
-    @Override public <T> T accept(PhysicalRelVisitor<T> visitor) {
-        return visitor.visit(this);
-    }
-
-    @Override public void writeExternal(ObjectOutput out) throws IOException {
-        out.writeLong(targetFragmentId);
-        out.writeObject(mapping);
-        out.writeObject(function);
-        out.writeObject(distributionKeys.toIntArray());
-        out.writeObject(input);
-    }
-
-    @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
-        targetFragmentId = in.readLong();
-        mapping = (NodesMapping) in.readObject();
-        function = (DistributionFunction) in.readObject();
-        distributionKeys = ImmutableIntList.of((int[]) in.readObject());
-        input = (PhysicalRel) in.readObject();
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/TableModifyPhysicalRel.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/TableModifyPhysicalRel.java
deleted file mode 100644
index 9ce72c3..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/TableModifyPhysicalRel.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.serialize;
-
-import java.io.IOException;
-import java.io.ObjectInput;
-import java.io.ObjectOutput;
-import java.util.ArrayList;
-import java.util.List;
-import org.apache.calcite.rel.core.TableModify;
-
-/**
- *
- */
-public class TableModifyPhysicalRel implements PhysicalRel {
-    /** */
-    private List<String> tableName;
-
-    /** */
-    private TableModify.Operation operation;
-
-    /** */
-    private List<String> updateColumnList;
-
-    /** */
-    private PhysicalRel input;
-
-    public TableModifyPhysicalRel() {
-    }
-
-    /***/
-    public TableModifyPhysicalRel(List<String> tableName, TableModify.Operation operation, List<String> updateColumnList, PhysicalRel input) {
-        this.tableName = tableName;
-        this.operation = operation;
-        this.updateColumnList = updateColumnList;
-        this.input = input;
-    }
-
-    public List<String> tableName() {
-        return tableName;
-    }
-
-    public TableModify.Operation operation() {
-        return operation;
-    }
-
-    public List<String> updateColumnList() {
-        return updateColumnList;
-    }
-
-    public PhysicalRel input() {
-        return input;
-    }
-
-    @Override public <T> T accept(PhysicalRelVisitor<T> visitor) {
-        return visitor.visit(this);
-    }
-
-    @Override public void writeExternal(ObjectOutput out) throws IOException {
-        out.writeInt(tableName.size());
-
-        for (String name : tableName)
-            out.writeUTF(name);
-
-        out.writeByte(operation.ordinal());
-
-        if (updateColumnList == null)
-            out.writeInt(-1);
-        else {
-            out.writeInt(updateColumnList.size());
-
-            for (String column : updateColumnList)
-                out.writeUTF(column);
-        }
-
-        out.writeObject(input);
-    }
-
-    @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
-        int tableNameSize = in.readInt();
-
-        List<String> tableName = new ArrayList<>(tableNameSize);
-
-        for (int i = 0; i < tableNameSize; i++)
-            tableName.add(in.readUTF());
-
-        this.tableName = tableName;
-
-        operation = TableModify.Operation.values()[in.readByte()];
-
-        int columnsSize = in.readInt();
-
-        if (columnsSize != -1) {
-            List<String> updateColumnList = new ArrayList<>(columnsSize);
-
-            for (int i = 0; i < columnsSize; i++)
-                updateColumnList.add(in.readUTF());
-        }
-
-        input = (PhysicalRel) in.readObject();
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/TableScanPhysicalRel.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/TableScanPhysicalRel.java
deleted file mode 100644
index 92f5f1d..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/TableScanPhysicalRel.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.serialize;
-
-import java.io.IOException;
-import java.io.ObjectInput;
-import java.io.ObjectOutput;
-import java.util.ArrayList;
-import java.util.List;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
-
-/**
- * Describes {@link IgniteTableScan}.
- */
-public class TableScanPhysicalRel implements PhysicalRel {
-    /** */
-    private List<String> tableName;
-
-    /** */
-    public TableScanPhysicalRel() {
-    }
-
-    /**
-     * @param tableName Qualified table name
-     */
-    public TableScanPhysicalRel(List<String> tableName) {
-        this.tableName = tableName;
-    }
-
-    public List<String> tableName() {
-        return tableName;
-    }
-
-    /** {@inheritDoc} */
-    @Override public <T> T accept(PhysicalRelVisitor<T> visitor) {
-        return visitor.visit(this);
-    }
-
-    /** {@inheritDoc} */
-    @Override public void writeExternal(ObjectOutput out) throws IOException {
-        out.writeInt(tableName.size());
-
-        for (String name : tableName)
-            out.writeUTF(name);
-    }
-
-    /** {@inheritDoc} */
-    @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
-        int tableNameSize = in.readInt();
-
-        List<String> tableName = new ArrayList<>(tableNameSize);
-
-        for (int i = 0; i < tableNameSize; i++)
-            tableName.add(in.readUTF());
-
-        this.tableName = tableName;
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/TrimExchangePhysicalRel.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/TrimExchangePhysicalRel.java
deleted file mode 100644
index d7cdc3a..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/TrimExchangePhysicalRel.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.serialize;
-
-import java.io.IOException;
-import java.io.ObjectInput;
-import java.io.ObjectOutput;
-import org.apache.calcite.util.ImmutableIntList;
-import org.apache.ignite.internal.processors.query.calcite.trait.DistributionFunction;
-
-/**
- *
- */
-public class TrimExchangePhysicalRel implements PhysicalRel {
-    /** */
-    private DistributionFunction function;
-
-    /** */
-    private ImmutableIntList keys;
-
-    /** */
-    private int partitions;
-
-    /** */
-    private PhysicalRel input;
-
-    /** */
-    public TrimExchangePhysicalRel() {
-    }
-
-    /** */
-    public TrimExchangePhysicalRel(DistributionFunction function, ImmutableIntList keys, int partitions, PhysicalRel input) {
-        this.function = function;
-        this.keys = keys;
-        this.partitions = partitions;
-        this.input = input;
-    }
-
-    /** */
-    public DistributionFunction function() {
-        return function;
-    }
-
-    /** */
-    public ImmutableIntList distributionKeys() {
-        return keys;
-    }
-
-    /** */
-    public int partitions() {
-        return partitions;
-    }
-
-    /** */
-    public PhysicalRel input() {
-        return input;
-    }
-
-    /** {@inheritDoc} */
-    @Override public <T> T accept(PhysicalRelVisitor<T> visitor) {
-        return visitor.visit(this);
-    }
-
-    /** {@inheritDoc} */
-    @Override public void writeExternal(ObjectOutput out) throws IOException {
-        out.writeObject(function);
-        out.writeObject(keys.toIntArray());
-        out.writeInt(partitions);
-        out.writeObject(input);
-    }
-
-    /** {@inheritDoc} */
-    @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
-        function = (DistributionFunction) in.readObject();
-        keys = ImmutableIntList.of((int[]) in.readObject());
-        partitions = in.readInt();
-        input = (PhysicalRel) in.readObject();
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/UnionAllPhysicalRel.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/UnionAllPhysicalRel.java
deleted file mode 100644
index ae39973..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/UnionAllPhysicalRel.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.serialize;
-
-import java.io.IOException;
-import java.io.ObjectInput;
-import java.io.ObjectOutput;
-import java.util.ArrayList;
-import java.util.List;
-
-/** */
-public class UnionAllPhysicalRel implements PhysicalRel {
-    /** */
-    private List<PhysicalRel> inputs;
-
-    /** */
-    public UnionAllPhysicalRel(List<PhysicalRel> inputs) {
-        this.inputs = inputs;
-    }
-
-    /** */
-    public UnionAllPhysicalRel() {
-    }
-
-    /** */
-    public List<PhysicalRel> inputs() {
-        return inputs;
-    }
-
-    /** {@inheritDoc} */
-    @Override public <T> T accept(PhysicalRelVisitor<T> visitor) {
-        return visitor.visit(this);
-    }
-
-    /** {@inheritDoc} */
-    @Override public void writeExternal(ObjectOutput out) throws IOException {
-        out.writeInt(inputs.size());
-        for (PhysicalRel input : inputs)
-            out.writeObject(input);
-    }
-
-    /** {@inheritDoc} */
-    @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
-        int size = in.readInt();
-        List<PhysicalRel> inputs = new ArrayList<>(size);
-
-        for (int i = 0; i < size; i++)
-            inputs.add((PhysicalRel) in.readObject());
-
-        this.inputs = inputs;
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/ValuesPhysicalRel.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/ValuesPhysicalRel.java
deleted file mode 100644
index 7ad80be..0000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/ValuesPhysicalRel.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.serialize;
-
-import java.io.IOException;
-import java.io.ObjectInput;
-import java.io.ObjectOutput;
-import java.util.ArrayList;
-import java.util.List;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.Expression;
-
-/**
- *
- */
-public class ValuesPhysicalRel implements PhysicalRel {
-    /** */
-    private List<Expression> values;
-
-    /** */
-    private int rowLen;
-
-    public ValuesPhysicalRel() {
-    }
-
-    /** */
-    public ValuesPhysicalRel(List<Expression> values, int rowLen) {
-        this.values = values;
-        this.rowLen = rowLen;
-    }
-
-    public List<Expression> values() {
-        return values;
-    }
-
-    public int rowLength() {
-        return rowLen;
-    }
-
-    @Override public <T> T accept(PhysicalRelVisitor<T> visitor) {
-        return visitor.visit(this);
-    }
-
-    @Override public void writeExternal(ObjectOutput out) throws IOException {
-        out.writeInt(rowLen);
-
-        out.writeInt(values.size());
-
-        for (Expression value : values)
-            out.writeObject(value);
-    }
-
-    @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
-        rowLen = in.readInt();
-
-        int valuesSize = in.readInt();
-
-        List<Expression> values = new ArrayList<>(valuesSize);
-
-        for (int i = 0; i < valuesSize; i++)
-            values.add((Expression) in.readObject());
-
-        this.values = values;
-    }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/DistributionFunction.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/DistributionFunction.java
index 085ce43..3c926d3 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/DistributionFunction.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/DistributionFunction.java
@@ -251,6 +251,11 @@ public abstract class DistributionFunction implements Serializable {
             this.key = key;
         }
 
+        /** */
+        public int cacheId() {
+            return cacheId;
+        }
+
         /** {@inheritDoc} */
         @Override public RelDistribution.Type type() {
             return RelDistribution.Type.HASH_DISTRIBUTED;
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/DistributionTraitDef.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/DistributionTraitDef.java
index b55c77d..1415940 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/DistributionTraitDef.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/DistributionTraitDef.java
@@ -60,7 +60,7 @@ public class DistributionTraitDef extends RelTraitDef<IgniteDistribution> {
         // special case
         if (fromDist.getType() == RelDistribution.Type.BROADCAST_DISTRIBUTED
             && toDist.getType() == RelDistribution.Type.HASH_DISTRIBUTED) {
-            newRel = planner.register(new IgniteTrimExchange(rel.getCluster(), newTraits, rel), rel);
+            newRel = planner.register(new IgniteTrimExchange(rel.getCluster(), newTraits, rel, toDist), rel);
         }
         else {
             RelNode input = RuleUtils.convert(rel, IgniteDistributions.any()); // erasing source distribution a bit reduces search space
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/Commons.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/Commons.java
index f6e9a19..7233df6 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/Commons.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/Commons.java
@@ -17,7 +17,9 @@
 
 package org.apache.ignite.internal.processors.query.calcite.util;
 
+import com.google.common.collect.ImmutableList;
 import java.io.PrintWriter;
+import java.io.StringReader;
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -29,25 +31,45 @@ import java.util.Objects;
 import java.util.Set;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+import org.apache.calcite.config.CalciteSystemProperty;
 import org.apache.calcite.linq4j.tree.Primitive;
 import org.apache.calcite.plan.Context;
 import org.apache.calcite.plan.Contexts;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelOptTable;
+import org.apache.calcite.plan.RelTrait;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.RelDistribution;
+import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.AggregateCall;
 import org.apache.calcite.rel.externalize.RelWriterImpl;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.ImmutableIntList;
+import org.apache.calcite.util.Util;
+import org.apache.ignite.IgniteException;
 import org.apache.ignite.IgniteLogger;
 import org.apache.ignite.internal.GridComponent;
 import org.apache.ignite.internal.GridKernalContext;
 import org.apache.ignite.internal.processors.query.QueryContext;
+import org.apache.ignite.internal.processors.query.calcite.exec.exp.ExpressionFactory;
 import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.Accumulator;
 import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.GroupKey;
+import org.apache.ignite.internal.processors.query.calcite.externalize.RelJsonReader;
+import org.apache.ignite.internal.processors.query.calcite.externalize.RelJsonWriter;
 import org.apache.ignite.internal.processors.query.calcite.prepare.PlanningContext;
 import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.U;
+import org.codehaus.commons.compiler.CompilerFactoryFactory;
+import org.codehaus.commons.compiler.IClassBodyEvaluator;
+import org.codehaus.commons.compiler.ICompilerFactory;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -131,7 +153,14 @@ public final class Commons {
      * Extracts planner context.
      */
     public static PlanningContext context(RelNode rel) {
-        return context(rel.getCluster().getPlanner().getContext());
+        return context(rel.getCluster());
+    }
+
+    /**
+     * Extracts planner context.
+     */
+    public static PlanningContext context(RelOptCluster cluster) {
+        return context(cluster.getPlanner().getContext());
     }
 
     /**
@@ -292,9 +321,160 @@ public final class Commons {
     }
 
     /** */
+    public static String toJson(RelNode rel) {
+        RelJsonWriter writer = new RelJsonWriter();
+        rel.explain(writer);
+
+        return writer.asString();
+    }
+
+    public static <T extends RelNode> T fromJson(RelOptCluster cluster, String json) {
+        RelJsonReader reader = new RelJsonReader(cluster, context(cluster).catalogReader());
+
+        return (T)reader.read(json);
+    }
+
+    /** */
     public static String explain(RelNode rel) {
         StringWriter writer = new StringWriter();
         rel.explain(new RelWriterImpl(new PrintWriter(writer)));
         return writer.toString();
     }
+
+    /** */
+    public static <T> T compile(Class<T> interfaceType, String body) {
+        final boolean debug = CalciteSystemProperty.DEBUG.value();
+
+        if (debug)
+            Util.debugCode(System.out, body);
+
+        try {
+            final ICompilerFactory compilerFactory;
+
+            try {
+                compilerFactory = CompilerFactoryFactory.getDefaultCompilerFactory();
+            } catch (Exception e) {
+                throw new IllegalStateException(
+                    "Unable to instantiate java compiler", e);
+            }
+
+            IClassBodyEvaluator cbe = compilerFactory.newClassBodyEvaluator();
+
+            cbe.setImplementedInterfaces(new Class[]{ interfaceType });
+            cbe.setParentClassLoader(ExpressionFactory.class.getClassLoader());
+
+            if (debug)
+                // Add line numbers to the generated janino class
+                cbe.setDebuggingInformation(true, true, true);
+
+            return (T) cbe.createInstance(new StringReader(body));
+        } catch (Exception e) {
+            throw new IgniteException(e);
+        }
+    }
+
+    /** */
+    public static RelInput changeTraits(RelInput input, RelTrait... traits) {
+        RelTraitSet traitSet = input.getTraitSet();
+
+        for (RelTrait trait : traits)
+            traitSet = traitSet.replace(trait);
+
+        RelTraitSet traitSet0 = traitSet;
+
+        return new RelInput() {
+            @Override public RelOptCluster getCluster() {
+                return input.getCluster();
+            }
+
+            @Override public RelTraitSet getTraitSet() {
+                return traitSet0;
+            }
+
+            @Override public RelOptTable getTable(String table) {
+                return input.getTable(table);
+            }
+
+            @Override public RelNode getInput() {
+                return input.getInput();
+            }
+
+            @Override public List<RelNode> getInputs() {
+                return input.getInputs();
+            }
+
+            @Override public RexNode getExpression(String tag) {
+                return input.getExpression(tag);
+            }
+
+            @Override public ImmutableBitSet getBitSet(String tag) {
+                return input.getBitSet(tag);
+            }
+
+            @Override public List<ImmutableBitSet> getBitSetList(String tag) {
+                return input.getBitSetList(tag);
+            }
+
+            @Override public List<AggregateCall> getAggregateCalls(String tag) {
+                return input.getAggregateCalls(tag);
+            }
+
+            @Override public Object get(String tag) {
+                return input.get(tag);
+            }
+
+            @Override public String getString(String tag) {
+                return input.getString(tag);
+            }
+
+            @Override public float getFloat(String tag) {
+                return input.getFloat(tag);
+            }
+
+            @Override public <E extends Enum<E>> E getEnum(String tag, Class<E> enumClass) {
+                return input.getEnum(tag, enumClass);
+            }
+
+            @Override public List<RexNode> getExpressionList(String tag) {
+                return input.getExpressionList(tag);
+            }
+
+            @Override public List<String> getStringList(String tag) {
+                return input.getStringList(tag);
+            }
+
+            @Override public List<Integer> getIntegerList(String tag) {
+                return input.getIntegerList(tag);
+            }
+
+            @Override public List<List<Integer>> getIntegerListList(String tag) {
+                return input.getIntegerListList(tag);
+            }
+
+            @Override public RelDataType getRowType(String tag) {
+                return input.getRowType(tag);
+            }
+
+            @Override public RelDataType getRowType(String expressionsTag, String fieldsTag) {
+                return input.getRowType(expressionsTag, fieldsTag);
+            }
+
+            @Override public RelCollation getCollation() {
+                return input.getCollation();
+            }
+
+            @Override public RelDistribution getDistribution() {
+                return input.getDistribution();
+            }
+
+            @Override public ImmutableList<ImmutableList<RexLiteral>> getTuples(
+                String tag) {
+                return input.getTuples(tag);
+            }
+
+            @Override public boolean getBoolean(String tag, boolean default_) {
+                return input.getBoolean(tag, default_);
+            }
+        };
+    }
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteMethod.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteMethod.java
index ff8f2bd..dd6e6ff 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteMethod.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteMethod.java
@@ -20,19 +20,17 @@ package org.apache.ignite.internal.processors.query.calcite.util;
 import java.lang.reflect.Method;
 import org.apache.calcite.linq4j.tree.Types;
 import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.CallOperation;
 import org.apache.ignite.internal.processors.query.calcite.exec.exp.Scalar;
 import org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMetadata.DerivedDistribution;
-import org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMetadata.FragmentMetadata;
+import org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMetadata.NodesMappingMetadata;
 
 /**
  * Contains methods used in metadata definitions.
  */
 public enum IgniteMethod {
-    CALL_APPLY(CallOperation.class, "apply", Object[].class),
     SCALAR_EXECUTE(Scalar.class, "execute", ExecutionContext.class, Object[].class, Object[].class),
     DERIVED_DISTRIBUTIONS(DerivedDistribution.class, "deriveDistributions"),
-    FRAGMENT_INFO(FragmentMetadata.class, "fragmentInfo");
+    NODES_MAPPING(NodesMappingMetadata.class, "nodesMapping");
 
     /** */
     private final Method method;
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 0896669..382a5db 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
@@ -57,11 +57,14 @@ import org.apache.ignite.internal.processors.query.calcite.exec.QueryTaskExecuto
 import org.apache.ignite.internal.processors.query.calcite.exec.rel.Node;
 import org.apache.ignite.internal.processors.query.calcite.exec.rel.Outbox;
 import org.apache.ignite.internal.processors.query.calcite.exec.rel.RootNode;
+import org.apache.ignite.internal.processors.query.calcite.externalize.RelJsonReader;
+import org.apache.ignite.internal.processors.query.calcite.externalize.RelJsonWriter;
 import org.apache.ignite.internal.processors.query.calcite.message.CalciteMessage;
 import org.apache.ignite.internal.processors.query.calcite.message.MessageServiceImpl;
 import org.apache.ignite.internal.processors.query.calcite.message.TestIoManager;
 import org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping;
 import org.apache.ignite.internal.processors.query.calcite.prepare.Fragment;
+import org.apache.ignite.internal.processors.query.calcite.prepare.FragmentDescription;
 import org.apache.ignite.internal.processors.query.calcite.prepare.IgnitePlanner;
 import org.apache.ignite.internal.processors.query.calcite.prepare.MultiStepPlan;
 import org.apache.ignite.internal.processors.query.calcite.prepare.MultiStepQueryPlan;
@@ -1263,7 +1266,16 @@ public class PlannerTest extends GridCommonAbstractTest {
             exchangeService.mailboxRegistry(mailboxRegistry);
             exchangeService.init();
 
-            ectx = new ExecutionContext(taskExecutor, ctx, queryId, fragment.fragmentId(), null, Commons.parametersMap(ctx.parameters()));
+            ectx = new ExecutionContext(taskExecutor,
+                ctx,
+                queryId,
+                new FragmentDescription(
+                    fragment.fragmentId(),
+                    null,
+                    0,
+                    plan.targetMapping(fragment),
+                    plan.remoteSources(fragment)),
+                Commons.parametersMap(ctx.parameters()));
 
             exec = new LogicalRelImplementor(ectx, c1 -> r1 -> 0, mailboxRegistry, exchangeService,
                 new TestFailureProcessor(kernal)).go(fragment.root());
@@ -1319,8 +1331,12 @@ public class PlannerTest extends GridCommonAbstractTest {
                     .logger(log)
                     .build(),
                 queryId,
-                fragment.fragmentId(),
-                null,
+                new FragmentDescription(
+                    fragment.fragmentId(),
+                    null,
+                    0,
+                    plan.targetMapping(fragment),
+                    plan.remoteSources(fragment)),
                 Commons.parametersMap(ctx.parameters()));
 
             exec = new LogicalRelImplementor(ectx, c -> r -> 0, mailboxRegistry, exchangeService,
@@ -1486,7 +1502,16 @@ public class PlannerTest extends GridCommonAbstractTest {
             exchangeService.mailboxRegistry(mailboxRegistry);
             exchangeService.init();
 
-            ectx = new ExecutionContext(taskExecutor, ctx, queryId, fragment.fragmentId(), null, Commons.parametersMap(ctx.parameters()));
+            ectx = new ExecutionContext(taskExecutor,
+                ctx,
+                queryId,
+                new FragmentDescription(
+                    fragment.fragmentId(),
+                    null,
+                    0,
+                    plan.targetMapping(fragment),
+                    plan.remoteSources(fragment)),
+                Commons.parametersMap(ctx.parameters()));
 
             exec = new LogicalRelImplementor(ectx, c1 -> r1 -> 0, mailboxRegistry, exchangeService,
                 new TestFailureProcessor(kernal)).go(fragment.root());
@@ -1542,8 +1567,12 @@ public class PlannerTest extends GridCommonAbstractTest {
                     .logger(log)
                     .build(),
                 queryId,
-                fragment.fragmentId(),
-                null,
+                new FragmentDescription(
+                    fragment.fragmentId(),
+                    null,
+                    -1,
+                    plan.targetMapping(fragment),
+                    plan.remoteSources(fragment)),
                 Commons.parametersMap(ctx.parameters()));
 
             exec = new LogicalRelImplementor(ectx, c -> r -> 0, mailboxRegistry, exchangeService,
@@ -2166,6 +2195,134 @@ public class PlannerTest extends GridCommonAbstractTest {
         assertEquals(2, plan.fragments().size());
     }
 
+    @Test
+    public void testSerializationDeserialization() throws Exception {
+        IgniteTypeFactory f = new IgniteTypeFactory(IgniteTypeSystem.INSTANCE);
+
+        TestTable developer = new TestTable(
+            new RelDataTypeFactory.Builder(f)
+                .add("ID", f.createJavaType(Integer.class))
+                .add("NAME", f.createJavaType(String.class))
+                .add("PROJECTID", f.createJavaType(Integer.class))
+                .build()) {
+            @Override public IgniteDistribution distribution() {
+                return IgniteDistributions.affinity(0, "Developer", "hash");
+            }
+        };
+
+        TestTable project = new TestTable(
+            new RelDataTypeFactory.Builder(f)
+                .add("ID", f.createJavaType(Integer.class))
+                .add("NAME", f.createJavaType(String.class))
+                .add("VER", f.createJavaType(Integer.class))
+                .build()) {
+            @Override public IgniteDistribution distribution() {
+                return IgniteDistributions.affinity(0, "Project", "hash");
+            }
+        };
+
+        IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
+
+        publicSchema.addTable("DEVELOPER", developer);
+        publicSchema.addTable("PROJECT", project);
+
+        SchemaPlus schema = createRootSchema(false)
+            .add("PUBLIC", publicSchema);
+
+        String sql = "SELECT d.id, d.name, d.projectId, p.id0, p.ver0 " +
+            "FROM PUBLIC.Developer d JOIN (" +
+            "SELECT pp.id as id0, pp.ver as ver0 FROM PUBLIC.Project pp" +
+            ") p " +
+            "ON d.projectId = p.id0 " +
+            "WHERE (d.projectId + 1) > ?";
+
+        RelTraitDef<?>[] traitDefs = {
+            DistributionTraitDef.INSTANCE,
+            ConventionTraitDef.INSTANCE
+        };
+
+        PlanningContext ctx = PlanningContext.builder()
+            .localNodeId(F.first(nodes))
+            .originatingNodeId(F.first(nodes))
+            .parentContext(Contexts.empty())
+            .frameworkConfig(newConfigBuilder(FRAMEWORK_CONFIG)
+                .defaultSchema(schema)
+                .traitDefs(traitDefs)
+                .build())
+            .logger(log)
+            .query(sql)
+            .parameters(new Object[]{2})
+            .topologyVersion(AffinityTopologyVersion.NONE)
+            .build();
+
+        RelNode rel;
+
+        try (IgnitePlanner planner = ctx.planner()){
+            assertNotNull(planner);
+
+            String query = ctx.query();
+
+            assertNotNull(query);
+
+            // Parse
+            SqlNode sqlNode = planner.parse(query);
+
+            // Validate
+            sqlNode = planner.validate(sqlNode);
+
+            // Convert to Relational operators graph
+            rel = planner.convert(sqlNode);
+
+            rel = planner.transform(PlannerPhase.HEURISTIC_OPTIMIZATION, rel.getTraitSet(), rel);
+
+            // Transformation chain
+            RelTraitSet desired = rel.getCluster().traitSet()
+                .replace(IgniteConvention.INSTANCE)
+                .replace(IgniteDistributions.single())
+                .simplify();
+
+            rel = planner.transform(PlannerPhase.OPTIMIZATION, desired, rel);
+        }
+
+        assertNotNull(rel);
+
+        List<Fragment> fragments = new Splitter().go((IgniteRel)rel);
+        List<String> serialized = new ArrayList<>(fragments.size());
+
+        for (Fragment fragment : fragments) {
+            RelJsonWriter writer = new RelJsonWriter();
+            fragment.root().explain(writer);
+            serialized.add(writer.asString());
+        }
+
+        assertNotNull(serialized);
+
+        ctx = PlanningContext.builder()
+            .localNodeId(F.first(nodes))
+            .originatingNodeId(F.first(nodes))
+            .parentContext(Contexts.empty())
+            .frameworkConfig(newConfigBuilder(FRAMEWORK_CONFIG)
+                .defaultSchema(schema)
+                .traitDefs(traitDefs)
+                .build())
+            .logger(log)
+            .query(sql)
+            .parameters(new Object[]{2})
+            .topologyVersion(AffinityTopologyVersion.NONE)
+            .build();
+
+        List<RelNode> nodes = new ArrayList<>();
+
+        try(IgnitePlanner ignored = ctx.planner()) {
+            for (String s : serialized) {
+                RelJsonReader reader = new RelJsonReader(ctx.createCluster(), ctx.catalogReader());
+                nodes.add(reader.read(s));
+            }
+        }
+
+        assertNotNull(nodes);
+    }
+
     /** */
     private NodesMapping intermediateMapping(@NotNull AffinityTopologyVersion topVer, int desiredCount, @Nullable Predicate<ClusterNode> filter) {
         List<UUID> nodes = desiredCount == 1 ? select(this.nodes, 0) : select(this.nodes, 0, 1, 2, 3);
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/ExpressionEvaluationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/ExpressionEvaluationTest.java
deleted file mode 100644
index d385f6e..0000000
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/ExpressionEvaluationTest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.exec;
-
-import java.util.UUID;
-import org.apache.calcite.rex.RexBuilder;
-import org.apache.calcite.rex.RexNode;
-import org.apache.calcite.sql.fun.SqlStdOperatorTable;
-import org.apache.calcite.sql.type.SqlTypeName;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.Expression;
-import org.apache.ignite.internal.processors.query.calcite.exec.exp.RexToExpTranslator;
-import org.apache.ignite.internal.processors.query.calcite.prepare.PlanningContext;
-import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
-import org.apache.ignite.internal.processors.query.calcite.util.Commons;
-import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
-import org.junit.Test;
-
-/**
- *
- */
-public class ExpressionEvaluationTest extends GridCommonAbstractTest {
-    /** */
-    private static final PlanningContext PLANNING_CONTEXT = PlanningContext.EMPTY;
-
-    /** */
-    private static final QueryTaskExecutor THROWING_EXECUTOR = (qid, fId, t) -> {
-        throw new AssertionError();
-    };
-
-    /** */
-    public static final IgniteTypeFactory TYPE_FACTORY = PLANNING_CONTEXT.typeFactory();
-
-    @Test
-    public void testCallEvaluation() throws Exception {
-
-        RexBuilder builder = new RexBuilder(TYPE_FACTORY);
-
-        RexNode call = builder.makeCall(
-            SqlStdOperatorTable.PLUS,
-            builder.makeInputRef(TYPE_FACTORY.createTypeWithNullability(TYPE_FACTORY.createSqlType(SqlTypeName.INTEGER), true), 0),
-            builder.makeInputRef(TYPE_FACTORY.createTypeWithNullability(TYPE_FACTORY.createSqlType(SqlTypeName.INTEGER), false), 1));
-
-        RexToExpTranslator translator = new RexToExpTranslator(TYPE_FACTORY);
-
-        Expression expression = translator.translate(call);
-
-        ExecutionContext ctx = context();
-
-        assertEquals(3, expression.<Object>evaluate(ctx, 1, 2));
-        assertEquals(4, expression.<Object>evaluate(ctx, 2, 2));
-        assertEquals(1, expression.<Object>evaluate(ctx, -1, 2));
-        assertEquals(null, expression.<Object>evaluate(ctx, null, 2));
-    }
-
-    public ExecutionContext context(Object... params) {
-        return new ExecutionContext(THROWING_EXECUTOR, PLANNING_CONTEXT, UUID.randomUUID(), 0, null, Commons.parametersMap(params));
-    }
-}
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/AbstractExecutionTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/AbstractExecutionTest.java
index 70c62153..10f17b8 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/AbstractExecutionTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/AbstractExecutionTest.java
@@ -34,6 +34,7 @@ import org.apache.ignite.internal.processors.query.calcite.exec.QueryTaskExecuto
 import org.apache.ignite.internal.processors.query.calcite.message.CalciteMessage;
 import org.apache.ignite.internal.processors.query.calcite.message.MessageServiceImpl;
 import org.apache.ignite.internal.processors.query.calcite.message.TestIoManager;
+import org.apache.ignite.internal.processors.query.calcite.prepare.FragmentDescription;
 import org.apache.ignite.internal.processors.query.calcite.prepare.PlanningContext;
 import org.apache.ignite.plugin.extensions.communication.Message;
 import org.apache.ignite.testframework.junits.GridTestKernalContext;
@@ -138,10 +139,11 @@ public class AbstractExecutionTest extends GridCommonAbstractTest {
     }
 
     protected ExecutionContext executionContext(UUID nodeId, UUID queryId, long fragmentId) {
+        FragmentDescription fragmentDescription = new FragmentDescription(fragmentId, null, -1, null, null);
         return new ExecutionContext(
             taskExecutor(nodeId),
             PlanningContext.builder().localNodeId(nodeId).logger(log()).build(),
-            queryId, fragmentId, null, ImmutableMap.of());
+            queryId, fragmentDescription, ImmutableMap.of());
     }
 
     /** */
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/jdbc/JdbcQueryTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/jdbc/JdbcQueryTest.java
index 27c8e5d..848e275 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/jdbc/JdbcQueryTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/jdbc/JdbcQueryTest.java
@@ -24,12 +24,14 @@ import java.sql.SQLException;
 import java.sql.Statement;
 import java.util.concurrent.TimeUnit;
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.testframework.junits.WithSystemProperty;
 import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
 import org.junit.Test;
 
 /**
  *
  */
+@WithSystemProperty(key = "calcite.debug", value = "true")
 public class JdbcQueryTest extends GridCommonAbstractTest {
     /** URL. */
     private final String url = "jdbc:ignite:thin://127.0.0.1?useExperimentalQueryEngine=true";