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 2019/12/13 13:03:45 UTC

[ignite] branch ignite-12248 updated (d8263c4 -> 1958c3e)

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

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


 discard d8263c4  ExchangeService API
 discard 255e289  Turn implementor to visitor
    omit aa3a0d9  planner rethinking, small refactoring
    omit b45e813  planner rethinking
    omit 8cdb219  query execution
    omit 4c8a246  simple execution
    omit 1824145  context refactoring
    omit 96281cf  pending
    omit a77161f  refactoring
    omit 7895303  pending
    omit ecbb326  pending
    omit c082b7b  pending
    omit 4584e07  refactoring
    omit e1274e2  refactoring
    omit 4a842bf  refactoring
    omit b85b901  pending
    omit 525dfdb  pending
    omit c33076c  pending
    omit 9125a61  pending
    omit e33767e  pending
    omit 1995d3b  pending
    omit e18d8a1  pending
    omit e797a6e  pending
    omit 9179fd0  pending
    omit 2413ae0  Merge branch 'ignite-master' into ignite-12248
    omit fc01a9d  Merge branch 'ignite-master' into ignite-12248
    omit accb91d  fix after merge
    omit 4219f2d  Merge branch 'ignite-master' into ignite-12248
    omit aa5b981  IGNITE-12248: Apache Calcite based query execution engine.
    omit ce8737f  IGNITE-12248: Apache Calcite based query execution engine.
    omit 6bee086  IGNITE-12248: Apache Calcite based query execution engine.
    omit d4aadae  IGNITE-12248: Apache Calcite based query execution engine.
     add 1550781  IGNITE-12267: ClassCastException after change column type (drop then add). This closes #6945.
     add a6e577e  IGNITE-12302 Fixed broken ZookeeperDiscoveryTopologyChangeAndReconnectTest.testDuplicatedNodeId - Fixes #6986.
     add 2035f9d  IGNITE-12305 Extend test coverage [IGNITE-11959] NullPointerException if transaction failed and failure handler dwas not configured explicitly - Fixes #6993.
     add 7711fc9  IGNITE-12292 Java thin client: Fixed transaction issue in case of txId intersection - Fixes #6979.
     add c55d91d  IGNITE-12324 Exception is masked in fieldOrder method with binary object of unregistered type - Fixes #7002.
     add d7be87e  IGNITE-12325 Enable deferred delete for caches with enabled cache store. - Fixes #7005.
     add de43f96  Updated readme file removing outdated and redundant content
     add 1578935  IGNITE-11709 .NET Thin Client: introduce Cluster API
     add 4409582  IGNITE-9732: [Spark] Add joins to Spark Dataframe examples (#6963)
     add 6c41ed2  IGNITE-12328 IgniteException "Failed to resolve nodes topology" during cache.removeAll() and constantly changing topology - Fixes #7015.
     add d11b4bb  IGNITE-12333: [ML] Cleanup the ML module code (#7024)
     add 8dbea36  IGNITE-12339: [ML] Remove outdated property isDistributed in Vector and Matrix classes (#7026)
     add 06c8a51  IGNITE-12336 Fixed creation of redundant CacheMetricsImpl instance in case of near cache is configured
     add 7d4bb49  IGNITE-12329 Invalid handling of remote entries causes partition desync and transaction hanging in COMMITTING state. - Fixes #7018.
     add 768c226  IGNITE-12329 Fixed license
     add 32fe1f0  IGNITE-12279: [ML] Added support for using H2O MOJO for model inference (#6964)
     add 299cf813e IGNITE-12277 Enable SQL index usage for mixed IN and EQUALS queries - Fixes #7029.
     add 4ce6826  IGNITE-12338 Use IgniteThread to notify about long query - Fixes #7025.
     add 720706d  IGNITE-12316 Extend test coverage [IGNITE-10761] GridCacheProcessor should add info about cache in exception message, if applicable - Fixes #6995
     add 7d5749e  IGNITE-5247 Always use LITTLE_ENDIAN for communication SPI - Fixes #7023.
     add 1bb32a7  IGNITE-12300 Use initiating node security context in ComputeJob.cancel - Fixes #7017.
     add bdb225c  IGNITE-9033 .NET Thin Client: add cache expiry policies
     add cee9615  IGNITE-12189 Implement correct limit for TextQuery - Fixes #6917.
     add dd5d9f3  IGNITE-12366: Cancel file transmission on a node-receiver (#7045)
     add bfcb0c3  IGNITE-12351 Append additional cp tracking activity - pages sort - Fixes #7036.
     add d72a123  IGNITE-11898 Java thin client: Affinity awareness support - Fixes #6980.
     add b821f8d  IGNITE-12367-17424 Disable by default bash pipefail/errexit/nounset options from Ignite startup scripts
     add 26698ed  IGNITE-10760 Fix confusing message about system worker blocking
     add 6ed0330  IGNITE-12323 Fixed flaky test GridCommandHandlerTest.testBaselineAutoAdjustmentAutoRemoveNode - Fixes #7001.
     add 0140234  IGNITE-7285 Add default query timeout - Fixes #6490.
     add c6696e5  IGNITE-6267 .NET: Get rid of Doxygen-specific files (#7053)
     add dcff79c  IGNITE-12377 .NET: Add IBinaryObjectBuilder.SetField(name, val, type)
     add 567f65a  IGNITE-12369 Fix compatibility between JdbcThinClient and Server - Fixes #7050.
     add 7464044  IGNITE-12223: Scan query system view (#7007)
     add ba42b59  Extend test coverage [IGNITE-11967] control.sh validate_indexes SQL Index issue must contain information about cache group - Fixes #6996.
     add 8701974  IGNITE-12224: SQL query & SQL query history system views. (#7059)
     add 839b414  IGNITE-12373: file transfer must skip recovery descriptor reservation for a channel connection (#7051)
     add 250f4a0  IGNITE-12386: TcpDiscoveryVmIpFinder must be used by default for security testInvalidServer (#7063)
     add 08cca28  IGNITE-12390 .NET: Add NuGet verification script
     add 8071692  IGNITE-12388 Fixed flaky ZookeeperDiscoveryClientReconnectTest.testReconnectServersRestart_3 - Fixes #7064.
     add 27b93ae  IGNITE-12185: New metric. Index rebuild in progress flag for caches and tables. (#6983)
     add e66bbef  IGNITE-12219: Cache operations performance metrics (#7076)
     add bb87041  IGNITE-12394 Fix log level for messages and thread dumps for ignored failures
     add 31506f0  IGNITE-12392 Faster transaction rolled back when one of backup node failed - Fixes #7072.
     add c28cefb  IGNITE-12340 Extend test coverage of ability to track system/user time held in transaction - Fixes #7027.
     add 6695709  IGNITE-12402: Unsatisfied dependency for HibernateL2CacheExample fixed (#7088)
     add 85cf73f  IGNITE-12124 Fixed possible NullPointerException/Error related to the cache stop with configured TTL
     add 4fb139d  IGNITE-12399 Java thin client: add cache expiry policies - Fixes #7085.
     add b6082be  IGNITE-12053: Total time threads parked if checkpoint throttling occurred metric added (#7080)
     add 1599912  IGNITE-12353 Additional sql benchmarks which covers Date types and inlining usage. This closes #7040.
     add a5bc728  IGNITE-12405 .NET: Remove WithReadRepair, deprecate WithAllowAtomicOpsInTx
     add c6cf3d9  IGNITE-12411: [ML] Finish ML API and fix typos in method names (#7096)
     add 64c56bc  IGNITE-12413 .NET: Fix xmldoc file extension for case-sensitive file systems
     add 6d72874  IGNITE-12393: Striped thread pool queue system view. (#7084)
     add 9265c04  Revert "IGNITE-11704 Write tombstones during rebalance to get rid of deferred delete buffer" (#7100)
     add e6a7f93  IGNITE-12303 Fix a comment for an enumeration item SecurityPermission.CACHE_DESTROY - Fixes #7101.
     add 98883f1  IGNITE-12409 Destroying a cache during cache load may lead to a hang - Fixes #7092.
     add 299e3c9  IGNITE-12188 Fixed CacheGroupMetrics.IndexBuildCountPartitionsLeft metric - Fixes #7078.
     add 9f3b915  IGNITE-12420 Fix broken the Check Code Style suite - Fixes #7105.
     add c0e8d2d  IGNITE-12421: Update master branch project version to 2.9.0-SNAPSHOT (#7106)
     add 3ac9a21  IGNITE-11410 Sandbox for user-defined code - Fixes #6707.
     add c6696de  IGNITE-12423: PME duration histogram updates only if log info enabled (#7108)
     add 15993fa  IGNITE-11857 PartitionTxUpdateCounter optimization  - Fixes #6686.
     add cec6dc4  IGNITE-12419 Fixed JCache TCK CacheLoader checks - Fixes #7103.
     add b694fd1  IGNITE-12365 Concurrent removeAll() on the same cache leads to deadlock - Fixes #7111.
     add 966b642  IGNITE-12421: update resources with 2.9.0-SNAPSHOT version (#7126)
     add 0c162e5  IGNITE-12247: [Spark] Add initial support of Spark 2.4 (#7058)
     new 1958c3e  IGNITE-12248: Apache Calcite based query execution engine. Initial commit.

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (d8263c4)
            \
             N -- N -- N   refs/heads/ignite-12248 (1958c3e)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 README.md                                          |  260 +--
 bin/control.sh                                     |   13 +-
 bin/ignite-tf.sh                                   |   13 +-
 bin/ignite.sh                                      |   13 +-
 bin/ignitevisorcmd.sh                              |   13 +-
 bin/include/functions.sh                           |   13 +-
 bin/include/parseargs.sh                           |   13 +-
 bin/include/setenv.sh                              |   13 +-
 examples/pom-standalone-lgpl.xml                   |   55 +
 examples/pom-standalone.xml                        |   56 +
 examples/pom.xml                                   |   59 +-
 .../ml/TrainingWithBinaryObjectExample.java        |    6 +-
 .../clustering/CustomersClusterizationExample.java |   32 +-
 .../ml/clustering/GmmClusterizationExample.java    |    2 +-
 .../dataset/AlgorithmSpecificDatasetExample.java   |    2 +-
 .../TrainingWithCustomPreprocessorsExample.java    |   13 +-
 .../change/OptimizeMakeChangeFitnessFunction.java  |    2 +-
 .../change/OptimizeMakeChangeGAExample.java        |    2 +-
 .../helloworld/HelloWorldFitnessFunction.java      |    2 +-
 .../genetic/knapsack/KnapsackFitnessFunction.java  |    4 +-
 .../knapsack/KnapsackTerminateCriteria.java        |    2 +-
 .../ml/genetic/movie/MovieFitnessFunction.java     |    4 +-
 .../inference/h2o/H2OMojoModelParserExample.java   |  103 +
 .../h2o}/package-info.java                         |    4 +-
 .../examples/ml/knn/KNNRegressionExample.java      |    2 +-
 .../ml/naivebayes/CompoundNaiveBayesExample.java   |    1 +
 .../examples/ml/naivebayes/package-info.java       |    2 +-
 .../ignite/examples/ml/nn/MLPTrainerExample.java   |   48 -
 .../ml/preprocessing/encoding/EncoderExample.java  |    4 +-
 .../encoding/EncoderExampleWithNormalization.java  |    4 +-
 .../encoding/LabelEncoderExample.java              |   14 +-
 .../examples/ml/preprocessing/package-info.java    |    2 +-
 .../ml/recommendation/MovieLensExample.java        |    6 +-
 .../ml/recommendation/MovieLensSQLExample.java     |    6 +-
 .../linear/BostonHousePricesPredictionExample.java |    2 +-
 .../linear/LinearRegressionLSQRTrainerExample.java |    2 +-
 .../selection/scoring/RegressionMetricExample.java |    2 +-
 ...eeClassificationTrainerSQLInferenceExample.java |   26 +-
 ...onTreeClassificationTrainerSQLTableExample.java |   20 +-
 .../ignite/examples/ml/sql/package-info.java       |    2 +-
 .../examples/ml/tree/FraudDetectionExample.java    |    3 +-
 .../RandomForestRegressionExample.java             |    4 +-
 .../ignite/examples/ml/tutorial/Step_8_CV.java     |    6 +-
 .../ml/tutorial/Step_8_CV_with_Param_Grid.java     |   10 +-
 .../Step_8_CV_with_Param_Grid_and_metrics.java     |   10 +-
 ...V_with_Param_Grid_and_metrics_and_pipeline.java |   10 +-
 .../hyperparametertuning/Step_13_RandomSearch.java |    8 +-
 .../Step_14_Parallel_BrutForce_Search.java         |   10 +-
 .../Step_15_Parallel_Random_Search.java            |   10 +-
 .../Step_16_Genetic_Programming_Search.java        |    8 +-
 ...tep_17_Parallel_Genetic_Programming_Search.java |   10 +-
 .../ParametricVectorGeneratorExample.java          |    2 +-
 .../util/generators/StandardGeneratorsExample.java |   14 +-
 .../generators/VectorGeneratorFamilyExample.java   |    4 +-
 .../VectorGeneratorPrimitivesExample.java          |   11 +-
 .../{titanik_test.csv => titanic_test.csv}         |    0
 .../{titanik_train.csv => titanic_train.csv}       |    0
 .../resources/models/h2o/agaricus-gbm-mojo.zip     |  Bin 0 -> 62818 bytes
 .../spark/IgniteDataFrameJoinExample.scala         |  180 ++
 .../spark/JavaIgniteDataFrameJoinExample.java      |  164 ++
 .../spark/examples/IgniteDataFrameSelfTest.java    |    9 +
 .../examples/JavaIgniteDataFrameSelfTest.java      |    9 +
 modules/aop/pom.xml                                |    2 +-
 modules/apache-license-gen/pom.xml                 |    2 +-
 modules/aws/pom.xml                                |    2 +-
 modules/benchmarks/pom.xml                         |    2 +-
 .../misc/JmhPartitionUpdateCounterBenchmark.java   |  112 +
 .../query/calcite/util/TableScanIterator.java      |    2 +-
 modules/camel/pom.xml                              |    2 +-
 modules/cassandra/pom.xml                          |    2 +-
 modules/cassandra/serializers/pom.xml              |    4 +-
 modules/cassandra/store/pom.xml                    |    4 +-
 modules/clients/pom.xml                            |    2 +-
 .../internal/jdbc2/JdbcMetadataSelfTest.java       |    9 +-
 .../thin/JdbcThinAffinityAwarenessSelfTest.java    |    4 +-
 ...cThinAffinityAwarenessTransactionsSelfTest.java |    4 +-
 .../ignite/jdbc/thin/JdbcThinMetadataSelfTest.java |  105 +-
 modules/cloud/pom.xml                              |    2 +-
 modules/codegen/pom.xml                            |    2 +-
 .../SystemViewRowAttributeWalkerGenerator.java     |   26 +-
 modules/compatibility/pom.xml                      |    2 +-
 modules/compress/pom.xml                           |    2 +-
 modules/core/pom.xml                               |    2 +-
 .../apache/ignite/cache/query/SqlFieldsQuery.java  |    5 +-
 .../org/apache/ignite/cache/query/TextQuery.java   |   50 +
 .../java/org/apache/ignite/client/ClientCache.java |    9 +
 .../ignite/client/ClientCacheConfiguration.java    |   20 +
 .../ignite/configuration/ClientConfiguration.java  |   73 +
 .../ignite/configuration/IgniteConfiguration.java  |   36 +
 .../org/apache/ignite/internal/IgniteKernal.java   |    2 +
 .../internal/StripedExecutorMXBeanAdapter.java     |    2 +-
 .../ignite/internal/binary/BinaryFieldImpl.java    |   15 +-
 .../ignite/internal/client/thin/ClientBinary.java  |    3 +-
 .../client/thin/ClientCacheAffinityContext.java    |  227 ++
 .../client/thin/ClientCacheAffinityMapping.java    |  269 +++
 .../ignite/internal/client/thin/ClientChannel.java |   17 +
 .../client/thin/ClientChannelConfiguration.java    |   70 +-
 .../internal/client/thin/ClientOperation.java      |    1 +
 .../ignite/internal/client/thin/ClientUtils.java   |   31 +-
 .../internal/client/thin/ProtocolVersion.java      |    5 +-
 .../internal/client/thin/ReliableChannel.java      |  344 ++-
 .../internal/client/thin/TcpClientCache.java       |  189 +-
 .../internal/client/thin/TcpClientChannel.java     |   42 +-
 .../client/thin/TcpClientTransactions.java         |   42 +-
 .../internal/client/thin/TcpIgniteClient.java      |   23 +-
 .../internal/cluster/ClusterGroupAdapter.java      |   12 +-
 .../managers/communication/GridIoManager.java      |    6 +
 .../communication/GridIoMessageFactory.java        |    6 -
 .../TransmissionCancelledException.java}           |   44 +-
 .../communication/TransmissionHandler.java         |   10 +
 .../managers/communication/TransmissionMeta.java   |    2 +-
 .../deployment/GridDeploymentClassLoader.java      |   17 +-
 .../managers/systemview/GridSystemViewManager.java |   51 +
 .../managers/systemview/ScanQuerySystemView.java   |  213 ++
 .../systemview/walker/ScanQueryViewWalker.java     |   79 +
 .../walker/SqlQueryHistoryViewWalker.java          |   59 +
 .../systemview/walker/SqlQueryViewWalker.java      |   58 +
 .../walker/StripedExecutorTaskViewWalker.java      |   50 +
 .../apache/ignite/internal/pagemem/FullPageId.java |    7 +
 .../apache/ignite/internal/pagemem/PageUtils.java  |    4 +-
 .../internal/pagemem/wal/record/WALRecord.java     |    5 +-
 .../delta/MetaPageUpdatePartitionDataRecord.java   |   19 +-
 .../delta/MetaPageUpdatePartitionDataRecordV2.java |   22 +-
 .../delta/MetaPageUpdatePartitionDataRecordV3.java |  108 -
 .../processors/cache/CacheGroupContext.java        |   19 -
 .../processors/cache/CacheGroupMetricsImpl.java    |   29 +-
 .../processors/cache/CacheMetricsImpl.java         |   88 +-
 .../internal/processors/cache/CacheObject.java     |    3 -
 .../processors/cache/GridCacheAdapter.java         |   16 +-
 .../processors/cache/GridCacheContext.java         |   21 +-
 .../processors/cache/GridCacheIdMessage.java       |    2 +-
 .../processors/cache/GridCacheMapEntry.java        |  343 ++-
 .../internal/processors/cache/GridCacheMvcc.java   |    3 +-
 .../cache/GridCachePartitionExchangeManager.java   |    4 +-
 .../processors/cache/GridCacheProcessor.java       |   15 +-
 .../processors/cache/GridCacheSharedContext.java   |    5 +-
 .../cache/GridCacheSharedTtlCleanupManager.java    |   94 +-
 .../processors/cache/GridCacheTtlManager.java      |    9 +
 .../cache/IgniteCacheOffheapManager.java           |   93 +-
 .../cache/IgniteCacheOffheapManagerImpl.java       |  395 +---
 .../processors/cache/IgniteCacheProxyImpl.java     |   22 +-
 .../processors/cache/IncompleteCacheObject.java    |   19 -
 .../processors/cache/IncompleteObject.java         |    2 +-
 .../cache/PartitionTxUpdateCounterImpl.java        |  153 +-
 .../processors/cache/PartitionUpdateCounter.java   |    4 +-
 .../processors/cache/TombstoneCacheObject.java     |   94 -
 .../binary/CacheObjectBinaryProcessorImpl.java     |    6 +-
 .../distributed/GridDistributedCacheAdapter.java   |  106 +-
 .../distributed/GridDistributedCacheEntry.java     |   19 +-
 .../GridDistributedTxRemoteAdapter.java            |   47 +-
 .../cache/distributed/dht/GridDhtCache.java        |    7 +-
 .../cache/distributed/dht/GridDhtCacheAdapter.java |   29 +-
 .../cache/distributed/dht/GridDhtLockFuture.java   |    2 +-
 .../dht/GridDhtTransactionalCacheAdapter.java      |    2 +-
 .../distributed/dht/GridDhtTxFinishFuture.java     |   27 +-
 .../distributed/dht/GridDhtTxLocalAdapter.java     |    2 +-
 .../distributed/dht/GridDhtTxPrepareFuture.java    |   14 +-
 .../cache/distributed/dht/GridDhtTxRemote.java     |    8 +-
 .../distributed/dht/atomic/GridDhtAtomicCache.java |   17 +-
 .../dht/colocated/GridDhtColocatedLockFuture.java  |   39 +-
 .../preloader/GridDhtPartitionsExchangeFuture.java |    5 +-
 .../dht/topology/GridDhtLocalPartition.java        |  307 +--
 .../dht/topology/GridDhtPartitionTopologyImpl.java |    5 +-
 .../dht/topology/PartitionsEvictManager.java       |  366 +--
 .../cache/distributed/near/GridNearLockFuture.java |    4 -
 .../distributed/near/GridNearTxPrepareRequest.java |    3 +-
 .../cache/local/GridLocalCacheEntry.java           |   22 +-
 .../processors/cache/persistence/CacheDataRow.java |    5 -
 .../cache/persistence/CacheDataRowAdapter.java     |   61 +-
 .../cache/persistence/DataRegionMetricsImpl.java   |   13 +
 .../GridCacheDatabaseSharedManager.java            |   33 +-
 .../cache/persistence/GridCacheOffheapManager.java |  369 ++-
 .../IgniteCacheDatabaseSharedManager.java          |  104 +-
 .../pagemem/CheckpointMetricsTracker.java          |   76 +-
 .../cache/persistence/pagemem/PageMemoryImpl.java  |   18 +-
 .../pagemem/PagesWriteSpeedBasedThrottle.java      |    2 +
 .../persistence/pagemem/PagesWriteThrottle.java    |    4 +
 .../persistence/tree/io/PagePartitionMetaIO.java   |   29 +-
 .../persistence/tree/io/PagePartitionMetaIOV2.java |   25 +-
 .../wal/serializer/RecordDataV1Serializer.java     |   11 -
 .../processors/cache/query/CacheQuery.java         |    8 +
 .../query/GridCacheDistributedQueryManager.java    |    3 +
 .../cache/query/GridCacheQueryAdapter.java         |   20 +
 .../cache/query/GridCacheQueryFutureAdapter.java   |  109 +-
 .../cache/query/GridCacheQueryManager.java         |  134 +-
 .../cache/query/GridCacheQueryRequest.java         |   41 +-
 .../cache/transactions/IgniteInternalTx.java       |    4 +-
 .../cache/transactions/IgniteTxAdapter.java        |   10 +-
 .../cache/transactions/IgniteTxEntry.java          |    5 +-
 .../cache/transactions/IgniteTxHandler.java        |   14 +-
 .../cache/transactions/IgniteTxManager.java        |   11 +-
 .../cache/transactions/IgniteTxRemoteEx.java       |    5 +-
 .../IgniteTxRemoteSingleStateImpl.java             |    3 +-
 .../cache/transactions/IgniteTxRemoteState.java    |    6 +-
 .../transactions/IgniteTxRemoteStateImpl.java      |   22 +-
 .../internal/processors/cache/tree/DataRow.java    |    6 +-
 .../continuous/GridContinuousProcessor.java        |    2 +-
 .../processors/datastreamer/DataStreamerImpl.java  |   20 +-
 .../datastreamer/DataStreamerUpdateJob.java        |   13 +-
 .../processors/failure/FailureProcessor.java       |   35 +-
 .../internal/processors/job/GridJobWorker.java     |   14 +-
 .../processors/metric/GridMetricManager.java       |    2 +-
 .../processors/metric/impl/MetricUtils.java        |    9 -
 .../processors/odbc/ClientListenerProcessor.java   |    2 +-
 .../odbc/ClientListenerRequestHandler.java         |    8 +
 .../processors/odbc/jdbc/JdbcRequestHandler.java   |    2 +-
 .../processors/odbc/odbc/OdbcRequestHandler.java   |    9 +-
 .../processors/platform/cache/PlatformCache.java   |    5 +
 .../cache/expiry/PlatformExpiryPolicy.java         |   17 +
 .../platform/client/ClientConnectionContext.java   |    6 +-
 .../platform/client/ClientMessageParser.java       |   29 +
 .../platform/client/ClientRequestHandler.java      |    4 +
 .../cache/ClientCacheConfigurationSerializer.java  |   12 +
 .../platform/client/cache/ClientCacheRequest.java  |   28 +-
 .../cache/ClientCacheSqlFieldsQueryRequest.java    |    6 +-
 .../cluster/ClientClusterChangeStateRequest.java   |   49 +
 .../cluster/ClientClusterIsActiveRequest.java      |   45 +
 .../ClientClusterWalChangeStateRequest.java        |   55 +
 .../ClientClusterWalChangeStateResponse.java}      |   39 +-
 .../cluster/ClientClusterWalGetStateRequest.java   |   49 +
 .../platform/utils/PlatformConfigurationUtils.java |    4 +-
 .../processors/query/GridQueryIndexing.java        |    3 +-
 .../processors/query/GridQueryProcessor.java       |   48 +-
 .../processors/query/GridRunningQueryInfo.java     |   11 +-
 ...{QueryHistoryMetrics.java => QueryHistory.java} |   22 +-
 ...HistoryMetricsKey.java => QueryHistoryKey.java} |    6 +-
 .../processors/query/QueryHistoryTracker.java      |   42 +-
 .../processors/query/RunningQueryManager.java      |   35 +-
 .../query/schema/SchemaIndexCacheVisitorImpl.java  |   40 +-
 .../processors/resource/GridResourceProcessor.java |   12 +
 .../GridResourceProxiedIgniteInjector.java         |   88 +
 .../processors/security/GridSecurityProcessor.java |   12 +
 .../processors/security/IgniteSecurity.java        |   11 +-
 .../security/IgniteSecurityProcessor.java          |   25 +
 .../security/NoOpIgniteSecurityProcessor.java      |   11 +
 .../processors/security/SecurityUtils.java         |  122 +
 .../security/sandbox/AccessControllerSandbox.java  |   74 +
 .../security/sandbox/IgniteDomainCombiner.java     |   52 +
 .../processors/security/sandbox/IgniteSandbox.java |   46 +
 .../processors/security/sandbox/NoOpSandbox.java}  |   21 +-
 .../sandbox/SandboxIgniteComponentProxy.java       |  110 +
 .../apache/ignite/internal/util/IgniteUtils.java   |   54 +-
 .../ignite/internal/util/StripedExecutor.java      |  157 +-
 .../ignite/internal/util/nio/GridNioServer.java    |    2 +-
 .../ignite/internal/worker/WorkersRegistry.java    |    7 +-
 .../ignite/plugin/security/SecurityPermission.java |    2 +-
 .../ignite/plugin/security/SecuritySubject.java    |   12 +
 .../spi/communication/tcp/TcpCommunicationSpi.java |   18 +-
 .../tcp/messages/HandshakeMessage2.java            |    2 +-
 .../ignite/spi/systemview/view/ScanQueryView.java  |  170 ++
 .../spi/systemview/view/SqlQueryHistoryView.java   |   85 +
 .../ignite/spi/systemview/view/SqlQueryView.java   |   79 +
 .../systemview/view/StripedExecutorTaskView.java   |   68 +
 modules/core/src/main/resources/ignite.properties  |    2 +-
 modules/core/src/test/config/log4j-test.xml        |   23 +-
 .../apache/ignite/cache/RemoveAllDeadlockTest.java |   95 +
 .../org/apache/ignite/client/FunctionalTest.java   |  114 +-
 .../org/apache/ignite/client/ReliabilityTest.java  |  116 +
 .../binary/BinaryFieldExtractionSelfTest.java      |   46 +-
 .../ThinClientAbstractAffinityAwarenessTest.java   |  322 +++
 ...nClientAffinityAwarenessStableTopologyTest.java |  206 ++
 ...lientAffinityAwarenessUnstableTopologyTest.java |  220 ++
 .../GridIoManagerFileTransmissionSelfTest.java     |  142 +-
 .../ignite/internal/metric/JmxExporterSpiTest.java |  249 ++
 .../ignite/internal/metric/SystemViewSelfTest.java |  329 ++-
 .../cache/CacheDeferredDeleteSanitySelfTest.java   |    6 +-
 .../cache/CacheLockCandidatesThreadTest.java       |    6 +-
 .../cache/ClientSlowDiscoveryAbstractTest.java     |  121 +
 ... => ClientSlowDiscoveryTopologyChangeTest.java} |  105 +-
 .../ClientSlowDiscoveryTransactionRemapTest.java   |  473 ++++
 .../cache/GridCacheAbstractFullApiSelfTest.java    |    7 +-
 .../cache/GridCacheAbstractMetricsSelfTest.java    |   74 +
 ...ridCacheFullTextQueryMultithreadedSelfTest.java |    3 +-
 .../cache/GridCacheProcessorActiveTxTest.java      |  248 ++
 .../GridTransactionsSystemUserTimeMetricsTest.java |  724 +++++-
 .../IgniteCacheConfigVariationsFullApiTest.java    |    2 +-
 .../processors/cache/SafeLogTxFinishErrorTest.java |  161 ++
 .../CacheRemoveWithTombstonesLoadTest.java         |  414 ----
 .../distributed/CacheRemoveWithTombstonesTest.java |  289 ---
 .../distributed/CacheRentingStateRepairTest.java   |    3 +-
 ...omicClientInvalidPartitionHandlingSelfTest.java |   10 +
 ...acheAtomicInvalidPartitionHandlingSelfTest.java |    4 +-
 .../CacheRemoveWithTombstonesFailoverTest.java     |  187 --
 .../DropCacheContextDuringEvictionTest.java        |   24 +-
 .../PartitionsEvictManagerAbstractTest.java        |  113 +-
 .../PartitionsEvictionTaskFailureHandlerTest.java  |   72 +-
 .../GridCacheFastNodeLeftForTransactionTest.java   |  394 ++++
 .../db/CheckpointBufferDeadlockTest.java           |    4 +-
 .../cache/persistence/db/IgnitePdsWithTtlTest.java |    8 +
 .../db/checkpoint/CheckpointStartLoggingTest.java  |  101 +
 .../pagemem/BPlusTreePageMemoryImplTest.java       |    4 +
 .../BPlusTreeReuseListPageMemoryImplTest.java      |    4 +
 .../pagemem/IgniteThrottlingUnitTest.java          |   15 +
 .../pagemem/IndexStoragePageMemoryImplTest.java    |    4 +
 .../pagemem/PageMemoryImplNoLoadTest.java          |    4 +
 .../persistence/pagemem/PageMemoryImplTest.java    |   14 +
 .../pagemem/PagesWriteThrottleSmokeTest.java       |   29 +-
 .../TxCrossCacheMapOnInvalidTopologyTest.java      |  134 +-
 .../TxCrossCachePartitionConsistencyTest.java      |  320 +++
 ...acheRemoteMultiplePartitionReservationTest.java |  165 ++
 .../TxPartitionCounterStateAbstractTest.java       |   12 +-
 .../TxPartitionCounterStateConsistencyTest.java    |  134 +-
 ...nterStateConsistencyVolatileRebalanceTest.java} |   47 +-
 .../processors/database/CacheFreeListSelfTest.java |    5 -
 .../datastreamer/DataStreamerStopCacheTest.java    |  213 ++
 .../failure/FailureProcessorLoggingTest.java       |  270 +++
 .../processors/query/DummyQueryIndexing.java       |    3 +-
 .../AbstractRemoteSecurityContextCheckTest.java    |   29 +-
 .../processors/security/AbstractSecurityTest.java  |  107 +-
 .../processors/security/InvalidServerTest.java     |    2 +-
 .../cache/EntryProcessorPermissionCheckTest.java   |    2 +-
 .../CacheLoadRemoteSecurityContextCheckTest.java   |   43 +-
 .../compute/ComputePermissionCheckTest.java        |   53 +-
 ...teTaskCancelRemoteSecurityContextCheckTest.java |  195 ++
 .../processors/security/impl/TestSecurityData.java |   22 +-
 .../security/impl/TestSecurityPluginProvider.java  |   17 +-
 .../security/impl/TestSecurityProcessor.java       |   21 +-
 .../security/impl/TestSecuritySubject.java         |   16 +
 .../security/sandbox/AbstractSandboxTest.java      |  128 ++
 .../security/sandbox/CacheSandboxTest.java         |  134 ++
 .../security/sandbox/ComputeSandboxTest.java       |  146 ++
 .../security/sandbox/DataStreamerSandboxTest.java  |   57 +
 .../sandbox/DoPrivilegedOnRemoteNodeTest.java      |  180 ++
 .../sandbox/IgniteOperationsInsideSandboxTest.java |  234 ++
 .../sandbox/SecuritySubjectPermissionsTest.java    |  132 ++
 .../ignite/testsuites/IgniteBasicTestSuite.java    |    4 +-
 .../testsuites/IgniteCacheMvccTestSuite5.java      |    2 +
 .../testsuites/IgniteCacheMvccTestSuite7.java      |    2 +
 .../testsuites/IgniteCacheMvccTestSuite9.java      |   22 +-
 .../ignite/testsuites/IgniteCacheTestSuite.java    |    2 +
 .../ignite/testsuites/IgniteCacheTestSuite2.java   |    2 +
 .../ignite/testsuites/IgniteCacheTestSuite4.java   |    3 +
 .../ignite/testsuites/IgniteCacheTestSuite5.java   |    6 +-
 .../ignite/testsuites/IgniteCacheTestSuite7.java   |    5 +
 .../ignite/testsuites/IgniteCacheTestSuite9.java   |   13 +-
 .../ignite/testsuites/IgnitePdsTestSuite2.java     |    3 +
 .../ignite/testsuites/IgniteReproducingSuite.java  |    5 +-
 .../ignite/testsuites/SecurityTestSuite.java       |   18 +-
 .../apache/ignite/util/GridCommandHandlerTest.java |   41 +-
 modules/dev-utils/ignite-modules-test/build.gradle |    3 +-
 modules/dev-utils/pom.xml                          |    2 +-
 modules/direct-io/pom.xml                          |    2 +-
 modules/extdata/p2p/pom.xml                        |    2 +-
 modules/extdata/platform/pom.xml                   |    2 +-
 modules/extdata/uri/modules/uri-dependency/pom.xml |    2 +-
 modules/extdata/uri/pom.xml                        |    2 +-
 modules/flink/pom.xml                              |    2 +-
 modules/flume/pom.xml                              |    2 +-
 modules/gce/pom.xml                                |    2 +-
 modules/geospatial/pom.xml                         |    2 +-
 modules/hadoop/pom.xml                             |    2 +-
 modules/hibernate-4.2/pom.xml                      |    2 +-
 modules/hibernate-5.1/pom.xml                      |    2 +-
 modules/hibernate-5.3/pom.xml                      |    2 +-
 modules/hibernate-core/pom.xml                     |    2 +-
 modules/ignored-tests/pom.xml                      |    2 +-
 modules/indexing/pom.xml                           |    2 +-
 .../systemview/walker/SqlTableViewWalker.java      |    4 +-
 .../processors/query/h2/IgniteH2Indexing.java      |   20 +-
 .../query/h2/LongRunningQueryManager.java          |    8 +-
 .../processors/query/h2/QueryParameters.java       |   39 +-
 .../internal/processors/query/h2/QueryParser.java  |   56 +-
 .../processors/query/h2/SchemaManager.java         |    4 -
 .../query/h2/database/H2PkHashIndex.java           |    4 +-
 .../processors/query/h2/opt/GridH2Table.java       |    2 +-
 .../processors/query/h2/opt/GridLuceneIndex.java   |    5 +-
 .../processors/query/h2/opt/H2CacheRow.java        |    5 -
 .../sys/view/SqlSystemViewQueryHistoryMetrics.java |   92 -
 .../h2/sys/view/SqlSystemViewRunningQueries.java   |  101 -
 .../processors/query/h2/twostep/ReduceTable.java   |    2 +-
 .../query/h2/twostep/ReduceTableWrapper.java       |    2 +-
 .../ignite/spi/systemview/SqlViewExporterSpi.java  |    8 +-
 .../ignite/spi/systemview/view/SqlTableView.java   |    5 +
 .../org/apache/ignite/client/ClientTestSuite.java  |    6 +-
 .../CacheGroupMetricsWithIndexBuildFailTest.java   |  187 ++
 .../cache/CacheGroupMetricsWithIndexTest.java      |   50 +-
 .../cache/GridCacheFullTextQuerySelfTest.java      |  224 +-
 ...acheDistributedQueryDefaultTimeoutSelfTest.java |  197 ++
 .../cache/index/AbstractIndexingCommonTest.java    |   71 +
 .../processors/cache/index/BasicIndexTest.java     |  122 +-
 .../processors/cache/index/IndexMetricsTest.java   |  153 ++
 ...gniteCacheLocalQueryDefaultTimeoutSelfTest.java |  152 ++
 .../cache/metric/SqlViewExporterSpiTest.java       |  280 ++-
 .../query/IgniteSqlKeyValueFieldsTest.java         |  194 +-
 .../processors/query/LongRunningQueryTest.java     |   52 +-
 .../processors/query/SqlQueryHistorySelfTest.java  |   16 +-
 .../processors/query/SqlSystemViewsSelfTest.java   |  119 +-
 .../IgniteBinaryCacheQueryTestSuite.java           |    2 +
 .../IgniteBinaryCacheQueryTestSuite2.java          |    7 +-
 .../IgniteCacheWithIndexingTestSuite.java          |    2 +
 ...idCommandHandlerIndexingClusterByClassTest.java |   19 +
 modules/jcl/pom.xml                                |    2 +-
 modules/jms11/pom.xml                              |    2 +-
 modules/jta/pom.xml                                |    2 +-
 modules/kafka/pom.xml                              |    2 +-
 modules/kubernetes/pom.xml                         |    2 +-
 modules/log4j/pom.xml                              |    2 +-
 modules/log4j2/pom.xml                             |    2 +-
 modules/mesos/pom.xml                              |    2 +-
 .../pom.xml                                        |   14 +-
 .../org/apache/ignite/ml/h2o/H2OMojoModel.java     |   88 +
 .../apache/ignite/ml/h2o/H2OMojoModelParser.java   |   86 +
 .../org/apache/ignite/ml/h2o}/package-info.java    |    5 +-
 .../apache/ignite/ml/h2o/H2OMojoParserTest.java    |   68 +
 .../org/apache/ignite/ml/h2o/H2OMojoTestSuite.java |   16 +-
 .../src/test/resources/mojos/gbm_prostate.zip      |  Bin 0 -> 42484 bytes
 modules/ml/mleap-model-parser/pom.xml              |    2 +-
 modules/ml/pom.xml                                 |    2 +-
 modules/ml/spark-model-parser/pom.xml              |    2 +-
 .../ml/sparkmodelparser/SparkModelParser.java      |    7 +-
 .../ml/sparkmodelparser/SupportedSparkModels.java  |    4 +
 .../UnsupportedSparkModelException.java            |    6 +-
 .../ml/sparkmodelparser/SparkModelParserTest.java  |    3 +-
 .../java/org/apache/ignite/ml/FileExporter.java    |    2 +-
 .../gmm/CovarianceMatricesAggregator.java          |   33 +-
 .../apache/ignite/ml/clustering/gmm/GmmModel.java  |    2 +-
 .../ignite/ml/clustering/gmm/GmmTrainer.java       |   16 +-
 .../ml/composition/bagging/BaggedTrainer.java      |    1 -
 .../composition/boosting/GDBLearningStrategy.java  |    3 +-
 .../boosting/convergence/package-info.java         |    2 +-
 .../ml/composition/combinators/package-info.java   |    2 +-
 .../combinators/parallel/package-info.java         |    2 +-
 .../sequential/TrainersSequentialComposition.java  |    2 +-
 .../stacking/SimpleStackedDatasetTrainer.java      |    1 -
 .../ml/dataset/feature/extractor/Vectorizer.java   |    2 +-
 .../extractor/impl/BinaryObjectVectorizer.java     |    6 +-
 .../extractor/impl/DoubleArrayVectorizer.java      |    8 +-
 .../feature/extractor/impl/DummyVectorizer.java    |    8 +-
 .../BootstrappedDatasetPartition.java              |    2 +-
 .../ml/dataset/impl/cache/CacheBasedDataset.java   |    6 +-
 .../impl/cache/CacheBasedDatasetBuilder.java       |    4 +-
 .../ml/dataset/impl/cache/util/ComputeUtils.java   |   20 +-
 .../FeatureMatrixWithLabelsOnHeapDataBuilder.java  |    2 +-
 .../builder/data/SimpleDatasetDataBuilder.java     |    2 +-
 .../DefaultLearningEnvironmentBuilder.java         |    4 +-
 .../ml/environment/deploy/DeployingContext.java    |    4 +-
 .../environment/deploy/DeployingContextImpl.java   |   21 +-
 .../ml/environment/logging/ConsoleLogger.java      |    3 -
 .../org/apache/ignite/ml/genetic/CrossOverJob.java |    6 +-
 .../apache/ignite/ml/genetic/CrossOverTask.java    |    4 +-
 .../org/apache/ignite/ml/genetic/FitnessJob.java   |   12 +-
 .../org/apache/ignite/ml/genetic/FitnessTask.java  |    2 +-
 .../java/org/apache/ignite/ml/genetic/GAGrid.java  |   46 +-
 .../java/org/apache/ignite/ml/genetic/Gene.java    |    4 +-
 .../org/apache/ignite/ml/genetic/MutateJob.java    |    4 +-
 .../org/apache/ignite/ml/genetic/MutateTask.java   |   10 +-
 .../ml/genetic/RouletteWheelSelectionJob.java      |   10 +-
 .../ml/genetic/RouletteWheelSelectionTask.java     |    8 +-
 .../ignite/ml/genetic/TruncateSelectionJob.java    |    2 +-
 .../ignite/ml/genetic/TruncateSelectionTask.java   |   10 +-
 .../ml/genetic/functions/GAGridFunction.java       |    2 +-
 .../ml/genetic/parameter/ChromosomeCriteria.java   |    2 +-
 .../ml/genetic/parameter/GAConfiguration.java      |   23 +-
 .../ml/genetic/parameter/GAGridConstants.java      |    4 +-
 .../ignite/ml/genetic/utils/GAGridUtils.java       |    4 +-
 .../storage/model/DefaultModelStorage.java         |    2 +-
 .../java/org/apache/ignite/ml/knn/KNNModel.java    |    6 +-
 .../java/org/apache/ignite/ml/knn/KNNTrainer.java  |    2 +-
 .../knn/classification/KNNClassificationModel.java |    2 +-
 .../classification/KNNClassificationTrainer.java   |    2 +-
 .../ml/knn/regression/KNNRegressionTrainer.java    |    2 +-
 .../ignite/ml/knn/utils/PointWithDistanceUtil.java |    2 +-
 .../ml/knn/utils/indices/ArraySpatialIndex.java    |    4 +-
 .../ml/knn/utils/indices/BallTreeSpatialIndex.java |    6 +-
 .../ml/knn/utils/indices/KDTreeSpatialIndex.java   |    6 +-
 .../apache/ignite/ml/math/StorageOpsMetrics.java   |    5 -
 .../java/org/apache/ignite/ml/math/Tracer.java     |    6 +-
 .../ignite/ml/math/distances/HammingDistance.java  |    6 +-
 .../preprocessing/IllegalFeatureTypeException.java |    8 +-
 .../preprocessing/IllegalLabelTypeException.java   |    8 +-
 .../apache/ignite/ml/math/functions/Functions.java |    7 +-
 .../ml/math/functions/IgniteCurriedBiFunction.java |    3 +-
 .../math/functions/IgniteCurriedTriFunction.java   |    4 +-
 .../ignite/ml/math/functions/IgniteFunction.java   |    2 +-
 .../ignite/ml/math/isolve/lsqr/AbstractLSQR.java   |    8 +-
 .../ml/math/primitives/matrix/AbstractMatrix.java  |   13 +-
 .../ignite/ml/math/primitives/matrix/Matrix.java   |    2 +-
 .../ml/math/primitives/matrix/MatrixStorage.java   |    2 +-
 .../matrix/storage/DenseMatrixStorage.java         |    5 -
 .../matrix/storage/SparseMatrixStorage.java        |    5 -
 .../matrix/storage/ViewMatrixStorage.java          |    5 -
 .../ml/math/primitives/vector/AbstractVector.java  |   19 +-
 .../ignite/ml/math/primitives/vector/Vector.java   |   14 +-
 .../ml/math/primitives/vector/VectorUtils.java     |   24 -
 .../primitives/vector/impl/DelegatingVector.java   |    5 -
 .../vector/storage/DenseVectorStorage.java         |   17 +-
 .../vector/storage/SparseVectorStorage.java        |    5 -
 .../vector/storage/VectorViewStorage.java          |    5 -
 .../storage/VectorizedViewMatrixStorage.java       |    7 +-
 .../stat/MultivariateGaussianDistribution.java     |    4 +-
 .../org/apache/ignite/ml/math/util/MatrixUtil.java |    4 +
 .../ignite/ml/multiclass/MultiClassModel.java      |    3 +-
 .../ignite/ml/multiclass/OneVsRestTrainer.java     |    4 +-
 .../apache/ignite/ml/naivebayes/BayesModel.java    |    5 +-
 .../compound/CompoundNaiveBayesModel.java          |   37 +-
 .../compound/CompoundNaiveBayesTrainer.java        |   15 +-
 .../discrete/DiscreteNaiveBayesModel.java          |   12 +-
 .../gaussian/GaussianNaiveBayesModel.java          |   20 +-
 .../apache/ignite/ml/nn/MultilayerPerceptron.java  |   21 +-
 .../ignite/ml/nn/ReplicatedVectorMatrix.java       |    5 -
 .../ml/preprocessing/PreprocessingTrainer.java     |    6 +-
 .../ignite/ml/preprocessing/Preprocessor.java      |    2 +-
 .../developer/MappedPreprocessor.java              |    2 +-
 .../encoding/EncoderPreprocessor.java              |    4 +-
 .../frequency/FrequencyEncoderPreprocessor.java    |    2 +-
 .../encoding/label/LabelEncoderPreprocessor.java   |    4 +-
 .../stringencoder/StringEncoderPreprocessor.java   |    4 +-
 .../VectorFinalizationPreprocessor.java            |    2 +-
 .../ml/recommendation/RecommendationTrainer.java   |   12 +-
 .../regressions/linear/LinearRegressionModel.java  |    3 +-
 .../logistic/LogisticRegressionModel.java          |    5 +-
 .../ml/selection/cv/AbstractCrossValidation.java   |   10 +-
 .../ignite/ml/selection/paramgrid/ParamGrid.java   |    2 +-
 .../ml/selection/paramgrid/RandomStrategy.java     |   10 +-
 .../scoring/evaluator/EvaluationResult.java        |   10 +-
 .../ml/selection/scoring/evaluator/Evaluator.java  |   18 +-
 ...assificationPointwiseMetricStatsAggregator.java |   14 +-
 .../ClassificationMetricsAggregator.java           |   36 +-
 .../aggregator/MetricStatsAggregator.java          |    6 +-
 .../RegressionMetricStatsAggregator.java           |    6 +-
 .../BinaryClassificationEvaluationContext.java     |   82 +-
 .../evaluator/context/EvaluationContext.java       |    4 +-
 .../metric/classification/BalancedAccuracy.java    |   16 +-
 .../classification/BinaryClassificationMetric.java |   10 +-
 .../scoring/metric/classification/FMeasure.java    |    4 +-
 .../scoring/metric/classification/FallOut.java     |    1 -
 .../scoring/metric/classification/MissRate.java    |    1 -
 .../scoring/metric/classification/Precision.java   |    2 +-
 .../scoring/metric/classification/Recall.java      |    2 +-
 .../selection/scoring/metric/regression/Mae.java   |    3 +-
 .../selection/scoring/metric/regression/Mse.java   |    3 +-
 .../ml/selection/scoring/metric/regression/R2.java |    3 +-
 .../selection/scoring/metric/regression/Rmse.java  |    3 +-
 .../selection/scoring/metric/regression/Rss.java   |    3 +-
 .../org/apache/ignite/ml/structures/Dataset.java   |   24 +-
 .../apache/ignite/ml/structures/DatasetRow.java    |    2 +-
 .../ignite/ml/structures/FeatureMetadata.java      |    3 +-
 .../apache/ignite/ml/structures/LabeledVector.java |    1 -
 .../ignite/ml/structures/LabeledVectorSet.java     |   37 +-
 .../partition/LabelPartitionDataBuilderOnHeap.java |    2 +-
 .../preprocessing/LabeledDatasetLoader.java        |   10 +-
 .../ml/svm/SVMLinearClassificationModel.java       |    5 +-
 .../ml/trainers/AdaptableDatasetTrainer.java       |    4 +-
 .../apache/ignite/ml/trainers/DatasetTrainer.java  |    2 +-
 .../GDBBinaryClassifierOnTreesTrainer.java         |    2 +-
 .../ignite/ml/tree/data/DecisionTreeData.java      |   11 +-
 .../ignite/ml/tree/impurity/util/StepFunction.java |    2 +-
 .../ml/tree/leaf/MeanDecisionTreeLeafBuilder.java  |    2 +-
 .../ml/tree/randomforest/RandomForestTrainer.java  |    2 +-
 .../data/FeaturesCountSelectionStrategies.java     |   16 +-
 .../randomforest/data/impurity/GiniHistogram.java  |    2 +-
 .../randomforest/data/impurity/MSEHistogram.java   |    2 +-
 .../data/impurity/basic/CountersHistogram.java     |    2 +-
 .../apache/ignite/ml/util/MLSandboxDatasets.java   |    2 +-
 .../org/apache/ignite/ml/util/SandboxMLCache.java  |   10 +-
 .../primitives/scalar/RandomProducer.java          |    2 +-
 .../scalar/RandomProducerWithGenerator.java        |    2 +-
 .../primitives/scalar/UniformRandomProducer.java   |   10 +-
 .../generators/primitives/scalar/package-info.java |    2 +-
 .../vector/ParametricVectorGenerator.java          |    2 +-
 .../primitives/vector/VectorGenerator.java         |    4 +-
 .../vector/VectorGeneratorPrimitives.java          |    8 +-
 .../primitives/vector/VectorGeneratorsFamily.java  |    4 +-
 .../generators/primitives/vector/package-info.java |    2 +-
 .../standard/GaussianMixtureDataStream.java        |    2 +-
 .../util/generators/standard/RingsDataStream.java  |    4 +-
 .../apache/ignite/ml/util/genetic/Chromosome.java  |    2 +-
 .../apache/ignite/ml/util/genetic/Population.java  |    8 +-
 .../test/java/org/apache/ignite/ml/TestUtils.java  |    3 +-
 .../ignite/ml/clustering/KMeansTrainerTest.java    |    2 +-
 .../ml/clustering/gmm/GmmPartitionDataTest.java    |    1 +
 .../ignite/ml/clustering/gmm/GmmTrainerTest.java   |   24 +-
 .../apache/ignite/ml/common/CollectionsTest.java   |    2 +-
 .../apache/ignite/ml/common/KeepBinaryTest.java    |    6 +-
 .../org/apache/ignite/ml/common/TrainerTest.java   |    3 +-
 .../ignite/ml/composition/bagging/BaggingTest.java |   18 +-
 .../ml/composition/boosting/GDBTrainerTest.java    |    2 +-
 .../convergence/ConvergenceCheckerTest.java        |    2 +-
 .../WeightedPredictionsAggregatorTest.java         |   17 +-
 .../dataset/feature/extractor/VectorizerTest.java  |    2 +-
 .../impl/cache/CacheBasedDatasetBuilderTest.java   |    2 +-
 .../impl/cache/util/PartitionDataStorageTest.java  |    2 +-
 .../ml/dataset/primitive/DatasetWrapperTest.java   |   12 +-
 .../ml/environment/LearningEnvironmentTest.java    |    2 +-
 .../deploy/DeployingContextImplTest.java           |    2 +-
 .../ml/environment/deploy/MLDeployingTest.java     |   30 +-
 .../ml/genetic/GAGridCalculateFitnessTest.java     |   18 +-
 .../ml/genetic/GAGridInitializePopulationTest.java |   26 +-
 .../ignite/ml/genetic/PasswordFitnessFunction.java |    6 +-
 .../ignite/ml/knn/ANNClassificationTest.java       |    2 +-
 .../apache/ignite/ml/knn/LabeledDatasetHelper.java |    2 +-
 .../primitives/matrix/MatrixAttributeTest.java     |    9 +-
 .../primitives/matrix/MatrixBaseStorageTest.java   |    2 +-
 .../primitives/matrix/MatrixStorageFixtures.java   |    6 +-
 .../matrix/MatrixViewConstructorTest.java          |    1 -
 .../math/primitives/vector/AbstractVectorTest.java |   12 +-
 .../primitives/vector/VectorAttributesTest.java    |   13 +-
 .../primitives/vector/VectorBaseStorageTest.java   |    2 +-
 .../vector/VectorImplementationsFixtures.java      |   14 +-
 .../vector/storage/AbstractStorageTest.java        |   13 +-
 .../vector/storage/DenseVectorStorageTest.java     |    1 -
 .../vector/storage/SparseVectorStorageTest.java    |    4 +-
 .../ml/math/stat/DistributionMixtureTest.java      |    9 +-
 .../ignite/ml/multiclass/OneVsRestTrainerTest.java |    2 +-
 .../compound/CompoundNaiveBayesModelTest.java      |   21 +-
 .../compound/CompoundNaiveBayesTest.java           |    9 +-
 .../compound/CompoundNaiveBayesTrainerTest.java    |    9 +-
 .../apache/ignite/ml/naivebayes/compound/Data.java |    3 +-
 .../discrete/DiscreteNaiveBayesModelTest.java      |    1 -
 .../discrete/DiscreteNaiveBayesTest.java           |    7 +-
 .../discrete/DiscreteNaiveBayesTrainerTest.java    |   20 +-
 .../gaussian/GaussianNaiveBayesModelTest.java      |    1 -
 .../gaussian/GaussianNaiveBayesTrainerTest.java    |   29 +-
 .../test/java/org/apache/ignite/ml/nn/MLPTest.java |    6 +-
 .../preprocessing/encoding/EncoderTrainerTest.java |    6 +-
 .../encoding/OneHotEncoderPreprocessorTest.java    |    2 +-
 .../standardscaling/StandardScalerTrainerTest.java |    4 +-
 .../logistic/LogisticRegressionSGDTrainerTest.java |    2 +-
 .../ml/selection/cv/CrossValidationTest.java       |    8 +-
 .../BinaryClassificationEvaluatorTest.java         |    4 +-
 .../scoring/evaluator/RegressionEvaluatorTest.java |    6 +-
 ...ficationPointwiseMetricStatsAggregatorTest.java |   23 +-
 .../RegressionMetricStatsAggregatorTest.java       |   16 +-
 .../BinaryClassificationEvaluationContextTest.java |   48 +-
 .../metric/regression/RegressionMetricsTest.java   |    6 +-
 .../ignite/ml/structures/DatasetStructureTest.java |    5 +-
 .../ignite/ml/structures/LabeledVectorSetTest.java |   10 +-
 .../apache/ignite/ml/svm/SVMBinaryTrainerTest.java |    2 +-
 .../ml/tree/randomforest/data/TreeNodeTest.java    |    4 +-
 .../DataStreamGeneratorFillCacheTest.java          |   14 +-
 .../util/generators/DataStreamGeneratorTest.java   |   30 +-
 .../scalar/UniformRandomProducerTest.java          |    5 +-
 .../primitives/vector/VectorGeneratorTest.java     |    2 +-
 .../vector/VectorGeneratorsFamilyTest.java         |    4 +-
 modules/ml/tensorflow-model-parser/pom.xml         |    2 +-
 modules/ml/xgboost-model-parser/pom.xml            |    2 +-
 modules/mqtt/pom.xml                               |    2 +-
 modules/opencensus/pom.xml                         |    2 +-
 modules/osgi-karaf/pom.xml                         |    2 +-
 modules/osgi-paxlogging/pom.xml                    |    2 +-
 modules/osgi/pom.xml                               |    2 +-
 modules/platforms/cpp/binary/configure.ac          |    2 +-
 modules/platforms/cpp/common/configure.ac          |    2 +-
 modules/platforms/cpp/configure.ac                 |    2 +-
 modules/platforms/cpp/configure.acrel              |    2 +-
 modules/platforms/cpp/core-test/configure.ac       |    2 +-
 modules/platforms/cpp/core/configure.ac            |    2 +-
 modules/platforms/cpp/examples/configure.ac        |    2 +-
 modules/platforms/cpp/ignite/configure.ac          |    2 +-
 modules/platforms/cpp/jni/configure.ac             |    2 +-
 modules/platforms/cpp/network/configure.ac         |    2 +-
 modules/platforms/cpp/odbc/configure.ac            |    2 +-
 .../cpp/odbc/install/ignite-odbc-amd64.wxs         |    2 +-
 .../platforms/cpp/odbc/install/ignite-odbc-x86.wxs |    2 +-
 .../platforms/cpp/thin-client-test/configure.ac    |    2 +-
 .../ExpiryCacheHolderTest.cs                       |    7 -
 .../Properties/AssemblyInfo.cs                     |    6 +-
 .../Apache.Ignite.AspNet.csproj                    |    3 +-
 .../dotnet/Apache.Ignite.AspNet/Package-Info.cs    |   26 -
 .../Properties/AssemblyInfo.cs                     |    6 +-
 .../Properties/AssemblyInfo.cs                     |    6 +-
 .../Apache.Ignite.Core.Tests.DotNetCore.csproj     |    1 +
 .../Properties/AssemblyInfo.cs                     |    4 +-
 .../Properties/AssemblyInfo.cs                     |    6 +-
 .../Properties/AssemblyInfo.cs                     |    6 +-
 .../Apache.Ignite.Core.Tests.csproj                |    1 +
 .../ApiParity/ParityTest.cs                        |    2 +-
 .../Binary/BinaryBuilderSelfTest.cs                |   70 +
 .../Binary/BinaryDynamicRegistrationTest.cs        |    2 +-
 .../Cache/CacheAbstractTest.cs                     |    2 +-
 .../Cache/CacheTestAsyncWrapper.cs                 |   17 +-
 .../Cache/DataRegionMetricsTest.cs                 |    1 -
 .../Cache/DataStorageMetricsTest.cs                |    1 -
 .../Client/Cache/BinaryBuilderTest.cs              |   21 +-
 .../Client/Cache/CacheClientAsyncWrapper.cs        |    7 +
 .../Client/Cache/CacheTest.cs                      |  167 +-
 .../Client/Cache/CacheTestKeepBinary.cs            |    2 +-
 .../Client/Cache/ClientCacheConfigurationTest.cs   |    3 -
 .../Client/Cache/CreateCacheTest.cs                |   34 +
 .../Client/ClientTestBase.cs                       |   12 +-
 .../Client/Cluster/ClientClusterTests.cs           |  221 ++
 .../Deployment/PeerAssemblyLoadingTest.cs          |    2 +-
 .../Apache.Ignite.Core.Tests/Examples/PathUtil.cs  |    2 +-
 .../IgniteConfigurationTest.cs                     |    2 +-
 .../Apache.Ignite.Core.Tests/IgniteManagerTest.cs  |    2 +-
 .../Log/DefaultLoggerTest.cs                       |    2 +-
 .../Process/IgniteProcess.cs                       |    2 +-
 .../Properties/AssemblyInfo.cs                     |    6 +-
 .../Apache.Ignite.Core.Tests/TestUtils.Windows.cs  |    1 -
 .../Apache.Ignite.Core/Apache.Ignite.Core.csproj   |   39 +-
 .../Binary/IBinaryObjectBuilder.cs                 |   14 +
 .../Apache.Ignite.Core/Binary/Package-Info.cs      |   26 -
 .../Cache/Affinity/Package-Info.cs                 |   26 -
 .../Cache/Affinity/Rendezvous/Package-Info.cs      |   26 -
 .../Cache/Configuration/Package-Info.cs            |   26 -
 .../Apache.Ignite.Core/Cache/Event/Package-Info.cs |   26 -
 .../Cache/Eviction/Package-Info.cs                 |   26 -
 .../Cache/Expiry/ExpiryPolicy.cs                   |    2 +-
 .../Cache/Expiry/Package-Info.cs                   |   26 -
 .../dotnet/Apache.Ignite.Core/Cache/ICache.cs      |   17 +-
 .../Apache.Ignite.Core/Cache/Package-Info.cs       |   26 -
 .../Cache/Query/Continuous/Package-Info.cs         |   26 -
 .../Apache.Ignite.Core/Cache/Query/Package-Info.cs |   26 -
 .../Apache.Ignite.Core/Cache/Store/Package-Info.cs |   26 -
 .../Client/Cache/CacheClientConfiguration.cs       |   10 +
 .../Client/Cache/ICacheClient.cs                   |   12 +
 .../Apache.Ignite.Core/Client/IClientCluster.cs    |   74 +
 .../Apache.Ignite.Core/Client/IIgniteClient.cs     |    6 +
 .../Apache.Ignite.Core/Cluster/Package-Info.cs     |   26 -
 .../Apache.Ignite.Core/Common/Package-Info.cs      |   26 -
 .../Communication/Package-Info.cs                  |   26 -
 .../Communication/Tcp/Package-Info.cs              |   26 -
 .../Apache.Ignite.Core/Compute/Package-Info.cs     |   26 -
 .../Configuration/Package-Info.cs                  |   26 -
 .../DataStructures/Configuration/Package-Info.cs   |   26 -
 .../DataStructures/Package-Info.cs                 |   26 -
 .../Apache.Ignite.Core/Datastream/Package-Info.cs  |   26 -
 .../Apache.Ignite.Core/Discovery/Package-Info.cs   |   26 -
 .../Discovery/Tcp/Multicast/Package-Info.cs        |   26 -
 .../Discovery/Tcp/Package-Info.cs                  |   26 -
 .../Discovery/Tcp/Static/Package-Info.cs           |   26 -
 .../Encryption/Keystore/Package-Info.cs            |   26 -
 .../Apache.Ignite.Core/Encryption/Package-Info.cs  |   26 -
 .../Apache.Ignite.Core/Events/Package-Info.cs      |   26 -
 .../Impl/Binary/BinaryObjectBuilder.cs             |   15 +-
 .../Apache.Ignite.Core/Impl/Cache/CacheImpl.cs     |   51 +-
 .../Apache.Ignite.Core/Impl/Cache/CacheOp.cs       |    3 +-
 .../Impl/Client/Cache/CacheClient.cs               |   61 +-
 .../Cache/ClientCacheConfigurationSerializer.cs    |   14 +-
 .../Apache.Ignite.Core/Impl/Client/ClientOp.cs     |    8 +-
 .../Apache.Ignite.Core/Impl/Client/ClientSocket.cs |   10 +-
 .../Impl/Client/Cluster/ClientCluster.cs           |  131 ++
 .../Apache.Ignite.Core/Impl/Client/IgniteClient.cs |    7 +
 .../dotnet/Apache.Ignite.Core/Impl/Ignite.cs       |    2 +-
 .../Apache.Ignite.Core/Interop/Package-Info.cs     |   26 -
 .../Apache.Ignite.Core/Lifecycle/Package-Info.cs   |   26 -
 .../Apache.Ignite.Core/Messaging/Package-Info.cs   |   26 -
 .../dotnet/Apache.Ignite.Core/Package-Info.cs      |   36 -
 .../PersistentStore/Package-Info.cs                |   26 -
 .../Apache.Ignite.Core/Properties/AssemblyInfo.cs  |    6 +-
 .../Apache.Ignite.Core/Resource/Package-Info.cs    |   26 -
 .../Apache.Ignite.Core/Services/Package-Info.cs    |   26 -
 .../Transactions/Package-Info.cs                   |   26 -
 .../Properties/AssemblyInfo.cs                     |    6 +-
 .../Apache.Ignite.EntityFramework.csproj           |    4 +-
 .../Properties/AssemblyInfo.cs                     |    6 +-
 .../Apache.Ignite.Linq/Apache.Ignite.Linq.csproj   |    3 +-
 .../dotnet/Apache.Ignite.Linq/Package-Info.cs      |   26 -
 .../Apache.Ignite.Linq/Properties/AssemblyInfo.cs  |    6 +-
 .../Apache.Ignite.Log4Net.csproj                   |    2 +-
 .../Properties/AssemblyInfo.cs                     |    6 +-
 .../Apache.Ignite.NLog/Apache.Ignite.NLog.csproj   |    2 +-
 .../Apache.Ignite.NLog/Properties/AssemblyInfo.cs  |    6 +-
 modules/platforms/dotnet/Apache.Ignite.dxg         | 2387 --------------------
 .../Apache.Ignite/Properties/AssemblyInfo.cs       |    6 +-
 modules/platforms/dotnet/docfx/README.txt          |    8 +-
 modules/platforms/dotnet/header.html               |   27 -
 modules/platforms/dotnet/release/Program.cs        |  100 +
 modules/platforms/dotnet/release/verify-nuget.ps1  |   91 +
 modules/rest-http/pom.xml                          |    2 +-
 modules/rocketmq/pom.xml                           |    2 +-
 modules/scalar-2.10/pom.xml                        |    2 +-
 modules/scalar/pom.xml                             |    2 +-
 modules/schedule/pom.xml                           |    2 +-
 modules/slf4j/pom.xml                              |    2 +-
 modules/{spark => spark-2.4}/README.txt            |    0
 .../licenses/apache-2.0.txt                        |    0
 modules/{spark => spark-2.4}/pom.xml               |   18 +-
 ...org.apache.spark.sql.sources.DataSourceRegister |    0
 .../org/apache/ignite/spark/IgniteContext.scala    |  237 ++
 .../ignite/spark/IgniteDataFrameSettings.scala     |    0
 .../scala/org/apache/ignite/spark/IgniteRDD.scala  |    0
 .../apache/ignite/spark/JavaIgniteContext.scala    |    0
 .../org/apache/ignite/spark/JavaIgniteRDD.scala    |    0
 .../ignite/spark/impl/IgniteAbstractRDD.scala      |    0
 .../spark/impl/IgniteDataFramePartition.scala      |    0
 .../apache/ignite/spark/impl/IgnitePartition.scala |    0
 .../ignite/spark/impl/IgniteQueryIterator.scala    |    0
 .../ignite/spark/impl/IgniteRelationProvider.scala |    0
 .../spark/impl/IgniteSQLAccumulatorRelation.scala  |    0
 .../ignite/spark/impl/IgniteSQLDataFrameRDD.scala  |    0
 .../ignite/spark/impl/IgniteSQLRelation.scala      |    0
 .../apache/ignite/spark/impl/IgniteSqlRDD.scala    |    0
 .../org/apache/ignite/spark/impl/QueryHelper.scala |    0
 .../org/apache/ignite/spark/impl/QueryUtils.scala  |    0
 .../impl/optimization/AggregateExpressions.scala   |    0
 .../impl/optimization/ConditionExpressions.scala   |    0
 .../spark/impl/optimization/DateExpressions.scala  |    0
 .../impl/optimization/IgniteQueryContext.scala     |    0
 .../spark/impl/optimization/MathExpressions.scala  |    0
 .../impl/optimization/SimpleExpressions.scala      |  203 ++
 .../impl/optimization/StringExpressions.scala      |    0
 .../impl/optimization/SupportedExpressions.scala   |    0
 .../impl/optimization/SystemExpressions.scala      |    0
 .../accumulator/JoinSQLAccumulator.scala           |  226 ++
 .../accumulator/QueryAccumulator.scala             |    0
 .../accumulator/SelectAccumulator.scala            |    0
 .../accumulator/SingleTableSQLAccumulator.scala    |    0
 .../accumulator/UnionSQLAccumulator.scala          |    0
 .../ignite/spark/impl/optimization/package.scala   |    0
 .../org/apache/ignite/spark/impl/package.scala     |    0
 .../spark/sql/ignite/IgniteExternalCatalog.scala   |  341 +++
 .../spark/sql/ignite/IgniteOptimization.scala      |  441 ++++
 .../spark/sql/ignite/IgniteSharedState.scala       |   45 +
 .../spark/sql/ignite/IgniteSparkSession.scala      |    0
 .../spark/JavaEmbeddedIgniteRDDSelfTest.java       |    0
 ...avaEmbeddedIgniteRDDWithLocalStoreSelfTest.java |    0
 .../spark/JavaStandaloneIgniteRDDSelfTest.java     |    0
 .../ignite/testsuites/IgniteRDDTestSuite.java      |    0
 .../src/test/resources/cities.json                 |    0
 .../src/test/resources/cities_non_unique.json      |    0
 .../src/test/resources/ignite-spark-config.xml     |    0
 .../ignite/spark/AbstractDataFrameSpec.scala       |  241 ++
 .../scala/org/apache/ignite/spark/Entity.scala     |    0
 .../ignite/spark/EntityTestAllTypeFields.scala     |    0
 .../apache/ignite/spark/IgniteCatalogSpec.scala    |  229 ++
 .../ignite/spark/IgniteDataFrameSchemaSpec.scala   |    0
 .../apache/ignite/spark/IgniteDataFrameSuite.scala |    0
 .../spark/IgniteDataFrameWrongConfigSpec.scala     |    0
 .../IgniteOptimizationAggregationFuncSpec.scala    |  189 ++
 .../spark/IgniteOptimizationDateFuncSpec.scala     |    0
 .../IgniteOptimizationDisableEnableSpec.scala      |    0
 .../ignite/spark/IgniteOptimizationJoinSpec.scala  |  539 +++++
 .../spark/IgniteOptimizationMathFuncSpec.scala     |    0
 .../ignite/spark/IgniteOptimizationSpec.scala      |  362 +++
 .../spark/IgniteOptimizationStringFuncSpec.scala   |    0
 .../spark/IgniteOptimizationSystemFuncSpec.scala   |  147 ++
 .../org/apache/ignite/spark/IgniteRDDSpec.scala    |    0
 .../IgniteSQLDataFrameIgniteSessionWriteSpec.scala |  109 +
 .../ignite/spark/IgniteSQLDataFrameSpec.scala      |    0
 .../ignite/spark/IgniteSQLDataFrameWriteSpec.scala |  388 ++++
 .../spark/sql/ignite/IgniteSparkSessionSpec.scala  |   79 +
 modules/spark/pom.xml                              |    2 +-
 modules/spring-data-2.0/pom.xml                    |    2 +-
 modules/spring-data/pom.xml                        |    2 +-
 modules/spring/pom.xml                             |    2 +-
 modules/sqlline/bin/sqlline.sh                     |   13 +-
 modules/sqlline/pom.xml                            |    2 +-
 modules/ssh/pom.xml                                |    2 +-
 modules/storm/pom.xml                              |    2 +-
 modules/tensorflow/pom.xml                         |    2 +-
 modules/tools/pom.xml                              |    2 +-
 modules/twitter/pom.xml                            |    2 +-
 modules/urideploy/pom.xml                          |    2 +-
 modules/visor-console-2.10/pom.xml                 |    2 +-
 modules/visor-console/pom.xml                      |    2 +-
 modules/visor-plugins/pom.xml                      |    2 +-
 modules/web-console/pom.xml                        |    2 +-
 modules/web-console/web-agent/pom.xml              |    2 +-
 modules/web/ignite-appserver-test/pom.xml          |    2 +-
 modules/web/ignite-websphere-test/pom.xml          |    2 +-
 modules/web/pom.xml                                |    2 +-
 .../config/benchmark-native-sql-inline.properties  |   85 +
 modules/yardstick/pom.xml                          |    2 +-
 .../apache/ignite/yardstick/jdbc/JdbcUtils.java    |   77 +-
 .../jdbc/NativeSqlMixedDateInlineBenchmark.java    |  139 ++
 modules/yarn/pom.xml                               |    2 +-
 modules/yarn/src/main/resources/ignite.properties  |    2 +-
 modules/zeromq/pom.xml                             |    2 +-
 modules/zookeeper/pom.xml                          |    2 +-
 .../ZookeeperDiscoveryClientReconnectTest.java     |    2 +-
 ...perDiscoveryTopologyChangeAndReconnectTest.java |   45 +-
 packaging/package.sh                               |   13 +-
 parent/pom.xml                                     |    5 +-
 pom.xml                                            |   13 +-
 865 files changed, 21129 insertions(+), 10034 deletions(-)
 create mode 100644 examples/src/main/java/org/apache/ignite/examples/ml/inference/h2o/H2OMojoModelParserExample.java
 copy examples/src/main/java/org/apache/ignite/examples/ml/{naivebayes => inference/h2o}/package-info.java (86%)
 rename examples/src/main/resources/datasets/{titanik_test.csv => titanic_test.csv} (100%)
 rename examples/src/main/resources/datasets/{titanik_train.csv => titanic_train.csv} (100%)
 create mode 100644 examples/src/main/resources/models/h2o/agaricus-gbm-mojo.zip
 create mode 100644 examples/src/main/spark/org/apache/ignite/examples/spark/IgniteDataFrameJoinExample.scala
 create mode 100644 examples/src/main/spark/org/apache/ignite/examples/spark/JavaIgniteDataFrameJoinExample.java
 create mode 100644 modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/misc/JmhPartitionUpdateCounterBenchmark.java
 create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/client/thin/ClientCacheAffinityContext.java
 create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/client/thin/ClientCacheAffinityMapping.java
 rename modules/core/src/main/java/org/apache/ignite/internal/{client/thin/Result.java => managers/communication/TransmissionCancelledException.java} (54%)
 create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/managers/systemview/ScanQuerySystemView.java
 create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/managers/systemview/walker/ScanQueryViewWalker.java
 create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/managers/systemview/walker/SqlQueryHistoryViewWalker.java
 create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/managers/systemview/walker/SqlQueryViewWalker.java
 create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/managers/systemview/walker/StripedExecutorTaskViewWalker.java
 delete mode 100644 modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/MetaPageUpdatePartitionDataRecordV3.java
 delete mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/TombstoneCacheObject.java
 create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cluster/ClientClusterChangeStateRequest.java
 create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cluster/ClientClusterIsActiveRequest.java
 create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cluster/ClientClusterWalChangeStateRequest.java
 copy modules/{ml/src/main/java/org/apache/ignite/ml/genetic/parameter/ChromosomeCriteria.java => core/src/main/java/org/apache/ignite/internal/processors/platform/client/cluster/ClientClusterWalChangeStateResponse.java} (50%)
 create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cluster/ClientClusterWalGetStateRequest.java
 rename modules/core/src/main/java/org/apache/ignite/internal/processors/query/{QueryHistoryMetrics.java => QueryHistory.java} (87%)
 rename modules/core/src/main/java/org/apache/ignite/internal/processors/query/{QueryHistoryMetricsKey.java => QueryHistoryKey.java} (92%)
 create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/resource/GridResourceProxiedIgniteInjector.java
 create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/security/sandbox/AccessControllerSandbox.java
 create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/security/sandbox/IgniteDomainCombiner.java
 create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/security/sandbox/IgniteSandbox.java
 copy modules/{ml/spark-model-parser/src/main/java/org/apache/ignite/ml/sparkmodelparser/UnsupportedSparkModelException.java => core/src/main/java/org/apache/ignite/internal/processors/security/sandbox/NoOpSandbox.java} (66%)
 create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/security/sandbox/SandboxIgniteComponentProxy.java
 create mode 100644 modules/core/src/main/java/org/apache/ignite/spi/systemview/view/ScanQueryView.java
 create mode 100644 modules/core/src/main/java/org/apache/ignite/spi/systemview/view/SqlQueryHistoryView.java
 create mode 100644 modules/core/src/main/java/org/apache/ignite/spi/systemview/view/SqlQueryView.java
 create mode 100644 modules/core/src/main/java/org/apache/ignite/spi/systemview/view/StripedExecutorTaskView.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/cache/RemoveAllDeadlockTest.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/client/thin/ThinClientAbstractAffinityAwarenessTest.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/client/thin/ThinClientAffinityAwarenessStableTopologyTest.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/client/thin/ThinClientAffinityAwarenessUnstableTopologyTest.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/cache/ClientSlowDiscoveryAbstractTest.java
 rename modules/core/src/test/java/org/apache/ignite/internal/processors/cache/{ClientDelayedJoinTest.java => ClientSlowDiscoveryTopologyChangeTest.java} (56%)
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/cache/ClientSlowDiscoveryTransactionRemapTest.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/cache/GridCacheProcessorActiveTxTest.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/cache/SafeLogTxFinishErrorTest.java
 delete mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/cache/distributed/CacheRemoveWithTombstonesLoadTest.java
 delete mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/cache/distributed/CacheRemoveWithTombstonesTest.java
 delete mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/cache/distributed/dht/topology/CacheRemoveWithTombstonesFailoverTest.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/cache/local/GridCacheFastNodeLeftForTransactionTest.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/checkpoint/CheckpointStartLoggingTest.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/cache/transactions/TxCrossCachePartitionConsistencyTest.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/cache/transactions/TxCrossCacheRemoteMultiplePartitionReservationTest.java
 copy modules/core/src/{main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxRemoteEx.java => test/java/org/apache/ignite/internal/processors/cache/transactions/TxPartitionCounterStateConsistencyVolatileRebalanceTest.java} (52%)
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerStopCacheTest.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/failure/FailureProcessorLoggingTest.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/security/compute/closure/ComputeTaskCancelRemoteSecurityContextCheckTest.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/AbstractSandboxTest.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/CacheSandboxTest.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/ComputeSandboxTest.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/DataStreamerSandboxTest.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/DoPrivilegedOnRemoteNodeTest.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/IgniteOperationsInsideSandboxTest.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/security/sandbox/SecuritySubjectPermissionsTest.java
 delete mode 100644 modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sys/view/SqlSystemViewQueryHistoryMetrics.java
 delete mode 100644 modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sys/view/SqlSystemViewRunningQueries.java
 create mode 100644 modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/CacheGroupMetricsWithIndexBuildFailTest.java
 create mode 100644 modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/distributed/near/IgniteCacheDistributedQueryDefaultTimeoutSelfTest.java
 create mode 100644 modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/IndexMetricsTest.java
 create mode 100644 modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/local/IgniteCacheLocalQueryDefaultTimeoutSelfTest.java
 copy modules/ml/{xgboost-model-parser => h2o-model-parser}/pom.xml (90%)
 create mode 100644 modules/ml/h2o-model-parser/src/main/java/org/apache/ignite/ml/h2o/H2OMojoModel.java
 create mode 100644 modules/ml/h2o-model-parser/src/main/java/org/apache/ignite/ml/h2o/H2OMojoModelParser.java
 copy modules/ml/{src/main/java/org/apache/ignite/ml/composition/combinators => h2o-model-parser/src/main/java/org/apache/ignite/ml/h2o}/package-info.java (88%)
 create mode 100644 modules/ml/h2o-model-parser/src/test/java/org/apache/ignite/ml/h2o/H2OMojoParserTest.java
 copy examples/src/main/java/org/apache/ignite/examples/ml/preprocessing/package-info.java => modules/ml/h2o-model-parser/src/test/java/org/apache/ignite/ml/h2o/H2OMojoTestSuite.java (75%)
 create mode 100644 modules/ml/h2o-model-parser/src/test/resources/mojos/gbm_prostate.zip
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.AspNet/Package-Info.cs
 create mode 100644 modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cluster/ClientClusterTests.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Binary/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Cache/Affinity/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Cache/Affinity/Rendezvous/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Cache/Event/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Cache/Eviction/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Cache/Expiry/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Cache/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Cache/Query/Continuous/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Cache/Query/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Cache/Store/Package-Info.cs
 create mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Client/IClientCluster.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Cluster/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Common/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Communication/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Communication/Tcp/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Compute/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Configuration/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/DataStructures/Configuration/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/DataStructures/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Datastream/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Discovery/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Discovery/Tcp/Multicast/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Discovery/Tcp/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Discovery/Tcp/Static/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Encryption/Keystore/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Encryption/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Events/Package-Info.cs
 create mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/Cluster/ClientCluster.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Interop/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Lifecycle/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Messaging/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/PersistentStore/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Resource/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Services/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Core/Transactions/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.Linq/Package-Info.cs
 delete mode 100644 modules/platforms/dotnet/Apache.Ignite.dxg
 delete mode 100644 modules/platforms/dotnet/header.html
 create mode 100644 modules/platforms/dotnet/release/Program.cs
 create mode 100644 modules/platforms/dotnet/release/verify-nuget.ps1
 copy modules/{spark => spark-2.4}/README.txt (100%)
 copy modules/{zookeeper => spark-2.4}/licenses/apache-2.0.txt (100%)
 copy modules/{spark => spark-2.4}/pom.xml (93%)
 copy modules/{spark => spark-2.4}/src/main/resources/META-INF/services/org.apache.spark.sql.sources.DataSourceRegister (100%)
 create mode 100644 modules/spark-2.4/src/main/scala/org/apache/ignite/spark/IgniteContext.scala
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/IgniteDataFrameSettings.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/IgniteRDD.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/JavaIgniteContext.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/JavaIgniteRDD.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/IgniteAbstractRDD.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/IgniteDataFramePartition.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/IgnitePartition.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/IgniteQueryIterator.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/IgniteRelationProvider.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/IgniteSQLAccumulatorRelation.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/IgniteSQLDataFrameRDD.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/IgniteSQLRelation.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/IgniteSqlRDD.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/QueryHelper.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/QueryUtils.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/optimization/AggregateExpressions.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/optimization/ConditionExpressions.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/optimization/DateExpressions.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/optimization/IgniteQueryContext.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/optimization/MathExpressions.scala (100%)
 create mode 100644 modules/spark-2.4/src/main/scala/org/apache/ignite/spark/impl/optimization/SimpleExpressions.scala
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/optimization/StringExpressions.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/optimization/SupportedExpressions.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/optimization/SystemExpressions.scala (100%)
 create mode 100644 modules/spark-2.4/src/main/scala/org/apache/ignite/spark/impl/optimization/accumulator/JoinSQLAccumulator.scala
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/optimization/accumulator/QueryAccumulator.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/optimization/accumulator/SelectAccumulator.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/optimization/accumulator/SingleTableSQLAccumulator.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/optimization/accumulator/UnionSQLAccumulator.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/optimization/package.scala (100%)
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/ignite/spark/impl/package.scala (100%)
 create mode 100644 modules/spark-2.4/src/main/scala/org/apache/spark/sql/ignite/IgniteExternalCatalog.scala
 create mode 100644 modules/spark-2.4/src/main/scala/org/apache/spark/sql/ignite/IgniteOptimization.scala
 create mode 100644 modules/spark-2.4/src/main/scala/org/apache/spark/sql/ignite/IgniteSharedState.scala
 copy modules/{spark => spark-2.4}/src/main/scala/org/apache/spark/sql/ignite/IgniteSparkSession.scala (100%)
 copy modules/{spark => spark-2.4}/src/test/java/org/apache/ignite/spark/JavaEmbeddedIgniteRDDSelfTest.java (100%)
 copy modules/{spark => spark-2.4}/src/test/java/org/apache/ignite/spark/JavaEmbeddedIgniteRDDWithLocalStoreSelfTest.java (100%)
 copy modules/{spark => spark-2.4}/src/test/java/org/apache/ignite/spark/JavaStandaloneIgniteRDDSelfTest.java (100%)
 copy modules/{spark => spark-2.4}/src/test/java/org/apache/ignite/testsuites/IgniteRDDTestSuite.java (100%)
 copy modules/{spark => spark-2.4}/src/test/resources/cities.json (100%)
 copy modules/{spark => spark-2.4}/src/test/resources/cities_non_unique.json (100%)
 copy modules/{spark => spark-2.4}/src/test/resources/ignite-spark-config.xml (100%)
 create mode 100644 modules/spark-2.4/src/test/scala/org/apache/ignite/spark/AbstractDataFrameSpec.scala
 copy modules/{spark => spark-2.4}/src/test/scala/org/apache/ignite/spark/Entity.scala (100%)
 copy modules/{spark => spark-2.4}/src/test/scala/org/apache/ignite/spark/EntityTestAllTypeFields.scala (100%)
 create mode 100644 modules/spark-2.4/src/test/scala/org/apache/ignite/spark/IgniteCatalogSpec.scala
 copy modules/{spark => spark-2.4}/src/test/scala/org/apache/ignite/spark/IgniteDataFrameSchemaSpec.scala (100%)
 copy modules/{spark => spark-2.4}/src/test/scala/org/apache/ignite/spark/IgniteDataFrameSuite.scala (100%)
 copy modules/{spark => spark-2.4}/src/test/scala/org/apache/ignite/spark/IgniteDataFrameWrongConfigSpec.scala (100%)
 create mode 100644 modules/spark-2.4/src/test/scala/org/apache/ignite/spark/IgniteOptimizationAggregationFuncSpec.scala
 copy modules/{spark => spark-2.4}/src/test/scala/org/apache/ignite/spark/IgniteOptimizationDateFuncSpec.scala (100%)
 copy modules/{spark => spark-2.4}/src/test/scala/org/apache/ignite/spark/IgniteOptimizationDisableEnableSpec.scala (100%)
 create mode 100644 modules/spark-2.4/src/test/scala/org/apache/ignite/spark/IgniteOptimizationJoinSpec.scala
 copy modules/{spark => spark-2.4}/src/test/scala/org/apache/ignite/spark/IgniteOptimizationMathFuncSpec.scala (100%)
 create mode 100644 modules/spark-2.4/src/test/scala/org/apache/ignite/spark/IgniteOptimizationSpec.scala
 copy modules/{spark => spark-2.4}/src/test/scala/org/apache/ignite/spark/IgniteOptimizationStringFuncSpec.scala (100%)
 create mode 100644 modules/spark-2.4/src/test/scala/org/apache/ignite/spark/IgniteOptimizationSystemFuncSpec.scala
 copy modules/{spark => spark-2.4}/src/test/scala/org/apache/ignite/spark/IgniteRDDSpec.scala (100%)
 create mode 100644 modules/spark-2.4/src/test/scala/org/apache/ignite/spark/IgniteSQLDataFrameIgniteSessionWriteSpec.scala
 copy modules/{spark => spark-2.4}/src/test/scala/org/apache/ignite/spark/IgniteSQLDataFrameSpec.scala (100%)
 create mode 100644 modules/spark-2.4/src/test/scala/org/apache/ignite/spark/IgniteSQLDataFrameWriteSpec.scala
 create mode 100644 modules/spark-2.4/src/test/scala/org/apache/spark/sql/ignite/IgniteSparkSessionSpec.scala
 create mode 100644 modules/yardstick/config/benchmark-native-sql-inline.properties
 create mode 100644 modules/yardstick/src/main/java/org/apache/ignite/yardstick/jdbc/NativeSqlMixedDateInlineBenchmark.java


[ignite] 01/01: IGNITE-12248: Apache Calcite based query execution engine. Initial commit.

Posted by gv...@apache.org.
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

commit 1958c3e542b8b0d8a9491678f5270cd53f56abd1
Author: Igor Seliverstov <gv...@gmail.com>
AuthorDate: Fri Dec 13 16:02:54 2019 +0300

    IGNITE-12248: Apache Calcite based query execution engine.
    Initial commit.
---
 modules/calcite/README.txt                         |   35 +
 modules/calcite/pom.xml                            |  112 ++
 .../calcite/interpreter/InterpreterUtils.java      |   28 +
 .../apache/calcite/plan/volcano/VolcanoUtils.java  |   34 +
 .../query/calcite/CalciteQueryProcessor.java       |  167 +++
 .../query/calcite/cluster/MappingServiceImpl.java  |  134 +++
 .../query/calcite/exchange/EndMarker.java          |   33 +
 .../query/calcite/exchange/ExchangeProcessor.java  |   36 +
 .../processors/query/calcite/exchange/Inbox.java   |   70 ++
 .../processors/query/calcite/exchange/Outbox.java  |  163 +++
 .../query/calcite/exec/AbstractNode.java           |   44 +
 .../query/calcite/exec/ConsumerNode.java           |   75 ++
 .../processors/query/calcite/exec/FilterNode.java  |   47 +
 .../processors/query/calcite/exec/Implementor.java |  174 +++
 .../processors/query/calcite/exec/JoinNode.java    |  101 ++
 .../processors/query/calcite/exec/Node.java        |   27 +
 .../processors/query/calcite/exec/ProjectNode.java |   47 +
 .../query/calcite/exec/ScalarFactory.java          |  151 +++
 .../processors/query/calcite/exec/ScanNode.java    |   70 ++
 .../processors/query/calcite/exec/SingleNode.java  |   33 +
 .../processors/query/calcite/exec/Sink.java        |   38 +
 .../processors/query/calcite/exec/Source.java      |   24 +
 .../query/calcite/metadata/FragmentInfo.java       |   75 ++
 .../metadata/IgniteMdDerivedDistribution.java      |  149 +++
 .../calcite/metadata/IgniteMdDistribution.java     |  123 ++
 .../calcite/metadata/IgniteMdFragmentInfo.java     |  110 ++
 .../query/calcite/metadata/IgniteMetadata.java     |   68 ++
 .../calcite/metadata/LocationMappingException.java |   26 +
 .../query/calcite/metadata/MappingService.java     |   28 +
 .../query/calcite/metadata/NodesMapping.java       |  196 ++++
 .../metadata/OptimisticPlanningException.java      |   35 +
 .../query/calcite/metadata/RelMetadataQueryEx.java |  107 ++
 .../query/calcite/prepare/ContextValue.java        |   44 +
 .../query/calcite/prepare/DataContextImpl.java     |   67 ++
 .../calcite/prepare/DistributedExecution.java      |  110 ++
 .../query/calcite/prepare/IgnitePlanner.java       |  436 +++++++
 .../query/calcite/prepare/IgniteSqlValidator.java  |   55 +
 .../query/calcite/prepare/PlannerContext.java      |  194 ++++
 .../query/calcite/prepare/PlannerPhase.java        |   60 +
 .../query/calcite/prepare/PlannerType.java         |   26 +
 .../processors/query/calcite/prepare/Query.java    |   73 ++
 .../query/calcite/prepare/QueryExecution.java      |   28 +
 .../query/calcite/rel/IgniteConvention.java        |   46 +
 .../query/calcite/rel/IgniteExchange.java          |   40 +
 .../processors/query/calcite/rel/IgniteFilter.java |   40 +
 .../processors/query/calcite/rel/IgniteJoin.java   |   43 +
 .../query/calcite/rel/IgniteProject.java           |   42 +
 .../query/calcite/rel/IgniteReceiver.java          |   61 +
 .../processors/query/calcite/rel/IgniteRel.java    |   26 +
 .../query/calcite/rel/IgniteRelVisitor.java        |   38 +
 .../processors/query/calcite/rel/IgniteSender.java |   57 +
 .../query/calcite/rel/IgniteTableScan.java         |   41 +
 .../processors/query/calcite/rel/RelOp.java        |   27 +
 .../query/calcite/rule/FilterConverter.java        |   62 +
 .../query/calcite/rule/IgniteConverter.java        |   74 ++
 .../query/calcite/rule/JoinConverter.java          |   68 ++
 .../query/calcite/rule/ProjectConverter.java       |   63 +
 .../query/calcite/rule/TableScanConverter.java     |   44 +
 .../query/calcite/schema/CalciteSchemaHolder.java  |   69 ++
 .../query/calcite/schema/IgniteSchema.java         |   88 ++
 .../query/calcite/schema/IgniteTable.java          |  137 +++
 .../processors/query/calcite/serialize/Graph.java  |   78 ++
 .../query/calcite/serialize/GraphNode.java         |   25 +
 .../serialize/expression/CallExpression.java       |   40 +
 .../expression/DynamicParamExpression.java         |   37 +
 .../serialize/expression/ExpImplementor.java       |   32 +
 .../serialize/expression/ExpToRexTranslator.java   |  100 ++
 .../calcite/serialize/expression/Expression.java   |   26 +
 .../serialize/expression/InputRefExpression.java   |   37 +
 .../serialize/expression/LiteralExpression.java    |   37 +
 .../serialize/expression/LocalRefExpression.java   |   37 +
 .../serialize/expression/RexToExpTranslator.java   |  101 ++
 .../serialize/relation/ConversionContext.java      |   36 +
 .../calcite/serialize/relation/FilterNode.java     |   58 +
 .../serialize/relation/GraphToRelConverter.java    |   90 ++
 .../query/calcite/serialize/relation/JoinNode.java |   66 ++
 .../calcite/serialize/relation/ProjectNode.java    |   62 +
 .../calcite/serialize/relation/ReceiverNode.java   |   53 +
 .../query/calcite/serialize/relation/RelGraph.java |   25 +
 .../calcite/serialize/relation/RelGraphNode.java   |   38 +
 .../serialize/relation/RelToGraphConverter.java    |  109 ++
 .../calcite/serialize/relation/SenderNode.java     |   56 +
 .../serialize/relation/SerializedTraits.java       |   65 ++
 .../calcite/serialize/relation/TableScanNode.java  |   44 +
 .../query/calcite/serialize/type/DataType.java     |   32 +
 .../query/calcite/serialize/type/SimpleType.java   |   59 +
 .../query/calcite/serialize/type/StructType.java   |   51 +
 .../processors/query/calcite/splitter/Edge.java    |   46 +
 .../query/calcite/splitter/Fragment.java           |   92 ++
 .../query/calcite/splitter/QueryPlan.java          |   76 ++
 .../query/calcite/splitter/RelSource.java          |   47 +
 .../query/calcite/splitter/RelSourceImpl.java      |   41 +
 .../query/calcite/splitter/RelTarget.java          |   29 +
 .../query/calcite/splitter/RelTargetImpl.java      |   48 +
 .../query/calcite/splitter/Splitter.java           |  122 ++
 .../trait/AbstractDestinationFunctionFactory.java  |   35 +
 .../query/calcite/trait/AffinityFactory.java       |   60 +
 .../query/calcite/trait/AllTargetsFactory.java     |   45 +
 .../query/calcite/trait/DestinationFunction.java   |   27 +
 .../calcite/trait/DestinationFunctionFactory.java  |   31 +
 .../query/calcite/trait/DistributionTrait.java     |  174 +++
 .../query/calcite/trait/DistributionTraitDef.java  |   78 ++
 .../query/calcite/trait/HashFunctionFactory.java   |   72 ++
 .../query/calcite/trait/IgniteDistribution.java    |   28 +
 .../query/calcite/trait/IgniteDistributions.java   |  272 +++++
 .../query/calcite/trait/NoOpFactory.java           |   41 +
 .../query/calcite/trait/RandomTargetFactory.java   |   47 +
 .../query/calcite/trait/SingleTargetFactory.java   |   48 +
 .../query/calcite/type/IgniteTypeFactory.java      |   33 +
 .../query/calcite/type/IgniteTypeSystem.java       |   28 +
 .../processors/query/calcite/type/RowType.java     |  140 +++
 .../processors/query/calcite/util/Commons.java     |  145 +++
 .../query/calcite/util/IgniteMethod.java           |   42 +
 .../query/calcite/util/ListFieldsQueryCursor.java  |   92 ++
 .../query/calcite/util/TableScanIterator.java      |  149 +++
 .../query/calcite/CalciteQueryProcessorTest.java   | 1203 ++++++++++++++++++++
 .../query/calcite/exchange/OutboxTest.java         |  146 +++
 .../query/calcite/exec/ExecutionTest.java          |   77 ++
 .../ignite/testsuites/IgniteCalciteTestSuite.java  |   35 +
 .../ignite/internal/IgniteComponentType.java       |    8 +
 .../processors/query/GridQueryProcessor.java       |   31 +-
 .../processors/query/GridQueryTypeDescriptor.java  |    5 +
 .../internal/processors/query/QueryContext.java    |   73 ++
 .../internal/processors/query/QueryEngine.java     |   61 +
 .../processors/query/QueryTypeDescriptorImpl.java  |    6 +-
 .../query/schema/SchemaChangeListener.java         |   35 +
 .../GridInternalSubscriptionProcessor.java         |   16 +
 .../processors/query/h2/SchemaManager.java         |   67 +-
 parent/pom.xml                                     |    2 +
 pom.xml                                            |    1 +
 130 files changed, 9960 insertions(+), 7 deletions(-)

diff --git a/modules/calcite/README.txt b/modules/calcite/README.txt
new file mode 100644
index 0000000..e144a77
--- /dev/null
+++ b/modules/calcite/README.txt
@@ -0,0 +1,35 @@
+Apache Ignite Calcite Module
+--------------------------
+
+Apache Ignite Calcite module provides experimental Apache Calcite based query engine.
+
+To enable Calcite module when starting a standalone node, move 'optional/ignite-calcite' folder to
+'libs' folder before running 'ignite.{sh|bat}' script. The content of the module folder will
+be added to classpath in this case.
+
+Note: At now some logic from ignite-indexing module is reused, this means ignite-indexing module also
+has to be present at classpath.
+
+Importing Calcite Module In Maven Project
+---------------------------------------
+
+If you are using Maven to manage dependencies of your project, you can add Calcite module
+dependency like this (replace '${ignite.version}' with actual Apache Ignite version you are
+interested in):
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+                        http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    ...
+    <dependencies>
+        ...
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-calcite</artifactId>
+            <version>${ignite.version}</version>
+        </dependency>
+        ...
+    </dependencies>
+    ...
+</project>
diff --git a/modules/calcite/pom.xml b/modules/calcite/pom.xml
new file mode 100644
index 0000000..d654edb
--- /dev/null
+++ b/modules/calcite/pom.xml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  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.
+-->
+
+<!--
+    POM file.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.ignite</groupId>
+        <artifactId>ignite-parent</artifactId>
+        <version>1</version>
+        <relativePath>../../parent</relativePath>
+    </parent>
+
+    <artifactId>ignite-calcite</artifactId>
+    <version>2.8.0-SNAPSHOT</version>
+    <url>http://ignite.apache.org</url>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <!--
+            At now the new calcite engine reuses some logic
+            and doesn't work without "old" indexing module.
+        -->
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-indexing</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.calcite</groupId>
+            <artifactId>calcite-core</artifactId>
+            <version>${calcite.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-core</artifactId>
+            <version>${project.version}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-core</artifactId>
+            <version>${spring.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+            <version>${slf4j.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-beans</artifactId>
+            <version>${spring.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
+            <version>${spring.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <!-- Generate the OSGi MANIFEST.MF for this bundle. -->
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+            </plugin>
+          </plugins>
+    </build>
+</project>
diff --git a/modules/calcite/src/main/java/org/apache/calcite/interpreter/InterpreterUtils.java b/modules/calcite/src/main/java/org/apache/calcite/interpreter/InterpreterUtils.java
new file mode 100644
index 0000000..edae3cc
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/calcite/interpreter/InterpreterUtils.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.calcite.interpreter;
+
+import org.apache.calcite.DataContext;
+
+/**
+ *
+ */
+public class InterpreterUtils {
+    public static Context createContext(DataContext ctx) {
+        return new Context(ctx);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/calcite/plan/volcano/VolcanoUtils.java b/modules/calcite/src/main/java/org/apache/calcite/plan/volcano/VolcanoUtils.java
new file mode 100644
index 0000000..a1874dc
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/calcite/plan/volcano/VolcanoUtils.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.calcite.plan.volcano;
+
+import org.apache.calcite.plan.RelTraitSet;
+
+/**
+ *
+ */
+public class VolcanoUtils {
+    public static RelSubset subset(RelSubset subset, RelTraitSet traits) {
+        return subset.set.getOrCreateSubset(subset.getCluster(), traits.simplify());
+    }
+
+    public static VolcanoPlanner impatient(VolcanoPlanner planner) {
+        planner.impatient = true;
+
+        return planner;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessor.java
new file mode 100644
index 0000000..86a9a2e
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessor.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.function.BiFunction;
+import org.apache.calcite.config.Lex;
+import org.apache.calcite.plan.Context;
+import org.apache.calcite.plan.Contexts;
+import org.apache.calcite.plan.RelTraitDef;
+import org.apache.calcite.sql.fun.SqlLibrary;
+import org.apache.calcite.sql.fun.SqlLibraryOperatorTableFactory;
+import org.apache.calcite.sql.parser.SqlParser;
+import org.apache.calcite.tools.FrameworkConfig;
+import org.apache.calcite.tools.Frameworks;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.cache.query.FieldsQueryCursor;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.internal.processors.query.IgniteSQLException;
+import org.apache.ignite.internal.processors.query.QueryContext;
+import org.apache.ignite.internal.processors.query.QueryEngine;
+import org.apache.ignite.internal.processors.query.calcite.cluster.MappingServiceImpl;
+import org.apache.ignite.internal.processors.query.calcite.prepare.DistributedExecution;
+import org.apache.ignite.internal.processors.query.calcite.prepare.IgnitePlanner;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PlannerContext;
+import org.apache.ignite.internal.processors.query.calcite.prepare.Query;
+import org.apache.ignite.internal.processors.query.calcite.prepare.QueryExecution;
+import org.apache.ignite.internal.processors.query.calcite.schema.CalciteSchemaHolder;
+import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeSystem;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.apache.ignite.internal.processors.subscription.GridInternalSubscriptionProcessor;
+import org.apache.ignite.resources.LoggerResource;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ *
+ */
+public class CalciteQueryProcessor implements QueryEngine {
+    /** */
+    private final CalciteSchemaHolder schemaHolder = new CalciteSchemaHolder();
+
+    /** */
+    private final FrameworkConfig config;
+
+    /** */
+    private IgniteLogger log;
+
+    /** */
+    private GridKernalContext kernalContext;
+
+    public CalciteQueryProcessor() {
+        config = Frameworks.newConfigBuilder()
+            .parserConfig(SqlParser.configBuilder()
+                // Lexical configuration defines how identifiers are quoted, whether they are converted to upper or lower
+                // case when they are read, and whether identifiers are matched case-sensitively.
+                .setLex(Lex.MYSQL)
+                .build())
+            // Dialects support.
+            .operatorTable(SqlLibraryOperatorTableFactory.INSTANCE
+                .getOperatorTable(
+                    SqlLibrary.STANDARD,
+                    SqlLibrary.MYSQL))
+            // Context provides a way to store data within the planner session that can be accessed in planner rules.
+            .context(Contexts.of(this))
+            // Custom cost factory to use during optimization
+            .costFactory(null)
+            .typeSystem(IgniteTypeSystem.DEFAULT)
+            .build();
+    }
+
+    /**
+     * @param log Logger.
+     */
+    @LoggerResource
+    public void setLogger(IgniteLogger log) {
+        this.log = log;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void start(@NotNull GridKernalContext ctx) {
+        kernalContext = ctx;
+
+        GridInternalSubscriptionProcessor prc = ctx.internalSubscriptionProcessor();
+
+        if (prc != null) // Stubbed context doesn't have such processor
+            prc.registerSchemaChangeListener(schemaHolder);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void stop() {
+    }
+
+    @Override public List<FieldsQueryCursor<List<?>>> query(@Nullable QueryContext ctx, String query, Object... params) throws IgniteSQLException {
+        PlannerContext context = context(Commons.convert(ctx), query, params, this::buildContext);
+        QueryExecution execution = prepare(context);
+        FieldsQueryCursor<List<?>> cur = execution.execute();
+        return Collections.singletonList(cur);
+    }
+
+    public FrameworkConfig config() {
+        return config;
+    }
+
+    public IgniteLogger log() {
+        return log;
+    }
+
+    /** */
+    public IgnitePlanner planner(RelTraitDef[] traitDefs, PlannerContext ctx0) {
+        FrameworkConfig cfg = Frameworks.newConfigBuilder(config())
+                .defaultSchema(ctx0.schema())
+                .traitDefs(traitDefs)
+                .context(ctx0)
+                .build();
+
+        return new IgnitePlanner(cfg);
+    }
+
+    /**
+     * @param ctx External context.
+     * @param query Query string.
+     * @param params Query parameters.
+     * @return Query execution context.
+     */
+    PlannerContext context(@NotNull Context ctx, String query, Object[] params, BiFunction<Context, Query, PlannerContext> clo) { // Package private visibility for tests.
+        return clo.apply(Contexts.chain(ctx, config.getContext()), new Query(query, params));
+    }
+
+    private PlannerContext buildContext(@NotNull Context parent, @NotNull Query query) {
+        return PlannerContext.builder()
+            .logger(log)
+            .kernalContext(kernalContext)
+            .queryProcessor(this)
+            .parentContext(parent)
+            .query(query)
+            .schema(schemaHolder.schema())
+            .topologyVersion(readyAffinityVersion())
+            .mappingService(new MappingServiceImpl(kernalContext))
+            .build();
+    }
+
+    private QueryExecution prepare(PlannerContext ctx) {
+        return new DistributedExecution(ctx);
+    }
+
+    private AffinityTopologyVersion readyAffinityVersion() {
+        return kernalContext.cache().context().exchange().readyAffinityVersion();
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/cluster/MappingServiceImpl.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/cluster/MappingServiceImpl.java
new file mode 100644
index 0000000..3e91219
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/cluster/MappingServiceImpl.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.cluster;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+import org.apache.ignite.cache.CacheWriteSynchronizationMode;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionState;
+import org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionTopology;
+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.util.Commons;
+import org.apache.ignite.internal.util.typedef.F;
+
+import static org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping.DEDUPLICATED;
+
+/**
+ *
+ */
+public class MappingServiceImpl implements MappingService {
+    private final GridKernalContext ctx;
+
+    public MappingServiceImpl(GridKernalContext ctx) {
+        this.ctx = ctx;
+    }
+
+    @Override public NodesMapping local() {
+        return new NodesMapping(Collections.singletonList(ctx.discovery().localNode().id()), null, DEDUPLICATED);
+    }
+
+    @Override public NodesMapping random(AffinityTopologyVersion topVer) {
+        List<ClusterNode> nodes = ctx.discovery().discoCache(topVer).serverNodes();
+
+        return new NodesMapping(Commons.transform(nodes, ClusterNode::id), null, DEDUPLICATED);
+    }
+
+    @Override public NodesMapping distributed(int cacheId, AffinityTopologyVersion topVer) {
+        GridCacheContext<?,?> cctx = ctx.cache().context().cacheContext(cacheId);
+
+        return cctx.isReplicated() ? replicatedLocation(cctx, topVer) : partitionedLocation(cctx, topVer);
+    }
+
+    private NodesMapping partitionedLocation(GridCacheContext<?,?> cctx, AffinityTopologyVersion topVer) {
+        byte flags = NodesMapping.HAS_PARTITIONED_CACHES;
+
+        List<List<ClusterNode>> assignments = cctx.affinity().assignments(topVer);
+        List<List<UUID>> res;
+
+        if (cctx.config().getWriteSynchronizationMode() == CacheWriteSynchronizationMode.PRIMARY_SYNC) {
+            res = new ArrayList<>(assignments.size());
+
+            for (List<ClusterNode> partNodes : assignments)
+                res.add(F.isEmpty(partNodes) ? Collections.emptyList() : Collections.singletonList(F.first(partNodes).id()));
+        }
+        else if (!cctx.topology().rebalanceFinished(topVer)) {
+            res = new ArrayList<>(assignments.size());
+
+            flags |= NodesMapping.HAS_MOVING_PARTITIONS;
+
+            for (int part = 0; part < assignments.size(); part++) {
+                List<ClusterNode> partNodes = assignments.get(part);
+                List<UUID> partIds = new ArrayList<>(partNodes.size());
+
+                for (ClusterNode node : partNodes) {
+                    if (cctx.topology().partitionState(node.id(), part) == GridDhtPartitionState.OWNING)
+                        partIds.add(node.id());
+                }
+
+                res.add(partIds);
+            }
+        }
+        else
+            res = Commons.transform(assignments, nodes -> Commons.transform(nodes, ClusterNode::id));
+
+        return new NodesMapping(null, res, flags);
+    }
+
+    private NodesMapping replicatedLocation(GridCacheContext<?,?> cctx, AffinityTopologyVersion topVer) {
+        byte flags = NodesMapping.HAS_REPLICATED_CACHES;
+
+        if (cctx.config().getNodeFilter() != null)
+            flags |= NodesMapping.PARTIALLY_REPLICATED;
+
+        GridDhtPartitionTopology topology = cctx.topology();
+
+        List<ClusterNode> nodes = cctx.discovery().discoCache(topVer).cacheGroupAffinityNodes(cctx.cacheId());
+        List<UUID> res;
+
+        if (!topology.rebalanceFinished(topVer)) {
+            flags |= NodesMapping.PARTIALLY_REPLICATED;
+
+            res = new ArrayList<>(nodes.size());
+
+            int parts = topology.partitions();
+
+            for (ClusterNode node : nodes) {
+                if (isOwner(node.id(), topology, parts))
+                    res.add(node.id());
+            }
+        }
+        else
+            res = Commons.transform(nodes, ClusterNode::id);
+
+        return new NodesMapping(res, null, flags);
+    }
+
+    private boolean isOwner(UUID nodeId, GridDhtPartitionTopology topology, int parts) {
+        for (int p = 0; p < parts; p++) {
+            if (topology.partitionState(nodeId, p) != GridDhtPartitionState.OWNING)
+                return false;
+        }
+        return true;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exchange/EndMarker.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exchange/EndMarker.java
new file mode 100644
index 0000000..ecbfef2
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exchange/EndMarker.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.exchange;
+
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+
+/**
+ *
+ */
+public final class EndMarker implements Serializable {
+    public static final EndMarker INSTANCE = new EndMarker();
+
+    private EndMarker(){}
+
+    private Object readResolve() throws ObjectStreamException {
+        return INSTANCE;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exchange/ExchangeProcessor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exchange/ExchangeProcessor.java
new file mode 100644
index 0000000..803417c
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exchange/ExchangeProcessor.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.exchange;
+
+import java.util.List;
+import java.util.UUID;
+import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+
+/**
+ *
+ */
+public interface ExchangeProcessor {
+    int BATCH_SIZE = 200;
+    int PER_NODE_BATCH_COUNT = 10;
+
+    <T> Outbox<T> register(Outbox<T> outbox);
+    <T> void unregister(Outbox<T> outbox);
+    <T> Inbox<T> register(Inbox<T> inbox);
+    <T> void unregister(Inbox<T> inbox);
+    void send(GridCacheVersion queryId, long exchangeId, UUID nodeId, int batchId, List<?> rows);
+    void acknowledge(GridCacheVersion queryId, long exchangeId, UUID nodeId, int batchId);
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exchange/Inbox.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exchange/Inbox.java
new file mode 100644
index 0000000..bd515a5
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exchange/Inbox.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.exchange;
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.UUID;
+import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.processors.query.calcite.exec.SingleNode;
+import org.apache.ignite.internal.processors.query.calcite.exec.Sink;
+import org.apache.ignite.internal.processors.query.calcite.exec.Source;
+
+/**
+ * TODO
+ */
+public class Inbox<T> implements SingleNode<T> {
+    private final GridCacheVersion queryId;
+    private final long exchangeId;
+
+    private Sink<T> target;
+    private Collection<UUID> sources;
+    private Comparator<T> comparator;
+    private ExchangeProcessor srvc;
+
+    public Inbox(GridCacheVersion queryId, long exchangeId) {
+        this.queryId = queryId;
+        this.exchangeId = exchangeId;
+    }
+
+    public void bind(Sink<T> target, Collection<UUID> sources, Comparator<T> comparator) {
+        this.target = target;
+        this.sources = sources;
+        this.comparator = comparator;
+    }
+
+    void init(ExchangeProcessor srvc) {
+        this.srvc = srvc;
+    }
+
+    @Override public void signal() {
+        // No-op.
+    }
+
+    @Override public void sources(List<Source> sources) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override public Sink<T> sink(int idx) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void push(UUID source, int batchId, List<?> rows) {
+
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exchange/Outbox.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exchange/Outbox.java
new file mode 100644
index 0000000..f118330
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exchange/Outbox.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.exchange;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.processors.query.calcite.exec.AbstractNode;
+import org.apache.ignite.internal.processors.query.calcite.exec.SingleNode;
+import org.apache.ignite.internal.processors.query.calcite.exec.Sink;
+import org.apache.ignite.internal.processors.query.calcite.trait.DestinationFunction;
+import org.apache.ignite.internal.util.typedef.F;
+
+/**
+ *
+ */
+public class Outbox<T> extends AbstractNode<T> implements SingleNode<T>, Sink<T> {
+    private final Map<UUID, Destination> perNode = new HashMap<>();
+
+    private final GridCacheVersion queryId;
+    private final long exchangeId;
+    private final Collection<UUID> targets;
+    private final DestinationFunction function;
+
+    private ExchangeProcessor srvc;
+
+    public Outbox(GridCacheVersion queryId, long exchangeId, Collection<UUID> targets, DestinationFunction function) {
+        super(Sink.noOp());
+        this.queryId = queryId;
+        this.exchangeId = exchangeId;
+
+        this.targets = targets;
+        this.function = function;
+    }
+
+    public void init(ExchangeProcessor srvc) {
+        this.srvc = srvc;
+
+        srvc.register(this);
+
+        signal();
+    }
+
+    public void acknowledge(UUID nodeId, int batchId) {
+        perNode.get(nodeId).acknowledge(batchId);
+    }
+
+    @Override public Sink<T> sink(int idx) {
+        if (idx != 0)
+            throw new IndexOutOfBoundsException();
+
+        return this;
+    }
+
+    @Override public boolean push(T row) {
+        List<UUID> nodes = function.destination(row);
+
+        if (F.isEmpty(nodes))
+            return true;
+
+        List<Destination> destinations = new ArrayList<>(nodes.size());
+
+        for (UUID node : nodes) {
+            Destination dest = perNode.computeIfAbsent(node, Destination::new);
+
+            if (!dest.ready()) {
+                dest.needSignal();
+
+                return false;
+            }
+
+            destinations.add(dest);
+        }
+
+        for (Destination dest : destinations)
+            dest.add(row);
+
+        return true;
+    }
+
+    @Override public void end() {
+        for (UUID node : targets)
+            perNode.computeIfAbsent(node, Destination::new).end();
+
+        srvc.unregister(this);
+    }
+
+    private final class Destination {
+        private final UUID nodeId;
+
+        private int hwm = -1;
+        private int lwm = -1;
+
+        private ArrayList<Object> curr = new ArrayList<>(ExchangeProcessor.BATCH_SIZE + 1); // extra space for end marker;
+
+        private boolean needSignal;
+
+        private Destination(UUID nodeId) {
+            this.nodeId = nodeId;
+        }
+
+        public void add(T row) {
+            if (curr.size() == ExchangeProcessor.BATCH_SIZE) {
+                assert ready() && srvc != null;
+
+                srvc.send(queryId, exchangeId, nodeId, ++hwm, curr);
+
+                curr = new ArrayList<>(ExchangeProcessor.BATCH_SIZE);
+            }
+
+            curr.add(row);
+        }
+
+        public void end() {
+            curr.add(EndMarker.INSTANCE);
+
+            assert srvc != null;
+
+            srvc.send(queryId, exchangeId, nodeId, hwm, curr);
+
+            curr = null;
+            hwm = Integer.MAX_VALUE;
+        }
+
+        boolean ready() {
+            return hwm - lwm < ExchangeProcessor.PER_NODE_BATCH_COUNT || curr.size() < ExchangeProcessor.BATCH_SIZE;
+        }
+
+        void acknowledge(int id) {
+            if (lwm < id) {
+                lwm = id;
+
+                if (needSignal) {
+                    needSignal = false;
+
+                    signal();
+                }
+            }
+        }
+
+        public void needSignal() {
+            needSignal = true;
+        }
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/AbstractNode.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/AbstractNode.java
new file mode 100644
index 0000000..66ba475
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/AbstractNode.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.Collections;
+import java.util.List;
+
+/**
+ *
+ */
+public abstract class AbstractNode<T> implements Node<T> {
+    protected final Sink<T> target;
+    protected List<Source> sources;
+
+    protected AbstractNode(Sink<T> target) {
+        this.target = target;
+    }
+
+    @Override public void sources(List<Source> sources) {
+        this.sources = Collections.unmodifiableList(sources);
+    }
+
+    public void signal(int idx) {
+        sources.get(idx).signal();
+    }
+
+    @Override public void signal() {
+        sources.forEach(Source::signal);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ConsumerNode.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ConsumerNode.java
new file mode 100644
index 0000000..100f21f
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ConsumerNode.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.ArrayDeque;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
+/**
+ *
+ */
+public class ConsumerNode extends AbstractNode<Object[]> implements SingleNode<Object[]>, Sink<Object[]>, Iterator<Object[]> {
+    private static final int DEFAULT_BUFFER_SIZE = 1000;
+    private static final Object[] END = new Object[0];
+
+    private ArrayDeque<Object[]> buff;
+
+    public ConsumerNode() {
+        super(Sink.noOp());
+
+        buff = new ArrayDeque<>(DEFAULT_BUFFER_SIZE);
+    }
+
+    @Override public Sink<Object[]> sink(int idx) {
+        if (idx != 0)
+            throw new IndexOutOfBoundsException();
+
+        return this;
+    }
+
+    @Override public boolean push(Object[] row) {
+        if (buff.size() == DEFAULT_BUFFER_SIZE)
+            return false;
+
+        buff.add(row);
+
+        return true;
+    }
+
+    @Override public void end() {
+        buff.add(END);
+    }
+
+    @Override public boolean hasNext() {
+        if (buff.isEmpty())
+            signal();
+
+        return buff.peek() != END;
+    }
+
+    @Override public Object[] next() {
+        if (buff.isEmpty())
+            signal();
+
+        if(!hasNext())
+            throw new NoSuchElementException();
+
+        return Objects.requireNonNull(buff.poll());
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/FilterNode.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/FilterNode.java
new file mode 100644
index 0000000..3075ee4
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/FilterNode.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.function.Predicate;
+
+/**
+ *
+ */
+public class FilterNode extends AbstractNode<Object[]> implements SingleNode<Object[]>, Sink<Object[]> {
+    private final Predicate<Object[]> predicate;
+
+    public FilterNode(Sink<Object[]> target, Predicate<Object[]> predicate) {
+        super(target);
+
+        this.predicate = predicate;
+    }
+
+    @Override public Sink<Object[]> sink(int idx) {
+        if (idx != 0)
+            throw new IndexOutOfBoundsException();
+
+        return this;
+    }
+
+    @Override public boolean push(Object[] row) {
+        return !predicate.test(row) || target.push(row);
+    }
+
+    @Override public void end() {
+        target.end();
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/Implementor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/Implementor.java
new file mode 100644
index 0000000..cc8ed64
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/Implementor.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+import java.util.UUID;
+import org.apache.calcite.DataContext;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.schema.ScannableTable;
+import org.apache.calcite.util.ImmutableIntList;
+import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.processors.query.calcite.exchange.Outbox;
+import org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PlannerContext;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteJoin;
+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.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.IgniteTableScan;
+import org.apache.ignite.internal.processors.query.calcite.rel.RelOp;
+import org.apache.ignite.internal.processors.query.calcite.trait.DestinationFunction;
+import org.apache.ignite.internal.processors.query.calcite.trait.DestinationFunctionFactory;
+import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
+
+import static org.apache.ignite.internal.processors.query.calcite.prepare.ContextValue.PLANNER_CONTEXT;
+import static org.apache.ignite.internal.processors.query.calcite.prepare.ContextValue.QUERY_ID;
+
+/**
+ *
+ */
+public class Implementor implements IgniteRelVisitor<Node<Object[]>>, RelOp<IgniteRel, Node<Object[]>> {
+    private final PlannerContext ctx;
+    private final DataContext root;
+    private final ScalarFactory factory;
+    private Deque<Sink<Object[]>> stack;
+
+    public Implementor(DataContext root) {
+        this.root = root;
+
+        ctx = PLANNER_CONTEXT.get(root);
+        factory = new ScalarFactory(new RexBuilder(ctx.typeFactory()));
+        stack = new ArrayDeque<>();
+    }
+
+    @Override public Node<Object[]> visit(IgniteSender rel) {
+        assert stack.isEmpty();
+
+        GridCacheVersion id = QUERY_ID.get(root);
+        long exchangeId = rel.target().exchangeId();
+        NodesMapping mapping = rel.target().mapping();
+        List<UUID> targets = mapping.nodes();
+        IgniteDistribution distribution = rel.target().distribution();
+        DestinationFunctionFactory destFactory = distribution.destinationFunctionFactory();
+        DestinationFunction function = destFactory.create(ctx, mapping, ImmutableIntList.copyOf(distribution.getKeys()));
+
+        Outbox<Object[]> res = new Outbox<>(id, exchangeId, targets, function);
+
+        stack.push(res.sink());
+
+        res.source(source(rel.getInput()));
+
+        return res;
+    }
+
+    @Override public Node<Object[]> visit(IgniteFilter rel) {
+        assert !stack.isEmpty();
+
+        FilterNode res = new FilterNode(stack.pop(), factory.filterPredicate(root, rel.getCondition(), rel.getRowType()));
+
+        stack.push(res.sink());
+
+        res.source(source(rel.getInput()));
+
+        return res;
+    }
+
+    @Override public Node<Object[]> visit(IgniteProject rel) {
+        assert !stack.isEmpty();
+
+        ProjectNode res = new ProjectNode(stack.pop(), factory.projectExpression(root, rel.getProjects(), rel.getInput().getRowType()));
+
+        stack.push(res.sink());
+
+        res.source(source(rel.getInput()));
+
+        return res;
+    }
+
+    @Override public Node<Object[]> visit(IgniteJoin rel) {
+        assert !stack.isEmpty();
+
+        JoinNode res = new JoinNode(stack.pop(), factory.joinExpression(root, rel.getCondition(), rel.getLeft().getRowType(), rel.getRight().getRowType()));
+
+        stack.push(res.sink(1));
+        stack.push(res.sink(0));
+
+        res.sources(sources(rel.getInputs()));
+
+        return res;
+    }
+
+    @Override public Node<Object[]> visit(IgniteTableScan rel) {
+        assert !stack.isEmpty();
+
+        Iterable<Object[]> source = rel.getTable().unwrap(ScannableTable.class).scan(root);
+
+        return new ScanNode(stack.pop(), source);
+    }
+
+    @Override public Node<Object[]> visit(IgniteReceiver rel) {
+        throw new AssertionError(); // TODO
+    }
+
+    @Override public Node<Object[]> visit(IgniteExchange rel) {
+        throw new AssertionError();
+    }
+
+    @Override public Node<Object[]> visit(IgniteRel other) {
+        throw new AssertionError();
+    }
+
+    private Source source(RelNode rel) {
+        if (rel.getConvention() != IgniteConvention.INSTANCE)
+            throw new IllegalStateException("INTERPRETABLE is required.");
+
+        return ((IgniteRel) rel).accept(this);
+    }
+
+    private List<Source> sources(List<RelNode> rels) {
+        ArrayList<Source> res = new ArrayList<>(rels.size());
+
+        for (RelNode rel : rels) {
+            res.add(source(rel));
+        }
+
+        return res;
+    }
+
+    @Override public Node<Object[]> go(IgniteRel rel) {
+        if (rel instanceof IgniteSender)
+            return visit((IgniteSender) rel);
+
+        ConsumerNode res = new ConsumerNode();
+
+        stack.push(res.sink());
+
+        res.source(source(rel));
+
+        return res;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/JoinNode.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/JoinNode.java
new file mode 100644
index 0000000..5188148
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/JoinNode.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.ArrayList;
+import java.util.function.BiFunction;
+
+/**
+ *
+ */
+public class JoinNode extends AbstractNode<Object[]> {
+    private final BiFunction<Object[], Object[], Object[]> expression;
+    private final ArraySink<Object[]> left;
+    private final ArraySink<Object[]> right;
+
+    private int leftIdx;
+    private int rightIdx;
+    private boolean end;
+
+    public JoinNode(Sink<Object[]> target, BiFunction<Object[], Object[], Object[]> expression) {
+        super(target);
+        this.expression = expression;
+
+        left = new ArraySink<>();
+        right = new ArraySink<>();
+    }
+
+    @Override public Sink<Object[]> sink(int idx) {
+        switch (idx) {
+            case 0:
+                return left;
+            case 1:
+                return right;
+            default:
+                throw new IndexOutOfBoundsException();
+        }
+    }
+
+    @Override public void signal() {
+        if (end)
+            return;
+
+        if (left.end && right.end)
+            tryFlush();
+
+        assert sources != null && sources.size() == 2;
+
+        if (!left.end)
+            signal(0);
+        if (!right.end)
+            signal(1);
+    }
+
+    public void tryFlush() {
+        if (left.end && right.end) {
+            for (int i = leftIdx; i < left.size(); i++) {
+                for (int j = rightIdx; j < right.size(); j++) {
+                    Object[] row = expression.apply(left.get(i), right.get(j));
+
+                    if (row != null && !target.push(row)) {
+                        leftIdx = i;
+                        rightIdx = j;
+
+                        return;
+                    }
+                }
+            }
+
+            end = true;
+            target.end();
+        }
+    }
+
+    private final class ArraySink<T> extends ArrayList<T> implements Sink<T> {
+        private boolean end;
+
+        @Override public boolean push(T row) {
+            return add(row);
+        }
+
+        @Override public void end() {
+            end = true;
+
+            tryFlush();
+        }
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/Node.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/Node.java
new file mode 100644
index 0000000..2472e8e
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/Node.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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;
+
+/**
+ *
+ */
+public interface Node<T> extends Source {
+    Sink<T> sink(int idx);
+    void sources(List<Source> sources);
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ProjectNode.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ProjectNode.java
new file mode 100644
index 0000000..d7b7e57
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ProjectNode.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.function.Function;
+
+/**
+ *
+ */
+public class ProjectNode extends AbstractNode<Object[]> implements SingleNode<Object[]>, Sink<Object[]> {
+    private final Function<Object[], Object[]> projection;
+
+    public ProjectNode(Sink<Object[]> target, Function<Object[], Object[]> projection) {
+        super(target);
+
+        this.projection = projection;
+    }
+
+    @Override public Sink<Object[]> sink(int idx) {
+        if (idx != 0)
+            throw new IndexOutOfBoundsException();
+
+        return this;
+    }
+
+    @Override public boolean push(Object[] row) {
+        return target.push(projection.apply(row));
+    }
+
+    @Override public void end() {
+        target.end();
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ScalarFactory.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ScalarFactory.java
new file mode 100644
index 0000000..e3ff512
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ScalarFactory.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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 com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import org.apache.calcite.DataContext;
+import org.apache.calcite.interpreter.Context;
+import org.apache.calcite.interpreter.InterpreterUtils;
+import org.apache.calcite.interpreter.JaninoRexCompiler;
+import org.apache.calcite.interpreter.Scalar;
+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.RexNode;
+
+/**
+ *
+ */
+public class ScalarFactory {
+    private final JaninoRexCompiler rexCompiler;
+    private final RexBuilder builder;
+
+    public ScalarFactory(RexBuilder builder) {
+        rexCompiler = new JaninoRexCompiler(builder);
+        this.builder = builder;
+    }
+
+    public <T> Predicate<T> filterPredicate(DataContext root, RexNode filter, RelDataType rowType) {
+        Scalar scalar = rexCompiler.compile(ImmutableList.of(filter), rowType);
+        Context ctx = InterpreterUtils.createContext(root);
+
+        return new FilterPredicate<>(ctx, scalar);
+    }
+
+    public <T> Function<T, T> projectExpression(DataContext root, List<RexNode> projects, RelDataType rowType) {
+        Scalar scalar = rexCompiler.compile(projects, rowType);
+        Context ctx = InterpreterUtils.createContext(root);
+        int count = projects.size();
+
+        return new ProjectExpression<>(ctx, scalar, count);
+    }
+
+    public <T> BiFunction<T, T, T> joinExpression(DataContext root, RexNode expression, RelDataType leftType, RelDataType rightType) {
+        RelDataType rowType = combinedType(leftType, rightType);
+
+        Scalar scalar = rexCompiler.compile(ImmutableList.of(expression), rowType);
+        Context ctx = InterpreterUtils.createContext(root);
+        ctx.values = new Object[rowType.getFieldCount()];
+
+        return new JoinExpression<>(ctx, scalar);
+    }
+
+    private RelDataType combinedType(RelDataType... types) {
+        RelDataTypeFactory.Builder typeBuilder = new RelDataTypeFactory.Builder(builder.getTypeFactory());
+
+        for (RelDataType type : types)
+            typeBuilder.addAll(type.getFieldList());
+
+        return typeBuilder.build();
+    }
+
+    private static class FilterPredicate<T> implements Predicate<T> {
+        private final Context ctx;
+        private final Scalar scalar;
+        private final Object[] vals;
+
+        private FilterPredicate(Context ctx, Scalar scalar) {
+            this.ctx = ctx;
+            this.scalar = scalar;
+
+            vals = new Object[1];
+        }
+
+        @Override public boolean test(T r) {
+            ctx.values = (Object[]) r;
+            scalar.execute(ctx, vals);
+            return (Boolean) vals[0];
+        }
+    }
+
+    private static class JoinExpression<T> implements BiFunction<T, T, T> {
+        private final Object[] vals;
+        private final Context ctx;
+        private final Scalar scalar;
+
+        private Object[] left0;
+
+        private JoinExpression(Context ctx, Scalar scalar) {
+            this.ctx = ctx;
+            this.scalar = scalar;
+
+            vals = new Object[1];
+        }
+
+        @Override public T apply(T left, T right) {
+            if (left0 != left) {
+                left0 = (Object[]) left;
+                System.arraycopy(left0, 0, ctx.values, 0, left0.length);
+            }
+
+            Object[] right0 = (Object[]) right;
+            System.arraycopy(right0, 0, ctx.values, left0.length, right0.length);
+
+            scalar.execute(ctx, vals);
+
+            if ((Boolean) vals[0])
+                return (T) Arrays.copyOf(ctx.values, ctx.values.length);
+
+            return null;
+        }
+    }
+
+    private static class ProjectExpression<T> implements Function<T, T> {
+        private final Context ctx;
+        private final Scalar scalar;
+        private final int count;
+
+        private ProjectExpression(Context ctx, Scalar scalar, int count) {
+            this.ctx = ctx;
+            this.scalar = scalar;
+            this.count = count;
+        }
+
+        @Override public T apply(T r) {
+            ctx.values = (Object[]) r;
+            Object[] res = new Object[count];
+            scalar.execute(ctx, res);
+
+            return (T) res;
+        }
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ScanNode.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ScanNode.java
new file mode 100644
index 0000000..6acaa2b
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ScanNode.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.Iterator;
+import java.util.List;
+
+/**
+ *
+ */
+public class ScanNode implements SingleNode<Object[]> {
+    private static final Object[] END = new Object[0];
+
+    /** */
+    private final Sink<Object[]> target;
+    private final Iterable<Object[]> source;
+
+    private Iterator<Object[]> it;
+    private Object[] row;
+
+    public ScanNode(Sink<Object[]> target, Iterable<Object[]> source) {
+        this.target = target;
+        this.source = source;
+    }
+
+    @Override public void signal() {
+        if (row == END)
+            return;
+
+        if (row != null && !target.push(row))
+            return;
+
+        row = null;
+
+        if (it == null)
+            it = source.iterator();
+
+        while (it.hasNext()) {
+            row = it.next();
+
+            if (!target.push(row))
+                return;
+        }
+
+        row = END;
+        target.end();
+    }
+
+    @Override public void sources(List<Source> sources) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override public Sink<Object[]> sink(int idx) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/SingleNode.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/SingleNode.java
new file mode 100644
index 0000000..a0476a0
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/SingleNode.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.Collections;
+import java.util.Objects;
+
+/**
+ *
+ */
+public interface SingleNode<T> extends Node<T> {
+    default Sink<T> sink() {
+        return Objects.requireNonNull(sink(0));
+    }
+
+    default void source(Source source) {
+        sources(Collections.singletonList(source));
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/Sink.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/Sink.java
new file mode 100644
index 0000000..842a973
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/Sink.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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;
+
+/**
+ *
+ */
+public interface Sink<T> {
+    Sink NO_OP = new Sink() {
+        @Override public boolean push(Object row) {
+            return true;
+        }
+
+        @Override public void end() {}
+    };
+
+    boolean push(T row);
+    void end();
+
+    @SuppressWarnings("unchecked")
+    static <T> Sink<T> noOp() {
+        return NO_OP;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/Source.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/Source.java
new file mode 100644
index 0000000..95262c7
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/Source.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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;
+
+/**
+ *
+ */
+public interface Source {
+    void signal();
+}
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
new file mode 100644
index 0000000..de15e4d
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/FragmentInfo.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.calcite.util.Pair;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteReceiver;
+import org.apache.ignite.internal.processors.query.calcite.splitter.RelSource;
+
+/**
+ *
+ */
+public class FragmentInfo {
+    private final NodesMapping mapping;
+    private final ImmutableList<Pair<IgniteReceiver, RelSource>> sources;
+
+    public FragmentInfo(Pair<IgniteReceiver, RelSource> source) {
+        this(ImmutableList.of(source), null);
+    }
+
+    public FragmentInfo(NodesMapping mapping) {
+        this(null, mapping);
+    }
+
+    public FragmentInfo(ImmutableList<Pair<IgniteReceiver, RelSource>> sources, NodesMapping mapping) {
+        this.sources = sources;
+        this.mapping = mapping;
+    }
+
+    public NodesMapping mapping() {
+        return mapping;
+    }
+
+    public ImmutableList<Pair<IgniteReceiver, RelSource>> sources() {
+        return sources;
+    }
+
+    public FragmentInfo merge(FragmentInfo other) throws LocationMappingException {
+        return new FragmentInfo(
+            merge(sources(), other.sources()),
+            merge(mapping(), other.mapping()));
+    }
+
+    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 (left == null)
+            return right;
+        if (right == null)
+            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/IgniteMdDerivedDistribution.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdDerivedDistribution.java
new file mode 100644
index 0000000..6858b13
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdDerivedDistribution.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import org.apache.calcite.plan.Convention;
+import org.apache.calcite.plan.hep.HepRelVertex;
+import org.apache.calcite.plan.volcano.AbstractConverter;
+import org.apache.calcite.plan.volcano.RelSubset;
+import org.apache.calcite.plan.volcano.VolcanoUtils;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.SingleRel;
+import org.apache.calcite.rel.core.Project;
+import org.apache.calcite.rel.logical.LogicalFilter;
+import org.apache.calcite.rel.logical.LogicalJoin;
+import org.apache.calcite.rel.logical.LogicalProject;
+import org.apache.calcite.rel.logical.LogicalTableScan;
+import org.apache.calcite.rel.logical.LogicalValues;
+import org.apache.calcite.rel.metadata.MetadataDef;
+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.calcite.util.mapping.Mappings;
+import org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMetadata.DerivedDistribution;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
+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.processors.query.calcite.util.IgniteMethod;
+import org.apache.ignite.internal.util.typedef.F;
+
+/**
+ *
+ */
+public class IgniteMdDerivedDistribution implements MetadataHandler<DerivedDistribution> {
+    /** */
+    private static final ThreadLocal<Convention> REQUESTED_CONVENTION = ThreadLocal.withInitial(() -> Convention.NONE);
+
+    public static final RelMetadataProvider SOURCE =
+        ReflectiveRelMetadataProvider.reflectiveSource(
+            IgniteMethod.DERIVED_DISTRIBUTIONS.method(), new IgniteMdDerivedDistribution());
+
+    @Override public MetadataDef<DerivedDistribution> getDef() {
+        return DerivedDistribution.DEF;
+    }
+
+    public List<IgniteDistribution> deriveDistributions(AbstractConverter rel, RelMetadataQuery mq) {
+        return Collections.emptyList();
+    }
+
+    public List<IgniteDistribution> deriveDistributions(RelNode rel, RelMetadataQuery mq) {
+        return F.asList(IgniteMdDistribution._distribution(rel, mq));
+    }
+
+    public List<IgniteDistribution> deriveDistributions(IgniteRel rel, RelMetadataQuery mq) {
+        return F.asList(IgniteMdDistribution._distribution(rel, mq));
+    }
+
+    public List<IgniteDistribution> deriveDistributions(LogicalTableScan rel, RelMetadataQuery mq) {
+        return F.asList(IgniteMdDistribution._distribution(rel, mq));
+    }
+
+    public List<IgniteDistribution> deriveDistributions(LogicalValues rel, RelMetadataQuery mq) {
+        return F.asList(IgniteMdDistribution._distribution(rel, mq));
+    }
+
+    public List<IgniteDistribution> deriveDistributions(LogicalProject rel, RelMetadataQuery mq) {
+        Mappings.TargetMapping mapping =
+            Project.getPartialMapping(rel.getInput().getRowType().getFieldCount(), rel.getProjects());
+
+        return Commons.transform(_deriveDistributions(rel.getInput(), mq), i -> i.apply(mapping));
+    }
+
+    public List<IgniteDistribution> deriveDistributions(SingleRel rel, RelMetadataQuery mq) {
+        if (rel instanceof IgniteRel)
+            return deriveDistributions((IgniteRel)rel, mq);
+
+        return _deriveDistributions(rel.getInput(), mq);
+    }
+
+    public List<IgniteDistribution> deriveDistributions(RelSubset rel, RelMetadataQuery mq) {
+        rel = VolcanoUtils.subset(rel, rel.getTraitSet().replace(REQUESTED_CONVENTION.get()));
+
+        HashSet<IgniteDistribution> res = new HashSet<>();
+
+        for (RelNode rel0 : rel.getRels())
+            res.addAll(_deriveDistributions(rel0, mq));
+
+        if (F.isEmpty(res)) {
+            RelSubset newRel = VolcanoUtils.subset(rel, rel.getTraitSet().replace(Convention.NONE));
+
+            if (newRel != rel) {
+                for (RelNode rel0 : newRel.getRels())
+                    res.addAll(_deriveDistributions(rel0, mq));
+            }
+        }
+
+        return new ArrayList<>(res);
+    }
+
+    public List<IgniteDistribution> deriveDistributions(HepRelVertex rel, RelMetadataQuery mq) {
+        return _deriveDistributions(rel.getCurrentRel(), mq);
+    }
+
+    public List<IgniteDistribution> deriveDistributions(LogicalFilter rel, RelMetadataQuery mq) {
+        return _deriveDistributions(rel.getInput(), mq);
+    }
+
+    public List<IgniteDistribution> deriveDistributions(LogicalJoin rel, RelMetadataQuery mq) {
+        List<IgniteDistribution> left = _deriveDistributions(rel.getLeft(), mq);
+        List<IgniteDistribution> right = _deriveDistributions(rel.getRight(), mq);
+
+        return Commons.transform(IgniteDistributions.suggestJoin(left, right, rel.analyzeCondition(), rel.getJoinType()),
+            IgniteDistributions.BiSuggestion::out);
+    }
+
+    private static List<IgniteDistribution> _deriveDistributions(RelNode rel, RelMetadataQuery mq) {
+        return RelMetadataQueryEx.wrap(mq).derivedDistributions(rel);
+    }
+
+    public static List<IgniteDistribution> deriveDistributions(RelNode rel, Convention convention, RelMetadataQuery mq) {
+        try {
+            REQUESTED_CONVENTION.set(convention);
+
+            return _deriveDistributions(rel, mq);
+        }
+        finally {
+            REQUESTED_CONVENTION.remove();
+        }
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdDistribution.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdDistribution.java
new file mode 100644
index 0000000..9673835
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdDistribution.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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 java.util.List;
+import org.apache.calcite.plan.hep.HepRelVertex;
+import org.apache.calcite.plan.volcano.RelSubset;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Exchange;
+import org.apache.calcite.rel.core.Filter;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinInfo;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.Project;
+import org.apache.calcite.rel.core.TableScan;
+import org.apache.calcite.rel.core.Values;
+import org.apache.calcite.rel.metadata.BuiltInMetadata;
+import org.apache.calcite.rel.metadata.MetadataDef;
+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.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.util.BuiltInMethod;
+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.trait.IgniteDistributions;
+import org.apache.ignite.internal.util.typedef.F;
+
+import static org.apache.calcite.rel.RelDistribution.Type.ANY;
+
+/**
+ *
+ */
+public class IgniteMdDistribution implements MetadataHandler<BuiltInMetadata.Distribution> {
+    public static final RelMetadataProvider SOURCE =
+        ReflectiveRelMetadataProvider.reflectiveSource(
+            BuiltInMethod.DISTRIBUTION.method, new IgniteMdDistribution());
+
+    @Override public MetadataDef<BuiltInMetadata.Distribution> getDef() {
+        return BuiltInMetadata.Distribution.DEF;
+    }
+
+    public IgniteDistribution distribution(RelNode rel, RelMetadataQuery mq) {
+        return DistributionTraitDef.INSTANCE.getDefault();
+    }
+
+    public IgniteDistribution distribution(Filter filter, RelMetadataQuery mq) {
+        return filter(mq, filter.getInput(), filter.getCondition());
+    }
+
+    public IgniteDistribution distribution(Project project, RelMetadataQuery mq) {
+        return project(mq, project.getInput(), project.getProjects());
+    }
+
+    public IgniteDistribution distribution(Join join, RelMetadataQuery mq) {
+        return join(mq, join.getLeft(), join.getRight(), join.analyzeCondition(), join.getJoinType());
+    }
+
+    public IgniteDistribution distribution(RelSubset rel, RelMetadataQuery mq) {
+        return rel.getTraitSet().getTrait(DistributionTraitDef.INSTANCE);
+    }
+
+    public IgniteDistribution distribution(TableScan rel, RelMetadataQuery mq) {
+        return rel.getTraitSet().getTrait(DistributionTraitDef.INSTANCE);
+    }
+
+    public IgniteDistribution distribution(Values values, RelMetadataQuery mq) {
+        return IgniteDistributions.broadcast();
+    }
+
+    public IgniteDistribution distribution(Exchange exchange, RelMetadataQuery mq) {
+        return (IgniteDistribution) exchange.distribution;
+    }
+
+    public IgniteDistribution distribution(HepRelVertex rel, RelMetadataQuery mq) {
+        return _distribution(rel.getCurrentRel(), mq);
+    }
+
+    public static IgniteDistribution project(RelMetadataQuery mq, RelNode input, List<? extends RexNode> projects) {
+        return project(input.getRowType(), _distribution(input, mq), projects);
+    }
+
+    public static IgniteDistribution project(RelDataType inType, IgniteDistribution inDistr, List<? extends RexNode> projects) {
+        return inDistr.apply(Project.getPartialMapping(inType.getFieldCount(), projects));
+    }
+
+    public static IgniteDistribution filter(RelMetadataQuery mq, RelNode input, RexNode condition) {
+        return _distribution(input, mq);
+    }
+
+    public static IgniteDistribution join(RelMetadataQuery mq, RelNode left, RelNode right, JoinInfo joinInfo, JoinRelType joinType) {
+        return join(_distribution(left, mq), _distribution(right, mq), joinInfo, joinType);
+    }
+
+    public static IgniteDistribution join(IgniteDistribution left, IgniteDistribution right, JoinInfo joinInfo, JoinRelType joinType) {
+        return F.first(IgniteDistributions.suggestJoin(left, right, joinInfo, joinType)).out();
+    }
+
+    public static IgniteDistribution _distribution(RelNode rel, RelMetadataQuery mq) {
+        IgniteDistribution distr = rel.getTraitSet().getTrait(DistributionTraitDef.INSTANCE);
+
+        if (distr.getType() != ANY)
+            return distr;
+
+        return (IgniteDistribution) mq.distribution(rel);
+    }
+}
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/IgniteMdFragmentInfo.java
new file mode 100644
index 0000000..c5f941e
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdFragmentInfo.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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 org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.volcano.RelSubset;
+import org.apache.calcite.rel.BiRel;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.SingleRel;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.metadata.MetadataDef;
+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.calcite.util.Pair;
+import org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMetadata.FragmentMetadata;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteReceiver;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
+import org.apache.ignite.internal.processors.query.calcite.schema.IgniteTable;
+import org.apache.ignite.internal.processors.query.calcite.splitter.Edge;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.apache.ignite.internal.processors.query.calcite.util.IgniteMethod;
+
+/**
+ *
+ */
+public class IgniteMdFragmentInfo implements MetadataHandler<FragmentMetadata> {
+    public static final RelMetadataProvider SOURCE =
+        ReflectiveRelMetadataProvider.reflectiveSource(
+            IgniteMethod.FRAGMENT_INFO.method(), new IgniteMdFragmentInfo());
+
+    @Override public MetadataDef<FragmentMetadata> getDef() {
+        return FragmentMetadata.DEF;
+    }
+
+    public FragmentInfo getFragmentInfo(RelNode rel, RelMetadataQuery mq) {
+        throw new AssertionError();
+    }
+
+    public FragmentInfo getFragmentInfo(RelSubset rel, RelMetadataQuery mq) {
+        throw new AssertionError();
+    }
+
+    public FragmentInfo getFragmentInfo(SingleRel rel, RelMetadataQuery mq) {
+        return fragmentInfo(rel.getInput(), mq);
+    }
+
+    public FragmentInfo getFragmentInfo(Join rel, RelMetadataQuery mq) {
+        mq = RelMetadataQueryEx.wrap(mq);
+
+        FragmentInfo left = fragmentInfo(rel.getLeft(), mq);
+        FragmentInfo right = fragmentInfo(rel.getRight(), mq);
+
+        try {
+            return left.merge(right);
+        }
+        catch (LocationMappingException e) {
+            // a replicated cache is cheaper to redistribute
+            if (!left.mapping().hasPartitionedCaches())
+                throw planningException(rel, e, true);
+            else if (!right.mapping().hasPartitionedCaches())
+                throw planningException(rel, e, false);
+
+            // both sub-trees have partitioned sources, less cost is better
+            RelOptCluster cluster = rel.getCluster();
+
+            RelOptCost leftCost = rel.getLeft().computeSelfCost(cluster.getPlanner(), mq);
+            RelOptCost rightCost = rel.getRight().computeSelfCost(cluster.getPlanner(), mq);
+
+            throw planningException(rel, e, leftCost.isLe(rightCost));
+        }
+    }
+
+    public FragmentInfo getFragmentInfo(IgniteReceiver rel, RelMetadataQuery mq) {
+        return new FragmentInfo(Pair.of(rel, rel.source()));
+    }
+
+    public FragmentInfo getFragmentInfo(IgniteTableScan rel, RelMetadataQuery mq) {
+        return rel.getTable().unwrap(IgniteTable.class).fragmentInfo(Commons.plannerContext(rel));
+    }
+
+    public static FragmentInfo fragmentInfo(RelNode rel, RelMetadataQuery mq) {
+        return RelMetadataQueryEx.wrap(mq).getFragmentLocation(rel);
+    }
+
+    private OptimisticPlanningException planningException(BiRel rel, Exception cause, boolean splitLeft) {
+        String msg = "Failed to calculate physical distribution";
+
+        if (splitLeft)
+            return new OptimisticPlanningException(msg, new Edge(rel, rel.getLeft(), 0), cause);
+
+        return new OptimisticPlanningException(msg, new Edge(rel, rel.getRight(), 1), cause);
+    }
+}
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
new file mode 100644
index 0000000..5579a14
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMetadata.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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 java.util.List;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.metadata.ChainedRelMetadataProvider;
+import org.apache.calcite.rel.metadata.DefaultRelMetadataProvider;
+import org.apache.calcite.rel.metadata.Metadata;
+import org.apache.calcite.rel.metadata.MetadataDef;
+import org.apache.calcite.rel.metadata.MetadataHandler;
+import org.apache.calcite.rel.metadata.RelMetadataProvider;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
+import org.apache.ignite.internal.processors.query.calcite.util.IgniteMethod;
+
+/**
+ *
+ */
+public class IgniteMetadata {
+    public static final RelMetadataProvider METADATA_PROVIDER =
+        ChainedRelMetadataProvider.of(
+            ImmutableList.of(
+                IgniteMdDerivedDistribution.SOURCE,
+                IgniteMdDistribution.SOURCE,
+                IgniteMdFragmentInfo.SOURCE,
+                DefaultRelMetadataProvider.INSTANCE));
+
+    public interface FragmentMetadata extends Metadata {
+        MetadataDef<FragmentMetadata> DEF = MetadataDef.of(FragmentMetadata.class,
+            FragmentMetadata.Handler.class, IgniteMethod.FRAGMENT_INFO.method());
+
+        /** Determines how the rows are distributed. */
+        FragmentInfo getFragmentInfo();
+
+        /** Handler API. */
+        interface Handler extends MetadataHandler<FragmentMetadata> {
+            FragmentInfo getFragmentInfo(RelNode r, RelMetadataQuery mq);
+        }
+    }
+
+    public interface DerivedDistribution extends Metadata {
+        MetadataDef<DerivedDistribution> DEF = MetadataDef.of(DerivedDistribution.class,
+            DerivedDistribution.Handler.class, IgniteMethod.DERIVED_DISTRIBUTIONS.method());
+
+        List<IgniteDistribution> deriveDistributions();
+
+        /** Handler API. */
+        interface Handler extends MetadataHandler<DerivedDistribution> {
+            List<IgniteDistribution> deriveDistributions(RelNode r, RelMetadataQuery mq);
+        }
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/LocationMappingException.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/LocationMappingException.java
new file mode 100644
index 0000000..e1d884a
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/LocationMappingException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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;
+
+/**
+ *
+ */
+public class LocationMappingException extends Exception {
+    public LocationMappingException(String message) {
+        super(message);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/MappingService.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/MappingService.java
new file mode 100644
index 0000000..9472cd1
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/MappingService.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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 org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+
+/**
+ *
+ */
+public interface MappingService {
+    NodesMapping local(); // returns local node with single partition
+    NodesMapping random(AffinityTopologyVersion topVer); // returns random distribution, partitions count depends on nodes count
+    NodesMapping distributed(int cacheId, AffinityTopologyVersion topVer); // returns cache distribution
+}
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
new file mode 100644
index 0000000..b63e3e2
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/NodesMapping.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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 java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.apache.ignite.internal.util.GridIntList;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+/**
+ *
+ */
+public class NodesMapping implements Serializable {
+    public static final byte HAS_MOVING_PARTITIONS = 1;
+    public static final byte HAS_REPLICATED_CACHES = 1 << 1;
+    public static final byte HAS_PARTITIONED_CACHES = 1 << 2;
+    public static final byte PARTIALLY_REPLICATED = 1 << 3;
+    public static final byte DEDUPLICATED = 1 << 4;
+
+    private final List<UUID> nodes;
+    private final List<List<UUID>> assignments;
+    private final byte flags;
+
+    public NodesMapping(List<UUID> nodes, List<List<UUID>> assignments, byte flags) {
+        this.nodes = nodes;
+        this.assignments = assignments;
+        this.flags = flags;
+    }
+
+    public List<UUID> nodes() {
+        return nodes;
+    }
+
+    public List<List<UUID>> assignments() {
+        return assignments;
+    }
+
+    public NodesMapping mergeWith(NodesMapping other) throws LocationMappingException {
+        byte flags = (byte) (this.flags | other.flags);
+
+        if ((flags & PARTIALLY_REPLICATED) == 0)
+            return new NodesMapping(U.firstNotNull(nodes, other.nodes), mergeAssignments(other, null), flags);
+
+        List<UUID> nodes;
+
+        if (this.nodes == null)
+            nodes = other.nodes;
+        else if (other.nodes == null)
+            nodes = this.nodes;
+        else
+            nodes = Commons.intersect(this.nodes, other.nodes);
+
+        if (nodes != null && nodes.isEmpty())
+            throw new LocationMappingException("Failed to map fragment to location.");
+
+        return new NodesMapping(nodes, mergeAssignments(other, nodes), flags);
+    }
+
+    public NodesMapping deduplicate() {
+        if (!excessive())
+            return this;
+
+        if (assignments == null) {
+            UUID node = nodes.get(ThreadLocalRandom.current().nextInt(nodes.size()));
+
+            return new NodesMapping(Collections.singletonList(node), null, (byte)(flags | DEDUPLICATED));
+        }
+
+        HashSet<UUID> nodes0 = new HashSet<>();
+        List<List<UUID>> assignments0 = new ArrayList<>(assignments.size());
+
+        for (List<UUID> partNodes : assignments) {
+            UUID node = F.first(partNodes);
+
+            if (node == null)
+                assignments0.add(Collections.emptyList());
+            else {
+                assignments0.add(Collections.singletonList(node));
+
+                nodes0.add(node);
+            }
+        }
+
+        return new NodesMapping(new ArrayList<>(nodes0), assignments0, (byte)(flags | DEDUPLICATED));
+    }
+
+    public int[] partitions(UUID node) {
+        if (assignments == null)
+            return null;
+
+        GridIntList parts = new GridIntList(assignments.size());
+
+        for (int i = 0; i < assignments.size(); i++) {
+            List<UUID> assignment = assignments.get(i);
+            if (Objects.equals(node, F.first(assignment)))
+                parts.add(i);
+        }
+
+        return parts.array();
+    }
+
+    public boolean excessive() {
+        return (flags & DEDUPLICATED) == 0;
+    }
+
+    public boolean hasMovingPartitions() {
+        return (flags & HAS_MOVING_PARTITIONS) == HAS_MOVING_PARTITIONS;
+    }
+
+    public boolean hasReplicatedCaches() {
+        return (flags & HAS_REPLICATED_CACHES) == HAS_REPLICATED_CACHES;
+    }
+
+    public boolean hasPartitionedCaches() {
+        return (flags & HAS_PARTITIONED_CACHES) == HAS_PARTITIONED_CACHES;
+    }
+
+    public boolean partiallyReplicated() {
+        return (flags & PARTIALLY_REPLICATED) == PARTIALLY_REPLICATED;
+    }
+
+    private List<List<UUID>> mergeAssignments(NodesMapping other, List<UUID> nodes) throws LocationMappingException {
+        byte flags = (byte) (this.flags | other.flags); List<List<UUID>> left = assignments, right = other.assignments;
+
+        if (left == null && right == null)
+            return null; // nothing to intersect;
+
+        if (left == null || right == null || (flags & HAS_MOVING_PARTITIONS) == 0) {
+            List<List<UUID>> assignments = U.firstNotNull(left, right);
+
+            if (nodes == null || (flags & PARTIALLY_REPLICATED) == 0)
+                return assignments;
+
+            List<List<UUID>> assignments0 = new ArrayList<>(assignments.size());
+            HashSet<UUID> nodesSet = new HashSet<>(nodes);
+
+            for (List<UUID> partNodes : assignments) {
+                List<UUID> partNodes0 = new ArrayList<>(partNodes.size());
+
+                for (UUID partNode : partNodes) {
+                    if (nodesSet.contains(partNode))
+                        partNodes0.add(partNode);
+                }
+
+                if (partNodes0.isEmpty())
+                    throw new LocationMappingException("Failed to map fragment to location.");
+
+                assignments0.add(partNodes0);
+            }
+
+            return assignments0;
+        }
+
+        List<List<UUID>> assignments = new ArrayList<>(left.size());
+        HashSet<UUID> nodesSet = nodes != null ? new HashSet<>(nodes) : null;
+
+        for (int i = 0; i < left.size(); i++) {
+            List<UUID> leftNodes = left.get(i), partNodes = new ArrayList<>(leftNodes.size());
+            HashSet<UUID> rightNodesSet = new HashSet<>(right.get(i));
+
+            for (UUID partNode : leftNodes) {
+                if (rightNodesSet.contains(partNode) && (nodesSet == null || nodesSet.contains(partNode)))
+                    partNodes.add(partNode);
+            }
+
+            if (partNodes.isEmpty())
+                throw new LocationMappingException("Failed to map fragment to location.");
+
+            assignments.add(partNodes);
+        }
+
+        return assignments;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/OptimisticPlanningException.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/OptimisticPlanningException.java
new file mode 100644
index 0000000..f0d3fe3
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/OptimisticPlanningException.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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 org.apache.ignite.internal.processors.query.calcite.splitter.Edge;
+
+/**
+ *
+ */
+public class OptimisticPlanningException extends RuntimeException{
+    private final Edge edge;
+
+    public OptimisticPlanningException(String message, Edge edge, Throwable cause) {
+        super(message, cause);
+        this.edge = edge;
+    }
+
+    public Edge edge() {
+        return edge;
+    }
+}
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
new file mode 100644
index 0000000..74d7682
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/RelMetadataQueryEx.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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 java.util.List;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.metadata.JaninoRelMetadataProvider;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+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.IgniteProject;
+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.processors.query.calcite.rel.IgniteTableScan;
+import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ *
+ */
+public class RelMetadataQueryEx extends RelMetadataQuery {
+    private static final RelMetadataQueryEx PROTO = new RelMetadataQueryEx();
+    public static final JaninoRelMetadataProvider PROVIDER = JaninoRelMetadataProvider.of(IgniteMetadata.METADATA_PROVIDER);
+
+    static {
+        PROVIDER.register(ImmutableList.of(
+                IgniteExchange.class,
+                IgniteReceiver.class,
+                IgniteSender.class,
+                IgniteFilter.class,
+                IgniteProject.class,
+                IgniteJoin.class,
+                IgniteTableScan.class));
+    }
+
+    private IgniteMetadata.FragmentMetadata.Handler sourceDistributionHandler;
+    private IgniteMetadata.DerivedDistribution.Handler derivedDistributionsHandler;
+
+    @SuppressWarnings("MethodOverridesStaticMethodOfSuperclass")
+    public static RelMetadataQueryEx instance() {
+        return new RelMetadataQueryEx(PROTO);
+    }
+
+    public static RelMetadataQueryEx wrap(@NotNull RelMetadataQuery mq) {
+        if (mq.getClass() == RelMetadataQueryEx.class)
+            return (RelMetadataQueryEx) mq;
+
+        return new RelMetadataQueryEx(mq);
+    }
+
+    private RelMetadataQueryEx(@NotNull RelMetadataQueryEx parent) {
+        super(PROVIDER, parent);
+
+        sourceDistributionHandler = parent.sourceDistributionHandler;
+        derivedDistributionsHandler = parent.derivedDistributionsHandler;
+    }
+
+    private RelMetadataQueryEx(@NotNull RelMetadataQuery parent) {
+        super(PROVIDER, parent);
+
+        sourceDistributionHandler = PROTO.sourceDistributionHandler;
+        derivedDistributionsHandler = PROTO.derivedDistributionsHandler;
+    }
+
+    private RelMetadataQueryEx() {
+        super(JaninoRelMetadataProvider.DEFAULT, RelMetadataQuery.EMPTY);
+
+        sourceDistributionHandler = initialHandler(IgniteMetadata.FragmentMetadata.Handler.class);
+        derivedDistributionsHandler = initialHandler(IgniteMetadata.DerivedDistribution.Handler.class);
+    }
+
+    public FragmentInfo getFragmentLocation(RelNode rel) {
+        for (;;) {
+            try {
+                return sourceDistributionHandler.getFragmentInfo(rel, this);
+            } catch (JaninoRelMetadataProvider.NoHandler e) {
+                sourceDistributionHandler = revise(e.relClass, IgniteMetadata.FragmentMetadata.DEF);
+            }
+        }
+    }
+
+    public List<IgniteDistribution> derivedDistributions(RelNode rel) {
+        for (;;) {
+            try {
+                return derivedDistributionsHandler.deriveDistributions(rel, this);
+            } catch (JaninoRelMetadataProvider.NoHandler e) {
+                derivedDistributionsHandler = revise(e.relClass, IgniteMetadata.DerivedDistribution.DEF);
+            }
+        }
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/ContextValue.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/ContextValue.java
new file mode 100644
index 0000000..0fe3dc5
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/ContextValue.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.calcite.DataContext;
+import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+
+/**
+ *
+ */
+public enum ContextValue {
+    QUERY_ID("_query_id", GridCacheVersion.class),
+    PLANNER_CONTEXT("_planner_context", PlannerContext.class);
+
+    private final String valueName;
+    private final Class type;
+
+    ContextValue(String valueName, Class type) {
+        this.valueName = valueName;
+        this.type = type;
+    }
+
+    public String valueName() {
+        return valueName;
+    }
+
+    public <T> T get(DataContext ctx) {
+        return (T) type.cast(ctx.get(valueName));
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/DataContextImpl.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/DataContextImpl.java
new file mode 100644
index 0000000..3c2d14c
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/DataContextImpl.java
@@ -0,0 +1,67 @@
+/*
+ * 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.util.Map;
+import org.apache.calcite.DataContext;
+import org.apache.calcite.adapter.java.JavaTypeFactory;
+import org.apache.calcite.linq4j.QueryProvider;
+import org.apache.calcite.schema.SchemaPlus;
+
+/**
+ *
+ */
+public class DataContextImpl implements DataContext {
+    /** */
+    private final PlannerContext ctx;
+
+    /** */
+    private final Map<String, Object> params;
+
+    /**
+     * @param params Parameters.
+     * @param ctx Query context.
+     */
+    public DataContextImpl(Map<String, Object> params, PlannerContext ctx) {
+        this.params = params;
+        this.ctx = ctx;
+    }
+
+    /** {@inheritDoc} */
+    @Override public SchemaPlus getRootSchema() {
+        return ctx.schema();
+    }
+
+    /** {@inheritDoc} */
+    @Override public JavaTypeFactory getTypeFactory() {
+        return ctx.typeFactory();
+    }
+
+    /** {@inheritDoc} */
+    @Override public QueryProvider getQueryProvider() {
+        return ctx.queryProvider();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Object get(String name) {
+        if (ContextValue.PLANNER_CONTEXT.valueName().equals(name))
+            return ctx;
+
+        return params.get(name);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/DistributedExecution.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/DistributedExecution.java
new file mode 100644
index 0000000..b81961f
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/DistributedExecution.java
@@ -0,0 +1,110 @@
+/*
+ * 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.util.Arrays;
+import java.util.List;
+import org.apache.calcite.linq4j.Linq4j;
+import org.apache.calcite.plan.ConventionTraitDef;
+import org.apache.calcite.plan.RelTraitDef;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelCollationTraitDef;
+import org.apache.calcite.rel.RelDistributionTraitDef;
+import org.apache.calcite.rel.RelDistributions;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.RelRoot;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.parser.SqlParseException;
+import org.apache.calcite.tools.ValidationException;
+import org.apache.ignite.cache.query.FieldsQueryCursor;
+import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
+import org.apache.ignite.internal.processors.query.IgniteSQLException;
+import org.apache.ignite.internal.processors.query.calcite.CalciteQueryProcessor;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
+import org.apache.ignite.internal.processors.query.calcite.util.ListFieldsQueryCursor;
+
+/**
+ *
+ */
+public class DistributedExecution implements QueryExecution {
+    /** */
+    private final PlannerContext ctx;
+
+    /**
+     * @param ctx Query context.
+     */
+    public DistributedExecution(PlannerContext ctx) {
+        this.ctx = ctx;
+    }
+
+    /** {@inheritDoc} */
+    @Override public FieldsQueryCursor<List<?>> execute() {
+        CalciteQueryProcessor proc = ctx.queryProcessor();
+        Query query = ctx.query();
+
+        RelTraitDef[] traitDefs = {
+            RelDistributionTraitDef.INSTANCE,
+            ConventionTraitDef.INSTANCE,
+            RelCollationTraitDef.INSTANCE
+        };
+
+        RelRoot relRoot;
+
+        try (IgnitePlanner planner = proc.planner(traitDefs, ctx)) {
+            // Parse
+            SqlNode sqlNode = planner.parse(query.sql());
+
+            // Validate
+            sqlNode = planner.validate(sqlNode);
+
+            // Convert to Relational operators graph
+            relRoot = planner.rel(sqlNode);
+
+            RelNode rel = relRoot.rel;
+
+            // Transformation chain
+            rel = planner.transform(PlannerType.HEP, PlannerPhase.SUBQUERY_REWRITE, rel, rel.getTraitSet());
+
+            RelTraitSet desired = rel.getTraitSet()
+                .replace(relRoot.collation)
+                .replace(IgniteConvention.INSTANCE)
+                .replace(RelDistributions.ANY)
+                .simplify();
+
+            rel = planner.transform(PlannerType.VOLCANO, PlannerPhase.OPTIMIZATION, rel, desired);
+
+            relRoot = relRoot.withRel(rel).withKind(sqlNode.getKind());
+        } catch (SqlParseException | ValidationException e) {
+            String msg = "Failed to parse query.";
+
+            proc.log().error(msg, e);
+
+            throw new IgniteSQLException(msg, IgniteQueryErrorCode.PARSING, e);
+        } catch (Exception e) {
+            String msg = "Failed to create query execution graph.";
+
+            proc.log().error(msg, e);
+
+            throw new IgniteSQLException(msg, IgniteQueryErrorCode.UNKNOWN, e);
+        }
+
+        // TODO physical plan.
+
+        return new ListFieldsQueryCursor<>(relRoot.rel.getRowType(), Linq4j.emptyEnumerable(), Arrays::asList);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgnitePlanner.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgnitePlanner.java
new file mode 100644
index 0000000..2f35a65
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgnitePlanner.java
@@ -0,0 +1,436 @@
+/*
+ * 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 com.google.common.collect.ImmutableList;
+import java.io.Reader;
+import java.util.List;
+import java.util.Properties;
+import org.apache.calcite.adapter.java.JavaTypeFactory;
+import org.apache.calcite.config.CalciteConnectionConfig;
+import org.apache.calcite.config.CalciteConnectionConfigImpl;
+import org.apache.calcite.config.CalciteConnectionProperty;
+import org.apache.calcite.jdbc.CalciteSchema;
+import org.apache.calcite.plan.Context;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelOptCostImpl;
+import org.apache.calcite.plan.RelOptPlanner;
+import org.apache.calcite.plan.RelOptRule;
+import org.apache.calcite.plan.RelOptSchema;
+import org.apache.calcite.plan.RelOptTable;
+import org.apache.calcite.plan.RelTraitDef;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.plan.hep.HepPlanner;
+import org.apache.calcite.plan.hep.HepProgramBuilder;
+import org.apache.calcite.plan.volcano.VolcanoPlanner;
+import org.apache.calcite.plan.volcano.VolcanoUtils;
+import org.apache.calcite.prepare.CalciteCatalogReader;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.RelRoot;
+import org.apache.calcite.rel.RelShuttleImpl;
+import org.apache.calcite.rel.core.TableFunctionScan;
+import org.apache.calcite.rel.core.TableScan;
+import org.apache.calcite.rel.logical.LogicalValues;
+import org.apache.calcite.rel.metadata.CachingRelMetadataProvider;
+import org.apache.calcite.rel.metadata.JaninoRelMetadataProvider;
+import org.apache.calcite.rel.metadata.RelMetadataProvider;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeSystem;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexExecutor;
+import org.apache.calcite.schema.SchemaPlus;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlOperatorTable;
+import org.apache.calcite.sql.parser.SqlParseException;
+import org.apache.calcite.sql.parser.SqlParser;
+import org.apache.calcite.sql.validate.SqlConformance;
+import org.apache.calcite.sql.validate.SqlValidator;
+import org.apache.calcite.sql2rel.RelDecorrelator;
+import org.apache.calcite.sql2rel.SqlRexConvertletTable;
+import org.apache.calcite.sql2rel.SqlToRelConverter;
+import org.apache.calcite.tools.FrameworkConfig;
+import org.apache.calcite.tools.Planner;
+import org.apache.calcite.tools.Program;
+import org.apache.calcite.tools.Programs;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.ValidationException;
+import org.apache.calcite.util.Pair;
+import org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMetadata;
+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.serialize.Graph;
+import org.apache.ignite.internal.processors.query.calcite.serialize.relation.GraphToRelConverter;
+import org.apache.ignite.internal.processors.query.calcite.serialize.relation.RelGraph;
+import org.apache.ignite.internal.processors.query.calcite.serialize.relation.RelToGraphConverter;
+import org.apache.ignite.internal.processors.query.calcite.splitter.QueryPlan;
+import org.apache.ignite.internal.processors.query.calcite.splitter.Splitter;
+import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
+import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeSystem;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+
+/**
+ *
+ */
+public class IgnitePlanner implements Planner, RelOptTable.ViewExpander {
+    private final SqlOperatorTable operatorTable;
+    private final ImmutableList<Program> programs;
+    private final FrameworkConfig frameworkConfig;
+    private final Context context;
+    private final CalciteConnectionConfig connectionConfig;
+    private final ImmutableList<RelTraitDef> traitDefs;
+    private final SqlParser.Config parserConfig;
+    private final SqlToRelConverter.Config sqlToRelConverterConfig;
+    private final SqlRexConvertletTable convertletTable;
+    private final RexExecutor executor;
+    private final SchemaPlus defaultSchema;
+    private final JavaTypeFactory typeFactory;
+
+    private boolean open;
+
+    private RelOptPlanner planner;
+    private RelMetadataProvider metadataProvider;
+    private SqlValidator validator;
+
+    /**
+     * @param config Framework config.
+     */
+    public IgnitePlanner(FrameworkConfig config) {
+        frameworkConfig = config;
+        defaultSchema = config.getDefaultSchema();
+        operatorTable = config.getOperatorTable();
+        programs = config.getPrograms();
+        parserConfig = config.getParserConfig();
+        sqlToRelConverterConfig = config.getSqlToRelConverterConfig();
+        traitDefs = config.getTraitDefs();
+        convertletTable = config.getConvertletTable();
+        executor = config.getExecutor();
+        context = config.getContext();
+        connectionConfig = connConfig();
+
+        RelDataTypeSystem typeSystem = connectionConfig
+            .typeSystem(RelDataTypeSystem.class, IgniteTypeSystem.DEFAULT);
+
+        typeFactory = new IgniteTypeFactory(typeSystem);
+
+        Commons.plannerContext(context).planner(this);
+    }
+
+    private CalciteConnectionConfig connConfig() {
+        CalciteConnectionConfig unwrapped = context.unwrap(CalciteConnectionConfig.class);
+        if (unwrapped != null)
+            return unwrapped;
+
+        Properties properties = new Properties();
+        properties.setProperty(CalciteConnectionProperty.CASE_SENSITIVE.camelName(),
+            String.valueOf(parserConfig.caseSensitive()));
+        properties.setProperty(CalciteConnectionProperty.CONFORMANCE.camelName(),
+            String.valueOf(frameworkConfig.getParserConfig().conformance()));
+        return new CalciteConnectionConfigImpl(properties);
+    }
+
+    /** {@inheritDoc} */
+    @Override public RelTraitSet getEmptyTraitSet() {
+        return planner.emptyTraitSet();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void close() {
+        reset();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void reset() {
+        planner = null;
+        metadataProvider = null;
+        validator = null;
+
+        RelMetadataQuery.THREAD_PROVIDERS.remove();
+
+        open = false;
+    }
+
+    private void ready() {
+        if (!open) {
+            planner = VolcanoUtils.impatient(new VolcanoPlanner(frameworkConfig.getCostFactory(), context));
+            planner.setExecutor(executor);
+            metadataProvider = new CachingRelMetadataProvider(IgniteMetadata.METADATA_PROVIDER, planner);
+
+            validator = new IgniteSqlValidator(operatorTable(), createCatalogReader(), typeFactory, conformance());
+            validator.setIdentifierExpansion(true);
+
+            for (RelTraitDef def : traitDefs) {
+                planner.addRelTraitDef(def);
+            }
+
+            open = true;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public SqlNode parse(Reader reader) throws SqlParseException {
+        return SqlParser.create(reader, parserConfig).parseStmt();
+    }
+
+    /** {@inheritDoc} */
+    @Override public SqlNode validate(SqlNode sqlNode) throws ValidationException {
+        ready();
+
+        try {
+            return validator.validate(sqlNode);
+        }
+        catch (RuntimeException e) {
+            throw new ValidationException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public Pair<SqlNode, RelDataType> validateAndGetType(SqlNode sqlNode) throws ValidationException {
+        ready();
+
+        SqlNode validatedNode = validate(sqlNode);
+        RelDataType type = validator.getValidatedNodeType(validatedNode);
+        return Pair.of(validatedNode, type);
+    }
+
+    /** {@inheritDoc} */
+    @Override public RelNode convert(SqlNode sql) {
+        return rel(sql).rel;
+    }
+
+    public RelNode convert(RelGraph graph) {
+        ready();
+
+        RelOptCluster cluster = createCluster(createRexBuilder());
+        RelBuilder relBuilder = createRelBuilder(cluster, createCatalogReader());
+
+        return new GraphToRelConverter(this, relBuilder, operatorTable).convert(graph);
+    }
+
+    /** {@inheritDoc} */
+    @Override public RelRoot rel(SqlNode sql) {
+        ready();
+
+        RexBuilder rexBuilder = createRexBuilder();
+        RelOptCluster cluster = createCluster(rexBuilder);
+        SqlToRelConverter.Config config = SqlToRelConverter.configBuilder()
+            .withConfig(sqlToRelConverterConfig)
+            .withTrimUnusedFields(false)
+            .withConvertTableAccess(false)
+            .build();
+        SqlToRelConverter sqlToRelConverter =
+            new SqlToRelConverter(this, validator, createCatalogReader(), cluster, convertletTable, config);
+        RelRoot root = sqlToRelConverter.convertQuery(sql, false, true);
+        root = root.withRel(sqlToRelConverter.flattenTypes(root.rel, true));
+        RelBuilder relBuilder = createRelBuilder(cluster, null);
+        root = root.withRel(RelDecorrelator.decorrelateQuery(root.rel, relBuilder));
+        return root;
+    }
+
+    public QueryPlan plan(RelNode rel) {
+        ready();
+
+        if (rel.getConvention() != IgniteConvention.INSTANCE)
+            throw new IllegalArgumentException("Physical node is required.");
+
+        return new Splitter().go((IgniteRel) rel);
+    }
+
+    public Graph graph(RelNode rel) {
+        ready();
+
+        if (rel.getConvention() != IgniteConvention.INSTANCE)
+            throw new IllegalArgumentException("Physical node is required.");
+
+        return new RelToGraphConverter().go((IgniteRel) rel);
+    }
+
+    /** {@inheritDoc} */
+    @Override public RelRoot expandView(RelDataType rowType, String queryString, List<String> schemaPath, List<String> viewPath) {
+        ready();
+
+        SqlParser parser = SqlParser.create(queryString, parserConfig);
+        SqlNode sqlNode;
+        try {
+            sqlNode = parser.parseQuery();
+        }
+        catch (SqlParseException e) {
+            throw new RuntimeException("parse failed", e);
+        }
+
+        SqlConformance conformance = conformance();
+        CalciteCatalogReader catalogReader =
+            createCatalogReader().withSchemaPath(schemaPath);
+        SqlValidator validator = new IgniteSqlValidator(operatorTable(), catalogReader, typeFactory, conformance);
+        validator.setIdentifierExpansion(true);
+
+        RexBuilder rexBuilder = createRexBuilder();
+        RelOptCluster cluster = createCluster(rexBuilder);
+        SqlToRelConverter.Config config = SqlToRelConverter
+            .configBuilder()
+            .withConfig(sqlToRelConverterConfig)
+            .withTrimUnusedFields(false)
+            .withConvertTableAccess(false)
+            .build();
+        SqlToRelConverter sqlToRelConverter =
+            new SqlToRelConverter(this, validator,
+                catalogReader, cluster, convertletTable, config);
+
+        RelRoot root = sqlToRelConverter.convertQuery(sqlNode, true, false);
+        RelRoot root2 = root.withRel(sqlToRelConverter.flattenTypes(root.rel, true));
+        RelBuilder relBuilder = createRelBuilder(cluster, null);
+        return root2.withRel(RelDecorrelator.decorrelateQuery(root.rel, relBuilder));
+    }
+
+    private RelOptCluster createCluster(RexBuilder rexBuilder) {
+        RelOptCluster cluster = RelOptCluster.create(planner, rexBuilder);
+
+        cluster.setMetadataProvider(metadataProvider);
+        RelMetadataQuery.THREAD_PROVIDERS.set(JaninoRelMetadataProvider.of(metadataProvider));
+
+        return cluster;
+    }
+
+    /** {@inheritDoc} */
+    @Override public RelNode transform(int programIdx, RelTraitSet targetTraits, RelNode rel) {
+        ready();
+
+        RelTraitSet toTraits = targetTraits.simplify();
+
+        rel.accept(new MetaDataProviderModifier(metadataProvider));
+
+        return programs.get(programIdx).run(planner, rel, toTraits, ImmutableList.of(), ImmutableList.of());
+    }
+
+    public RelNode transform(PlannerType plannerType, PlannerPhase plannerPhase, RelNode input, RelTraitSet targetTraits)  {
+        ready();
+
+        RelTraitSet toTraits = targetTraits.simplify();
+
+        input.accept(new MetaDataProviderModifier(metadataProvider));
+
+        RelNode output;
+
+        switch (plannerType) {
+            case HEP:
+                final HepProgramBuilder programBuilder = new HepProgramBuilder();
+
+                for (RelOptRule rule : plannerPhase.getRules(Commons.plannerContext(context))) {
+                    programBuilder.addRuleInstance(rule);
+                }
+
+                final HepPlanner hepPlanner =
+                    new HepPlanner(programBuilder.build(), context, true, null, RelOptCostImpl.FACTORY);
+
+                hepPlanner.setRoot(input);
+
+                if (!input.getTraitSet().equals(targetTraits))
+                    hepPlanner.changeTraits(input, toTraits);
+
+                output = hepPlanner.findBestExp();
+
+                break;
+            case VOLCANO:
+                Program program = Programs.of(plannerPhase.getRules(Commons.plannerContext(context)));
+
+                output = program.run(planner, input, toTraits,
+                    ImmutableList.of(), ImmutableList.of());
+
+                break;
+            default:
+                throw new AssertionError("Unknown planner type: " + plannerType);
+        }
+
+        return output;
+    }
+
+    /** {@inheritDoc} */
+    @Override public JavaTypeFactory getTypeFactory() {
+        return typeFactory;
+    }
+
+    private SqlConformance conformance() {
+        return connectionConfig.conformance();
+    }
+
+    private SqlOperatorTable operatorTable() {
+        return operatorTable;
+    }
+
+    private RexBuilder createRexBuilder() {
+        return new RexBuilder(typeFactory);
+    }
+
+    private RelBuilder createRelBuilder(RelOptCluster cluster, RelOptSchema schema) {
+        return sqlToRelConverterConfig.getRelBuilderFactory().create(cluster, schema);
+    }
+
+    private CalciteCatalogReader createCatalogReader() {
+        SchemaPlus rootSchema = rootSchema(defaultSchema);
+
+        return new CalciteCatalogReader(
+            CalciteSchema.from(rootSchema),
+            CalciteSchema.from(defaultSchema).path(null),
+            typeFactory, connectionConfig);
+    }
+
+    private static SchemaPlus rootSchema(SchemaPlus schema) {
+        for (; ; ) {
+            if (schema.getParentSchema() == null) {
+                return schema;
+            }
+            schema = schema.getParentSchema();
+        }
+    }
+
+    /** */
+    private static class MetaDataProviderModifier extends RelShuttleImpl {
+        /** */
+        private final RelMetadataProvider metadataProvider;
+
+        /** */
+        private MetaDataProviderModifier(RelMetadataProvider metadataProvider) {
+            this.metadataProvider = metadataProvider;
+        }
+
+        /** {@inheritDoc} */
+        @Override public RelNode visit(TableScan scan) {
+            scan.getCluster().setMetadataProvider(metadataProvider);
+            return super.visit(scan);
+        }
+
+        /** {@inheritDoc} */
+        @Override public RelNode visit(TableFunctionScan scan) {
+            scan.getCluster().setMetadataProvider(metadataProvider);
+            return super.visit(scan);
+        }
+
+        /** {@inheritDoc} */
+        @Override public RelNode visit(LogicalValues values) {
+            values.getCluster().setMetadataProvider(metadataProvider);
+            return super.visit(values);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected RelNode visitChild(RelNode parent, int i, RelNode child) {
+            child.accept(this);
+            parent.getCluster().setMetadataProvider(metadataProvider);
+            return parent;
+        }
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlValidator.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlValidator.java
new file mode 100644
index 0000000..74bd1df
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlValidator.java
@@ -0,0 +1,55 @@
+/*
+ * 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.calcite.adapter.java.JavaTypeFactory;
+import org.apache.calcite.prepare.CalciteCatalogReader;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.sql.SqlInsert;
+import org.apache.calcite.sql.SqlOperatorTable;
+import org.apache.calcite.sql.validate.SqlConformance;
+import org.apache.calcite.sql.validate.SqlValidatorImpl;
+
+/** Validator. */
+public class IgniteSqlValidator extends SqlValidatorImpl {
+    public IgniteSqlValidator(SqlOperatorTable opTab,
+        CalciteCatalogReader catalogReader, JavaTypeFactory typeFactory,
+        SqlConformance conformance) {
+        super(opTab, catalogReader, typeFactory, conformance);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected RelDataType getLogicalSourceRowType(
+        RelDataType sourceRowType, SqlInsert insert) {
+        final RelDataType superType =
+            super.getLogicalSourceRowType(sourceRowType, insert);
+        return ((JavaTypeFactory) typeFactory).toSql(superType);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected RelDataType getLogicalTargetRowType(
+        RelDataType targetRowType, SqlInsert insert) {
+        final RelDataType superType =
+            super.getLogicalTargetRowType(targetRowType, insert);
+        return ((JavaTypeFactory) typeFactory).toSql(superType);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerContext.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerContext.java
new file mode 100644
index 0000000..352980f
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerContext.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.calcite.adapter.java.JavaTypeFactory;
+import org.apache.calcite.linq4j.QueryProvider;
+import org.apache.calcite.plan.Context;
+import org.apache.calcite.schema.SchemaPlus;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.internal.processors.query.calcite.CalciteQueryProcessor;
+import org.apache.ignite.internal.processors.query.calcite.exchange.ExchangeProcessor;
+import org.apache.ignite.internal.processors.query.calcite.metadata.MappingService;
+import org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping;
+
+/**
+ *
+ */
+public final class PlannerContext implements Context {
+    private final Context parentContext;
+    private final Query query;
+    private final AffinityTopologyVersion topologyVersion;
+    private final SchemaPlus schema;
+    private final IgniteLogger logger;
+    private final GridKernalContext kernalContext;
+    private final CalciteQueryProcessor queryProcessor;
+    private final MappingService mappingService;
+    private final ExchangeProcessor exchangeProcessor;
+
+    private IgnitePlanner planner;
+
+    private PlannerContext(Context parentContext, Query query, AffinityTopologyVersion topologyVersion,
+        SchemaPlus schema, IgniteLogger logger, GridKernalContext kernalContext, CalciteQueryProcessor queryProcessor, MappingService mappingService,
+        ExchangeProcessor exchangeProcessor) {
+        this.parentContext = parentContext;
+        this.query = query;
+        this.topologyVersion = topologyVersion;
+        this.schema = schema;
+        this.logger = logger;
+        this.kernalContext = kernalContext;
+        this.queryProcessor = queryProcessor;
+        this.mappingService = mappingService;
+        this.exchangeProcessor = exchangeProcessor;
+    }
+
+    public Query query() {
+        return query;
+    }
+
+    public AffinityTopologyVersion topologyVersion() {
+        return topologyVersion;
+    }
+
+    public SchemaPlus schema() {
+        return schema;
+    }
+
+    public IgniteLogger logger() {
+        return logger;
+    }
+
+    public GridKernalContext kernalContext() {
+        return kernalContext;
+    }
+
+    public CalciteQueryProcessor queryProcessor() {
+        return queryProcessor;
+    }
+
+    void planner(IgnitePlanner planner) {
+        this.planner = planner;
+    }
+
+    public IgnitePlanner planner() {
+        return planner;
+    }
+
+    public MappingService mappingService() {
+        return mappingService;
+    }
+
+    public ExchangeProcessor exchangeProcessor() {
+        return exchangeProcessor;
+    }
+
+    // Helper methods
+
+    public JavaTypeFactory typeFactory() {
+        return planner.getTypeFactory();
+    }
+
+    public NodesMapping mapForLocal() {
+        return mappingService.local();
+    }
+
+    public NodesMapping mapForRandom(AffinityTopologyVersion topVer) {
+        return mappingService.random(topVer);
+    }
+
+    public NodesMapping mapForCache(int cacheId, AffinityTopologyVersion topVer) {
+        return mappingService.distributed(cacheId, topVer);
+    }
+
+    public QueryProvider queryProvider() {
+        return null; // TODO
+    }
+
+    @Override public <C> C unwrap(Class<C> aClass) {
+        if (aClass == getClass())
+            return aClass.cast(this);
+
+        return parentContext.unwrap(aClass);
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static class Builder {
+        private Context parentContext;
+        private Query query;
+        private AffinityTopologyVersion topologyVersion;
+        private SchemaPlus schema;
+        private IgniteLogger logger;
+        private GridKernalContext kernalContext;
+        private CalciteQueryProcessor queryProcessor;
+        private MappingService mappingService;
+        private ExchangeProcessor exchangeProcessor;
+
+        public Builder parentContext(Context parentContext) {
+            this.parentContext = parentContext;
+            return this;
+        }
+
+        public Builder query(Query query) {
+            this.query = query;
+            return this;
+        }
+
+        public Builder topologyVersion(AffinityTopologyVersion topologyVersion) {
+            this.topologyVersion = topologyVersion;
+            return this;
+        }
+
+        public Builder schema(SchemaPlus schema) {
+            this.schema = schema;
+            return this;
+        }
+
+        public Builder logger(IgniteLogger logger) {
+            this.logger = logger;
+            return this;
+        }
+
+        public Builder kernalContext(GridKernalContext kernalContext) {
+            this.kernalContext = kernalContext;
+            return this;
+        }
+
+        public Builder queryProcessor(CalciteQueryProcessor queryProcessor) {
+            this.queryProcessor = queryProcessor;
+            return this;
+        }
+
+        public Builder mappingService(MappingService mappingService) {
+            this.mappingService = mappingService;
+            return this;
+        }
+
+        public Builder exchangeProcessor(ExchangeProcessor exchangeProcessor) {
+            this.exchangeProcessor = exchangeProcessor;
+            return this;
+        }
+
+        public PlannerContext build() {
+            return new PlannerContext(parentContext, query, topologyVersion, schema, logger, kernalContext, queryProcessor, mappingService, exchangeProcessor);
+        }
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerPhase.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerPhase.java
new file mode 100644
index 0000000..14ef394
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerPhase.java
@@ -0,0 +1,60 @@
+/*
+ * 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.calcite.rel.rules.SubQueryRemoveRule;
+import org.apache.calcite.tools.RuleSet;
+import org.apache.calcite.tools.RuleSets;
+import org.apache.ignite.internal.processors.query.calcite.rule.FilterConverter;
+import org.apache.ignite.internal.processors.query.calcite.rule.JoinConverter;
+import org.apache.ignite.internal.processors.query.calcite.rule.ProjectConverter;
+import org.apache.ignite.internal.processors.query.calcite.rule.TableScanConverter;
+
+/**
+ *
+ */
+public enum PlannerPhase {
+    /** */
+    SUBQUERY_REWRITE("Sub-queries rewrites") {
+        @Override public RuleSet getRules(PlannerContext ctx) {
+            return RuleSets.ofList(
+                SubQueryRemoveRule.FILTER,
+                SubQueryRemoveRule.PROJECT,
+                SubQueryRemoveRule.JOIN);
+        }
+    },
+
+    /** */
+    OPTIMIZATION("Main optimization phase") {
+        @Override public RuleSet getRules(PlannerContext ctx) {
+            return RuleSets.ofList(
+                TableScanConverter.INSTANCE,
+                JoinConverter.INSTANCE,
+                ProjectConverter.INSTANCE,
+                FilterConverter.INSTANCE);
+        }
+    };
+
+    public final String description;
+
+    PlannerPhase(String description) {
+        this.description = description;
+    }
+
+    public abstract RuleSet getRules(PlannerContext ctx);
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerType.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerType.java
new file mode 100644
index 0000000..cddc4d5
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerType.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+/**
+ *
+ */
+public enum PlannerType {
+    HEP,
+    VOLCANO;
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/Query.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/Query.java
new file mode 100644
index 0000000..d18c82c
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/Query.java
@@ -0,0 +1,73 @@
+/*
+ * 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.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.ignite.internal.util.typedef.F;
+
+/**
+ *
+ */
+public class Query {
+    private final String sql;
+    private final Object[] params;
+
+    public Query(String sql, Object[] params) {
+        this.sql = sql;
+        this.params = params;
+    }
+
+    public String sql() {
+        return sql;
+    }
+
+    public Object[] params() {
+        return params;
+    }
+
+    public Map<String, Object> params(Map<String, Object> stashed) {
+        Map<String, Object> res = new HashMap<>(stashed);
+        if (!F.isEmpty(params)) {
+            for (int i = 0; i < params.length; i++) {
+                res.put("?" + i, params[i]);
+            }
+        }
+        return res;
+    }
+
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (!(o instanceof Query))
+            return false;
+
+        Query query = (Query) o;
+
+        if (!sql.equals(query.sql))
+            return false;
+        return Arrays.equals(params, query.params);
+    }
+
+    @Override public int hashCode() {
+        int result = sql.hashCode();
+        result = 31 * result + Arrays.hashCode(params);
+        return result;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/QueryExecution.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/QueryExecution.java
new file mode 100644
index 0000000..bcfed58
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/QueryExecution.java
@@ -0,0 +1,28 @@
+/*
+ * 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.util.List;
+import org.apache.ignite.cache.query.FieldsQueryCursor;
+
+/**
+ *
+ */
+public interface QueryExecution {
+    FieldsQueryCursor<List<?>> execute();
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteConvention.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteConvention.java
new file mode 100644
index 0000000..a0f7878
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteConvention.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.rel;
+
+import org.apache.calcite.plan.Convention;
+import org.apache.calcite.plan.ConventionTraitDef;
+import org.apache.calcite.plan.RelOptPlanner;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.plan.volcano.AbstractConverter;
+
+/**
+ *
+ */
+public class IgniteConvention extends Convention.Impl {
+    public static final IgniteConvention INSTANCE = new IgniteConvention();
+
+    private IgniteConvention() {
+        super("IGNITE", IgniteRel.class);
+    }
+
+    @Override public ConventionTraitDef getTraitDef() {
+        return ConventionTraitDef.INSTANCE;
+    }
+
+    @Override public void register(RelOptPlanner planner) {
+        planner.addRule(AbstractConverter.ExpandConversionRule.INSTANCE);
+    }
+
+    @Override public boolean useAbstractConvertersForConversion(RelTraitSet fromTraits, RelTraitSet toTraits) {
+        return toTraits.contains(INSTANCE) && fromTraits.contains(INSTANCE);
+    }
+}
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
new file mode 100644
index 0000000..9d01fbb
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteExchange.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.rel;
+
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelDistribution;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Exchange;
+
+/**
+ *
+ */
+public class IgniteExchange extends Exchange implements IgniteRel {
+    public IgniteExchange(RelOptCluster cluster, RelTraitSet traitSet, RelNode input, RelDistribution distribution) {
+        super(cluster, traitSet, input, distribution);
+    }
+
+    @Override public Exchange copy(RelTraitSet traitSet, RelNode newInput, RelDistribution newDistribution) {
+        return new IgniteExchange(getCluster(), traitSet, newInput, newDistribution);
+    }
+
+    @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/IgniteFilter.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteFilter.java
new file mode 100644
index 0000000..c408769
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteFilter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.rel;
+
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Filter;
+import org.apache.calcite.rex.RexNode;
+
+/**
+ *
+ */
+public class IgniteFilter extends Filter implements IgniteRel {
+    public IgniteFilter(RelOptCluster cluster, RelTraitSet traits, RelNode input, RexNode condition) {
+        super(cluster, traits, input, condition);
+    }
+
+    @Override public Filter copy(RelTraitSet traitSet, RelNode input, RexNode condition) {
+        return new IgniteFilter(getCluster(), traitSet, input, condition);
+    }
+
+    @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/IgniteJoin.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteJoin.java
new file mode 100644
index 0000000..b1ad44b
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteJoin.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.rel;
+
+import java.util.Set;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.CorrelationId;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rex.RexNode;
+
+/**
+ *
+ */
+public class IgniteJoin extends Join implements IgniteRel {
+    public IgniteJoin(RelOptCluster cluster, RelTraitSet traitSet, RelNode left, RelNode right, RexNode condition, Set<CorrelationId> variablesSet, JoinRelType joinType) {
+        super(cluster, traitSet, left, right, condition, variablesSet, joinType);
+    }
+
+    @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);
+    }
+
+    @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/IgniteProject.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteProject.java
new file mode 100644
index 0000000..42d2af2
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteProject.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.rel;
+
+import java.util.List;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelTraitSet;
+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;
+
+/**
+ *
+ */
+public class IgniteProject extends Project implements IgniteRel {
+    public IgniteProject(RelOptCluster cluster, RelTraitSet traits, RelNode input, List<? extends RexNode> projects, RelDataType rowType) {
+        super(cluster, traits, input, projects, rowType);
+    }
+
+    @Override public Project copy(RelTraitSet traitSet, RelNode input, List<RexNode> projects, RelDataType rowType) {
+        return new IgniteProject(getCluster(), traitSet, input, projects, rowType);
+    }
+
+    @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/IgniteReceiver.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteReceiver.java
new file mode 100644
index 0000000..545c6e8
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteReceiver.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.rel;
+
+import java.util.List;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.AbstractRelNode;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.ignite.internal.processors.query.calcite.splitter.RelSource;
+import org.apache.ignite.internal.processors.query.calcite.trait.DistributionTraitDef;
+import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
+
+/**
+ *
+ */
+public class IgniteReceiver extends AbstractRelNode implements IgniteRel {
+    private RelSource source;
+
+    public IgniteReceiver(RelOptCluster cluster, RelTraitSet traits, RelDataType rowType, RelSource source) {
+        super(cluster, traits);
+
+        this.rowType = rowType;
+        this.source = source;
+    }
+
+    @Override public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) {
+        return new IgniteReceiver(getCluster(), traitSet, rowType, source);
+    }
+
+    @Override public <T> T accept(IgniteRelVisitor<T> visitor) {
+        return visitor.visit(this);
+    }
+
+    public RelSource source() {
+        return source;
+    }
+
+    public void source(RelSource source) {
+        this.source = source;
+    }
+
+    public IgniteDistribution distribution() {
+        return getTraitSet().getTrait(DistributionTraitDef.INSTANCE);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteRel.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteRel.java
new file mode 100644
index 0000000..8e1567a
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteRel.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.rel;
+
+import org.apache.calcite.rel.RelNode;
+
+/**
+ *
+ */
+public interface IgniteRel extends RelNode {
+    <T> T accept(IgniteRelVisitor<T> visitor);
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteRelVisitor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteRelVisitor.java
new file mode 100644
index 0000000..6c6ffa1
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteRelVisitor.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.rel;
+
+/**
+ *
+ */
+public interface IgniteRelVisitor<T> {
+    T visit(IgniteSender rel);
+
+    T visit(IgniteFilter rel);
+
+    T visit(IgniteProject rel);
+
+    T visit(IgniteJoin rel);
+
+    T visit(IgniteTableScan rel);
+
+    T visit(IgniteReceiver rel);
+
+    T visit(IgniteExchange rel);
+
+    T visit(IgniteRel other);
+}
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
new file mode 100644
index 0000000..4763c38
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteSender.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.rel;
+
+import java.util.List;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.SingleRel;
+import org.apache.ignite.internal.processors.query.calcite.splitter.RelTarget;
+
+/**
+ *
+ */
+public class IgniteSender extends SingleRel implements IgniteRel {
+    private RelTarget target;
+
+    public IgniteSender(RelOptCluster cluster, RelTraitSet traits, RelNode input, RelTarget target) {
+        super(cluster, traits, input);
+
+        this.target = target;
+    }
+
+    public IgniteSender(RelOptCluster cluster, RelTraitSet traits, RelNode input) {
+        super(cluster, traits, input);
+    }
+
+    @Override public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) {
+        return new IgniteSender(getCluster(), traitSet, sole(inputs), target);
+    }
+
+    @Override public <T> T accept(IgniteRelVisitor<T> visitor) {
+        return visitor.visit(this);
+    }
+
+    public RelTarget target() {
+        return target;
+    }
+
+    public void target(RelTarget target) {
+        this.target = target;
+    }
+}
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
new file mode 100644
index 0000000..59af858
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTableScan.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.rel;
+
+import java.util.List;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelOptTable;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.TableScan;
+
+/**
+ *
+ */
+public class IgniteTableScan extends TableScan implements IgniteRel {
+    public IgniteTableScan(RelOptCluster cluster, RelTraitSet traitSet, RelOptTable table) {
+        super(cluster, traitSet, table);
+    }
+
+    @Override public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) {
+        return this;
+    }
+
+    @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/RelOp.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/RelOp.java
new file mode 100644
index 0000000..fb073c9
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/RelOp.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.rel;
+
+import org.apache.calcite.rel.RelNode;
+
+/**
+ *
+ */
+public interface RelOp<T extends RelNode, R> {
+    R go(T rel);
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/FilterConverter.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/FilterConverter.java
new file mode 100644
index 0000000..56587ba
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/FilterConverter.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.rule;
+
+import java.util.List;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.convert.ConverterRule;
+import org.apache.calcite.rel.logical.LogicalFilter;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMdDerivedDistribution;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
+import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+
+/**
+ *
+ */
+public class FilterConverter extends IgniteConverter {
+    public static final ConverterRule INSTANCE = new FilterConverter();
+
+    public FilterConverter() {
+        super(LogicalFilter.class, "FilterConverter");
+    }
+
+    @Override protected List<RelNode> convert0(RelNode rel) {
+        LogicalFilter filter = (LogicalFilter) rel;
+
+        RelNode input = convert(filter.getInput(), IgniteConvention.INSTANCE);
+
+        RelOptCluster cluster = rel.getCluster();
+        RelMetadataQuery mq = cluster.getMetadataQuery();
+
+        List<IgniteDistribution> distrs = IgniteMdDerivedDistribution.deriveDistributions(input, IgniteConvention.INSTANCE, mq);
+
+        return Commons.transform(distrs, d -> create(filter, input, d));
+    }
+
+    private static IgniteFilter create(LogicalFilter filter, RelNode input, IgniteDistribution distr) {
+        RelTraitSet traits = filter.getTraitSet()
+            .replace(distr)
+            .replace(IgniteConvention.INSTANCE);
+
+        return new IgniteFilter(filter.getCluster(), traits, convert(input, distr), filter.getCondition());
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/IgniteConverter.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/IgniteConverter.java
new file mode 100644
index 0000000..4653fec
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/IgniteConverter.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.rule;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.calcite.plan.Convention;
+import org.apache.calcite.plan.RelOptPlanner;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.convert.ConverterRule;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
+import org.apache.ignite.internal.util.typedef.F;
+
+/**
+ *
+ */
+public abstract class IgniteConverter extends ConverterRule {
+    protected IgniteConverter(Class<? extends RelNode> clazz, String descriptionPrefix) {
+        super(clazz, Convention.NONE, IgniteConvention.INSTANCE, descriptionPrefix);
+    }
+
+    @Override public void onMatch(RelOptRuleCall call) {
+        RelNode rel = call.rel(0);
+        if (rel.getTraitSet().contains(Convention.NONE)) {
+            List<RelNode> rels = convert0(rel);
+            if (F.isEmpty(rels))
+                return;
+
+            Map<RelNode, RelNode> equiv = ImmutableMap.of();
+
+            if (rels.size() > 1) {
+                equiv = new HashMap<>();
+
+                for (int i = 1; i < rels.size(); i++) {
+                    equiv.put(rels.get(i), rel);
+                }
+            }
+
+            call.transformTo(F.first(rels), equiv);
+        }
+    }
+
+    @Override public RelNode convert(RelNode rel) {
+        List<RelNode> converted = convert0(rel);
+
+        if (converted.size() > 1) {
+            RelOptPlanner planner = rel.getCluster().getPlanner();
+
+            for (int i = 1; i < converted.size(); i++)
+                planner.ensureRegistered(converted.get(i), rel);
+        }
+
+        return F.first(converted);
+    }
+
+    protected abstract List<RelNode> convert0(RelNode rel);
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/JoinConverter.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/JoinConverter.java
new file mode 100644
index 0000000..b10fe20
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/JoinConverter.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.rule;
+
+import java.util.List;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.convert.ConverterRule;
+import org.apache.calcite.rel.logical.LogicalJoin;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMdDerivedDistribution;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteJoin;
+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;
+
+/**
+ *
+ */
+public class JoinConverter extends IgniteConverter {
+    public static final ConverterRule INSTANCE = new JoinConverter();
+
+    public JoinConverter() {
+        super(LogicalJoin.class, "JoinConverter");
+    }
+
+    @Override protected List<RelNode> convert0(RelNode rel) {
+        LogicalJoin join = (LogicalJoin) rel;
+
+        RelNode left = convert(join.getLeft(), IgniteConvention.INSTANCE);
+        RelNode right = convert(join.getRight(), IgniteConvention.INSTANCE);
+
+        RelOptCluster cluster = join.getCluster();
+        RelMetadataQuery mq = cluster.getMetadataQuery();
+
+        List<IgniteDistribution> leftTraits = IgniteMdDerivedDistribution.deriveDistributions(left, IgniteConvention.INSTANCE, mq);
+        List<IgniteDistribution> rightTraits = IgniteMdDerivedDistribution.deriveDistributions(left, IgniteConvention.INSTANCE, mq);
+
+        List<IgniteDistributions.BiSuggestion> suggestions = IgniteDistributions.suggestJoin(leftTraits, rightTraits, join.analyzeCondition(), join.getJoinType());
+
+        return Commons.transform(suggestions, s -> create(join, left, right, s));
+    }
+
+    private static RelNode create(LogicalJoin join, RelNode left, RelNode right, IgniteDistributions.BiSuggestion suggest) {
+        left = convert(left, suggest.left());
+        right = convert(right, suggest.right());
+
+        RelTraitSet traitSet = join.getTraitSet().replace(IgniteConvention.INSTANCE).replace(suggest.out());
+
+        return new IgniteJoin(join.getCluster(), traitSet, left, right, join.getCondition(), join.getVariablesSet(), join.getJoinType());
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/ProjectConverter.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/ProjectConverter.java
new file mode 100644
index 0000000..2fe1147
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/ProjectConverter.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.rule;
+
+import java.util.List;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.convert.ConverterRule;
+import org.apache.calcite.rel.logical.LogicalProject;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMdDerivedDistribution;
+import org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMdDistribution;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteProject;
+import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+
+/**
+ *
+ */
+public class ProjectConverter extends IgniteConverter {
+    public static final ConverterRule INSTANCE = new ProjectConverter();
+
+    public ProjectConverter() {
+        super(LogicalProject.class, "ProjectConverter");
+    }
+
+    @Override protected List<RelNode> convert0(RelNode rel) {
+        LogicalProject project = (LogicalProject) rel;
+
+        RelNode input = convert(project.getInput(), IgniteConvention.INSTANCE);
+
+        RelOptCluster cluster = rel.getCluster();
+        RelMetadataQuery mq = cluster.getMetadataQuery();
+
+        List<IgniteDistribution> distrs = IgniteMdDerivedDistribution.deriveDistributions(input, IgniteConvention.INSTANCE, mq);
+
+        return Commons.transform(distrs, d -> create(project, input, d));
+    }
+
+    private static IgniteProject create(LogicalProject project, RelNode input, IgniteDistribution distr) {
+        RelTraitSet traits = project.getTraitSet()
+            .replace(IgniteMdDistribution.project(input.getRowType(), distr, project.getProjects()))
+            .replace(IgniteConvention.INSTANCE);
+
+        return new IgniteProject(project.getCluster(), traits, convert(input, distr), project.getProjects(), project.getRowType());
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/TableScanConverter.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/TableScanConverter.java
new file mode 100644
index 0000000..2d5aea0
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/TableScanConverter.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.rule;
+
+import java.util.List;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.convert.ConverterRule;
+import org.apache.calcite.rel.logical.LogicalTableScan;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
+import org.apache.ignite.internal.util.typedef.F;
+
+/**
+ *
+ */
+public class TableScanConverter extends IgniteConverter {
+    public static final ConverterRule INSTANCE = new TableScanConverter();
+
+    public TableScanConverter() {
+        super(LogicalTableScan.class, "TableScanConverter");
+    }
+
+    @Override protected List<RelNode> convert0(RelNode rel) {
+        LogicalTableScan scan = (LogicalTableScan) rel;
+
+        RelTraitSet traitSet = scan.getTraitSet().replace(IgniteConvention.INSTANCE);
+        return F.asList(new IgniteTableScan(rel.getCluster(), traitSet, scan.getTable()));
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CalciteSchemaHolder.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CalciteSchemaHolder.java
new file mode 100644
index 0000000..bad106f
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CalciteSchemaHolder.java
@@ -0,0 +1,69 @@
+/*
+ * 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.schema;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.calcite.schema.SchemaPlus;
+import org.apache.calcite.tools.Frameworks;
+import org.apache.ignite.internal.processors.cache.GridCacheContextInfo;
+import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
+import org.apache.ignite.internal.processors.query.QueryUtils;
+import org.apache.ignite.internal.processors.query.schema.SchemaChangeListener;
+
+/**
+ *
+ */
+public class CalciteSchemaHolder implements SchemaChangeListener {
+    private final Map<String, IgniteSchema> schemas = new HashMap<>();
+    private volatile SchemaPlus schema;
+
+    public void schema(SchemaPlus schema) {
+        this.schema = schema;
+    }
+
+    public SchemaPlus schema() {
+        return schema;
+    }
+
+    @Override public synchronized void onSchemaCreate(String schemaName) {
+        schemas.putIfAbsent(schemaName, new IgniteSchema(schemaName));
+        rebuild();
+    }
+
+    @Override public synchronized void onSchemaDrop(String schemaName) {
+        schemas.remove(schemaName);
+        rebuild();
+    }
+
+    @Override public synchronized void onSqlTypeCreate(String schemaName, GridQueryTypeDescriptor typeDescriptor, GridCacheContextInfo cacheInfo) {
+        schemas.computeIfAbsent(schemaName, IgniteSchema::new).onSqlTypeCreate(typeDescriptor, cacheInfo);
+        rebuild();
+    }
+
+    @Override public synchronized void onSqlTypeDrop(String schemaName, GridQueryTypeDescriptor typeDescriptor, GridCacheContextInfo cacheInfo) {
+        schemas.computeIfAbsent(schemaName, IgniteSchema::new).onSqlTypeDrop(typeDescriptor, cacheInfo);
+        rebuild();
+    }
+
+    private void rebuild() {
+        SchemaPlus schema = Frameworks.createRootSchema(false);
+        schemas.forEach(schema::add);
+        schema(schema.getSubSchema(QueryUtils.DFLT_SCHEMA));
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteSchema.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteSchema.java
new file mode 100644
index 0000000..271c5f6
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteSchema.java
@@ -0,0 +1,88 @@
+/*
+ * 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.schema;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.calcite.schema.Table;
+import org.apache.calcite.schema.impl.AbstractSchema;
+import org.apache.ignite.cache.CacheMode;
+import org.apache.ignite.internal.processors.cache.GridCacheContextInfo;
+import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+
+/**
+ *
+ */
+public class IgniteSchema extends AbstractSchema {
+    /** */
+    private final String schemaName;
+
+    /** */
+    private final Map<String, Table> tableMap = new ConcurrentHashMap<>();
+
+    public IgniteSchema(String schemaName) {
+        this.schemaName = schemaName;
+    }
+
+    public String getName() {
+        return schemaName;
+    }
+
+    @Override protected Map<String, Table> getTableMap() {
+        return Collections.unmodifiableMap(tableMap);
+    }
+
+    /**
+     * Callback method.
+     *
+     * @param typeDesc Query type descriptor.
+     * @param cacheInfo Cache info.
+     */
+    public void onSqlTypeCreate(GridQueryTypeDescriptor typeDesc, GridCacheContextInfo cacheInfo) {
+        Object identityKey = cacheInfo.config().getCacheMode() == CacheMode.PARTITIONED ?
+            cacheInfo.cacheContext().group().affinity().similarAffinityKey() : null;
+
+        addTable(new IgniteTable(typeDesc.tableName(), cacheInfo.name(), Commons.rowType(typeDesc), identityKey));
+    }
+
+    /**
+     * Callback method.
+     *
+     * @param typeDesc Query type descriptor.
+     * @param cacheInfo Cache info.
+     */
+    public void onSqlTypeDrop(GridQueryTypeDescriptor typeDesc, GridCacheContextInfo cacheInfo) {
+        removeTable(typeDesc.tableName());
+    }
+
+    /**
+     * @param table Table.
+     */
+    public void addTable(IgniteTable table) {
+        tableMap.put(table.tableName(), table);
+    }
+
+    /**
+     * @param tableName Table name.
+     */
+    public void removeTable(String tableName) {
+        tableMap.remove(tableName);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteTable.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteTable.java
new file mode 100644
index 0000000..56e9302
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteTable.java
@@ -0,0 +1,137 @@
+/*
+ * 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.schema;
+
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.apache.calcite.DataContext;
+import org.apache.calcite.linq4j.Enumerable;
+import org.apache.calcite.plan.Convention;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelOptTable;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.RelDistribution;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.RelReferentialConstraint;
+import org.apache.calcite.rel.logical.LogicalTableScan;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.schema.ScannableTable;
+import org.apache.calcite.schema.Statistic;
+import org.apache.calcite.schema.TranslatableTable;
+import org.apache.calcite.schema.impl.AbstractTable;
+import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.ignite.internal.processors.query.calcite.metadata.FragmentInfo;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PlannerContext;
+import org.apache.ignite.internal.processors.query.calcite.trait.AffinityFactory;
+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.trait.IgniteDistributions;
+import org.apache.ignite.internal.processors.query.calcite.type.RowType;
+import org.apache.ignite.internal.util.typedef.internal.CU;
+
+/** */
+public class IgniteTable extends AbstractTable implements TranslatableTable, ScannableTable {
+    private final String tableName;
+    private final String cacheName;
+    private final RowType rowType;
+    private final Object identityKey;
+
+    public IgniteTable(String tableName, String cacheName, RowType rowType, Object identityKey) {
+        this.tableName = tableName;
+        this.cacheName = cacheName;
+        this.rowType = rowType;
+        this.identityKey = identityKey;
+    }
+
+    /**
+     * @return Table name;
+     */
+    public String tableName() {
+        return tableName;
+    }
+
+    /**
+     * @return Cache name.
+     */
+    public String cacheName() {
+        return cacheName;
+    }
+
+    /** {@inheritDoc} */
+    @Override public RelDataType getRowType(RelDataTypeFactory typeFactory) {
+        return rowType.asRelDataType(typeFactory);
+    }
+
+    @Override public Statistic getStatistic() {
+        return new TableStatistics();
+    }
+
+    /** {@inheritDoc} */
+    @Override public RelNode toRel(RelOptTable.ToRelContext context, RelOptTable relOptTable) {
+        RelOptCluster cluster = context.getCluster();
+        RelTraitSet traitSet = cluster.traitSetOf(Convention.NONE)
+                .replaceIf(DistributionTraitDef.INSTANCE, this::getDistribution);
+
+        return new LogicalTableScan(cluster, traitSet, relOptTable);
+    }
+
+    public IgniteDistribution getDistribution() {
+        Object key = identityKey();
+
+        if (key == null)
+            return IgniteDistributions.broadcast();
+
+        return IgniteDistributions.hash(rowType.distributionKeys(), new AffinityFactory(CU.cacheId(cacheName), key));
+    }
+
+    protected Object identityKey() {
+        return identityKey;
+    }
+
+    public FragmentInfo fragmentInfo(PlannerContext ctx) {
+        return new FragmentInfo(ctx.mapForCache(CU.cacheId(cacheName), ctx.topologyVersion()));
+    }
+
+    @Override public Enumerable<Object[]> scan(DataContext root) {
+        throw new AssertionError(); // TODO
+    }
+
+    private class TableStatistics implements Statistic {
+        @Override public Double getRowCount() {
+            return null;
+        }
+
+        @Override public boolean isKey(ImmutableBitSet columns) {
+            return false;
+        }
+
+        @Override public List<RelReferentialConstraint> getReferentialConstraints() {
+            return ImmutableList.of();
+        }
+
+        @Override public List<RelCollation> getCollations() {
+            return ImmutableList.of();
+        }
+
+        @Override public RelDistribution getDistribution() {
+            return IgniteTable.this.getDistribution();
+        }
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/Graph.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/Graph.java
new file mode 100644
index 0000000..0427a5f
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/Graph.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.calcite.linq4j.Ord;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.apache.ignite.internal.util.GridIntList;
+
+/**
+ *
+ */
+public class Graph<T extends GraphNode> implements Serializable {
+    private final List<T> nodes = new ArrayList<>();
+    private final List<GridIntList> edges = new ArrayList<>();
+
+    public List<Ord<T>> nodes() {
+        return Ord.zip(nodes);
+    }
+
+    public List<GridIntList> edges() {
+        return Commons.transform(edges, GridIntList::copy);
+    }
+
+    public int addNode(int parentId, T node) {
+        int id = addNode(node);
+
+        addEdge(parentId, id);
+
+        return id;
+    }
+
+    public int addNode(T node) {
+        assert nodes.size() == edges.size();
+
+        int id = nodes.size();
+
+        nodes.add(node);
+        edges.add(new GridIntList());
+
+        return id;
+    }
+
+    public void addEdge(int parentId, int childId) {
+        assert parentId == -1 || (parentId >= 0 && parentId < edges.size());
+        assert nodes.size() == edges.size();
+
+        if (parentId != -1)
+            edges.get(parentId).add(childId);
+    }
+
+    public List<Ord<T>> children(int parentId) {
+        GridIntList children = edges.get(parentId);
+
+        ArrayList<Ord<T>> ords = new ArrayList<>(children.size());
+
+        for (int i = 0; i < children.size(); i++)
+            ords.add(Ord.of(children.get(i), nodes.get(children.get(i))));
+
+        return ords;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/GraphNode.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/GraphNode.java
new file mode 100644
index 0000000..82e3f22
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/GraphNode.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.Serializable;
+
+/**
+ *
+ */
+public interface GraphNode extends Serializable {
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/CallExpression.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/CallExpression.java
new file mode 100644
index 0000000..ab75446
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/CallExpression.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.expression;
+
+import java.util.List;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.SqlSyntax;
+
+/**
+ *
+ */
+public class CallExpression implements Expression {
+    public final String opName;
+    public final SqlSyntax opSyntax;
+    public final List<Expression> operands;
+
+    public CallExpression(SqlOperator op, List<Expression> operands) {
+        this.operands = operands;
+        opName = op.getName();
+        opSyntax = op.getSyntax();
+    }
+
+    @Override public <T> T implement(ExpImplementor<T> implementor) {
+        return implementor.implement(this);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/DynamicParamExpression.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/DynamicParamExpression.java
new file mode 100644
index 0000000..94b2b85
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/DynamicParamExpression.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.expression;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.ignite.internal.processors.query.calcite.serialize.type.DataType;
+
+/**
+ *
+ */
+public class DynamicParamExpression implements Expression {
+    public final DataType type;
+    public final int index;
+
+    public DynamicParamExpression(RelDataType type, int index) {
+        this.type = DataType.fromType(type);
+        this.index = index;
+    }
+
+    @Override public <T> T implement(ExpImplementor<T> implementor) {
+        return implementor.implement(this);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/ExpImplementor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/ExpImplementor.java
new file mode 100644
index 0000000..2997c7d
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/ExpImplementor.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.expression;
+
+/**
+ *
+ */
+public interface ExpImplementor<T> {
+    T implement(CallExpression callExpression);
+
+    T implement(InputRefExpression inputRefExpression);
+
+    T implement(LiteralExpression literalExpression);
+
+    T implement(LocalRefExpression localRefExpression);
+
+    T implement(DynamicParamExpression dynamicParamExpression);
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/ExpToRexTranslator.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/ExpToRexTranslator.java
new file mode 100644
index 0000000..946e483
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/ExpToRexTranslator.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.expression;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexLocalRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.SqlOperatorTable;
+import org.apache.calcite.sql.SqlSyntax;
+import org.apache.calcite.util.Pair;
+import org.apache.ignite.internal.util.typedef.F;
+
+/**
+ *
+ */
+public class ExpToRexTranslator implements ExpImplementor<RexNode> {
+    private final RexBuilder builder;
+    private final RelDataTypeFactory typeFactory;
+    private final Map<Pair<String, SqlSyntax>, SqlOperator> ops;
+
+    public ExpToRexTranslator(RexBuilder builder, RelDataTypeFactory typeFactory, SqlOperatorTable opTable) {
+        this.builder = builder;
+        this.typeFactory = typeFactory;
+
+        List<SqlOperator> opList = opTable.getOperatorList();
+
+        HashMap<Pair<String, SqlSyntax>, SqlOperator> ops = new HashMap<>(opList.size());
+
+        for (SqlOperator op : opList) {
+            ops.put(Pair.of(op.getName(), op.getSyntax()), op);
+        }
+
+        this.ops = ops;
+    }
+
+    public List<RexNode> translate(List<Expression> exps) {
+        if (F.isEmpty(exps))
+            return Collections.emptyList();
+
+        if (exps.size() == 1)
+            return F.asList(translate(F.first(exps)));
+
+        List<RexNode> res = new ArrayList<>(exps.size());
+
+        for (Expression exp : exps) {
+            res.add(exp.implement(this));
+        }
+
+        return res;
+    }
+
+    public RexNode translate(Expression exp) {
+        return exp.implement(this);
+    }
+
+    @Override public RexNode implement(CallExpression exp) {
+        return builder.makeCall(op(exp.opName, exp.opSyntax), translate(exp.operands));
+    }
+
+    @Override public RexNode implement(InputRefExpression exp) {
+        return builder.makeInputRef(exp.type.toRelDataType(typeFactory), exp.index);
+    }
+
+    @Override public RexNode implement(LiteralExpression exp) {
+        return builder.makeLiteral(exp.value, exp.type.toRelDataType(typeFactory), false);
+    }
+
+    @Override public RexNode implement(LocalRefExpression exp) {
+        return new RexLocalRef(exp.index, exp.type.toRelDataType(typeFactory));
+    }
+
+    @Override public RexNode implement(DynamicParamExpression exp) {
+        return builder.makeDynamicParam(exp.type.toRelDataType(typeFactory), exp.index);
+    }
+
+    private SqlOperator op(String name, SqlSyntax syntax) {
+        return ops.get(Pair.of(name, syntax));
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/Expression.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/Expression.java
new file mode 100644
index 0000000..7e92b6f
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/Expression.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.expression;
+
+import java.io.Serializable;
+
+/**
+ *
+ */
+public interface Expression extends Serializable {
+    <T> T implement(ExpImplementor<T> implementor);
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/InputRefExpression.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/InputRefExpression.java
new file mode 100644
index 0000000..b561b1e
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/InputRefExpression.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.expression;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.ignite.internal.processors.query.calcite.serialize.type.DataType;
+
+/**
+ *
+ */
+public class InputRefExpression implements Expression {
+    public final DataType type;
+    public final int index;
+
+    public InputRefExpression(RelDataType type, int index) {
+        this.type = DataType.fromType(type);
+        this.index = index;
+    }
+
+    @Override public <T> T implement(ExpImplementor<T> implementor) {
+        return implementor.implement(this);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/LiteralExpression.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/LiteralExpression.java
new file mode 100644
index 0000000..038b142
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/LiteralExpression.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.expression;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.ignite.internal.processors.query.calcite.serialize.type.DataType;
+
+/**
+ *
+ */
+public class LiteralExpression implements Expression {
+    public final DataType type;
+    public final Comparable value;
+
+    public LiteralExpression(RelDataType type, Comparable value) {
+        this.type = DataType.fromType(type);
+        this.value = value;
+    }
+
+    @Override public <T> T implement(ExpImplementor<T> implementor) {
+        return implementor.implement(this);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/LocalRefExpression.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/LocalRefExpression.java
new file mode 100644
index 0000000..e6023b8
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/LocalRefExpression.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.expression;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.ignite.internal.processors.query.calcite.serialize.type.DataType;
+
+/**
+ *
+ */
+public class LocalRefExpression implements Expression {
+    public final DataType type;
+    public final int index;
+
+    public LocalRefExpression(RelDataType type, int index) {
+        this.type = DataType.fromType(type);
+        this.index = index;
+    }
+
+    @Override public <T> T implement(ExpImplementor<T> implementor) {
+        return implementor.implement(this);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/RexToExpTranslator.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/RexToExpTranslator.java
new file mode 100644
index 0000000..22db0db
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/expression/RexToExpTranslator.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.expression;
+
+import java.util.ArrayList;
+import java.util.List;
+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;
+
+/**
+ *
+ */
+public class RexToExpTranslator implements RexVisitor<Expression> {
+    public List<Expression> translate(List<RexNode> operands) {
+        ArrayList<Expression> res = new ArrayList<>(operands.size());
+
+        for (RexNode operand : operands) {
+            res.add(translate(operand));
+        }
+
+        return res;
+    }
+
+    public Expression translate(RexNode rex) {
+        return rex.accept(this);
+    }
+
+     @Override public Expression visitInputRef(RexInputRef inputRef) {
+        return new InputRefExpression(inputRef.getType(), inputRef.getIndex());
+    }
+
+    @Override public Expression visitLocalRef(RexLocalRef localRef) {
+        return new LocalRefExpression(localRef.getType(), localRef.getIndex());
+    }
+
+    @Override public Expression visitLiteral(RexLiteral literal) {
+        return new LiteralExpression(literal.getType(), literal.getValue());
+    }
+
+    @Override public Expression visitCall(RexCall call) {
+        return new CallExpression(call.getOperator(), translate(call.getOperands()));
+    }
+
+    @Override public Expression visitOver(RexOver over) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override public Expression visitCorrelVariable(RexCorrelVariable correlVariable) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override public Expression visitDynamicParam(RexDynamicParam dynamicParam) {
+        return new DynamicParamExpression(dynamicParam.getType(), dynamicParam.getIndex());
+    }
+
+    @Override public Expression visitRangeRef(RexRangeRef rangeRef) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override public Expression visitFieldAccess(RexFieldAccess fieldAccess) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override public Expression visitSubQuery(RexSubQuery subQuery) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override public Expression visitTableInputRef(RexTableInputRef fieldRef) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override public Expression visitPatternFieldRef(RexPatternFieldRef fieldRef) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/ConversionContext.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/ConversionContext.java
new file mode 100644
index 0000000..0d8399e
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/ConversionContext.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.relation;
+
+import org.apache.calcite.plan.RelOptSchema;
+import org.apache.calcite.plan.RelOptTable;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PlannerContext;
+import org.apache.ignite.internal.processors.query.calcite.serialize.expression.ExpToRexTranslator;
+
+/**
+ *
+ */
+public interface ConversionContext extends RelOptTable.ToRelContext {
+    RelDataTypeFactory getTypeFactory();
+
+    RelOptSchema getSchema();
+
+    PlannerContext getContext();
+
+    ExpToRexTranslator getExpressionTranslator();
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/FilterNode.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/FilterNode.java
new file mode 100644
index 0000000..1fd3da2
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/FilterNode.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.relation;
+
+import java.util.List;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rex.RexNode;
+import org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMdDistribution;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
+import org.apache.ignite.internal.processors.query.calcite.serialize.expression.Expression;
+import org.apache.ignite.internal.processors.query.calcite.serialize.expression.RexToExpTranslator;
+import org.apache.ignite.internal.processors.query.calcite.trait.DistributionTraitDef;
+import org.apache.ignite.internal.util.typedef.F;
+
+/**
+ *
+ */
+public class FilterNode extends RelGraphNode {
+    private final Expression condition;
+
+    private FilterNode(Expression condition) {
+        this.condition = condition;
+    }
+
+    public static FilterNode create(IgniteFilter rel, RexToExpTranslator expTranslator) {
+        return new FilterNode(expTranslator.translate(rel.getCondition()));
+    }
+
+    @Override public RelNode toRel(ConversionContext ctx, List<RelNode> children) {
+        RelNode input = F.first(children);
+        RexNode condition = this.condition.implement(ctx.getExpressionTranslator());
+        RelOptCluster cluster = input.getCluster();
+        RelMetadataQuery mq = cluster.getMetadataQuery();
+
+        RelTraitSet traits = cluster.traitSetOf(IgniteConvention.INSTANCE)
+            .replaceIf(DistributionTraitDef.INSTANCE, () -> IgniteMdDistribution.filter(mq, input, condition));
+
+        return new IgniteFilter(cluster, traits, input, condition);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/GraphToRelConverter.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/GraphToRelConverter.java
new file mode 100644
index 0000000..f7574bf
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/GraphToRelConverter.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.relation;
+
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.apache.calcite.linq4j.Ord;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelOptSchema;
+import org.apache.calcite.plan.RelOptTable;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.RelRoot;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.sql.SqlOperatorTable;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PlannerContext;
+import org.apache.ignite.internal.processors.query.calcite.serialize.expression.ExpToRexTranslator;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.apache.ignite.internal.util.typedef.F;
+
+/**
+ *
+ */
+public class GraphToRelConverter implements ConversionContext {
+    private final RelOptTable.ViewExpander viewExpander;
+    private final RelBuilder relBuilder;
+    private final ExpToRexTranslator expTranslator;
+
+    public GraphToRelConverter(RelOptTable.ViewExpander viewExpander, RelBuilder relBuilder, SqlOperatorTable operatorTable) {
+        this.viewExpander = viewExpander;
+        this.relBuilder = relBuilder;
+
+        expTranslator = new ExpToRexTranslator(
+            relBuilder.getRexBuilder(),
+            getTypeFactory(),
+            operatorTable);
+    }
+
+    @Override public RelDataTypeFactory getTypeFactory() {
+        return getCluster().getTypeFactory();
+    }
+
+    @Override public RelOptSchema getSchema() {
+        return relBuilder.getRelOptSchema();
+    }
+
+    @Override public PlannerContext getContext() {
+        return Commons.plannerContext(getCluster().getPlanner().getContext());
+    }
+
+    @Override public ExpToRexTranslator getExpressionTranslator() {
+        return expTranslator;
+    }
+
+    @Override public RelOptCluster getCluster() {
+        return relBuilder.getCluster();
+    }
+
+    @Override public RelRoot expandView(RelDataType rowType, String queryString, List<String> schemaPath, List<String> viewPath) {
+        return viewExpander.expandView(rowType, queryString, schemaPath, viewPath);
+    }
+
+    public RelNode convert(RelGraph graph) {
+        return F.first(convertRecursive(this, graph, graph.nodes().subList(0, 1)));
+    }
+
+    private List<RelNode> convertRecursive(ConversionContext ctx, RelGraph graph, List<Ord<RelGraphNode>> src) {
+        ImmutableList.Builder<RelNode> b = ImmutableList.builder();
+
+        for (Ord<RelGraphNode> node : src)
+            b.add(node.e.toRel(ctx, convertRecursive(ctx, graph, graph.children(node.i))));
+
+        return b.build();
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/JoinNode.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/JoinNode.java
new file mode 100644
index 0000000..c26d2c7
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/JoinNode.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.relation;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.CorrelationId;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteJoin;
+import org.apache.ignite.internal.processors.query.calcite.serialize.expression.Expression;
+import org.apache.ignite.internal.processors.query.calcite.serialize.expression.RexToExpTranslator;
+
+/**
+ *
+ */
+public class JoinNode extends RelGraphNode {
+    private final Expression condition;
+    private final int[] variables;
+    private final JoinRelType joinType;
+
+    private JoinNode(RelTraitSet traits, Expression condition, int[] variables, JoinRelType joinType) {
+        super(traits);
+        this.condition = condition;
+        this.variables = variables;
+        this.joinType = joinType;
+    }
+
+    public static JoinNode create(IgniteJoin rel, RexToExpTranslator expTranslator) {
+        return new JoinNode(rel.getTraitSet(),
+            expTranslator.translate(rel.getCondition()),
+            rel.getVariablesSet().stream().mapToInt(CorrelationId::getId).toArray(),
+            rel.getJoinType());
+    }
+
+    @Override public RelNode toRel(ConversionContext ctx, List<RelNode> children) {
+        assert children.size() == 2;
+
+        RelNode left = children.get(0);
+        RelNode right = children.get(1);
+
+        return new IgniteJoin(ctx.getCluster(),
+            traitSet.toTraitSet(ctx.getCluster()),
+            left,
+            right,
+            ctx.getExpressionTranslator().translate(condition),
+            Arrays.stream(variables).mapToObj(CorrelationId::new).collect(Collectors.toSet()),
+            joinType);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/ProjectNode.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/ProjectNode.java
new file mode 100644
index 0000000..56c60df
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/ProjectNode.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.relation;
+
+import java.util.List;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rex.RexNode;
+import org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMdDistribution;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteProject;
+import org.apache.ignite.internal.processors.query.calcite.serialize.expression.Expression;
+import org.apache.ignite.internal.processors.query.calcite.serialize.expression.RexToExpTranslator;
+import org.apache.ignite.internal.processors.query.calcite.serialize.type.DataType;
+import org.apache.ignite.internal.processors.query.calcite.trait.DistributionTraitDef;
+import org.apache.ignite.internal.util.typedef.F;
+
+/**
+ *
+ */
+public class ProjectNode extends RelGraphNode {
+    private final List<Expression> projects;
+    private final DataType dataType;
+
+    private ProjectNode(List<Expression> projects, DataType dataType) {
+        this.projects = projects;
+        this.dataType = dataType;
+    }
+
+    public static ProjectNode create(IgniteProject rel, RexToExpTranslator rexTranslator) {
+        return new ProjectNode(rexTranslator.translate(rel.getProjects()),
+            DataType.fromType(rel.getRowType()));
+    }
+
+    @Override public RelNode toRel(ConversionContext ctx, List<RelNode> children) {
+        RelNode input = F.first(children);
+        List<RexNode> projects = ctx.getExpressionTranslator().translate(this.projects);
+        RelOptCluster cluster = input.getCluster();
+        RelMetadataQuery mq = cluster.getMetadataQuery();
+
+        RelTraitSet traits = cluster.traitSetOf(IgniteConvention.INSTANCE)
+            .replaceIf(DistributionTraitDef.INSTANCE, () -> IgniteMdDistribution.project(mq, input, projects));
+
+        return new IgniteProject(cluster, traits, input, projects, dataType.toRelDataType(ctx.getTypeFactory()));
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/ReceiverNode.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/ReceiverNode.java
new file mode 100644
index 0000000..96bc0dd
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/ReceiverNode.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.relation;
+
+import java.util.List;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelNode;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteReceiver;
+import org.apache.ignite.internal.processors.query.calcite.serialize.type.DataType;
+import org.apache.ignite.internal.processors.query.calcite.splitter.RelSource;
+import org.apache.ignite.internal.processors.query.calcite.splitter.RelSourceImpl;
+
+
+/**
+ *
+ */
+public class ReceiverNode extends RelGraphNode {
+    private final DataType dataType;
+    private final RelSource source;
+
+    private ReceiverNode(RelTraitSet traits, DataType dataType, RelSource source) {
+        super(traits);
+        this.dataType = dataType;
+        this.source = source;
+    }
+
+    public static ReceiverNode create(IgniteReceiver rel) {
+        RelSource source = new RelSourceImpl(rel.source().exchangeId(), rel.source().mapping());
+
+        return new ReceiverNode(rel.getTraitSet(), DataType.fromType(rel.getRowType()), source);
+    }
+
+    @Override public RelNode toRel(ConversionContext ctx, List<RelNode> children) {
+        return new IgniteReceiver(ctx.getCluster(),
+            traitSet.toTraitSet(ctx.getCluster()),
+            dataType.toRelDataType(ctx.getTypeFactory()),
+            source);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/RelGraph.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/RelGraph.java
new file mode 100644
index 0000000..bb38efe
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/RelGraph.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.relation;
+
+import org.apache.ignite.internal.processors.query.calcite.serialize.Graph;
+
+/**
+ *
+ */
+public class RelGraph extends Graph<RelGraphNode> {
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/RelGraphNode.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/RelGraphNode.java
new file mode 100644
index 0000000..ab53320
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/RelGraphNode.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.relation;
+
+import java.util.List;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelNode;
+import org.apache.ignite.internal.processors.query.calcite.serialize.GraphNode;
+
+/**
+ *
+ */
+public abstract class RelGraphNode implements GraphNode {
+    protected SerializedTraits traitSet;
+
+    protected RelGraphNode() {
+    }
+
+    protected RelGraphNode(RelTraitSet traits) {
+        traitSet = new SerializedTraits(traits);
+    }
+
+    public abstract RelNode toRel(ConversionContext ctx, List<RelNode> children);
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/RelToGraphConverter.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/RelToGraphConverter.java
new file mode 100644
index 0000000..56d77aa
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/RelToGraphConverter.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.relation;
+
+import java.util.ArrayDeque;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.List;
+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.IgniteProject;
+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.IgniteRelVisitor;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSender;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
+import org.apache.ignite.internal.processors.query.calcite.rel.RelOp;
+import org.apache.ignite.internal.processors.query.calcite.serialize.expression.RexToExpTranslator;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.apache.ignite.internal.util.typedef.F;
+
+/**
+ *
+ */
+public class RelToGraphConverter implements RelOp<IgniteRel, RelGraph> {
+    private final RexToExpTranslator rexTranslator = new RexToExpTranslator();
+
+    private RelGraph graph;
+    private int curParent;
+
+    private static final class Item {
+        final int parentId;
+        final List<IgniteRel> children;
+
+        private Item(int parentId, List<IgniteRel> children) {
+            this.parentId = parentId;
+            this.children = children;
+        }
+    }
+
+    private final class ItemTranslator implements IgniteRelVisitor<Item> {
+        @Override public Item visit(IgniteFilter rel) {
+            return new Item(graph.addNode(curParent, FilterNode.create(rel, rexTranslator)), Commons.cast(rel.getInputs()));
+        }
+
+        @Override public Item visit(IgniteJoin rel) {
+            return new Item(graph.addNode(curParent, JoinNode.create(rel, rexTranslator)), Commons.cast(rel.getInputs()));
+        }
+
+        @Override public Item visit(IgniteProject rel) {
+            return new Item(graph.addNode(curParent, ProjectNode.create(rel, rexTranslator)), Commons.cast(rel.getInputs()));
+        }
+
+        @Override public Item visit(IgniteTableScan rel) {
+            return new Item(graph.addNode(curParent, TableScanNode.create(rel)), Commons.cast(rel.getInputs()));
+        }
+
+        @Override public Item visit(IgniteReceiver rel) {
+            return new Item(graph.addNode(curParent, ReceiverNode.create(rel)), Collections.emptyList());
+        }
+
+        @Override public Item visit(IgniteSender rel) {
+            return new Item(graph.addNode(curParent, SenderNode.create(rel)), Commons.cast(rel.getInputs()));
+        }
+
+        @Override public Item visit(IgniteRel rel) {
+            return rel.accept(this);
+        }
+
+        @Override public Item visit(IgniteExchange rel) {
+            throw new AssertionError("Unexpected node: " + rel);
+        }
+    }
+
+    @Override public RelGraph go(IgniteRel root) {
+        graph = new RelGraph();
+
+        ItemTranslator itemTranslator = new ItemTranslator();
+        Deque<Item> stack = new ArrayDeque<>();
+        stack.push(new Item(-1, F.asList(root)));
+
+        while (!stack.isEmpty()) {
+            Item item = stack.pop();
+
+            curParent = item.parentId;
+
+            for (IgniteRel child : item.children) {
+                stack.push(itemTranslator.visit(child));
+            }
+        }
+
+        return graph;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/SenderNode.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/SenderNode.java
new file mode 100644
index 0000000..2940015
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/SenderNode.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.relation;
+
+import java.util.List;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMdDistribution;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSender;
+import org.apache.ignite.internal.processors.query.calcite.splitter.RelTarget;
+import org.apache.ignite.internal.processors.query.calcite.trait.DistributionTraitDef;
+import org.apache.ignite.internal.util.typedef.F;
+
+/**
+ *
+ */
+public class SenderNode extends RelGraphNode {
+    private final RelTarget target;
+
+    private SenderNode(RelTarget target) {
+        this.target = target;
+    }
+
+    public static SenderNode create(IgniteSender rel) {
+        return new SenderNode(rel.target());
+    }
+
+    @Override public RelNode toRel(ConversionContext ctx, List<RelNode> children) {
+        RelNode input = F.first(children);
+        RelOptCluster cluster = input.getCluster();
+        RelMetadataQuery mq = cluster.getMetadataQuery();
+
+        RelTraitSet traits = cluster.traitSet()
+            .replace(IgniteConvention.INSTANCE)
+            .replaceIf(DistributionTraitDef.INSTANCE, () -> IgniteMdDistribution._distribution(input, mq));
+
+        return new IgniteSender(cluster, traits, input, target);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/SerializedTraits.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/SerializedTraits.java
new file mode 100644
index 0000000..94efe86
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/SerializedTraits.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.relation;
+
+import java.io.Serializable;
+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.ignite.internal.processors.query.calcite.rel.IgniteConvention;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+
+/**
+ *
+ */
+public class SerializedTraits implements Serializable {
+    private static final Byte CONVENTION = 0;
+
+    private final List<Serializable> traits;
+
+    public SerializedTraits(RelTraitSet traits) {
+        this.traits = Commons.transform(traits, this::toSerializable);
+    }
+
+    public RelTraitSet toTraitSet(RelOptCluster cluster) {
+        RelTraitSet traits = cluster.traitSet();
+
+        for (Serializable trait : this.traits)
+            traits.replace(fromSerializable(trait));
+
+        return traits.simplify();
+    }
+
+    private Serializable toSerializable(RelTrait trait) {
+        if (trait instanceof Serializable)
+            return (Serializable) trait;
+        if (trait == IgniteConvention.INSTANCE)
+            return CONVENTION;
+
+        throw new AssertionError();
+    }
+
+    private RelTrait fromSerializable(Serializable trait) {
+        if (trait instanceof RelTrait)
+            return (RelTrait) trait;
+        if (CONVENTION.equals(trait))
+            return IgniteConvention.INSTANCE;
+
+        throw new AssertionError();
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/TableScanNode.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/TableScanNode.java
new file mode 100644
index 0000000..097a8fa
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/relation/TableScanNode.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.relation;
+
+import java.util.List;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelNode;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
+
+/**
+ *
+ */
+public class TableScanNode extends RelGraphNode {
+    private final List<String> tableName;
+
+    private TableScanNode(RelTraitSet traits, List<String> tableName) {
+        super(traits);
+        this.tableName = tableName;
+    }
+
+    public static TableScanNode create(IgniteTableScan rel) {
+        return new TableScanNode(rel.getTraitSet(), rel.getTable().getQualifiedName());
+    }
+
+    @Override public RelNode toRel(ConversionContext ctx, List<RelNode> children) {
+        return new IgniteTableScan(ctx.getCluster(),
+            traitSet.toTraitSet(ctx.getCluster()),
+            ctx.getSchema().getTableForMember(tableName));
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/type/DataType.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/type/DataType.java
new file mode 100644
index 0000000..317c95c
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/type/DataType.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.type;
+
+import java.io.Serializable;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+
+/**
+ *
+ */
+public interface DataType extends Serializable {
+    static DataType fromType(RelDataType type) {
+        return type.isStruct() ? StructType.fromType(type) : SimpleType.fromType(type);
+    }
+
+    RelDataType toRelDataType(RelDataTypeFactory factory);
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/type/SimpleType.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/type/SimpleType.java
new file mode 100644
index 0000000..a5bb3f4
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/type/SimpleType.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.type;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.rel.type.RelDataTypeFactoryImpl;
+import org.apache.calcite.sql.type.SqlTypeName;
+
+/**
+ *
+ */
+public class SimpleType implements DataType {
+    private final Class clazz;
+    private final SqlTypeName typeName;
+    private final int precision;
+    private final int scale;
+
+    public static SimpleType fromType(RelDataType type) {
+        assert !type.isStruct();
+
+        if (type instanceof RelDataTypeFactoryImpl.JavaType)
+            return new SimpleType(((RelDataTypeFactoryImpl.JavaType) type).getJavaClass(), null, 0, 0);
+
+        return new SimpleType(null, type.getSqlTypeName(), type.getPrecision(), type.getScale());
+    }
+
+    private SimpleType(Class clazz, SqlTypeName typeName, int precision, int scale) {
+        this.clazz = clazz;
+        this.typeName = typeName;
+        this.precision = precision;
+        this.scale = scale;
+    }
+
+    @Override public RelDataType toRelDataType(RelDataTypeFactory factory) {
+        if (clazz != null)
+            return factory.createJavaType(clazz);
+        if (typeName.allowsNoPrecNoScale())
+            return factory.createSqlType(typeName);
+        if (typeName.allowsPrecNoScale())
+            return factory.createSqlType(typeName, precision);
+
+        return factory.createSqlType(typeName, precision, scale);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/type/StructType.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/type/StructType.java
new file mode 100644
index 0000000..c712754
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/serialize/type/StructType.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.type;
+
+import java.util.LinkedHashMap;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.rel.type.RelDataTypeField;
+
+/**
+ *
+ */
+public class StructType implements DataType {
+    private final LinkedHashMap<String, DataType> fields;
+
+    static StructType fromType(RelDataType type) {
+        assert type.isStruct();
+
+        LinkedHashMap<String, DataType> fields = new LinkedHashMap<>();
+
+        for (RelDataTypeField field : type.getFieldList()) {
+            fields.put(field.getName(), DataType.fromType(field.getType()));
+        }
+
+        return new StructType(fields);
+    }
+
+    private StructType(LinkedHashMap<String, DataType> fields) {
+        this.fields = fields;
+    }
+
+    @Override public RelDataType toRelDataType(RelDataTypeFactory factory) {
+        RelDataTypeFactory.Builder builder = new RelDataTypeFactory.Builder(factory);
+        fields.forEach((n,f) -> builder.add(n,f.toRelDataType(factory)));
+        return builder.build();
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/splitter/Edge.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/splitter/Edge.java
new file mode 100644
index 0000000..a065dbe
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/splitter/Edge.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.splitter;
+
+import org.apache.calcite.rel.RelNode;
+
+/**
+ *
+ */
+public class Edge {
+    private final RelNode parent;
+    private final RelNode child;
+    private final int childIdx;
+
+    public Edge(RelNode parent, RelNode child, int childIdx) {
+        this.parent = parent;
+        this.child = child;
+        this.childIdx = childIdx;
+    }
+
+    public RelNode parent() {
+        return parent;
+    }
+
+    public RelNode child() {
+        return child;
+    }
+
+    public int childIdx() {
+        return childIdx;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/splitter/Fragment.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/splitter/Fragment.java
new file mode 100644
index 0000000..c812f66
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/splitter/Fragment.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.splitter;
+
+import com.google.common.collect.ImmutableList;
+import java.util.concurrent.atomic.AtomicLong;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.util.Pair;
+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.NodesMapping;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PlannerContext;
+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.processors.query.calcite.trait.IgniteDistribution;
+import org.apache.ignite.internal.util.typedef.F;
+
+/**
+ *
+ */
+public class Fragment implements RelSource {
+    private static final AtomicLong ID_GEN = new AtomicLong();
+
+    private final long exchangeId = ID_GEN.getAndIncrement();
+
+    private final RelNode root;
+
+    private NodesMapping mapping;
+
+    public Fragment(RelNode root) {
+        this.root = root;
+    }
+
+    public void init(PlannerContext ctx, RelMetadataQuery mq) {
+        FragmentInfo info = IgniteMdFragmentInfo.fragmentInfo(root, mq);
+
+        if (info.mapping() == null)
+            mapping = remote() ? ctx.mapForRandom(ctx.topologyVersion()) : ctx.mapForLocal();
+        else
+            mapping = info.mapping().deduplicate();
+
+        ImmutableList<Pair<IgniteReceiver, RelSource>> sources = info.sources();
+
+        if (!F.isEmpty(sources)) {
+            for (Pair<IgniteReceiver, RelSource> input : sources) {
+                IgniteReceiver receiver = input.left;
+                RelSource source = input.right;
+
+                source.init(mapping, receiver.distribution(), ctx, mq);
+            }
+        }
+    }
+
+    @Override public long exchangeId() {
+        return exchangeId;
+    }
+
+    @Override public void init(NodesMapping mapping, IgniteDistribution distribution, PlannerContext ctx, RelMetadataQuery mq) {
+        assert remote();
+
+        ((IgniteSender) root).target(new RelTargetImpl(exchangeId, mapping, distribution));
+
+        init(ctx, mq);
+    }
+
+    public RelNode root() {
+        return root;
+    }
+
+    @Override public NodesMapping mapping() {
+        return mapping;
+    }
+
+    private boolean remote() {
+        return root instanceof IgniteSender;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/splitter/QueryPlan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/splitter/QueryPlan.java
new file mode 100644
index 0000000..4a8ac5d
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/splitter/QueryPlan.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.splitter;
+
+import java.util.List;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelNode;
+import org.apache.ignite.internal.processors.query.IgniteSQLException;
+import org.apache.ignite.internal.processors.query.calcite.metadata.OptimisticPlanningException;
+import org.apache.ignite.internal.processors.query.calcite.metadata.RelMetadataQueryEx;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PlannerContext;
+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;
+
+/**
+ *
+ */
+public class QueryPlan {
+    private final List<Fragment> fragments;
+
+    public QueryPlan(List<Fragment> fragments) {
+        this.fragments = fragments;
+    }
+
+    public void init(PlannerContext ctx) {
+        int i = 0;
+
+        RelMetadataQueryEx mq = RelMetadataQueryEx.instance();
+
+        while (true) {
+            try {
+                F.first(fragments).init(ctx, mq);
+
+                break;
+            }
+            catch (OptimisticPlanningException e) {
+                if (++i > 3)
+                    throw new IgniteSQLException("Failed to map query.", e);
+
+                Edge edge = e.edge();
+
+                RelNode parent = edge.parent();
+                RelNode child = edge.child();
+
+                RelOptCluster cluster = child.getCluster();
+                RelTraitSet traitSet = child.getTraitSet();
+
+                Fragment fragment = new Fragment(new IgniteSender(cluster, traitSet, child));
+
+                fragments.add(fragment);
+
+                parent.replaceInput(edge.childIdx(), new IgniteReceiver(cluster, traitSet, child.getRowType(), fragment));
+            }
+        }
+    }
+
+    public List<Fragment> fragments() {
+        return fragments;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/splitter/RelSource.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/splitter/RelSource.java
new file mode 100644
index 0000000..b4eb49e
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/splitter/RelSource.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.splitter;
+
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PlannerContext;
+import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
+
+/**
+ *
+ */
+public interface RelSource {
+    /**
+     * @return Exchange id, has to be unique in scope of query.
+     */
+    long exchangeId();
+
+    /**
+     * @return Source mapping.
+     */
+    NodesMapping mapping();
+
+    /**
+     * @param mapping Target mapping.
+     * @param distribution Target distribution.
+     * @param ctx Context.
+     * @param mq Metadata query instance.
+     */
+    default void init(NodesMapping mapping, IgniteDistribution distribution, PlannerContext ctx, RelMetadataQuery mq) {
+        // No-op
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/splitter/RelSourceImpl.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/splitter/RelSourceImpl.java
new file mode 100644
index 0000000..b0ca757
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/splitter/RelSourceImpl.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.splitter;
+
+import java.io.Serializable;
+import org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping;
+
+/**
+ *
+ */
+public class RelSourceImpl implements RelSource, Serializable {
+    private final long exchangeId;
+    private final NodesMapping mapping;
+
+    public RelSourceImpl(long exchangeId, NodesMapping mapping) {
+        this.exchangeId = exchangeId;
+        this.mapping = mapping;
+    }
+
+    @Override public long exchangeId() {
+        return exchangeId;
+    }
+
+    @Override public NodesMapping mapping() {
+        return mapping;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/splitter/RelTarget.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/splitter/RelTarget.java
new file mode 100644
index 0000000..b031b8e
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/splitter/RelTarget.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.splitter;
+
+import org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping;
+import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
+
+/**
+ *
+ */
+public interface RelTarget {
+    long exchangeId();
+    NodesMapping mapping();
+    IgniteDistribution distribution();
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/splitter/RelTargetImpl.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/splitter/RelTargetImpl.java
new file mode 100644
index 0000000..a3c9b29
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/splitter/RelTargetImpl.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.splitter;
+
+import java.io.Serializable;
+import org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping;
+import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
+
+/**
+ *
+ */
+public class RelTargetImpl implements RelTarget, Serializable {
+    private final long exchangeId;
+    private final NodesMapping mapping;
+    private final IgniteDistribution distribution;
+
+    public RelTargetImpl(long exchangeId, NodesMapping mapping, IgniteDistribution distribution) {
+        this.exchangeId = exchangeId;
+        this.mapping = mapping;
+        this.distribution = distribution;
+    }
+
+    @Override public long exchangeId() {
+        return exchangeId;
+    }
+
+    @Override public NodesMapping mapping() {
+        return mapping;
+    }
+
+    @Override public IgniteDistribution distribution() {
+        return distribution;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/splitter/Splitter.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/splitter/Splitter.java
new file mode 100644
index 0000000..8dd3678
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/splitter/Splitter.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.splitter;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.apache.calcite.linq4j.Ord;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.SingleRel;
+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.IgniteProject;
+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.IgniteRelVisitor;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSender;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
+import org.apache.ignite.internal.processors.query.calcite.rel.RelOp;
+
+import static org.apache.ignite.internal.processors.query.calcite.util.Commons.igniteRel;
+
+/**
+ *
+ */
+public class Splitter implements IgniteRelVisitor<IgniteRel>, RelOp<IgniteRel, QueryPlan> {
+    private List<Fragment> fragments;
+
+    @Override public QueryPlan go(IgniteRel root) {
+        fragments = new ArrayList<>();
+
+        fragments.add(new Fragment(visit(root)));
+
+        Collections.reverse(fragments);
+
+        return new QueryPlan(fragments);
+    }
+
+    @Override public IgniteRel visit(IgniteExchange rel) {
+        RelOptCluster cluster = rel.getCluster();
+        RelTraitSet outTraits = rel.getTraitSet();
+
+        IgniteRel input = visit(igniteRel(rel.getInput()));
+        RelTraitSet inTraits = input.getTraitSet();
+
+        Fragment fragment = new Fragment(new IgniteSender(cluster, inTraits, input));
+
+        fragments.add(fragment);
+
+        return new IgniteReceiver(cluster, outTraits, input.getRowType(), fragment);
+    }
+
+    @Override public IgniteRel visit(IgniteFilter rel) {
+        return visitChild(rel);
+    }
+
+    @Override public IgniteRel visit(IgniteProject rel) {
+        return visitChild(rel);
+    }
+
+    @Override public IgniteRel visit(IgniteJoin rel) {
+        return visitChildren(rel);
+    }
+
+    @Override public IgniteRel visit(IgniteTableScan rel) {
+        return rel;
+    }
+
+    @Override public IgniteRel visit(IgniteRel rel) {
+        return rel.accept(this);
+    }
+
+    @Override public IgniteRel visit(IgniteReceiver rel) {
+        throw new AssertionError("An attempt to split an already split task.");
+    }
+
+    @Override public IgniteRel visit(IgniteSender rel) {
+        throw new AssertionError("An attempt to split an already split task.");
+    }
+
+    private IgniteRel visitChildren(IgniteRel rel) {
+        for (Ord<RelNode> input : Ord.zip(rel.getInputs()))
+            visitChild(rel, input.i, igniteRel(input.e));
+
+        return rel;
+    }
+
+    /**
+     * Visits a single child of a parent.
+     */
+    private <T extends SingleRel & IgniteRel> IgniteRel visitChild(T rel) {
+        visitChild(rel, 0, igniteRel(rel.getInput()));
+
+        return rel;
+    }
+
+    /**
+     * Visits a particular child of a parent.
+     */
+    private void visitChild(IgniteRel parent, int i, IgniteRel child) {
+        IgniteRel child2 = visit(child);
+        if (child2 != child)
+            parent.replaceInput(i, child2);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/AbstractDestinationFunctionFactory.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/AbstractDestinationFunctionFactory.java
new file mode 100644
index 0000000..e6b6bb5
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/AbstractDestinationFunctionFactory.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.trait;
+
+import java.util.Objects;
+
+/**
+ *
+ */
+public abstract class AbstractDestinationFunctionFactory implements DestinationFunctionFactory {
+    @Override public int hashCode() {
+        return Objects.hashCode(key());
+    }
+
+    @Override public boolean equals(Object obj) {
+        if (obj instanceof DestinationFunctionFactory)
+            return Objects.equals(key(), ((DestinationFunctionFactory) obj).key());
+
+        return false;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/AffinityFactory.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/AffinityFactory.java
new file mode 100644
index 0000000..f1dbbac
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/AffinityFactory.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.trait;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.function.ToIntFunction;
+import org.apache.calcite.util.ImmutableIntList;
+import org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PlannerContext;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+/**
+ *
+ */
+public final class AffinityFactory extends AbstractDestinationFunctionFactory {
+    private final int cacheId;
+    private final Object key;
+
+    public AffinityFactory(int cacheId, Object key) {
+        this.cacheId = cacheId;
+        this.key = key;
+    }
+
+    @Override public DestinationFunction create(PlannerContext ctx, NodesMapping mapping, ImmutableIntList keys) {
+        assert keys.size() == 1 && mapping != null && !F.isEmpty(mapping.assignments());
+
+        List<List<UUID>> assignments = mapping.assignments();
+
+        if (U.assertionsEnabled()) {
+            for (List<UUID> assignment : assignments) {
+                assert F.isEmpty(assignment) || assignment.size() == 1;
+            }
+        }
+
+        ToIntFunction<Object> rowToPart = ctx.kernalContext()
+            .cache().context().cacheContext(cacheId).affinity()::partition;
+
+        return row -> assignments.get(rowToPart.applyAsInt(((Object[]) row)[keys.getInt(0)]));
+    }
+
+    @Override public Object key() {
+        return key;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/AllTargetsFactory.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/AllTargetsFactory.java
new file mode 100644
index 0000000..30ce8b8
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/AllTargetsFactory.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.trait;
+
+import java.io.ObjectStreamException;
+import java.util.List;
+import java.util.UUID;
+import org.apache.calcite.util.ImmutableIntList;
+import org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PlannerContext;
+
+/**
+ *
+ */
+public final class AllTargetsFactory extends AbstractDestinationFunctionFactory {
+    public static final DestinationFunctionFactory INSTANCE = new AllTargetsFactory();
+
+    @Override public DestinationFunction create(PlannerContext ctx, NodesMapping m, ImmutableIntList k) {
+        List<UUID> nodes = m.nodes();
+
+        return r -> nodes;
+    }
+
+    @Override public Object key() {
+        return "AllTargetsFactory";
+    }
+
+    private Object readResolve() throws ObjectStreamException {
+        return INSTANCE;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/DestinationFunction.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/DestinationFunction.java
new file mode 100644
index 0000000..3d4dc41
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/DestinationFunction.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.trait;
+
+import java.util.List;
+import java.util.UUID;
+
+/**
+ *
+ */
+public interface DestinationFunction {
+    List<UUID> destination(Object row);
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/DestinationFunctionFactory.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/DestinationFunctionFactory.java
new file mode 100644
index 0000000..d12ec15
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/DestinationFunctionFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.trait;
+
+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.prepare.PlannerContext;
+
+/**
+ *
+ */
+public interface DestinationFunctionFactory extends Serializable {
+    DestinationFunction create(PlannerContext ctx, NodesMapping mapping, ImmutableIntList keys);
+
+    Object key();
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/DistributionTrait.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/DistributionTrait.java
new file mode 100644
index 0000000..5972a11
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/DistributionTrait.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.trait;
+
+import com.google.common.collect.Ordering;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Objects;
+import org.apache.calcite.plan.RelMultipleTrait;
+import org.apache.calcite.plan.RelOptPlanner;
+import org.apache.calcite.plan.RelTrait;
+import org.apache.calcite.rel.RelDistribution;
+import org.apache.calcite.util.ImmutableIntList;
+import org.apache.calcite.util.mapping.Mappings;
+
+import static org.apache.calcite.rel.RelDistribution.Type.ANY;
+import static org.apache.calcite.rel.RelDistribution.Type.HASH_DISTRIBUTED;
+import static org.apache.calcite.rel.RelDistribution.Type.RANDOM_DISTRIBUTED;
+import static org.apache.calcite.rel.RelDistribution.Type.RANGE_DISTRIBUTED;
+import static org.apache.calcite.rel.RelDistribution.Type.ROUND_ROBIN_DISTRIBUTED;
+
+/**
+ *
+ */
+public final class DistributionTrait implements IgniteDistribution, Serializable {
+    private static final Ordering<Iterable<Integer>> ORDERING =
+        Ordering.<Integer>natural().lexicographical();
+
+    private RelDistribution.Type type;
+    private ImmutableIntList keys;
+    private DestinationFunctionFactory functionFactory;
+
+    public DistributionTrait() {
+    }
+
+    public DistributionTrait(RelDistribution.Type type, ImmutableIntList keys, DestinationFunctionFactory functionFactory) {
+        if (type == RANGE_DISTRIBUTED || type == ROUND_ROBIN_DISTRIBUTED)
+            throw new IllegalArgumentException("Distribution type " + type + " is unsupported.");
+
+        this.type = type;
+        this.keys = keys;
+        this.functionFactory = functionFactory;
+    }
+
+    @Override public RelDistribution.Type getType() {
+        return type;
+    }
+
+    @Override public DestinationFunctionFactory destinationFunctionFactory() {
+        return functionFactory;
+    }
+
+    @Override public ImmutableIntList getKeys() {
+        return keys;
+    }
+
+    @Override public void register(RelOptPlanner planner) {}
+
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+
+        if (o instanceof DistributionTrait) {
+            DistributionTrait that = (DistributionTrait) o;
+
+            return type == that.type
+                && Objects.equals(keys, that.keys)
+                && Objects.equals(functionFactory.key(), that.functionFactory.key());
+        }
+
+        return false;
+    }
+
+    @Override public int hashCode() {
+        return Objects.hash(type, keys);
+    }
+
+    @Override public String toString() {
+        return type + (type == Type.HASH_DISTRIBUTED ? "[" + functionFactory.key() + "]" + keys : "");
+    }
+
+    @Override public DistributionTraitDef getTraitDef() {
+        return DistributionTraitDef.INSTANCE;
+    }
+
+    @Override public boolean satisfies(RelTrait trait) {
+        if (trait == this)
+            return true;
+
+        if (!(trait instanceof DistributionTrait))
+            return false;
+
+        DistributionTrait other = (DistributionTrait) trait;
+
+        if (other.getType() == ANY)
+            return true;
+
+        if (getType() == other.getType())
+            return getType() != HASH_DISTRIBUTED
+                || (Objects.equals(keys, other.keys)
+                    && Objects.equals(functionFactory, other.functionFactory));
+
+        return other.getType() == RANDOM_DISTRIBUTED && getType() == HASH_DISTRIBUTED;
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        out.writeObject(type);
+        out.writeObject(keys.toIntArray());
+        out.writeObject(functionFactory);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        type = (Type) in.readObject();
+        keys = ImmutableIntList.of((int[])in.readObject());
+        functionFactory = (DestinationFunctionFactory) in.readObject();
+    }
+
+    private Object readResolve() throws ObjectStreamException {
+        return DistributionTraitDef.INSTANCE.canonize(this);
+    }
+
+    @Override public IgniteDistribution apply(Mappings.TargetMapping mapping) {
+        if (keys.isEmpty())
+            return this;
+
+        assert type == HASH_DISTRIBUTED;
+
+        List<Integer> newKeys = IgniteDistributions.projectDistributionKeys(mapping, keys);
+
+        return newKeys.size() == keys.size() ? IgniteDistributions.hash(newKeys, functionFactory) :
+            IgniteDistributions.random();
+    }
+
+    @Override public boolean isTop() {
+        return type == Type.ANY;
+    }
+
+    @Override public int compareTo(RelMultipleTrait o) {
+        // TODO is this method really needed??
+
+        final IgniteDistribution distribution = (IgniteDistribution) o;
+
+        if (type == distribution.getType()
+            && (type == Type.HASH_DISTRIBUTED
+            || type == Type.RANGE_DISTRIBUTED)) {
+            int cmp = ORDERING.compare(getKeys(), distribution.getKeys());
+
+            if (cmp == 0)
+                cmp = Integer.compare(functionFactory.key().hashCode(), distribution.destinationFunctionFactory().key().hashCode());
+
+            return cmp;
+        }
+
+        return type.compareTo(distribution.getType());
+    }
+}
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
new file mode 100644
index 0000000..306b7aa
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/DistributionTraitDef.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.trait;
+
+import org.apache.calcite.plan.Convention;
+import org.apache.calcite.plan.RelOptPlanner;
+import org.apache.calcite.plan.RelTraitDef;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelDistribution;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Exchange;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange;
+
+/**
+ *
+ */
+public class DistributionTraitDef extends RelTraitDef<IgniteDistribution> {
+    /** */
+    public static final DistributionTraitDef INSTANCE = new DistributionTraitDef();
+
+    @Override public Class<IgniteDistribution> getTraitClass() {
+        return IgniteDistribution.class;
+    }
+
+    @Override public String getSimpleName() {
+        return "distr";
+    }
+
+    @Override public RelNode convert(RelOptPlanner planner, RelNode rel, IgniteDistribution targetDist, boolean allowInfiniteCostConverters) {
+        if (rel.getConvention() == Convention.NONE)
+            return null;
+
+        RelDistribution srcDist = rel.getTraitSet().getTrait(INSTANCE);
+
+        if (srcDist == targetDist) // has to be interned
+            return rel;
+
+        switch(targetDist.getType()){
+            case HASH_DISTRIBUTED:
+            case BROADCAST_DISTRIBUTED:
+            case SINGLETON:
+                Exchange exchange = new IgniteExchange(rel.getCluster(), rel.getTraitSet().replace(targetDist), rel, targetDist);
+                RelNode newRel = planner.register(exchange, rel);
+                RelTraitSet newTraits = rel.getTraitSet().replace(targetDist);
+
+                if (!newRel.getTraitSet().equals(newTraits))
+                    newRel = planner.changeTraits(newRel, newTraits);
+
+                return newRel;
+            case ANY:
+                return rel;
+            default:
+                return null;
+        }
+    }
+
+    @Override public boolean canConvert(RelOptPlanner planner, IgniteDistribution fromTrait, IgniteDistribution toTrait) {
+        return true;
+    }
+
+    @Override public IgniteDistribution getDefault() {
+        return IgniteDistributions.any();
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/HashFunctionFactory.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/HashFunctionFactory.java
new file mode 100644
index 0000000..82c7325
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/HashFunctionFactory.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.trait;
+
+import java.io.ObjectStreamException;
+import java.util.List;
+import java.util.UUID;
+import java.util.function.ToIntFunction;
+import org.apache.calcite.util.ImmutableIntList;
+import org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PlannerContext;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+/**
+ *
+ */
+public final class HashFunctionFactory extends AbstractDestinationFunctionFactory {
+    public static final DestinationFunctionFactory INSTANCE = new HashFunctionFactory();
+
+    @Override public DestinationFunction create(PlannerContext ctx, NodesMapping m, ImmutableIntList k) {
+        assert m != null && !F.isEmpty(m.assignments());
+
+        int[] fields = k.toIntArray();
+
+        ToIntFunction<Object> hashFun = r -> {
+            Object[] row = (Object[]) r;
+
+            if (row == null)
+                return 0;
+
+            int hash = 1;
+
+            for (int i : fields)
+                hash = 31 * hash + (row[i] == null ? 0 : row[i].hashCode());
+
+            return hash;
+        };
+
+        List<List<UUID>> assignments = m.assignments();
+
+        if (U.assertionsEnabled()) {
+            for (List<UUID> assignment : assignments) {
+                assert F.isEmpty(assignment) || assignment.size() == 1;
+            }
+        }
+
+        return r -> assignments.get(hashFun.applyAsInt(r) % assignments.size());
+    }
+
+    @Override public Object key() {
+        return "DefaultHashFunction";
+    }
+
+    private Object readResolve() throws ObjectStreamException {
+        return INSTANCE;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/IgniteDistribution.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/IgniteDistribution.java
new file mode 100644
index 0000000..5f3b175
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/IgniteDistribution.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.trait;
+
+import org.apache.calcite.rel.RelDistribution;
+import org.apache.calcite.util.mapping.Mappings;
+
+/**
+ *
+ */
+public interface IgniteDistribution extends RelDistribution {
+    DestinationFunctionFactory destinationFunctionFactory();
+    @Override IgniteDistribution apply(Mappings.TargetMapping mapping);
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/IgniteDistributions.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/IgniteDistributions.java
new file mode 100644
index 0000000..3e3aa55
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/IgniteDistributions.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.trait;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import org.apache.calcite.rel.core.JoinInfo;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.util.ImmutableIntList;
+import org.apache.calcite.util.mapping.Mappings;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.jetbrains.annotations.NotNull;
+
+import static org.apache.calcite.rel.RelDistribution.Type.ANY;
+import static org.apache.calcite.rel.RelDistribution.Type.BROADCAST_DISTRIBUTED;
+import static org.apache.calcite.rel.RelDistribution.Type.HASH_DISTRIBUTED;
+import static org.apache.calcite.rel.RelDistribution.Type.RANDOM_DISTRIBUTED;
+import static org.apache.calcite.rel.RelDistribution.Type.SINGLETON;
+import static org.apache.calcite.rel.core.JoinRelType.INNER;
+import static org.apache.calcite.rel.core.JoinRelType.LEFT;
+import static org.apache.calcite.rel.core.JoinRelType.RIGHT;
+
+/**
+ *
+ */
+public class IgniteDistributions {
+    private static final int BEST_CNT = 3;
+
+    private static final IgniteDistribution BROADCAST_DISTR = new DistributionTrait(BROADCAST_DISTRIBUTED, ImmutableIntList.of(), AllTargetsFactory.INSTANCE);
+    private static final IgniteDistribution SINGLETON_DISTR = new DistributionTrait(SINGLETON, ImmutableIntList.of(), SingleTargetFactory.INSTANCE);
+    private static final IgniteDistribution RANDOM_DISTR = new DistributionTrait(RANDOM_DISTRIBUTED, ImmutableIntList.of(), RandomTargetFactory.INSTANCE);
+    private static final IgniteDistribution ANY_DISTR = new DistributionTrait(ANY, ImmutableIntList.of(), NoOpFactory.INSTANCE);
+
+    public static IgniteDistribution any() {
+        return ANY_DISTR;
+    }
+
+    public static IgniteDistribution random() {
+        return RANDOM_DISTR;
+    }
+
+    public static IgniteDistribution single() {
+        return SINGLETON_DISTR;
+    }
+
+    public static IgniteDistribution broadcast() {
+        return BROADCAST_DISTR;
+    }
+
+    public static IgniteDistribution hash(List<Integer> keys) {
+        return DistributionTraitDef.INSTANCE.canonize(
+            new DistributionTrait(HASH_DISTRIBUTED, ImmutableIntList.copyOf(keys), HashFunctionFactory.INSTANCE));
+    }
+
+    public static IgniteDistribution hash(List<Integer> keys, DestinationFunctionFactory factory) {
+        return DistributionTraitDef.INSTANCE.canonize(
+            new DistributionTrait(HASH_DISTRIBUTED, ImmutableIntList.copyOf(keys), factory));
+    }
+
+    public static List<BiSuggestion> suggestJoin(IgniteDistribution leftIn, IgniteDistribution rightIn,
+        JoinInfo joinInfo, JoinRelType joinType) {
+        return topN(suggestJoin0(leftIn, rightIn, joinInfo, joinType), BEST_CNT);
+    }
+
+    public static List<BiSuggestion> suggestJoin(List<IgniteDistribution> leftIn, List<IgniteDistribution> rightIn,
+        JoinInfo joinInfo, JoinRelType joinType) {
+        HashSet<BiSuggestion> suggestions = new HashSet<>();
+
+        int bestCnt = 0;
+
+        for (IgniteDistribution leftIn0 : leftIn) {
+            for (IgniteDistribution rightIn0 : rightIn) {
+                for (BiSuggestion suggest : suggestJoin0(leftIn0, rightIn0, joinInfo, joinType)) {
+                    if (suggestions.add(suggest) && suggest.needExchange == 0 && (++bestCnt) == BEST_CNT)
+                        topN(new ArrayList<>(suggestions), BEST_CNT);
+                }
+            }
+        }
+
+        return topN(new ArrayList<>(suggestions), BEST_CNT);
+    }
+
+    private static ArrayList<BiSuggestion> suggestJoin0(IgniteDistribution leftIn, IgniteDistribution rightIn,
+        JoinInfo joinInfo, JoinRelType joinType) {
+        /*
+         * Distributions table:
+         *
+         * ===============INNER JOIN==============
+         * hash + hash = hash
+         * broadcast + hash = hash
+         * hash + broadcast = hash
+         * broadcast + broadcast = broadcast
+         * single + single = single
+         *
+         * ===============LEFT JOIN===============
+         * hash + hash = hash
+         * hash + broadcast = hash
+         * broadcast + broadcast = broadcast
+         * single + single = single
+         *
+         * ===============RIGHT JOIN==============
+         * hash + hash = hash
+         * broadcast + hash = hash
+         * broadcast + broadcast = broadcast
+         * single + single = single
+         *
+         * ===========FULL JOIN/CROSS JOIN========
+         * broadcast + broadcast = broadcast
+         * single + single = single
+         *
+         *
+         * others require redistribution
+         */
+
+        ArrayList<BiSuggestion> res = new ArrayList<>();
+
+        IgniteDistribution out, left, right;
+
+        if (joinType == LEFT || joinType == RIGHT || (joinType == INNER && !F.isEmpty(joinInfo.keys()))) {
+            HashSet<DestinationFunctionFactory> factories = U.newHashSet(3);
+
+            if (leftIn.getKeys().equals(joinInfo.leftKeys))
+                factories.add(leftIn.destinationFunctionFactory());
+
+            if (rightIn.getKeys().equals(joinInfo.rightKeys))
+                factories.add(rightIn.destinationFunctionFactory());
+
+            factories.add(HashFunctionFactory.INSTANCE);
+
+            for (DestinationFunctionFactory factory : factories) {
+                out = hash(joinInfo.leftKeys, factory);
+
+                left = hash(joinInfo.leftKeys, factory); right = hash(joinInfo.rightKeys, factory);
+                add(res, out, leftIn, rightIn, left, right);
+
+                if (joinType == INNER || joinType == LEFT) {
+                    left = hash(joinInfo.leftKeys, factory); right = broadcast();
+                    add(res, out, leftIn, rightIn, left, right);
+                }
+
+                if (joinType == INNER || joinType == RIGHT) {
+                    left = broadcast(); right = hash(joinInfo.rightKeys, factory);
+                    add(res, out, leftIn, rightIn, left, right);
+                }
+            }
+        }
+
+        out = left = right = broadcast();
+        add(res, out, leftIn, rightIn, left, right);
+
+        out = left = right = single();
+        add(res, out, leftIn, rightIn, left, right);
+
+        return res;
+    }
+
+    private static int add(ArrayList<BiSuggestion> dst, IgniteDistribution out, IgniteDistribution left, IgniteDistribution right,
+        IgniteDistribution newLeft, IgniteDistribution newRight) {
+        int exch = 0;
+
+        if (!left.satisfies(newLeft))
+            exch++;
+
+        if (!right.satisfies(newRight))
+            exch++;
+
+        dst.add(new BiSuggestion(out, newLeft, newRight, exch));
+
+        return exch;
+    }
+
+    private static List<BiSuggestion> topN(ArrayList<BiSuggestion> src, int n) {
+        Collections.sort(src);
+
+        return src.size() <= n ? src : src.subList(0, n);
+    }
+
+    public static List<Integer> projectDistributionKeys(Mappings.TargetMapping mapping, ImmutableIntList keys) {
+        if (mapping.getTargetCount() < keys.size())
+            return Collections.emptyList();
+
+        List<Integer> resKeys = new ArrayList<>(mapping.getTargetCount());
+
+        parent:
+        for (int i = 0; i < keys.size(); i++) {
+            int key = keys.getInt(i);
+
+            for (int j = 0; j < mapping.getTargetCount(); j++) {
+                if (mapping.getSourceOpt(j) == key) {
+                    resKeys.add(j);
+
+                    continue parent;
+                }
+            }
+
+            return Collections.emptyList();
+        }
+
+        return resKeys;
+    }
+
+    public static class BiSuggestion implements Comparable<BiSuggestion> {
+        private final IgniteDistribution out;
+        private final IgniteDistribution left;
+        private final IgniteDistribution right;
+        private final int needExchange;
+
+        public BiSuggestion(IgniteDistribution out, IgniteDistribution left, IgniteDistribution right, int needExchange) {
+            this.out = out;
+            this.left = left;
+            this.right = right;
+            this.needExchange = needExchange;
+        }
+
+        public IgniteDistribution out() {
+            return out;
+        }
+
+        public IgniteDistribution left() {
+            return left;
+        }
+
+        public IgniteDistribution right() {
+            return right;
+        }
+
+        public int needExchange() {
+            return needExchange;
+        }
+
+        @Override public int compareTo(@NotNull IgniteDistributions.BiSuggestion o) {
+            return Integer.compare(needExchange, o.needExchange);
+        }
+
+        @Override public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            BiSuggestion that = (BiSuggestion) o;
+
+            if (needExchange != that.needExchange) return false;
+            if (out != that.out) return false;
+            if (left != that.left) return false;
+            return right == that.right;
+        }
+
+        @Override public int hashCode() {
+            int result = out.hashCode();
+            result = 31 * result + left.hashCode();
+            result = 31 * result + right.hashCode();
+            result = 31 * result + needExchange;
+            return result;
+        }
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/NoOpFactory.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/NoOpFactory.java
new file mode 100644
index 0000000..7086277
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/NoOpFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.trait;
+
+import java.io.ObjectStreamException;
+import org.apache.calcite.util.ImmutableIntList;
+import org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PlannerContext;
+
+/**
+ *
+ */
+public final class NoOpFactory extends AbstractDestinationFunctionFactory {
+    public static final DestinationFunctionFactory INSTANCE = new NoOpFactory();
+
+    @Override public DestinationFunction create(PlannerContext ctx, NodesMapping m, ImmutableIntList k) {
+        return null;
+    }
+
+    @Override public Object key() {
+        return "NoOpFactory";
+    }
+
+    private Object readResolve() throws ObjectStreamException {
+        return INSTANCE;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/RandomTargetFactory.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/RandomTargetFactory.java
new file mode 100644
index 0000000..8a643c3
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/RandomTargetFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.trait;
+
+import java.io.ObjectStreamException;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
+import org.apache.calcite.util.ImmutableIntList;
+import org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PlannerContext;
+
+/**
+ *
+ */
+public final class RandomTargetFactory extends AbstractDestinationFunctionFactory {
+    public static final DestinationFunctionFactory INSTANCE = new RandomTargetFactory();
+
+    @Override public DestinationFunction create(PlannerContext ctx, NodesMapping m, ImmutableIntList k) {
+        List<UUID> nodes = m.nodes();
+
+        return r -> Collections.singletonList(nodes.get(ThreadLocalRandom.current().nextInt(nodes.size())));
+    }
+
+    @Override public Object key() {
+        return "RandomTargetFactory";
+    }
+
+    private Object readResolve() throws ObjectStreamException {
+        return INSTANCE;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/SingleTargetFactory.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/SingleTargetFactory.java
new file mode 100644
index 0000000..8a8b27c
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/SingleTargetFactory.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.trait;
+
+import java.io.ObjectStreamException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+import org.apache.calcite.util.ImmutableIntList;
+import org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PlannerContext;
+import org.apache.ignite.internal.util.typedef.F;
+
+/**
+ *
+ */
+public final class SingleTargetFactory extends AbstractDestinationFunctionFactory {
+    public static final DestinationFunctionFactory INSTANCE = new SingleTargetFactory();
+
+    @Override public DestinationFunction create(PlannerContext ctx, NodesMapping m, ImmutableIntList k) {
+        List<UUID> nodes = Collections.singletonList(Objects.requireNonNull(F.first(m.nodes())));
+
+        return r -> nodes;
+    }
+
+    @Override public Object key() {
+        return "SingleTargetFactory";
+    }
+
+    private Object readResolve() throws ObjectStreamException {
+        return INSTANCE;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/IgniteTypeFactory.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/IgniteTypeFactory.java
new file mode 100644
index 0000000..83a2a19
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/IgniteTypeFactory.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.type;
+
+import org.apache.calcite.jdbc.JavaTypeFactoryImpl;
+import org.apache.calcite.rel.type.RelDataTypeSystem;
+
+/**
+ *
+ */
+public class IgniteTypeFactory extends JavaTypeFactoryImpl {
+    public IgniteTypeFactory() {
+        super(IgniteTypeSystem.DEFAULT);
+    }
+
+    public IgniteTypeFactory(RelDataTypeSystem typeSystem) {
+        super(typeSystem);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/IgniteTypeSystem.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/IgniteTypeSystem.java
new file mode 100644
index 0000000..6dbfd02
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/IgniteTypeSystem.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.type;
+
+import java.io.Serializable;
+import org.apache.calcite.rel.type.RelDataTypeSystem;
+import org.apache.calcite.rel.type.RelDataTypeSystemImpl;
+
+/**
+ *
+ */
+public class IgniteTypeSystem extends RelDataTypeSystemImpl implements Serializable {
+    public static final RelDataTypeSystem DEFAULT = new IgniteTypeSystem();
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/RowType.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/RowType.java
new file mode 100644
index 0000000..2133c56
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/RowType.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.type;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.util.ImmutableIntList;
+
+/**
+ *
+ */
+public class RowType {
+    private final String[] fields;
+    private final Class[] types;
+    private final BitSet keyFields;
+    private final int affinityKey;
+
+    public RowType(String[] fields, Class[] types, BitSet keyFields, int affinityKey) {
+        this.fields = fields;
+        this.types = types;
+        this.keyFields = keyFields;
+        this.affinityKey = affinityKey;
+    }
+
+    public RelDataType asRelDataType(RelDataTypeFactory factory) {
+        RelDataTypeFactory.Builder builder = new RelDataTypeFactory.Builder(factory);
+
+        int len = fields.length;
+
+        for (int i = 0; i < len; i++)
+            builder.add(fields[i], factory.createJavaType(types[i]));
+
+        return builder.build();
+    }
+
+    public List<Integer> distributionKeys() {
+        return ImmutableIntList.of(affinityKey);
+    }
+
+    public boolean isKeyField(int idx) {
+        return keyFields.get(idx);
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static class Builder {
+        private int affinityKey;
+        private final LinkedHashSet<String> fields;
+        private final BitSet keyFields;
+        private final ArrayList<Class> types;
+
+        private Builder() {
+            fields = new LinkedHashSet<>();
+            types = new ArrayList<>();
+            keyFields = new BitSet();
+
+            fields.add("_key"); types.add(Object.class);
+            fields.add("_val"); types.add(Object.class);
+        }
+
+        public Builder key(Class type) {
+            if (types.get(0) != Object.class && types.get(0) != type)
+                throw new IllegalStateException("Key type is already set.");
+
+            types.set(0, type);
+
+            return this;
+        }
+
+        public Builder val(Class type) {
+            if (types.get(1) != Object.class && types.get(1) != type)
+                throw new IllegalStateException("Value type is already set.");
+
+            types.set(1, type);
+
+            return this;
+        }
+
+        public Builder field(String name, Class type) {
+            if (!fields.add(name))
+                throw new IllegalStateException("Field name must be unique.");
+
+            types.add(type);
+
+            return this;
+        }
+
+        public Builder keyField(String name, Class type) {
+            if (!fields.add(name))
+                throw new IllegalStateException("Field name must be unique.");
+
+            types.add(type);
+
+            keyFields.set(types.size() - 1);
+
+            return this;
+        }
+
+        public Builder keyField(String name, Class type, boolean affinityKey) {
+            if (affinityKey && this.affinityKey > 0)
+                throw new IllegalStateException("Affinity key field must be unique.");
+
+            if (!fields.add(name))
+                throw new IllegalStateException("Field name must be unique.");
+
+            types.add(type);
+
+            keyFields.set(types.size() - 1);
+
+            if (affinityKey)
+                this.affinityKey = types.size() - 1;
+
+            return this;
+        }
+
+        public RowType build() {
+            return new RowType(fields.toArray(new String[0]), types.toArray(new Class[0]), keyFields, affinityKey);
+        }
+    }
+}
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
new file mode 100644
index 0000000..1eb5c76
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/Commons.java
@@ -0,0 +1,145 @@
+/*
+ * 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.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+import org.apache.calcite.plan.Context;
+import org.apache.calcite.plan.Contexts;
+import org.apache.calcite.plan.RelOptRule;
+import org.apache.calcite.plan.RelOptRuleOperand;
+import org.apache.calcite.rel.RelNode;
+import org.apache.ignite.internal.processors.query.GridQueryProperty;
+import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
+import org.apache.ignite.internal.processors.query.QueryContext;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PlannerContext;
+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.type.RowType;
+import org.apache.ignite.internal.util.typedef.F;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ *
+ */
+public final class Commons {
+    private Commons(){}
+
+    public static Context convert(QueryContext ctx) {
+        return ctx == null ? Contexts.empty() : Contexts.of(ctx.unwrap(Object[].class));
+    }
+
+    /** */
+    public static RowType rowType(GridQueryTypeDescriptor desc) {
+        RowType.Builder b = RowType.builder();
+
+        Map<String, Class<?>> fields = desc.fields();
+
+        b.key(desc.keyClass()).val(desc.valueClass());
+
+        for (Map.Entry<String, Class<?>> entry : fields.entrySet()) {
+            GridQueryProperty prop = desc.property(entry.getKey());
+
+            if (prop.key())
+                b.keyField(prop.name(), prop.type(), Objects.equals(desc.affinityKey(), prop.name()));
+            else
+                b.field(prop.name(), prop.type());
+        }
+
+        return b.build();
+    }
+
+    public static RelOptRuleOperand any(Class<? extends RelNode> first, Class<? extends RelNode> second) {
+        return RelOptRule.operand(first, RelOptRule.operand(second, RelOptRule.any()));
+    }
+
+    public static <T> List<T> intersect(List<T> left, List<T> right) {
+        if (F.isEmpty(left) || F.isEmpty(right))
+            return Collections.emptyList();
+        else if (left.size() > right.size())
+            return intersect0(right, left);
+        else
+            return intersect0(left, right);
+    }
+
+    public static <T> List<T> intersect0(List<T> left, List<T> right) {
+        List<T> res = new ArrayList<>(Math.min(left.size(), right.size()));
+        HashSet<T> set = new HashSet<>(left);
+
+        for (T t : right) {
+            if (set.contains(t))
+                res.add(t);
+        }
+
+        return res;
+    }
+
+    public static <T> List<T> concat(List<T> col, T... elements) {
+        ArrayList<T> res = new ArrayList<>(col.size() + elements.length);
+
+        res.addAll(col);
+        res.addAll(Arrays.asList(elements));
+
+        return res;
+    }
+
+    @SuppressWarnings("unchecked")
+    public static <T> List<T> cast(List<?> src) {
+        return (List)src;
+    }
+
+    public static <T,R> List<R> transform(@NotNull List<T> src, @NotNull Function<T,R> mapFun) {
+        List<R> list = new ArrayList<>(src.size());
+
+        for (T t : src)
+            list.add(mapFun.apply(t));
+
+        return list;
+    }
+
+    public static <T,R> Set<R> transform(@NotNull Set<T> src, @NotNull Function<T,R> mapFun) {
+        Set<R> set = new HashSet<>(src.size());
+
+        for (T t : src)
+            set.add(mapFun.apply(t));
+
+        return set;
+    }
+
+    public static PlannerContext plannerContext(RelNode rel) {
+        return plannerContext(rel.getCluster().getPlanner().getContext());
+    }
+
+    public static PlannerContext plannerContext(Context ctx) {
+        return Objects.requireNonNull(ctx.unwrap(PlannerContext.class));
+    }
+
+    public static IgniteRel igniteRel(RelNode rel) {
+        if (rel.getConvention() != IgniteConvention.INSTANCE)
+            throw new AssertionError("Unexpected node: " + rel);
+
+        return (IgniteRel) rel;
+    }
+}
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
new file mode 100644
index 0000000..5104816
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteMethod.java
@@ -0,0 +1,42 @@
+/*
+ * 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.util;
+
+import java.lang.reflect.Method;
+import org.apache.calcite.linq4j.tree.Types;
+import org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMetadata.DerivedDistribution;
+import org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMetadata.FragmentMetadata;
+
+/**
+ *
+ */
+public enum IgniteMethod {
+    DERIVED_DISTRIBUTIONS(DerivedDistribution.class, "deriveDistributions"),
+    FRAGMENT_INFO(FragmentMetadata.class, "getFragmentInfo");
+
+    private final Method method;
+
+    IgniteMethod(Class clazz, String methodName, Class... argumentTypes) {
+        method = Types.lookupMethod(clazz, methodName, argumentTypes);
+    }
+
+    /** */
+    public Method method() {
+        return method;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/ListFieldsQueryCursor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/ListFieldsQueryCursor.java
new file mode 100644
index 0000000..c46e762
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/ListFieldsQueryCursor.java
@@ -0,0 +1,92 @@
+/*
+ * 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.util;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+import org.apache.calcite.linq4j.Enumerable;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.ignite.cache.query.FieldsQueryCursor;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ *
+ */
+public class ListFieldsQueryCursor<T> implements FieldsQueryCursor<List<?>> {
+    /** */
+    private final RelDataType rowType;
+
+    /** */
+    private final Enumerable<T> enumerable;
+
+    /** */
+    private final Function<T, List<?>> converter;
+
+    /** */
+    private Iterator<T> it;
+
+    /**
+     * @param rowType Row data type description.
+     * @param enumerable Rows source.
+     * @param converter Row converter.
+     */
+    public ListFieldsQueryCursor(RelDataType rowType, Enumerable<T> enumerable, Function<T, List<?>> converter) {
+        this.rowType = rowType;
+        this.enumerable = enumerable;
+        this.converter = converter;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getFieldName(int idx) {
+        return rowType.getFieldList().get(idx).getName();
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getColumnsCount() {
+        return rowType.getFieldCount();
+    }
+
+    /** {@inheritDoc} */
+    @Override public List<List<?>> getAll() {
+        return StreamSupport.stream(enumerable.spliterator(), false)
+            .map(converter)
+            .collect(Collectors.toList());
+    }
+
+    /** {@inheritDoc} */
+    @Override public void close() {
+        closeIterator();
+    }
+
+    /** {@inheritDoc} */
+    @NotNull @Override public Iterator<List<?>> iterator() {
+        closeIterator();
+
+        return F.iterator(it = enumerable.iterator(), converter::apply, true);
+    }
+
+    private void closeIterator() {
+        if (it instanceof AutoCloseable)
+            U.closeQuiet((AutoCloseable)it);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/TableScanIterator.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/TableScanIterator.java
new file mode 100644
index 0000000..64f05dc
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/TableScanIterator.java
@@ -0,0 +1,149 @@
+/*
+ * 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.util;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.processors.cache.IgniteCacheOffheapManager;
+import org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtLocalPartition;
+import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
+import org.apache.ignite.internal.processors.query.IgniteSQLException;
+import org.apache.ignite.internal.util.GridCloseableIteratorAdapter;
+import org.apache.ignite.internal.util.lang.GridCursor;
+
+import static org.apache.ignite.internal.processors.cache.GridCacheUtils.UNDEFINED_CACHE_ID;
+
+/**
+ *
+ */
+public class TableScanIterator<T> extends GridCloseableIteratorAdapter<T> {
+    private final int cacheId;
+    private final Iterator<GridDhtLocalPartition> parts;
+    private final Function<CacheDataRow, T> typeWrapper;
+    private final Predicate<CacheDataRow> typeFilter;
+
+    /**
+     *
+     */
+    private GridCursor<? extends CacheDataRow> cur;
+    /**
+     *
+     */
+    private GridDhtLocalPartition curPart;
+
+    /**
+     *
+     */
+    private T next;
+
+    public TableScanIterator(int cacheId, Iterator<GridDhtLocalPartition> parts, Function<CacheDataRow, T> typeWrapper,
+        Predicate<CacheDataRow> typeFilter) {
+        this.cacheId = cacheId;
+        this.parts = parts;
+        this.typeWrapper = typeWrapper;
+        this.typeFilter = typeFilter;
+    }
+
+    @Override
+    protected T onNext() {
+        if (next == null)
+            throw new NoSuchElementException();
+
+        T next = this.next;
+
+        this.next = null;
+
+        return next;
+    }
+
+    @Override
+    protected boolean onHasNext() throws IgniteCheckedException {
+        if (next != null)
+            return true;
+
+        while (true) {
+            if (cur == null) {
+                if (parts.hasNext()) {
+                    GridDhtLocalPartition part = parts.next();
+
+                    if (!reservePartition(part))
+                        throw new IgniteSQLException("Failed to reserve partition, please retry on stable topology.");
+
+                    IgniteCacheOffheapManager.CacheDataStore ds = part.dataStore();
+
+                    cur = cacheId == UNDEFINED_CACHE_ID ? ds.cursor() : ds.cursor(cacheId);
+                } else
+                    break;
+            }
+
+            if (cur.next()) {
+                CacheDataRow row = cur.get();
+
+                if (!typeFilter.test(row))
+                    continue;
+
+                next = typeWrapper.apply(row);
+
+                break;
+            } else {
+                cur = null;
+
+                releaseCurrentPartition();
+            }
+        }
+
+        return next != null;
+    }
+
+    /**
+     *
+     */
+    private void releaseCurrentPartition() {
+        GridDhtLocalPartition p = curPart;
+
+        assert p != null;
+
+        curPart = null;
+
+        p.release();
+    }
+
+    /**
+     *
+     */
+    private boolean reservePartition(GridDhtLocalPartition p) {
+        if (p != null && p.reserve()) {
+            curPart = p;
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override protected void onClose() {
+        if (curPart != null)
+            releaseCurrentPartition();
+    }
+}
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessorTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessorTest.java
new file mode 100644
index 0000000..f0afa1a
--- /dev/null
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessorTest.java
@@ -0,0 +1,1203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite;
+
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import org.apache.calcite.DataContext;
+import org.apache.calcite.linq4j.Enumerable;
+import org.apache.calcite.linq4j.Linq4j;
+import org.apache.calcite.plan.Context;
+import org.apache.calcite.plan.Contexts;
+import org.apache.calcite.plan.ConventionTraitDef;
+import org.apache.calcite.plan.RelTraitDef;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.RelRoot;
+import org.apache.calcite.rel.core.Project;
+import org.apache.calcite.schema.SchemaPlus;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.tools.Frameworks;
+import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.processors.query.calcite.exec.ConsumerNode;
+import org.apache.ignite.internal.processors.query.calcite.exec.Implementor;
+import org.apache.ignite.internal.processors.query.calcite.exec.Node;
+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.prepare.ContextValue;
+import org.apache.ignite.internal.processors.query.calcite.prepare.DataContextImpl;
+import org.apache.ignite.internal.processors.query.calcite.prepare.IgnitePlanner;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PlannerContext;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PlannerPhase;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PlannerType;
+import org.apache.ignite.internal.processors.query.calcite.prepare.Query;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
+import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
+import org.apache.ignite.internal.processors.query.calcite.schema.IgniteTable;
+import org.apache.ignite.internal.processors.query.calcite.serialize.expression.Expression;
+import org.apache.ignite.internal.processors.query.calcite.serialize.expression.RexToExpTranslator;
+import org.apache.ignite.internal.processors.query.calcite.serialize.relation.RelGraph;
+import org.apache.ignite.internal.processors.query.calcite.serialize.relation.RelToGraphConverter;
+import org.apache.ignite.internal.processors.query.calcite.splitter.QueryPlan;
+import org.apache.ignite.internal.processors.query.calcite.splitter.Splitter;
+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.RowType;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.CU;
+import org.apache.ignite.marshaller.jdk.JdkMarshaller;
+import org.apache.ignite.testframework.junits.GridTestKernalContext;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.apache.ignite.internal.processors.query.calcite.util.Commons.igniteRel;
+
+/**
+ *
+ */
+//@WithSystemProperty(key = "calcite.debug", value = "true")
+public class CalciteQueryProcessorTest extends GridCommonAbstractTest {
+
+    private GridTestKernalContext kernalContext;
+    private CalciteQueryProcessor proc;
+    private SchemaPlus schema;
+    private List<UUID> nodes;
+
+    private TestIgniteTable city;
+    private TestIgniteTable country;
+    private TestIgniteTable project;
+    private TestIgniteTable developer;
+
+    @Before
+    public void setup() {
+        kernalContext = new GridTestKernalContext(log);
+        proc = new CalciteQueryProcessor();
+        proc.setLogger(log);
+        proc.start(kernalContext);
+
+        IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
+
+        developer = new TestIgniteTable("Developer", "Developer",
+            RowType.builder()
+                .keyField("id", Integer.class, true)
+                .field("name", String.class)
+                .field("projectId", Integer.class)
+                .field("cityId", Integer.class)
+                .build(), Arrays.asList(
+            new Object[]{0, null, 0, "Igor", 0, 1},
+            new Object[]{1, null, 1, "Roman", 0, 0}
+        ));
+
+        project = new TestIgniteTable("Project", "Project",
+            RowType.builder()
+                .keyField("id", Integer.class, true)
+                .field("name", String.class)
+                .field("ver", Integer.class)
+                .build(), Arrays.asList(
+            new Object[]{0, null, 0, "Calcite", 1},
+            new Object[]{1, null, 1, "Ignite", 1}
+        ));
+
+        country = new TestIgniteTable("Country", "Country",
+            RowType.builder()
+                .keyField("id", Integer.class, true)
+                .field("name", String.class)
+                .field("countryCode", Integer.class)
+                .build(), Arrays.<Object[]>asList(
+            new Object[]{0, null, 0, "Russia", 7}
+        ));
+
+        city = new TestIgniteTable("City", "City",
+            RowType.builder()
+                .keyField("id", Integer.class, true)
+                .field("name", String.class)
+                .field("countryId", Integer.class)
+                .build(), Arrays.asList(
+            new Object[]{0, null, 0, "Moscow", 0},
+            new Object[]{1, null, 1, "Saint Petersburg", 0}
+        ));
+
+        publicSchema.addTable(developer);
+        publicSchema.addTable(project);
+        publicSchema.addTable(country);
+        publicSchema.addTable(city);
+
+        schema = Frameworks
+            .createRootSchema(false)
+            .add("PUBLIC", publicSchema);
+
+        nodes = new ArrayList<>(4);
+
+        for (int i = 0; i < 4; i++) {
+            nodes.add(UUID.randomUUID());
+        }
+    }
+
+    @Test
+    public void testLogicalPlan() throws Exception {
+        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 + 1" +
+            "WHERE (d.projectId + 1) > ?";
+
+        PlannerContext ctx = proc.context(Contexts.empty(), sql, new Object[]{2}, this::context);
+
+        assertNotNull(ctx);
+
+        RelTraitDef[] traitDefs = {
+            ConventionTraitDef.INSTANCE
+        };
+
+        RelRoot relRoot;
+
+        try (IgnitePlanner planner = proc.planner(traitDefs, ctx)){
+            assertNotNull(planner);
+
+            Query query = ctx.query();
+
+            assertNotNull(query);
+
+            // Parse
+            SqlNode sqlNode = planner.parse(query.sql());
+
+            // Validate
+            sqlNode = planner.validate(sqlNode);
+
+            // Convert to Relational operators graph
+            relRoot = planner.rel(sqlNode);
+        }
+
+        assertNotNull(relRoot.rel);
+    }
+
+    @Test
+    public void testLogicalPlanDefaultSchema() throws Exception {
+        String sql = "SELECT d.id, d.name, d.projectId, p.id0, p.ver0 " +
+            "FROM Developer d JOIN (" +
+            "SELECT pp.id as id0, pp.ver as ver0 FROM Project pp" +
+            ") p " +
+            "ON d.projectId = p.id0 " +
+            "WHERE (d.projectId + 1) > ?";
+
+        PlannerContext ctx = proc.context(Contexts.empty(), sql, new Object[]{2}, this::context);
+
+        assertNotNull(ctx);
+
+        RelTraitDef[] traitDefs = {
+            ConventionTraitDef.INSTANCE
+        };
+
+        RelRoot relRoot;
+
+        try (IgnitePlanner planner = proc.planner(traitDefs, ctx)){
+            assertNotNull(planner);
+
+            Query query = ctx.query();
+
+            assertNotNull(query);
+
+            // Parse
+            SqlNode sqlNode = planner.parse(query.sql());
+
+            // Validate
+            sqlNode = planner.validate(sqlNode);
+
+            // Convert to Relational operators graph
+            relRoot = planner.rel(sqlNode);
+        }
+
+        assertNotNull(relRoot.rel);
+
+
+    }
+
+    @Test
+    public void testCorrelatedQuery() throws Exception {
+        String sql = "SELECT d.id, (SELECT p.name FROM Project p WHERE p.id = d.id) name, d.projectId " +
+            "FROM Developer d";
+
+        PlannerContext ctx = proc.context(Contexts.empty(), sql, new Object[]{2}, this::context);
+
+        assertNotNull(ctx);
+
+        RelTraitDef[] traitDefs = {
+            ConventionTraitDef.INSTANCE
+        };
+
+        RelRoot relRoot;
+
+        try (IgnitePlanner planner = proc.planner(traitDefs, ctx)){
+            assertNotNull(planner);
+
+            Query query = ctx.query();
+
+            assertNotNull(query);
+
+            // Parse
+            SqlNode sqlNode = planner.parse(query.sql());
+
+            // Validate
+            sqlNode = planner.validate(sqlNode);
+
+            // Convert to Relational operators graph
+            relRoot = planner.rel(sqlNode);
+        }
+
+        assertNotNull(relRoot.rel);
+    }
+
+    @Test
+    public void testHepPlaner() throws Exception {
+        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) > ?";
+
+        PlannerContext ctx = proc.context(Contexts.empty(), sql, new Object[]{2}, this::context);
+
+        assertNotNull(ctx);
+
+        RelTraitDef[] traitDefs = {
+            ConventionTraitDef.INSTANCE
+        };
+
+        RelRoot relRoot;
+
+        try (IgnitePlanner planner = proc.planner(traitDefs, ctx)){
+            assertNotNull(planner);
+
+            Query query = ctx.query();
+
+            assertNotNull(query);
+
+            // Parse
+            SqlNode sqlNode = planner.parse(query.sql());
+
+            // Validate
+            sqlNode = planner.validate(sqlNode);
+
+            // Convert to Relational operators graph
+            relRoot = planner.rel(sqlNode);
+
+            RelNode rel = relRoot.rel;
+
+            // Transformation chain
+            rel = planner.transform(PlannerType.HEP, PlannerPhase.SUBQUERY_REWRITE, rel, rel.getTraitSet());
+
+            relRoot = relRoot.withRel(rel).withKind(sqlNode.getKind());
+        }
+
+        assertNotNull(relRoot.rel);
+    }
+
+    @Test
+    public void testVolcanoPlanerDistributed() throws Exception {
+        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) > ?";
+
+        PlannerContext ctx = proc.context(Contexts.empty(), sql, new Object[]{2}, this::context);
+
+        assertNotNull(ctx);
+
+        RelTraitDef[] traitDefs = {
+            DistributionTraitDef.INSTANCE,
+            ConventionTraitDef.INSTANCE
+        };
+
+        RelRoot relRoot;
+
+        try (IgnitePlanner planner = proc.planner(traitDefs, ctx)){
+            assertNotNull(planner);
+
+            Query query = ctx.query();
+
+            assertNotNull(query);
+
+            // Parse
+            SqlNode sqlNode = planner.parse(query.sql());
+
+            // Validate
+            sqlNode = planner.validate(sqlNode);
+
+            // Convert to Relational operators graph
+            relRoot = planner.rel(sqlNode);
+
+            RelNode rel = relRoot.rel;
+
+            // Transformation chain
+            RelTraitSet desired = rel.getCluster().traitSet()
+                .replace(IgniteConvention.INSTANCE)
+                .replace(IgniteDistributions.single())
+                .simplify();
+
+            rel = planner.transform(PlannerType.VOLCANO, PlannerPhase.OPTIMIZATION, rel, desired);
+
+            relRoot = relRoot.withRel(rel).withKind(sqlNode.getKind());
+        }
+
+        assertNotNull(relRoot.rel);
+
+        RexToExpTranslator translator = new RexToExpTranslator();
+
+        Project proj = (Project) relRoot.rel.getInput(0);
+
+        List<Expression> expressions = translator.translate(proj.getProjects());
+
+        assertNotNull(expressions);
+    }
+
+    @Test
+    public void testPlanSerializationDeserialization() throws Exception {
+        String sql = "SELECT d.id, d.name, d.projectId, p.id0, p.ver0 " +
+            "FROM PUBLIC.Developer d JOIN (" +
+            "SELECT pp.id as id0, pp.ver as ver0 FROM PUBLIC.Project pp" +
+            ") p " +
+            "ON d.id = p.id0 " +
+            "WHERE (d.projectId + 1) > ?";
+
+        PlannerContext ctx = proc.context(Contexts.empty(), sql, new Object[]{2}, this::context);
+
+        assertNotNull(ctx);
+
+        RelTraitDef[] traitDefs = {
+            DistributionTraitDef.INSTANCE,
+            ConventionTraitDef.INSTANCE
+        };
+
+        byte[] convertedBytes;
+
+        try (IgnitePlanner planner = proc.planner(traitDefs, ctx)){
+            assertNotNull(planner);
+
+            Query query = ctx.query();
+
+            assertNotNull(planner);
+
+            // Parse
+            SqlNode sqlNode = planner.parse(query.sql());
+
+            // Validate
+            sqlNode = planner.validate(sqlNode);
+
+            // Convert to Relational operators graph
+            RelNode rel = planner.convert(sqlNode);
+
+            // Transformation chain
+            rel = planner.transform(PlannerType.HEP, PlannerPhase.SUBQUERY_REWRITE, rel, rel.getTraitSet());
+
+            RelTraitSet desired = rel.getCluster().traitSet()
+                .replace(IgniteConvention.INSTANCE)
+                .replace(IgniteDistributions.single())
+                .simplify();
+
+            rel = planner.transform(PlannerType.VOLCANO, PlannerPhase.OPTIMIZATION, rel, desired);
+
+            assertNotNull(rel);
+
+            QueryPlan plan = planner.plan(rel);
+
+            assertNotNull(plan);
+
+            assertTrue(plan.fragments().size() == 2);
+
+            plan.init(ctx);
+
+            RelGraph graph = new RelToGraphConverter().go(igniteRel(plan.fragments().get(1).root()));
+
+            convertedBytes = new JdkMarshaller().marshal(graph);
+
+            assertNotNull(convertedBytes);
+        }
+
+        try (IgnitePlanner planner = proc.planner(traitDefs, ctx)) {
+            assertNotNull(planner);
+
+            RelGraph graph = new JdkMarshaller().unmarshal(convertedBytes, getClass().getClassLoader());
+
+            assertNotNull(graph);
+
+            RelNode rel = planner.convert(graph);
+
+            assertNotNull(rel);
+        }
+    }
+
+    @Test
+    public void testSplitterCollocatedPartitionedPartitioned() throws Exception {
+        Object key = new Object();
+
+        developer.identityKey(key);
+        project.identityKey(key);
+
+        String sql = "SELECT d.id, d.name, d.projectId, p.id0, p.ver0 " +
+            "FROM PUBLIC.Developer d JOIN (" +
+            "SELECT pp.id as id0, pp.ver as ver0 FROM PUBLIC.Project pp" +
+            ") p " +
+            "ON d.id = p.id0 " +
+            "WHERE (d.projectId + 1) > ?";
+
+        PlannerContext ctx = proc.context(Contexts.empty(), sql, new Object[]{2}, this::context);
+
+        assertNotNull(ctx);
+
+        RelTraitDef[] traitDefs = {
+            DistributionTraitDef.INSTANCE,
+            ConventionTraitDef.INSTANCE
+        };
+
+        RelRoot relRoot;
+
+        try (IgnitePlanner planner = proc.planner(traitDefs, ctx)){
+            assertNotNull(planner);
+
+            Query query = ctx.query();
+
+            assertNotNull(planner);
+
+            // Parse
+            SqlNode sqlNode = planner.parse(query.sql());
+
+            // Validate
+            sqlNode = planner.validate(sqlNode);
+
+            // Convert to Relational operators graph
+            relRoot = planner.rel(sqlNode);
+
+            RelNode rel = relRoot.rel;
+
+            // Transformation chain
+            rel = planner.transform(PlannerType.HEP, PlannerPhase.SUBQUERY_REWRITE, rel, rel.getTraitSet());
+
+            RelTraitSet desired = rel.getCluster().traitSet()
+                .replace(IgniteConvention.INSTANCE)
+                .replace(IgniteDistributions.single())
+                .simplify();
+
+            rel = planner.transform(PlannerType.VOLCANO, PlannerPhase.OPTIMIZATION, rel, desired);
+
+            relRoot = relRoot.withRel(rel).withKind(sqlNode.getKind());
+        }
+
+        assertNotNull(relRoot);
+
+        QueryPlan plan = new Splitter().go(igniteRel(relRoot.rel));
+
+        assertNotNull(plan);
+
+        plan.init(ctx);
+
+        assertNotNull(plan);
+
+        assertTrue(plan.fragments().size() == 2);
+    }
+
+    @Test
+    public void testPhysicalPlan() throws Exception {
+        String sql = "SELECT d.id, d.name, d.projectId, p.name0, p.ver0 " +
+            "FROM PUBLIC.Developer d JOIN (" +
+            "SELECT pp.id as id0, pp.name as name0, pp.ver as ver0 FROM PUBLIC.Project pp" +
+            ") p " +
+            "ON d.projectId = p.id0 " +
+            "WHERE (d.projectId + 1) > ?";
+
+        PlannerContext ctx = proc.context(Contexts.empty(), sql, new Object[]{-10}, this::context);
+
+        assertNotNull(ctx);
+
+        RelTraitDef[] traitDefs = {
+            DistributionTraitDef.INSTANCE,
+            ConventionTraitDef.INSTANCE
+        };
+
+        try (IgnitePlanner planner = proc.planner(traitDefs, ctx)){
+            assertNotNull(planner);
+
+            Query query = ctx.query();
+
+            assertNotNull(planner);
+
+            // Parse
+            SqlNode sqlNode = planner.parse(query.sql());
+
+            // Validate
+            sqlNode = planner.validate(sqlNode);
+
+            // Convert to Relational operators graph
+            RelRoot relRoot = planner.rel(sqlNode);
+
+            RelNode rel = relRoot.rel;
+
+            // Transformation chain
+            rel = planner.transform(PlannerType.HEP, PlannerPhase.SUBQUERY_REWRITE, rel, rel.getTraitSet());
+
+            RelTraitSet desired = rel.getCluster().traitSetOf(IgniteConvention.INSTANCE);
+
+            RelNode phys = planner.transform(PlannerType.VOLCANO, PlannerPhase.OPTIMIZATION, rel, desired);
+
+            assertNotNull(phys);
+
+            Map<String, Object> params = ctx.query().params(F.asMap(ContextValue.QUERY_ID.valueName(), new GridCacheVersion()));
+
+            Implementor implementor = new Implementor(new DataContextImpl(params, ctx));
+
+            Node<Object[]> exec = implementor.go(igniteRel(phys));
+
+            assertNotNull(exec);
+
+            assertTrue(exec instanceof ConsumerNode);
+
+            ConsumerNode consumer = (ConsumerNode) exec;
+
+            assertTrue(consumer.hasNext());
+
+            ArrayList<Object[]> res = new ArrayList<>();
+
+            while (consumer.hasNext())
+                res.add(consumer.next());
+
+            assertFalse(res.isEmpty());
+
+            Assert.assertArrayEquals(new Object[]{0, "Igor", 0, "Calcite", 1}, res.get(0));
+            Assert.assertArrayEquals(new Object[]{1, "Roman", 0, "Calcite", 1}, res.get(1));
+        }
+    }
+
+    @Test
+    public void testSplitterCollocatedReplicatedReplicated() throws Exception {
+        String sql = "SELECT d.id, (d.id + 1) as id2, d.name, d.projectId, p.id0, p.ver0 " +
+            "FROM PUBLIC.Developer d JOIN (" +
+            "SELECT pp.id as id0, pp.ver as ver0 FROM PUBLIC.Project pp" +
+            ") p " +
+            "ON d.id = p.id0 " +
+            "WHERE (d.projectId + 1) > ?";
+
+        MappingService ms = new MappingService() {
+            @Override public NodesMapping random(AffinityTopologyVersion topVer) {
+                return new NodesMapping(select(nodes, 0,1,2,3), null, (byte) 0);
+            }
+
+            @Override public NodesMapping local() {
+                return new NodesMapping(select(nodes, 0), null, (byte) 0);
+            }
+
+            @Override public NodesMapping distributed(int cacheId, AffinityTopologyVersion topVer) {
+                if (cacheId == CU.cacheId("Developer"))
+                    return new NodesMapping(select(nodes, 0,1,2), null, NodesMapping.HAS_REPLICATED_CACHES);
+                if (cacheId == CU.cacheId("Project"))
+                    return new NodesMapping(select(nodes, 0,1,2), null, NodesMapping.HAS_REPLICATED_CACHES);
+
+                throw new AssertionError("Unexpected cache id:" + cacheId);
+            }
+        };
+
+        PlannerContext ctx = proc.context(Contexts.empty(), sql, new Object[]{2}, (c, q) -> context(c, q, ms));
+        assertNotNull(ctx);
+
+        RelTraitDef[] traitDefs = {
+            DistributionTraitDef.INSTANCE,
+            ConventionTraitDef.INSTANCE
+        };
+
+        RelRoot relRoot;
+
+        try (IgnitePlanner planner = proc.planner(traitDefs, ctx)){
+            assertNotNull(planner);
+
+            Query query = ctx.query();
+
+            assertNotNull(planner);
+
+            // Parse
+            SqlNode sqlNode = planner.parse(query.sql());
+
+            // Validate
+            sqlNode = planner.validate(sqlNode);
+
+            // Convert to Relational operators graph
+            relRoot = planner.rel(sqlNode);
+
+            RelNode rel = relRoot.rel;
+
+            // Transformation chain
+            rel = planner.transform(PlannerType.HEP, PlannerPhase.SUBQUERY_REWRITE, rel, rel.getTraitSet());
+
+            RelTraitSet desired = rel.getCluster().traitSet()
+                .replace(IgniteConvention.INSTANCE)
+                .replace(IgniteDistributions.single())
+                .simplify();
+
+            rel = planner.transform(PlannerType.VOLCANO, PlannerPhase.OPTIMIZATION, rel, desired);
+
+            relRoot = relRoot.withRel(rel).withKind(sqlNode.getKind());
+        }
+
+        assertNotNull(relRoot);
+
+        QueryPlan plan = new Splitter().go(igniteRel(relRoot.rel));
+
+        assertNotNull(plan);
+
+        plan.init(ctx);
+
+        assertNotNull(plan);
+
+        assertTrue(plan.fragments().size() == 2);
+    }
+
+    @Test
+    public void testSplitterCollocatedReplicatedAndPartitioned() throws Exception {
+        developer.identityKey(new Object());
+
+        String sql = "SELECT d.id, d.name, d.projectId, p.id0, p.ver0 " +
+            "FROM PUBLIC.Developer d JOIN (" +
+            "SELECT pp.id as id0, pp.ver as ver0 FROM PUBLIC.Project pp" +
+            ") p " +
+            "ON d.id = p.id0 " +
+            "WHERE (d.projectId + 1) > ?";
+
+        MappingService ms = new MappingService() {
+            @Override public NodesMapping random(AffinityTopologyVersion topVer) {
+                return new NodesMapping(select(nodes, 0,1,2,3), null, (byte) 0);
+            }
+
+            @Override public NodesMapping local() {
+                return new NodesMapping(select(nodes, 0), null, (byte) 0);
+            }
+
+            @Override public NodesMapping distributed(int cacheId, AffinityTopologyVersion topVer) {
+                if (cacheId == CU.cacheId("Developer"))
+                    return new NodesMapping(null, Arrays.asList(
+                        select(nodes, 0,1),
+                        select(nodes, 1,2),
+                        select(nodes, 2,0),
+                        select(nodes, 0,1),
+                        select(nodes, 1,2)
+                    ), NodesMapping.HAS_PARTITIONED_CACHES);
+                if (cacheId == CU.cacheId("Project"))
+                    return new NodesMapping(select(nodes, 0,1), null, (byte)(NodesMapping.HAS_REPLICATED_CACHES | NodesMapping.PARTIALLY_REPLICATED));
+
+                throw new AssertionError("Unexpected cache id:" + cacheId);
+            }
+        };
+
+        PlannerContext ctx = proc.context(Contexts.empty(), sql, new Object[]{2}, (c, q) -> context(c, q, ms));
+
+        assertNotNull(ctx);
+
+        RelTraitDef[] traitDefs = {
+            DistributionTraitDef.INSTANCE,
+            ConventionTraitDef.INSTANCE
+        };
+
+        RelRoot relRoot;
+
+        try (IgnitePlanner planner = proc.planner(traitDefs, ctx)){
+            assertNotNull(planner);
+
+            Query query = ctx.query();
+
+            assertNotNull(planner);
+
+            // Parse
+            SqlNode sqlNode = planner.parse(query.sql());
+
+            // Validate
+            sqlNode = planner.validate(sqlNode);
+
+            // Convert to Relational operators graph
+            relRoot = planner.rel(sqlNode);
+
+            RelNode rel = relRoot.rel;
+
+            // Transformation chain
+            rel = planner.transform(PlannerType.HEP, PlannerPhase.SUBQUERY_REWRITE, rel, rel.getTraitSet());
+
+            RelTraitSet desired = rel.getCluster().traitSet()
+                .replace(IgniteConvention.INSTANCE)
+                .replace(IgniteDistributions.single())
+                .simplify();
+
+            rel = planner.transform(PlannerType.VOLCANO, PlannerPhase.OPTIMIZATION, rel, desired);
+
+            relRoot = relRoot.withRel(rel).withKind(sqlNode.getKind());
+        }
+
+        assertNotNull(relRoot);
+
+        QueryPlan plan = new Splitter().go(igniteRel(relRoot.rel));
+
+        assertNotNull(plan);
+
+        plan.init(ctx);
+
+        assertNotNull(plan);
+
+        assertTrue(plan.fragments().size() == 2);
+    }
+
+    @Test
+    public void testSplitterPartiallyCollocated() throws Exception {
+        developer.identityKey(new Object());
+
+        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) > ?";
+
+        MappingService ms = new MappingService() {
+            @Override public NodesMapping random(AffinityTopologyVersion topVer) {
+                return new NodesMapping(select(nodes, 0,1,2,3), null, (byte) 0);
+            }
+
+            @Override public NodesMapping local() {
+                return new NodesMapping(select(nodes, 0), null, (byte) 0);
+            }
+
+            @Override public NodesMapping distributed(int cacheId, AffinityTopologyVersion topVer) {
+                if (cacheId == CU.cacheId("Developer"))
+                    return new NodesMapping(null, Arrays.asList(
+                        select(nodes, 1),
+                        select(nodes, 2),
+                        select(nodes, 2),
+                        select(nodes, 0),
+                        select(nodes, 1)
+                    ), NodesMapping.HAS_PARTITIONED_CACHES);
+                if (cacheId == CU.cacheId("Project"))
+                    return new NodesMapping(select(nodes, 0,1), null, (byte)(NodesMapping.HAS_REPLICATED_CACHES | NodesMapping.PARTIALLY_REPLICATED));
+
+                throw new AssertionError("Unexpected cache id:" + cacheId);
+            }
+        };
+
+        PlannerContext ctx = proc.context(Contexts.empty(), sql, new Object[]{2}, (c, q) -> context(c, q, ms));
+
+        assertNotNull(ctx);
+
+        RelTraitDef[] traitDefs = {
+            DistributionTraitDef.INSTANCE,
+            ConventionTraitDef.INSTANCE
+        };
+
+        RelRoot relRoot;
+
+        try (IgnitePlanner planner = proc.planner(traitDefs, ctx)){
+            assertNotNull(planner);
+
+            Query query = ctx.query();
+
+            assertNotNull(planner);
+
+            // Parse
+            SqlNode sqlNode = planner.parse(query.sql());
+
+            // Validate
+            sqlNode = planner.validate(sqlNode);
+
+            // Convert to Relational operators graph
+            relRoot = planner.rel(sqlNode);
+
+            RelNode rel = relRoot.rel;
+
+            // Transformation chain
+            rel = planner.transform(PlannerType.HEP, PlannerPhase.SUBQUERY_REWRITE, rel, rel.getTraitSet());
+
+            RelTraitSet desired = rel.getCluster().traitSet()
+                .replace(IgniteConvention.INSTANCE)
+                .replace(IgniteDistributions.single())
+                .simplify();
+
+            rel = planner.transform(PlannerType.VOLCANO, PlannerPhase.OPTIMIZATION, rel, desired);
+
+            relRoot = relRoot.withRel(rel).withKind(sqlNode.getKind());
+        }
+
+        assertNotNull(relRoot);
+
+        QueryPlan plan = new Splitter().go(igniteRel(relRoot.rel));
+
+        assertNotNull(plan);
+
+        plan.init(ctx);
+
+        assertNotNull(plan);
+
+        assertTrue(plan.fragments().size() == 3);
+    }
+
+    @Test
+    public void testSplitterNonCollocated() throws Exception {
+        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.ver0 " +
+            "WHERE (d.projectId + 1) > ?";
+
+        MappingService ms = new MappingService() {
+            @Override public NodesMapping random(AffinityTopologyVersion topVer) {
+                return new NodesMapping(select(nodes, 0,1,2,3), null, (byte) 0);
+            }
+
+            @Override public NodesMapping local() {
+                return new NodesMapping(select(nodes, 0), null, (byte) 0);
+            }
+
+            @Override public NodesMapping distributed(int cacheId, AffinityTopologyVersion topVer) {
+                if (cacheId == CU.cacheId("Developer"))
+                    return new NodesMapping(select(nodes, 2), null, (byte)(NodesMapping.HAS_REPLICATED_CACHES | NodesMapping.PARTIALLY_REPLICATED));
+
+                else if (cacheId == CU.cacheId("Project"))
+                    return new NodesMapping(select(nodes, 0,1), null, (byte)(NodesMapping.HAS_REPLICATED_CACHES | NodesMapping.PARTIALLY_REPLICATED));
+
+                throw new AssertionError("Unexpected cache id:" + cacheId);
+            }
+        };
+
+        PlannerContext ctx = proc.context(Contexts.empty(), sql, new Object[]{2}, (c, q) -> context(c, q, ms));
+
+        assertNotNull(ctx);
+
+        RelTraitDef[] traitDefs = {
+            DistributionTraitDef.INSTANCE,
+            ConventionTraitDef.INSTANCE
+        };
+
+        RelRoot relRoot;
+
+        try (IgnitePlanner planner = proc.planner(traitDefs, ctx)){
+            assertNotNull(planner);
+
+            Query query = ctx.query();
+
+            assertNotNull(planner);
+
+            // Parse
+            SqlNode sqlNode = planner.parse(query.sql());
+
+            // Validate
+            sqlNode = planner.validate(sqlNode);
+
+            // Convert to Relational operators graph
+            relRoot = planner.rel(sqlNode);
+
+            RelNode rel = relRoot.rel;
+
+            // Transformation chain
+            rel = planner.transform(PlannerType.HEP, PlannerPhase.SUBQUERY_REWRITE, rel, rel.getTraitSet());
+
+            RelTraitSet desired = rel.getCluster().traitSet()
+                .replace(IgniteConvention.INSTANCE)
+                .replace(IgniteDistributions.single())
+                .simplify();
+
+            rel = planner.transform(PlannerType.VOLCANO, PlannerPhase.OPTIMIZATION, rel, desired);
+
+            relRoot = relRoot.withRel(rel).withKind(sqlNode.getKind());
+        }
+
+        assertNotNull(relRoot);
+
+        QueryPlan plan = new Splitter().go(igniteRel(relRoot.rel));
+
+        assertNotNull(plan);
+
+        plan.init(ctx);
+
+        assertNotNull(plan);
+
+        assertTrue(plan.fragments().size() == 3);
+    }
+
+    @Test
+    public void testSplitterPartiallyReplicated1() throws Exception {
+        developer.identityKey(new Object());
+
+        String sql = "SELECT d.id, d.name, d.projectId, p.id0, p.ver0 " +
+            "FROM PUBLIC.Developer d JOIN (" +
+            "SELECT pp.id as id0, pp.ver as ver0 FROM PUBLIC.Project pp" +
+            ") p " +
+            "ON d.id = p.id0 " +
+            "WHERE (d.projectId + 1) > ?";
+
+        MappingService ms = new MappingService() {
+            @Override public NodesMapping random(AffinityTopologyVersion topVer) {
+                return new NodesMapping(select(nodes, 0,1,2,3), null, (byte) 0);
+            }
+
+            @Override public NodesMapping local() {
+                return new NodesMapping(select(nodes, 0), null, (byte) 0);
+            }
+
+            @Override public NodesMapping distributed(int cacheId, AffinityTopologyVersion topVer) {
+                if (cacheId == CU.cacheId("Developer"))
... 1036 lines suppressed ...