You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by al...@apache.org on 2022/03/24 14:42:03 UTC

[ignite] branch master updated: IGNITE-15436 Calcite-based SQL engine - Fixes #9855.

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 49e063c  IGNITE-15436 Calcite-based SQL engine - Fixes #9855.
49e063c is described below

commit 49e063cb3ce748d4f6caa1dea66ae1f4fd21997d
Author: Aleksey Plekhanov <pl...@gmail.com>
AuthorDate: Thu Mar 24 17:32:04 2022 +0300

    IGNITE-15436 Calcite-based SQL engine - Fixes #9855.
    
    Experimental, Apache Calcite based SQL engine:
    https://cwiki.apache.org/confluence/display/IGNITE/IEP-37%3A+New+query+execution+engine
    
    See modules/calcite/README.txt file for configuration instructions.
    
    Co-authored-by: Igor Seliverstov <gv...@gmail.com>
    Co-authored-by: rkondakov <ko...@mail.ru>
    Co-authored-by: Andrey V. Mashenkov <an...@gmail.com>
    Co-authored-by: Taras Ledkov <tl...@gridgain.com>
    Co-authored-by: korlov42 <ko...@gridgain.com>
    Co-authored-by: Evgeniy Stanilovskiy <st...@gmail.com>
    Co-authored-by: Yuriy Gerzhedovich <yg...@gridgain.com>
    Co-authored-by: Ivan Daschinskiy <iv...@apache.org>
    Co-authored-by: Berkof <sa...@mail.ru>
    Co-authored-by: Pavel Pereslegin <xx...@gmail.com>
    Co-authored-by: Vladimir Steshin <vl...@gmail.com>
    Co-authored-by: Vladimir Ermakov <85...@users.noreply.github.com>
    Signed-off-by: Aleksey Plekhanov <pl...@gmail.com>
---
 modules/benchmarks/pom.xml                         |    12 +
 .../benchmarks/jmh/sql/JmhSqlBenchmark.java        |   285 +
 modules/calcite/README.txt                         |    38 +
 modules/calcite/pom.xml                            |   312 +
 modules/calcite/src/main/codegen/config.fmpp       |   691 +
 .../src/main/codegen/includes/parserImpls.ftl      |   620 +
 .../apache/calcite/plan/volcano/VolcanoUtils.java} |    29 +-
 .../calcite/CalciteQueryEngineConfiguration.java}  |    46 +-
 .../org/apache/ignite/calcite/package-info.java    |    22 +
 .../query/calcite/CalciteQueryProcessor.java       |   442 +
 .../internal/processors/query/calcite/Query.java   |   242 +
 .../processors/query/calcite/QueryRegistry.java    |    54 +
 .../query/calcite/QueryRegistryImpl.java           |   108 +
 .../query/calcite/RemoteFragmentKey.java}          |    69 +-
 .../processors/query/calcite/RootQuery.java        |   405 +
 .../processors/query/calcite/RunningFragment.java  |    79 +
 .../query/calcite/exec/AbstractIndexScan.java      |   165 +
 .../query/calcite/exec/ArrayRowHandler.java        |    77 +
 .../calcite/exec/ClosableIteratorsHolder.java      |   165 +
 .../query/calcite/exec/ExchangeService.java        |    93 +
 .../query/calcite/exec/ExchangeServiceImpl.java    |   316 +
 .../calcite/exec/ExecutionCancelledException.java} |    28 +-
 .../query/calcite/exec/ExecutionContext.java       |   327 +
 .../query/calcite/exec/ExecutionService.java}      |    33 +-
 .../query/calcite/exec/ExecutionServiceImpl.java   |   757 +
 .../processors/query/calcite/exec/IndexScan.java   |   318 +
 .../query/calcite/exec/LogicalRelImplementor.java  |   775 +
 .../query/calcite/exec/MailboxRegistry.java        |   100 +
 .../query/calcite/exec/MailboxRegistryImpl.java    |   216 +
 .../query/calcite/exec/QueryTaskExecutor.java}     |    29 +-
 .../query/calcite/exec/QueryTaskExecutorImpl.java  |   113 +
 .../processors/query/calcite/exec/RowHandler.java  |    75 +
 .../query/calcite/exec/RuntimeHashIndex.java       |   150 +
 .../query/calcite/exec/RuntimeIndex.java}          |    28 +-
 .../query/calcite/exec/RuntimeSortedIndex.java     |   210 +
 .../query/calcite/exec/SystemViewScan.java         |   148 +
 .../query/calcite/exec/TableFunctionScan.java      |    46 +
 .../processors/query/calcite/exec/TableScan.java   |   282 +
 .../processors/query/calcite/exec/TreeIndex.java}  |    33 +-
 .../query/calcite/exec/ddl/DdlCommandHandler.java  |   403 +
 .../calcite/exec/ddl/NativeCommandHandler.java     |    51 +
 .../query/calcite/exec/ddl/SchemaManager.java      |    85 +
 .../query/calcite/exec/exp/BiScalar.java           |    27 +
 .../query/calcite/exec/exp/CallImplementor.java    |    42 +
 .../query/calcite/exec/exp/ConverterUtils.java     |   458 +
 .../query/calcite/exec/exp/ExpressionFactory.java  |   113 +
 .../calcite/exec/exp/ExpressionFactoryImpl.java    |   687 +
 .../calcite/exec/exp/IgniteScalarFunction.java     |    67 +
 .../query/calcite/exec/exp/IgniteSqlFunctions.java |   255 +
 .../calcite/exec/exp/ImplementableFunction.java    |    36 +
 .../query/calcite/exec/exp/NotNullImplementor.java |    43 +
 .../exec/exp/ReflectiveCallNotNullImplementor.java |    82 +
 .../query/calcite/exec/exp/RexExecutorImpl.java    |   193 +
 .../query/calcite/exec/exp/RexImpTable.java        |  2576 ++
 .../query/calcite/exec/exp/RexToLixTranslator.java |  1374 +
 .../processors/query/calcite/exec/exp/Scalar.java  |    22 +
 .../query/calcite/exec/exp/SingleScalar.java}      |    31 +-
 .../exec/exp/TableFunctionCallImplementor.java     |    44 +
 .../query/calcite/exec/exp/agg/Accumulator.java}   |    44 +-
 .../calcite/exec/exp/agg/AccumulatorWrapper.java}  |    31 +-
 .../query/calcite/exec/exp/agg/Accumulators.java   |  1020 +
 .../calcite/exec/exp/agg/AccumulatorsFactory.java  |   334 +
 .../query/calcite/exec/exp/agg/AggregateType.java} |    30 +-
 .../query/calcite/exec/exp/agg/GroupKey.java       |   100 +
 .../query/calcite/exec/rel/AbstractNode.java       |   205 +
 .../query/calcite/exec/rel/AbstractSetOpNode.java  |   366 +
 .../exec/rel/CorrelatedNestedLoopJoinNode.java     |   496 +
 .../query/calcite/exec/rel/Downstream.java}        |    36 +-
 .../query/calcite/exec/rel/FilterNode.java         |   144 +
 .../query/calcite/exec/rel/HashAggregateNode.java  |   383 +
 .../processors/query/calcite/exec/rel/Inbox.java   |   536 +
 .../query/calcite/exec/rel/IndexSpoolNode.java     |   206 +
 .../query/calcite/exec/rel/IntersectNode.java      |   118 +
 .../query/calcite/exec/rel/LimitNode.java          |   135 +
 .../query/calcite/exec/rel/Mailbox.java}           |    38 +-
 .../query/calcite/exec/rel/MergeJoinNode.java      |  1138 +
 .../query/calcite/exec/rel/MinusNode.java          |   102 +
 .../query/calcite/exec/rel/ModifyNode.java         |   327 +
 .../query/calcite/exec/rel/NestedLoopJoinNode.java |   823 +
 .../processors/query/calcite/exec/rel/Node.java    |    81 +
 .../processors/query/calcite/exec/rel/Outbox.java  |   373 +
 .../query/calcite/exec/rel/ProjectNode.java        |    82 +
 .../query/calcite/exec/rel/RootNode.java           |   269 +
 .../query/calcite/exec/rel/ScanNode.java           |   144 +
 .../query/calcite/exec/rel/SingleNode.java}        |    32 +-
 .../query/calcite/exec/rel/SortAggregateNode.java  |   313 +
 .../query/calcite/exec/rel/SortNode.java           |   147 +
 .../query/calcite/exec/rel/TableSpoolNode.java     |   158 +
 .../query/calcite/exec/rel/UnionAllNode.java       |    96 +
 .../query/calcite/externalize/RelInputEx.java      |    29 +
 .../query/calcite/externalize/RelJson.java         |   949 +
 .../query/calcite/externalize/RelJsonReader.java   |   334 +
 .../query/calcite/externalize/RelJsonWriter.java   |   172 +
 .../query/calcite/message/CalciteMessage.java}     |    34 +-
 .../calcite/message/CalciteMessageFactory.java}    |    36 +-
 .../query/calcite/message/ErrorMessage.java}       |   114 +-
 .../calcite/message/ExecutionContextAware.java}    |    29 +-
 .../query/calcite/message/GenericValueMessage.java |   117 +
 .../query/calcite/message/InboxCloseMessage.java   |   153 +
 .../query/calcite/message/MarshalableMessage.java} |    31 +-
 .../query/calcite/message/MarshallingContext.java  |    58 +
 .../query/calcite/message/MessageListener.java}    |    27 +-
 .../query/calcite/message/MessageService.java}     |    40 +-
 .../query/calcite/message/MessageServiceImpl.java  |   282 +
 .../query/calcite/message/MessageType.java         |    89 +
 .../message/QueryBatchAcknowledgeMessage.java      |   174 +
 .../query/calcite/message/QueryBatchMessage.java   |   267 +
 .../query/calcite/message/QueryCloseMessage.java   |   101 +
 .../query/calcite/message/QueryStartRequest.java   |   296 +
 .../query/calcite/message/QueryStartResponse.java} |   126 +-
 .../query/calcite/message/ValueMessage.java}       |    36 +-
 .../query/calcite/metadata/AffinityService.java}   |    28 +-
 .../calcite/metadata/AffinityServiceImpl.java      |    63 +
 .../query/calcite/metadata/ColocationGroup.java    |   532 +
 .../metadata/ColocationMappingException.java}      |    26 +-
 .../calcite/metadata/FragmentDescription.java      |   221 +
 .../query/calcite/metadata/FragmentMapping.java    |   217 +
 .../calcite/metadata/FragmentMappingException.java |    58 +
 .../query/calcite/metadata/IgniteMdCollation.java  |   408 +
 .../calcite/metadata/IgniteMdColumnOrigins.java    |   386 +
 .../calcite/metadata/IgniteMdCumulativeCost.java   |   113 +
 .../calcite/metadata/IgniteMdDistinctRowCount.java |    53 +
 .../calcite/metadata/IgniteMdDistribution.java     |   111 +
 .../calcite/metadata/IgniteMdFragmentMapping.java  |   226 +
 .../metadata/IgniteMdNonCumulativeCost.java        |    46 +
 .../metadata/IgniteMdPercentageOriginalRows.java   |   161 +
 .../query/calcite/metadata/IgniteMdPredicates.java |    50 +
 .../query/calcite/metadata/IgniteMdRowCount.java   |   146 +
 .../calcite/metadata/IgniteMdSelectivity.java      |   676 +
 .../query/calcite/metadata/IgniteMetadata.java     |    73 +
 .../query/calcite/metadata/MappingService.java     |    43 +
 .../query/calcite/metadata/MappingServiceImpl.java |    78 +
 .../calcite/metadata/NodeMappingException.java}    |    36 +-
 .../query/calcite/metadata/RelMetadataQueryEx.java |   104 +
 .../query/calcite/metadata/RemoteException.java}   |    49 +-
 .../query/calcite/metadata/cost/IgniteCost.java    |   232 +
 .../calcite/metadata/cost/IgniteCostFactory.java   |   101 +
 .../calcite/prepare/AbstractMultiStepPlan.java     |   105 +
 .../calcite/prepare/AbstractQueryContext.java}     |    33 +-
 .../query/calcite/prepare/BaseDataContext.java     |    90 +
 .../query/calcite/prepare/BaseQueryContext.java    |   322 +
 .../processors/query/calcite/prepare/CacheKey.java |    78 +
 .../calcite/prepare/CalciteQueryFieldMetadata.java |   129 +
 .../processors/query/calcite/prepare/Cloner.java   |   249 +
 .../processors/query/calcite/prepare/DdlPlan.java} |    42 +-
 .../query/calcite/prepare/ExecutionPlan.java}      |    41 +-
 .../query/calcite/prepare/ExplainPlan.java}        |    53 +-
 .../query/calcite/prepare/FieldsMetadata.java      |    75 +
 .../query/calcite/prepare/FieldsMetadataImpl.java} |    42 +-
 .../processors/query/calcite/prepare/Fragment.java |   181 +
 .../query/calcite/prepare/FragmentPlan.java}       |    47 +-
 .../query/calcite/prepare/FragmentSplitter.java    |   143 +
 .../query/calcite/prepare/IdGenerator.java}        |    30 +-
 .../calcite/prepare/IgniteConvertletTable.java     |   133 +
 .../query/calcite/prepare/IgnitePlanner.java       |   539 +
 .../query/calcite/prepare/IgnitePrograms.java      |    77 +
 .../query/calcite/prepare/IgniteRelShuttle.java    |   215 +
 .../calcite/prepare/IgniteSqlToRelConvertor.java   |   168 +
 .../query/calcite/prepare/IgniteSqlValidator.java  |   483 +
 .../query/calcite/prepare/IgniteTypeCoercion.java  |   120 +
 .../query/calcite/prepare/MappingQueryContext.java |    68 +
 .../query/calcite/prepare/MultiStepDmlPlan.java}   |    36 +-
 .../query/calcite/prepare/MultiStepPlan.java       |    59 +
 .../query/calcite/prepare/MultiStepQueryPlan.java} |    36 +-
 .../query/calcite/prepare/PlannerHelper.java       |   236 +
 .../query/calcite/prepare/PlannerPhase.java        |   278 +
 .../query/calcite/prepare/PlanningContext.java     |   277 +
 .../query/calcite/prepare/PrepareService.java}     |    27 +-
 .../query/calcite/prepare/PrepareServiceImpl.java  |   199 +
 .../query/calcite/prepare/QueryPlan.java}          |    30 +-
 .../query/calcite/prepare/QueryPlanCache.java}     |    36 +-
 .../query/calcite/prepare/QueryPlanCacheImpl.java  |   157 +
 .../query/calcite/prepare/QueryTemplate.java       |   139 +
 .../processors/query/calcite/prepare/Splitter.java |   124 +
 .../query/calcite/prepare/ValidationResult.java    |    70 +
 .../prepare/ddl/AbstractAlterTableCommand.java     |    74 +
 .../calcite/prepare/ddl/AlterTableAddCommand.java  |    61 +
 .../calcite/prepare/ddl/AlterTableDropCommand.java |    61 +
 .../calcite/prepare/ddl/ColumnDefinition.java      |    83 +
 .../calcite/prepare/ddl/CreateTableCommand.java    |   321 +
 .../query/calcite/prepare/ddl/DdlCommand.java}     |    29 +-
 .../prepare/ddl/DdlSqlToCommandConverter.java      |   473 +
 .../calcite/prepare/ddl/DropTableCommand.java      |    74 +
 .../calcite/prepare/ddl/NativeCommandWrapper.java} |    33 +-
 .../prepare/ddl/SqlToNativeCommandConverter.java   |   229 +
 .../calcite/prepare/ddl/TransactionCommand.java}   |    27 +-
 .../query/calcite/rel/AbstractIgniteJoin.java      |   317 +
 .../query/calcite/rel/AbstractIndexScan.java       |   166 +
 .../query/calcite/rel/IgniteAggregate.java         |   120 +
 .../query/calcite/rel/IgniteConvention.java}       |    46 +-
 .../rel/IgniteCorrelatedNestedLoopJoin.java        |   246 +
 .../query/calcite/rel/IgniteExchange.java          |    98 +
 .../processors/query/calcite/rel/IgniteFilter.java |   139 +
 .../query/calcite/rel/IgniteHashIndexSpool.java    |   144 +
 .../query/calcite/rel/IgniteIndexScan.java         |   149 +
 .../processors/query/calcite/rel/IgniteLimit.java  |   198 +
 .../query/calcite/rel/IgniteMergeJoin.java         |   321 +
 .../query/calcite/rel/IgniteNestedLoopJoin.java    |   115 +
 .../query/calcite/rel/IgniteProject.java           |   222 +
 .../query/calcite/rel/IgniteReceiver.java          |   145 +
 .../processors/query/calcite/rel/IgniteRel.java    |    89 +
 .../query/calcite/rel/IgniteRelVisitor.java        |   173 +
 .../processors/query/calcite/rel/IgniteSender.java |   146 +
 .../processors/query/calcite/rel/IgniteSort.java   |   132 +
 .../query/calcite/rel/IgniteSortedIndexSpool.java  |   152 +
 .../query/calcite/rel/IgniteTableFunctionScan.java |    87 +
 .../query/calcite/rel/IgniteTableModify.java       |   105 +
 .../query/calcite/rel/IgniteTableScan.java         |   136 +
 .../query/calcite/rel/IgniteTableSpool.java        |    91 +
 .../query/calcite/rel/IgniteTrimExchange.java      |   119 +
 .../query/calcite/rel/IgniteUnionAll.java          |   146 +
 .../processors/query/calcite/rel/IgniteValues.java |    74 +
 .../rel/ProjectableFilterableTableScan.java        |   194 +
 .../query/calcite/rel/SourceAwareIgniteRel.java}   |    29 +-
 .../rel/agg/IgniteColocatedAggregateBase.java      |   109 +
 .../rel/agg/IgniteColocatedHashAggregate.java      |    71 +
 .../rel/agg/IgniteColocatedSortAggregate.java      |   106 +
 .../calcite/rel/agg/IgniteHashAggregateBase.java   |    46 +
 .../calcite/rel/agg/IgniteMapAggregateBase.java    |    94 +
 .../calcite/rel/agg/IgniteMapHashAggregate.java    |   103 +
 .../calcite/rel/agg/IgniteMapSortAggregate.java    |   140 +
 .../calcite/rel/agg/IgniteReduceAggregateBase.java |   174 +
 .../calcite/rel/agg/IgniteReduceHashAggregate.java |   116 +
 .../calcite/rel/agg/IgniteReduceSortAggregate.java |   126 +
 .../calcite/rel/agg/IgniteSortAggregateBase.java   |    90 +
 .../rel/logical/IgniteLogicalIndexScan.java        |    84 +
 .../rel/logical/IgniteLogicalTableScan.java        |    64 +
 .../calcite/rel/set/IgniteColocatedIntersect.java  |    67 +
 .../calcite/rel/set/IgniteColocatedMinus.java      |    67 +
 .../calcite/rel/set/IgniteColocatedSetOp.java      |   128 +
 .../query/calcite/rel/set/IgniteIntersect.java     |    67 +
 .../query/calcite/rel/set/IgniteMapIntersect.java  |    73 +
 .../query/calcite/rel/set/IgniteMapMinus.java      |    73 +
 .../query/calcite/rel/set/IgniteMapSetOp.java      |   107 +
 .../query/calcite/rel/set/IgniteMinus.java         |    70 +
 .../calcite/rel/set/IgniteReduceIntersect.java     |    89 +
 .../query/calcite/rel/set/IgniteReduceMinus.java   |    89 +
 .../query/calcite/rel/set/IgniteReduceSetOp.java   |    74 +
 .../query/calcite/rel/set/IgniteSetOp.java         |    77 +
 .../calcite/rule/AbstractIgniteConverterRule.java  |    55 +
 .../calcite/rule/CorrelateToNestedLoopRule.java    |    90 +
 .../calcite/rule/CorrelatedNestedLoopJoinRule.java |   149 +
 .../query/calcite/rule/FilterConverterRule.java    |    71 +
 .../rule/FilterSpoolMergeToHashIndexSpoolRule.java |   114 +
 .../FilterSpoolMergeToSortedIndexSpoolRule.java    |   164 +
 .../calcite/rule/HashAggregateConverterRule.java   |   117 +
 .../calcite/rule/LogicalScanConverterRule.java     |   198 +
 .../query/calcite/rule/MergeJoinConverterRule.java |    73 +
 .../calcite/rule/NestedLoopJoinConverterRule.java  |    56 +
 .../query/calcite/rule/ProjectConverterRule.java   |    69 +
 .../query/calcite/rule/SetOpConverterRule.java     |   185 +
 .../calcite/rule/SortAggregateConverterRule.java   |   142 +
 .../query/calcite/rule/SortConverterRule.java      |    81 +
 .../rule/TableFunctionScanConverterRule.java       |    63 +
 .../calcite/rule/TableModifyConverterRule.java     |    60 +
 .../query/calcite/rule/UnionConverterRule.java     |    94 +
 .../query/calcite/rule/ValuesConverterRule.java    |    56 +
 .../calcite/rule/logical/ExposeIndexRule.java      |   103 +
 .../calcite/rule/logical/FilterScanMergeRule.java  |   184 +
 .../calcite/rule/logical/LogicalOrToUnionRule.java |   206 +
 .../calcite/rule/logical/ProjectScanMergeRule.java |   257 +
 .../calcite/rule/logical/RuleFactoryConfig.java    |    39 +
 .../query/calcite/rule/package-info.java}          |    27 +-
 .../calcite/schema/CacheColumnDescriptor.java      |    52 +
 .../query/calcite/schema/CacheIndexImpl.java       |   137 +
 .../query/calcite/schema/CacheTableDescriptor.java |    69 +
 .../calcite/schema/CacheTableDescriptorImpl.java   |   797 +
 .../query/calcite/schema/CacheTableImpl.java       |   192 +
 .../query/calcite/schema/ColumnDescriptor.java}    |    39 +-
 .../query/calcite/schema/IgniteCacheTable.java}    |    30 +-
 .../query/calcite/schema/IgniteIndex.java          |    89 +
 .../query/calcite/schema/IgniteSchema.java         |    91 +
 .../query/calcite/schema/IgniteStatisticsImpl.java |   139 +
 .../query/calcite/schema/IgniteTable.java          |   158 +
 .../query/calcite/schema/ModifyTuple.java}         |    55 +-
 .../query/calcite/schema/SchemaHolder.java}        |    26 +-
 .../query/calcite/schema/SchemaHolderImpl.java     |   324 +
 .../schema/SystemViewColumnDescriptor.java}        |    30 +-
 .../query/calcite/schema/SystemViewIndexImpl.java  |   115 +
 .../schema/SystemViewTableDescriptorImpl.java      |   277 +
 .../query/calcite/schema/SystemViewTableImpl.java  |   178 +
 .../query/calcite/schema/TableDescriptor.java      |   118 +
 .../calcite/sql/IgniteAbstractSqlAlterTable.java   |    84 +
 .../query/calcite/sql/IgniteSqlAlterTable.java     |    55 +
 .../calcite/sql/IgniteSqlAlterTableAddColumn.java  |    75 +
 .../calcite/sql/IgniteSqlAlterTableDropColumn.java |    75 +
 .../query/calcite/sql/IgniteSqlAlterUser.java      |    82 +
 .../query/calcite/sql/IgniteSqlCommit.java         |    51 +
 .../query/calcite/sql/IgniteSqlConformance.java    |    38 +
 .../query/calcite/sql/IgniteSqlCreateIndex.java    |   162 +
 .../query/calcite/sql/IgniteSqlCreateTable.java    |   134 +
 .../calcite/sql/IgniteSqlCreateTableOption.java    |   113 +
 .../sql/IgniteSqlCreateTableOptionEnum.java        |    55 +
 .../query/calcite/sql/IgniteSqlCreateUser.java     |    82 +
 .../query/calcite/sql/IgniteSqlDropIndex.java      |    76 +
 .../query/calcite/sql/IgniteSqlDropUser.java       |    66 +
 .../calcite/sql/IgniteSqlIntervalTypeNameSpec.java |    62 +
 .../query/calcite/sql/IgniteSqlKill.java           |   110 +
 .../query/calcite/sql/IgniteSqlRollback.java       |    51 +
 .../calcite/sql/fun/IgniteOwnSqlOperatorTable.java |    92 +
 .../calcite/sql/fun/IgniteStdSqlOperatorTable.java |   291 +
 .../calcite/sql/fun/SqlSystemRangeFunction.java    |    60 +
 .../calcite/sql/generated/IgniteSqlParserImpl.java | 33498 +++++++++++++++
 .../generated/IgniteSqlParserImplConstants.java    |  1584 +
 .../generated/IgniteSqlParserImplTokenManager.java | 26455 ++++++++++++
 .../calcite/sql/generated/ParseException.java      |   192 +
 .../calcite/sql/generated/SimpleCharStream.java    |   439 +
 .../query/calcite/sql/generated/Token.java         |    81 +
 .../query/calcite/sql/generated/TokenMgrError.java |   133 +
 .../query/calcite/sql/generated/package-info.java  |    28 +
 .../calcite/sql/kill/IgniteSqlKillComputeTask.java |    68 +
 .../sql/kill/IgniteSqlKillContinuousQuery.java     |    82 +
 .../query/calcite/sql/kill/IgniteSqlKillQuery.java |    98 +
 .../calcite/sql/kill/IgniteSqlKillScanQuery.java   |    95 +
 .../calcite/sql/kill/IgniteSqlKillService.java     |    68 +
 .../calcite/sql/kill/IgniteSqlKillTransaction.java |    68 +
 .../query/calcite/trait/AffinityAdapter.java       |    49 +
 .../processors/query/calcite/trait/AllNodes.java}  |    38 +-
 .../query/calcite/trait/CorrelationTrait.java      |   122 +
 .../query/calcite/trait/CorrelationTraitDef.java   |    53 +
 .../query/calcite/trait/Destination.java}          |    31 +-
 .../query/calcite/trait/DistributionFunction.java  |   319 +
 .../query/calcite/trait/DistributionTrait.java     |   185 +
 .../query/calcite/trait/DistributionTraitDef.java  |    55 +
 .../query/calcite/trait/IgniteDistribution.java    |    51 +
 .../query/calcite/trait/IgniteDistributions.java   |   122 +
 .../query/calcite/trait/Partitioned.java}          |    60 +-
 .../query/calcite/trait/RandomNode.java}           |    51 +-
 .../query/calcite/trait/RelFactory.java}           |    31 +-
 .../query/calcite/trait/RewindabilityTrait.java    |   102 +
 .../query/calcite/trait/RewindabilityTraitDef.java |    53 +
 .../processors/query/calcite/trait/TraitUtils.java |   561 +
 .../query/calcite/trait/TraitsAwareIgniteRel.java  |   156 +
 .../query/calcite/type/IgniteTypeFactory.java      |   309 +
 .../query/calcite/type/IgniteTypeSystem.java       |   104 +
 .../processors/query/calcite/type/UuidType.java}   |    49 +-
 .../query/calcite/util/AbstractService.java}       |    38 +-
 .../processors/query/calcite/util/Commons.java     |   447 +
 .../calcite/util/ConvertingClosableIterator.java   |    72 +
 .../processors/query/calcite/util/HintUtils.java   |    60 +
 .../query/calcite/util/IgniteMethod.java           |   101 +
 .../query/calcite/util/IgniteResource.java         |    67 +
 .../query/calcite/util/IndexConditions.java        |   142 +
 .../query/calcite/util/LifecycleAware.java}        |    31 +-
 .../query/calcite/util/ListFieldsQueryCursor.java  |   115 +
 .../processors/query/calcite/util/PlanUtils.java   |    72 +
 .../processors/query/calcite/util/RexUtils.java    |   566 +
 .../processors/query/calcite/util/Service.java}    |    31 +-
 .../processors/query/calcite/util/TypeUtils.java   |   398 +
 .../query/calcite/CalciteQueryProcessorTest.java   |  1329 +
 .../processors/query/calcite/CancelTest.java       |   258 +
 .../processors/query/calcite/DataTypesTest.java    |   348 +
 .../processors/query/calcite/DateTimeTest.java     |   268 +
 .../processors/query/calcite/FunctionsTest.java    |   317 +
 .../processors/query/calcite/LimitOffsetTest.java  |   349 +
 .../processors/query/calcite/QueryChecker.java     |   490 +
 .../processors/query/calcite/QueryCheckerTest.java |    61 +
 .../query/calcite/SqlFieldsQueryUsageTest.java     |   107 +
 .../query/calcite/StdSqlOperatorsTest.java         |   335 +
 .../processors/query/calcite/TestUtils.java}       |    33 +-
 .../query/calcite/UnstableTopologyTest.java        |   206 +
 .../calcite/exec/ClosableIteratorsHolderTest.java  |   109 +
 .../calcite/exec/LogicalRelImplementorTest.java    |   329 +
 .../query/calcite/exec/RuntimeSortedIndexTest.java |   196 +
 .../calcite/exec/exp/IgniteSqlFunctionsTest.java   |   139 +
 .../calcite/exec/rel/AbstractExecutionTest.java    |   465 +
 .../exec/rel/AbstractSetOpExecutionTest.java       |   195 +
 .../query/calcite/exec/rel/BaseAggregateTest.java  |   692 +
 .../calcite/exec/rel/ContinuousExecutionTest.java  |   184 +
 .../query/calcite/exec/rel/ExecutionTest.java      |   693 +
 .../exec/rel/HashAggregateExecutionTest.java       |   208 +
 .../rel/HashAggregateSingleGroupExecutionTest.java |   530 +
 .../exec/rel/HashIndexSpoolExecutionTest.java      |   205 +
 .../calcite/exec/rel/IntersectExecutionTest.java   |    82 +
 .../query/calcite/exec/rel/LimitExecutionTest.java |   113 +
 .../calcite/exec/rel/MergeJoinExecutionTest.java   |   452 +
 .../query/calcite/exec/rel/MinusExecutionTest.java |    82 +
 .../exec/rel/NestedLoopJoinExecutionTest.java      |   380 +
 .../exec/rel/SortAggregateExecutionTest.java       |   146 +
 .../exec/rel/SortedIndexSpoolExecutionTest.java    |   242 +
 .../calcite/exec/rel/TableSpoolExecutionTest.java  |   170 +
 .../integration/AbstractBasicIntegrationTest.java  |   258 +
 .../integration/AbstractDdlIntegrationTest.java    |    73 +
 .../integration/AggregatesIntegrationTest.java     |   197 +
 .../CalciteBasicSecondaryIndexIntegrationTest.java |  1183 +
 .../CalciteErrorHandlilngIntegrationTest.java      |   242 +
 .../integration/CorrelatesIntegrationTest.java     |    79 +
 .../integration/HashSpoolIntegrationTest.java      |    83 +
 .../integration/IndexDdlIntegrationTest.java       |   200 +
 .../integration/IndexRebuildIntegrationTest.java   |   316 +
 .../integration/IndexScanlIntegrationTest.java     |    92 +
 .../integration/IndexSpoolIntegrationTest.java     |   179 +
 .../query/calcite/integration/IntervalTest.java    |   379 +
 .../calcite/integration/JoinIntegrationTest.java   |   863 +
 .../integration/KillCommandDdlIntegrationTest.java |   315 +
 .../KillQueryCommandDdlIntegrationTest.java        |   165 +
 .../integration/MetadataIntegrationTest.java       |    75 +
 .../QueryEngineConfigurationIntegrationTest.java   |   251 +
 .../integration/RunningQueriesIntegrationTest.java |   333 +
 .../ServerStatisticsIntegrationTest.java           |   616 +
 .../calcite/integration/SetOpIntegrationTest.java  |   469 +
 .../integration/SortAggregateIntegrationTest.java  |   176 +
 .../integration/SystemViewsIntegrationTest.java    |   149 +
 .../integration/TableDdlIntegrationTest.java       |   792 +
 .../integration/TableDmlIntegrationTest.java       |   545 +
 .../integration/UserDdlIntegrationTest.java        |    98 +
 .../UserDefinedFunctionsIntegrationTest.java       |   169 +
 .../query/calcite/jdbc/JdbcCrossEngineTest.java    |   208 +
 .../query/calcite/jdbc/JdbcQueryTest.java          |   198 +
 .../logical/ScriptRunnerTestsEnvironment.java      |    58 +
 .../query/calcite/logical/ScriptTestRunner.java    |   271 +
 .../query/calcite/logical/SqlScriptRunner.java     |   759 +
 .../query/calcite/message/TestIoManager.java}      |    34 +-
 .../planner/AbstractAggregatePlannerTest.java      |    87 +
 .../query/calcite/planner/AbstractPlannerTest.java |   878 +
 .../planner/AggregateDistinctPlannerTest.java      |   147 +
 .../calcite/planner/AggregatePlannerTest.java      |   462 +
 .../CorrelatedNestedLoopJoinPlannerTest.java       |   170 +
 .../planner/CorrelatedSubqueryPlannerTest.java     |   267 +
 .../calcite/planner/HashAggregatePlannerTest.java  |   140 +
 .../calcite/planner/HashIndexSpoolPlannerTest.java |   230 +
 .../calcite/planner/IndexRebuildPlannerTest.java   |    93 +
 .../calcite/planner/JoinColocationPlannerTest.java |   201 +
 .../calcite/planner/JoinCommutePlannerTest.java    |   214 +
 .../calcite/planner/JoinWithUsingPlannerTest.java  |   185 +
 .../calcite/planner/LimitOffsetPlannerTest.java    |   189 +
 .../calcite/planner/MergeJoinPlannerTest.java      |  2799 ++
 .../query/calcite/planner/PlannerTest.java         |  1302 +
 .../query/calcite/planner/PlannerTimeoutTest.java  |   114 +
 .../planner/ProjectFilterScanMergePlannerTest.java |   247 +
 .../query/calcite/planner/SetOpPlannerTest.java    |   496 +
 .../calcite/planner/SortAggregatePlannerTest.java  |   284 +
 .../planner/SortedIndexSpoolPlannerTest.java       |   272 +
 .../calcite/planner/StatisticsPlannerTest.java     |   457 +
 .../query/calcite/planner/TableDmlPlannerTest.java |   172 +
 .../calcite/planner/TableFunctionPlannerTest.java  |   110 +
 .../calcite/planner/TableSpoolPlannerTest.java     |    86 +
 .../query/calcite/planner/TestTable.java           |   259 +
 .../query/calcite/planner/UnionPlannerTest.java    |   163 +
 .../query/calcite/rules/JoinCommuteRulesTest.java  |   112 +
 .../query/calcite/rules/OrToUnionRuleTest.java     |   313 +
 .../calcite/rules/ProjectScanMergeRuleTest.java    |   173 +
 .../query/calcite/sql/SqlCustomParserTest.java     |   908 +
 .../ignite/testsuites/ExecutionTestSuite.java      |    58 +
 .../ignite/testsuites/IgniteCalciteTestSuite.java  |    46 +
 .../ignite/testsuites/IntegrationTestSuite.java    |   106 +
 .../apache/ignite/testsuites/PlannerTestSuite.java |    76 +
 .../apache/ignite/testsuites/ScriptTestSuite.java  |    77 +
 .../sql/aggregate/aggregates/test_aggr_string.test |    82 +
 .../aggregate/aggregates/test_aggregate_types.test |   176 +
 .../aggregates/test_aggregate_types.test_ignore    |   191 +
 .../aggregates/test_aggregate_types_scalar.test    |    93 +
 .../test_aggregate_types_scalar.test_ignored       |   111 +
 .../aggregates/test_approx_quantile.test_ignore    |   170 +
 .../test_approximate_distinct_count.test_ignore    |    93 +
 .../aggregates/test_arg_min_max.test_ignore        |   131 +
 .../test/sql/aggregate/aggregates/test_avg.test    |    40 +
 .../sql/aggregate/aggregates/test_avg.test_ignored |    54 +
 .../aggregate/aggregates/test_bit_and.test_ignore  |    57 +
 .../aggregate/aggregates/test_bit_or.test_ignore   |    57 +
 .../aggregate/aggregates/test_bit_xor.test_ignore  |    57 +
 .../test/sql/aggregate/aggregates/test_count.test  |    51 +
 .../sql/aggregate/aggregates/test_count_star.test  |    46 +
 .../aggregate/aggregates/test_covar.test_ignore    |   100 +
 .../aggregate/aggregates/test_distinct_aggr.test   |    21 +
 .../test_distinct_string_agg.test_ignore           |    17 +
 .../aggregate/aggregates/test_empty_aggregate.test |    29 +
 .../aggregates/test_empty_aggregate.test_ignore    |    31 +
 .../aggregates/test_group_by_many_groups.test_slow |    14 +
 .../aggregates/test_group_on_expression.test       |    75 +
 .../aggregates/test_histogram.test_ignore          |    94 +
 .../sql/aggregate/aggregates/test_mode.test_ignore |    97 +
 .../aggregate/aggregates/test_null_aggregates.test |   406 +
 .../sql/aggregate/aggregates/test_perfect_ht.test  |   168 +
 .../aggregates/test_perfect_ht.test_ignore         |   177 +
 .../aggregate/aggregates/test_quantile.test_ignore |   222 +
 .../aggregates/test_quantile_list.test_ignore      |   115 +
 .../aggregates/test_regression.test_ignore         |   347 +
 .../sql/aggregate/aggregates/test_scalar_aggr.test |    54 +
 .../aggregates/test_scalar_aggr.test_ignore        |    58 +
 .../aggregate/aggregates/test_stddev.test_ignore   |   123 +
 .../aggregates/test_string_agg.test_ignore         |    91 +
 .../aggregates/test_string_agg_big.test_ignore     |    17 +
 .../test_string_agg_many_groups.test_slow_ignore   |    23 +
 .../test/sql/aggregate/aggregates/test_sum.test    |    81 +
 .../test/sql/aggregate/distinct/test_distinct.test |    55 +
 .../distinct/test_distinct_on.test_ignore          |   133 +
 .../aggregate/distinct/test_distinct_order_by.test |    41 +
 .../test/sql/aggregate/group/test_group_by.test    |   160 +
 .../sql/aggregate/group/test_group_by.test_ignore  |   182 +
 .../sql/aggregate/group/test_group_by_alias.test   |    38 +
 .../group/test_group_by_alias.test_ignore          |    68 +
 .../group/test_group_by_large_string.test          |    16 +
 .../group/test_group_by_multi_column.test          |    17 +
 .../test/sql/aggregate/group/test_group_null.test  |    17 +
 .../test_corel_subquery_in_having.test_ignore      |    38 +
 .../src/test/sql/aggregate/having/test_having.test |    45 +
 .../having/test_scalar_having.test_ignore          |   119 +
 .../src/test/sql/cast/test_boolean_cast.test       |   130 +
 .../test/sql/cast/test_boolean_cast.test_ignore    |   160 +
 .../sql/cast/test_exponent_in_cast.test_ignore     |    45 +
 .../src/test/sql/cast/test_string_cast.test        |    55 +
 .../calcite/src/test/sql/cast/test_try_cast.test   |    22 +
 .../calcite/src/test/sql/delete/test_delete.test   |    33 +
 .../src/test/sql/delete/test_large_delete.test     |    15 +
 .../src/test/sql/filter/test_alias_filter.test     |    20 +
 .../test/sql/filter/test_constant_comparisons.test |   103 +
 .../filter/test_constant_comparisons.test_ignore   |    12 +
 .../src/test/sql/filter/test_filter_clause.test    |   460 +
 .../test/sql/filter/test_filter_clause.test_ignore |   528 +
 .../src/test/sql/filter/test_illegal_filters.test  |    17 +
 .../src/test/sql/filter/test_obsolete_filters.test |   271 +
 .../sql/filter/test_obsolete_filters.test_ignore   |   281 +
 .../test/sql/filter/test_transitive_filters.test   |   321 +
 .../calcite/src/test/sql/filter/test_zonemap.test  |    34 +
 .../calcite/src/test/sql/function/blob/base64.test |    27 +
 .../src/test/sql/function/date/date_part.test      |   189 +
 .../test/sql/function/date/date_part.test_ignore   |   214 +
 .../src/test/sql/function/date/test_date_part.test |    61 +
 .../sql/function/date/test_date_part.test_ignore   |   166 +
 .../src/test/sql/function/date/test_extract.test   |   111 +
 .../sql/function/date/test_extract.test_ignore     |   112 +
 .../sql/function/date/test_extract_edge_cases.test |    67 +
 .../date/test_extract_edge_cases.test_ignore       |   342 +
 .../test/sql/function/date/test_extract_month.test |   748 +
 .../test/sql/function/date/test_extract_year.test  |   653 +
 .../test/sql/function/generic/test_between.test    |   219 +
 .../src/test/sql/function/generic/test_case.test   |    93 +
 .../test/sql/function/generic/test_coalesce.test   |    52 +
 .../sql/function/generic/test_coalesce.test_ignore |    15 +
 .../src/test/sql/function/generic/test_decode.test |    24 +
 .../sql/function/generic/test_decode.test_ignore   |    20 +
 .../src/test/sql/function/generic/test_in.test     |   135 +
 .../sql/function/generic/test_large_in.test_ignore |    49 +
 .../sql/function/generic/test_least_greatest.test  |   118 +
 .../test/sql/function/generic/test_null_if.test    |    54 +
 .../src/test/sql/function/generic/test_nvl.test    |    29 +
 .../test/sql/function/interval/test_extract.test   |   233 +
 .../src/test/sql/function/json/test_json.test      |    28 +
 .../test/sql/function/numeric/test_floor_ceil.test |   140 +
 .../sql/function/numeric/test_invalid_math.test    |    37 +
 .../src/test/sql/function/numeric/test_mod.test    |    25 +
 .../sql/function/numeric/test_oracle_math.test     |    20 +
 .../test/sql/function/numeric/test_pg_math.test    |    86 +
 .../src/test/sql/function/numeric/test_pow.test    |    35 +
 .../src/test/sql/function/numeric/test_random.test |    31 +
 .../src/test/sql/function/numeric/test_round.test  |    55 +
 .../src/test/sql/function/numeric/test_trigo.test  |   445 +
 .../test/sql/function/numeric/test_truncate.test   |    44 +
 .../sql/function/numeric/test_type_resolution.test |   202 +
 .../src/test/sql/function/numeric/test_unary.test  |    61 +
 .../sql/function/operator/test_arithmetic.test     |   143 +
 .../operator/test_arithmetic_sqllogic.test         |    51 +
 .../sql/function/operator/test_comparison.test     |    42 +
 .../sql/function/operator/test_conjunction.test    |   189 +
 .../calcite/src/test/sql/function/string/md5.test  |    37 +
 .../sql/function/string/regex_filter_pushdown.test |    31 +
 .../sql/function/string/regex_replace.test_ignore  |    77 +
 .../src/test/sql/function/string/regex_search.test |   129 +
 .../sql/function/string/regex_search.test_ignore   |   143 +
 .../calcite/src/test/sql/function/string/sha1.test |     9 +
 .../src/test/sql/function/string/test_ascii.test   |    78 +
 .../test/sql/function/string/test_caseconvert.test |    69 +
 .../test/sql/function/string/test_char_length.test |    36 +
 .../function/string/test_char_length.test_ignore   |     9 +
 .../string/test_complex_unicode.test_ignore        |    83 +
 .../test/sql/function/string/test_compress.test    |    10 +
 .../src/test/sql/function/string/test_concat.test  |    75 +
 .../sql/function/string/test_concat_function.test  |    62 +
 .../test/sql/function/string/test_difference.test  |    40 +
 .../src/test/sql/function/string/test_initcap.test |    29 +
 .../src/test/sql/function/string/test_left.test    |    80 +
 .../test/sql/function/string/test_left.test_ignore |   102 +
 .../src/test/sql/function/string/test_length.test  |    26 +
 .../sql/function/string/test_length.test_ignore    |    27 +
 .../src/test/sql/function/string/test_like.test    |   285 +
 .../test/sql/function/string/test_like_escape.test |   100 +
 .../function/string/test_like_escape.test_ignore   |   106 +
 .../src/test/sql/function/string/test_overlay.test |    19 +
 .../test/sql/function/string/test_position.test    |    58 +
 .../src/test/sql/function/string/test_repeat.test  |    58 +
 .../src/test/sql/function/string/test_replace.test |    76 +
 .../src/test/sql/function/string/test_reverse.test |    49 +
 .../src/test/sql/function/string/test_right.test   |    63 +
 .../sql/function/string/test_right.test_ignore     |    84 +
 .../test/sql/function/string/test_similar_to.test  |   137 +
 .../src/test/sql/function/string/test_soundex.test |    28 +
 .../src/test/sql/function/string/test_space.test   |     9 +
 .../src/test/sql/function/string/test_strcmp.test  |    18 +
 .../test/sql/function/string/test_substring.test   |   191 +
 .../string/test_substring_utf8.test_ignore         |    57 +
 .../test/sql/function/string/test_translate.test   |    19 +
 .../src/test/sql/function/string/test_trim.test    |   106 +
 .../src/test/sql/function/time/test_extract.test   |    74 +
 .../sql/function/time/test_extract.test_ignore     |   102 +
 .../test/sql/function/timestamp/current_time.test  |     6 +
 .../test/sql/function/timestamp/test_extract.test  |    92 +
 .../function/timestamp/test_extract.test_ignore    |   100 +
 .../sql/function/timestamp/test_extract_ms.test    |    23 +
 .../function/timestamp/test_extract_ms.test_ignore |    35 +
 .../sql/function/timestamp/test_timestampadd.test  |    90 +
 .../sql/function/timestamp/test_timestampdiff.test |   138 +
 .../timestamp/test_timestampdiff.test_ignore       |    15 +
 .../src/test/sql/insert/test_big_insert.test       |    55 +
 .../src/test/sql/insert/test_insert.test_ignore    |    41 +
 .../src/test/sql/insert/test_insert_invalid.test   |    28 +
 .../src/test/sql/insert/test_insert_query.test     |    19 +
 .../src/test/sql/insert/test_insert_type.test      |    33 +
 .../sql/join/full_outer/test_full_outer_join.test  |    53 +
 .../full_outer/test_full_outer_join_complex.test   |    31 +
 .../test_full_outer_join_inequality.test           |    31 +
 .../test_full_outer_join_many_matches.test         |    21 +
 .../full_outer/test_full_outer_join_range.test     |    32 +
 .../test/sql/join/inner/join_cross_product.test    |    39 +
 .../src/test/sql/join/inner/test_eq_ineq_join.test |   131 +
 .../calcite/src/test/sql/join/inner/test_join.test |   100 +
 .../join/inner/test_join_duplicates.test_ignore    |    27 +
 .../sql/join/inner/test_join_types.test_ignore     |   402 +
 .../test/sql/join/inner/test_lt_join.test_ignore   |    31 +
 .../src/test/sql/join/inner/test_range_join.test   |    59 +
 .../src/test/sql/join/inner/test_unequal_join.test |    83 +
 .../inner/test_unequal_join_duplicates.test_ignore |    25 +
 .../src/test/sql/join/inner/test_using_chain.test  |    84 +
 .../src/test/sql/join/inner/test_using_join.test   |    69 +
 .../sql/join/inner/test_using_join.test_ignore     |    87 +
 .../src/test/sql/join/inner/test_varchar_join.test |    27 +
 .../sql/join/left_outer/left_join_issue_1172.test  |   120 +
 .../test/sql/join/left_outer/test_left_outer.test  |   180 +
 .../sql/join/mark/test_mark_join_types.test_ignore |   678 +
 .../src/test/sql/join/natural/natural_join.test    |    74 +
 .../test/sql/join/natural/natural_join.test_ignore |   122 +
 .../sql/join/right_outer/test_right_outer.test     |   153 +
 .../src/test/sql/join/test_complex_join_expr.test  |    37 +
 .../test_cross_product_parallelism.test_ignore     |    22 +
 .../src/test/sql/join/test_join_on_aggregates.test |    32 +
 .../src/test/sql/join/test_not_distinct_from.test  |   191 +
 .../sql/join/test_not_distinct_from.test_ignore    |   256 +
 modules/calcite/src/test/sql/order/test_limit.test |    45 +
 .../src/test/sql/order/test_limit.test_ignore      |   186 +
 .../src/test/sql/order/test_nulls_first.ignore     |    20 +
 .../src/test/sql/order/test_nulls_first.test       |    78 +
 .../calcite/src/test/sql/order/test_order_by.test  |   128 +
 .../src/test/sql/order/test_order_by.test_ignore   |   174 +
 .../test/sql/order/test_order_by_exceptions.test   |    60 +
 .../src/test/sql/order/test_order_large.test       |    12 +
 .../src/test/sql/order/test_order_pragma.test      |    28 +
 .../test/sql/order/test_order_same_value.test_slow |    79 +
 .../test_order_variable_size_payload.test_ignore   |   408 +
 modules/calcite/src/test/sql/order/test_top_n.test |    43 +
 .../src/test/sql/sqlite/aggregates/agg1.test       | 34403 ++++++++++++++++
 .../test/sql/sqlite/aggregates/agg1.test_ignored   | 34492 ++++++++++++++++
 .../src/test/sql/sqlite/aggregates/agg2.test       | 32481 +++++++++++++++
 .../test/sql/sqlite/aggregates/agg2.test_ignored   | 32616 +++++++++++++++
 .../src/test/sql/sqlite/aggregates/agg3.test       | 33678 +++++++++++++++
 .../test/sql/sqlite/aggregates/agg3.test_ignored   | 33723 +++++++++++++++
 .../src/test/sql/sqlite/aggregates/agg4.test       | 34042 ++++++++++++++++
 .../test/sql/sqlite/aggregates/agg4.test_ignored   | 34088 ++++++++++++++++
 .../calcite/src/test/sql/sqlite/join/join1.test    |   158 +
 .../src/test/sql/sqlite/orderby/orderby1_10_1.test |  5758 +++
 .../test/sql/sqlite/orderby/orderby1_10_10.test    |  7805 ++++
 .../src/test/sql/sqlite/orderby/orderby1_10_2.test |  5082 +++
 .../sql/sqlite/orderby/orderby1_10_2_long1.test    |   123 +
 .../sql/sqlite/orderby/orderby1_10_2_long2.test    |   133 +
 .../sql/sqlite/orderby/orderby1_10_2_long3.test    |   132 +
 .../sql/sqlite/orderby/orderby1_10_2_long4.test    |   135 +
 .../sql/sqlite/orderby/orderby1_10_2_long5.test    |   127 +
 .../sql/sqlite/orderby/orderby1_10_2_long6.test    |   125 +
 .../sql/sqlite/orderby/orderby1_10_2_long7.test    |   129 +
 .../src/test/sql/sqlite/orderby/orderby1_10_3.test |  6208 +++
 .../src/test/sql/sqlite/orderby/orderby1_10_4.test |  7477 ++++
 .../src/test/sql/sqlite/orderby/orderby1_10_5.test |  5472 +++
 .../src/test/sql/sqlite/orderby/orderby1_10_6.test |  6273 +++
 .../src/test/sql/sqlite/orderby/orderby1_10_7.test |  4841 +++
 .../src/test/sql/sqlite/orderby/orderby1_10_8.test |  5260 +++
 .../src/test/sql/sqlite/orderby/orderby1_10_9.test |  6575 +++
 .../test/sql/sqlite/select1/select1.test_native    | 12187 ++++++
 .../select1/select1_erroneous_res.test_ignored     |  2684 ++
 .../sql/sqlite/select1/select1_hashed_results.test |  7774 ++++
 .../test/sql/sqlite/select1/select1_results.test   |  1068 +
 .../test/sql/sqlite/select2/select2.test_native    | 11215 +++++
 .../select2_erroneous_hash_res.test_ignored        |  8490 ++++
 .../select2/select2_erroneous_res.test_ignored     |   223 +
 .../sql/sqlite/select2/select2_hashed_results.test |  1085 +
 .../test/sql/sqlite/select2/select2_results.test   |  1487 +
 .../test/sql/sqlite/select3/select3.test_native    | 40766 +++++++++++++++++++
 .../select3/select3_erroneous_hash_res.test_ignore | 19817 +++++++++
 .../select3/select3_erroneous_res.test_ignore      |  8962 ++++
 .../test/sql/sqlite/select3/select3_results.test   | 11906 ++++++
 .../test/sql/subquery/any_all/test_any_all.test    |    82 +
 .../any_all/test_correlated_any_all.test_ignore    |   182 +
 .../any_all/test_scalar_any_all.test_ignore        |    50 +
 .../test/sql/subquery/any_all/test_scalar_in.test  |    38 +
 .../subquery/any_all/test_scalar_in.test_ignore    |    77 +
 .../sql/subquery/any_all/test_simple_not_in.test   |    36 +
 .../any_all/test_uncorrelated_all_subquery.test    |   196 +
 .../test_uncorrelated_all_subquery.test_ignore     |   217 +
 .../any_all/test_uncorrelated_any_subquery.test    |   131 +
 .../subquery/exists/test_correlated_exists.test    |    67 +
 .../exists/test_correlated_exists.test_ignore      |    94 +
 .../sql/subquery/exists/test_scalar_exists.test    |    36 +
 .../exists/test_uncorrelated_exists_subquery.test  |   115 +
 .../sql/subquery/lateral/test_lateral_join.test    |    26 +
 .../scalar/test_complex_correlated_subquery.test   |    82 +
 .../test_complex_correlated_subquery.test_ignore   |   162 +
 .../test_complex_nested_correlated_subquery.test   |    27 +
 ..._complex_nested_correlated_subquery.test_ignore |    50 +
 .../scalar/test_correlated_aggregate_subquery.test |   110 +
 .../test_correlated_aggregate_subquery.test_ignore |   359 +
 .../subquery/scalar/test_correlated_subquery.test  |   137 +
 .../scalar/test_correlated_subquery_cte.test       |   123 +
 .../test_correlated_subquery_cte.test_ignore       |   133 +
 .../scalar/test_correlated_subquery_where.test     |    33 +
 .../subquery/scalar/test_count_star_subquery.test  |    20 +
 .../scalar/test_count_star_subquery.test_ignore    |   102 +
 .../sql/subquery/scalar/test_delete_subquery.test  |    34 +
 .../scalar/test_grouped_correlated_subquery.test   |    38 +
 .../test_grouped_correlated_subquery.test_ignore   |   137 +
 .../sql/subquery/scalar/test_join_in_subquery.test |    34 +
 .../test_many_correlated_columns.test_ignore       |    65 +
 .../test_nested_correlated_subquery.test_ignore    |   329 +
 .../sql/subquery/scalar/test_scalar_subquery.test  |   134 +
 .../subquery/scalar/test_scalar_subquery_cte.test  |   141 +
 .../scalar/test_tpcds_correlated_subquery.test     |    11 +
 .../test_tpcds_correlated_subquery.test_ignore     |    16 +
 .../scalar/test_uncorrelated_scalar_subquery.test  |    41 +
 .../test_uncorrelated_scalar_subquery.test_ignore  |   128 +
 .../scalar/test_uncorrelated_varchar_subquery.test |    67 +
 .../sql/subquery/scalar/test_update_subquery.test  |    61 +
 .../scalar/test_update_subquery.test_ignore        |    73 +
 .../test_varchar_correlated_subquery.test_ignore   |    93 +
 .../test_window_function_subquery.test_ignore      |    79 +
 .../sql/subquery/table/test_aliasing.test_ignore   |    24 +
 .../subquery/table/test_nested_table_subquery.test |    27 +
 .../sql/subquery/table/test_subquery_union.test    |    13 +
 .../sql/subquery/table/test_table_subquery.test    |    44 +
 .../subquery/table/test_table_subquery.test_ignore |    55 +
 .../src/test/sql/subquery/test_neumann.test        |    59 +
 .../calcite/src/test/sql/types/blob/test_blob.test |   108 +
 .../src/test/sql/types/blob/test_blob.test_ignore  |   106 +
 .../src/test/sql/types/blob/test_blob_cast.test    |    71 +
 .../test/sql/types/blob/test_blob_function.test    |    84 +
 .../test/sql/types/blob/test_blob_operator.test    |    92 +
 .../sql/types/blob/test_blob_operator.test_ignore  |    65 +
 .../src/test/sql/types/blob/test_blob_string.test  |    35 +
 .../test/sql/types/date/date_parsing.test_ignore   |   344 +
 .../test/sql/types/date/test_bc_dates.test_ignore  |    87 +
 .../src/test/sql/types/date/test_date.test_ignore  |    71 +
 .../test/sql/types/date/test_incorrect_dates.test  |    54 +
 .../test/sql/types/decimal/cast_from_decimal.test  |    72 +
 .../test/sql/types/decimal/cast_to_decimal.test    |   248 +
 .../test/sql/types/decimal/decimal_aggregates.test |    90 +
 .../test/sql/types/decimal/decimal_arithmetic.test |   197 +
 .../decimal/decimal_decimal_overflow_cast.test     |    94 +
 .../test/sql/types/decimal/decimal_overflow.test   |    51 +
 .../sql/types/decimal/decimal_overflow_table.test  |    39 +
 .../sql/types/decimal/large_decimal_constants.test |    43 +
 .../src/test/sql/types/decimal/test_decimal.test   |   151 +
 .../sql/types/decimal/test_decimal_cast.test_slow  |    87 +
 .../test/sql/types/decimal/test_decimal_ops.test   |   250 +
 .../sql/types/interval/interval_constants.test     |   152 +
 .../src/test/sql/types/interval/test_interval.test |   163 +
 .../sql/types/interval/test_interval_addition.test |   250 +
 .../types/interval/test_interval_comparison.test   |    31 +
 .../test/sql/types/interval/test_interval_ops.test |    51 +
 .../types/interval/test_interval_ops.test_ignore   |    52 +
 .../src/test/sql/types/list/array_agg.test_ignore  |    60 +
 .../src/test/sql/types/list/list.test_slow_ignore  |    12 +
 .../sql/types/list/list_aggregates.test_ignore     |    63 +
 .../src/test/sql/types/null/test_boolean_null.test |    36 +
 .../src/test/sql/types/null/test_is_null.test      |    24 +
 .../calcite/src/test/sql/types/null/test_null.test |    66 +
 .../src/test/sql/types/null/test_null_aggr.test    |    52 +
 .../test/sql/types/string/test_big_strings.test    |    29 +
 .../types/string/test_scan_big_varchar.test_slow   |   152 +
 .../sql/types/string/test_unicode.test_ignored     |    33 +
 .../src/test/sql/types/time/test_time.test_ignore  |    32 +
 .../test/sql/types/time/time_parsing.test_ignore   |    62 +
 .../sql/types/timestamp/bc_timestamp.test_ignore   |    19 +
 .../timestamp/test_incorrect_timestamp.test_ignore |    53 +
 .../sql/types/timestamp/test_timestamp.test_ignore |   144 +
 .../types/timestamp/test_timestamp_ms.test_ignore  |    15 +
 .../unsigned/test_unsigned_arithmetic.test_ignored |    99 +
 .../unsigned/test_unsigned_auto_cast.test_ignored  |   197 +
 .../unsigned/test_unsigned_conversion.test_ignored |   507 +
 .../src/test/sql/update/null_update_merge.test     |   128 +
 .../sql/update/test_big_string_update.test_ignore  |    96 +
 .../test/sql/update/test_null_update.test_ignore   |   194 +
 .../update/test_repeated_string_update.test_ignore |    62 +
 .../test/sql/update/test_string_update.test_ignore |    66 +
 .../test_string_update_many_strings.test_ignore    |   128 +
 .../sql/update/test_string_update_null.test_ignore |    40 +
 .../update/test_string_update_rollback.test_ignore |   118 +
 .../test_string_update_rollback_null.test_ignore   |    89 +
 .../src/test/sql/update/test_update.test_ignore    |    80 +
 .../test_update_delete_same_tuple.test_ignore      |    44 +
 .../test/sql/update/test_update_from.test_ignore   |   160 +
 .../update/test_update_many_updaters.test_ignore   |   316 +
 .../test_update_many_updaters_nulls.test_ignore    |   119 +
 .../test/sql/update/test_update_mix.test_ignore    |    75 +
 .../sql/update/test_update_same_value.test_ignore  |   180 +
 .../internal/jdbc2/JdbcMetadataSelfTest.java       |     8 +-
 .../ignite/jdbc/thin/JdbcThinMetadataSelfTest.java |    23 +-
 modules/codegen/pom.xml                            |     6 +
 .../ignite/codegen/MessageCodeGenerator.java       |     3 +
 .../SystemViewRowAttributeWalkerGenerator.java     |     2 +
 .../apache/ignite/util/SystemViewCommandTest.java  |     6 +-
 .../org/apache/ignite/IgniteSystemProperties.java  |    35 +
 .../configuration/QueryEngineConfiguration.java    |    44 +
 .../ignite/configuration/SqlConfiguration.java     |    32 +
 .../ignite/internal/GridKernalContextImpl.java     |     4 +-
 .../java/org/apache/ignite/internal/GridTopic.java |     5 +-
 .../ignite/internal/IgniteComponentType.java       |     9 +
 .../org/apache/ignite/internal/IgniteKernal.java   |     5 +
 .../apache/ignite/internal/QueryMXBeanImpl.java    |    25 +-
 .../internal/jdbc/thin/ConnectionProperties.java   |    12 +
 .../jdbc/thin/ConnectionPropertiesImpl.java        |    17 +-
 .../ignite/internal/jdbc/thin/JdbcThinTcpIo.java   |    13 +-
 .../managers/communication/GridIoManager.java      |     2 +
 .../managers/communication/GridIoPolicy.java       |     3 +
 .../processors/cache/IgniteCacheProxyImpl.java     |     4 +
 .../internal/processors/cache/QueryCursorImpl.java |     2 +-
 .../cache/persistence/metastorage/MetaStorage.java |    46 +-
 .../cache/query/IgniteQueryErrorCode.java          |     3 +
 .../processors/cache/query/QueryCursorEx.java      |     7 +
 .../odbc/jdbc/JdbcConnectionContext.java           |    33 +-
 .../processors/odbc/jdbc/JdbcQueryCursor.java      |    10 +-
 .../processors/odbc/jdbc/JdbcRequestHandler.java   |    27 +-
 .../odbc/odbc/OdbcConnectionContext.java           |    33 +-
 .../processors/odbc/odbc/OdbcQueryResults.java     |     4 +-
 .../processors/odbc/odbc/OdbcRequestHandler.java   |     6 +-
 .../processors/odbc/odbc/OdbcResultSet.java        |     8 +-
 .../internal/processors/odbc/odbc/OdbcUtils.java   |     4 +-
 .../cache/query/PlatformContinuousQueryImpl.java   |     8 +
 .../internal/processors/pool/PoolProcessor.java    |     4 +
 .../internal/processors/query/GridQueryCancel.java |     5 +
 .../processors/query/GridQueryIndexing.java        |    17 +-
 .../processors/query/GridQueryProcessor.java       |   196 +-
 .../processors/query/GridQuerySchemaManager.java   |    47 +
 .../processors/query/GridQueryTypeDescriptor.java  |    17 +
 .../processors/query/IndexingQueryEngine.java}     |    27 +-
 .../internal/processors/query/NoOpQueryEngine.java |    53 +
 .../internal/processors/query/QueryContext.java    |    74 +
 .../internal/processors/query/QueryEngine.java}    |    42 +-
 .../query/QueryEngineConfigurationEx.java}         |    29 +-
 .../internal/processors/query/QueryEntityEx.java   |    19 +
 .../processors/query/QueryHistoryTracker.java      |     2 +-
 .../internal/processors/query/QueryState.java}     |    37 +-
 .../query/QuerySysIndexDescriptorImpl.java         |    69 +
 .../processors/query/QueryTypeDescriptorImpl.java  |    19 +-
 .../internal/processors/query/QueryUtils.java      |    68 +-
 .../internal/processors/query/RunningQuery.java}   |    30 +-
 .../processors/query/RunningQueryManager.java      |   350 +-
 .../processors/query/SqlClientContext.java         |    26 +-
 .../query/schema/SchemaChangeListener.java         |   127 +
 .../query/stat/IgniteStatisticsManager.java        |     8 +
 .../processors/query/stat/StatisticsKey.java       |    12 +-
 .../stat/config/StatisticsColumnConfiguration.java |    24 +-
 .../stat/config/StatisticsObjectConfiguration.java |    11 +-
 .../GridInternalSubscriptionProcessor.java         |    16 +
 .../ignite/internal/sql/SqlCommandProcessor.java   |   364 +
 .../internal/sql/command/SqlAlterTableCommand.java |    18 +
 .../internal/sql/command/SqlAlterUserCommand.java  |    18 +
 .../sql/command/SqlCreateIndexCommand.java         |    36 +
 .../internal/sql/command/SqlCreateUserCommand.java |    18 +
 .../internal/sql/command/SqlDropIndexCommand.java  |    17 +
 .../internal/sql/command/SqlDropUserCommand.java   |    14 +
 .../sql/command/SqlKillComputeTaskCommand.java     |    14 +
 .../sql/command/SqlKillContinuousQueryCommand.java |    17 +
 .../internal/sql/command/SqlKillQueryCommand.java  |    20 +
 .../sql/command/SqlKillScanQueryCommand.java       |    18 +
 .../sql/command/SqlKillServiceCommand.java         |    14 +
 .../sql/command/SqlKillTransactionCommand.java     |    14 +
 .../apache/ignite/internal/util/IgniteUtils.java   |    13 +-
 .../internal/visor/query/VisorQueryCancelTask.java |     2 +-
 .../ignite/startup/cmdline/CommandLineStartup.java |     4 +
 .../apache/ignite/thread/SameThreadExecutor.java}  |    31 +-
 .../processors/query/DummyQueryIndexing.java       |    13 +-
 .../IndexingQueryEngineConfiguration.java}         |    44 +-
 .../org/apache/ignite/indexing/package-info.java   |    22 +
 .../managers/systemview/SystemViewLocal.java       |     5 +
 .../StatisticsColumnGlobalDataViewWalker.java      |    90 +
 .../metric/sql/MetricRegistryLocalSystemView.java  |     7 +
 .../processors/query/h2/CommandProcessor.java      |   669 +-
 .../internal/processors/query/h2/H2Utils.java      |     8 +
 .../processors/query/h2/IgniteH2Indexing.java      |    42 +-
 .../internal/processors/query/h2/QueryParser.java  |    56 +-
 .../processors/query/h2/SchemaManager.java         |   287 +-
 .../query/h2/database/H2PkHashIndex.java           |     1 -
 .../query/h2/database/H2TreeClientIndex.java       |     9 +
 .../processors/query/h2/database/H2TreeIndex.java  |    11 +-
 .../processors/query/h2/opt/GridH2IndexBase.java   |    15 +
 .../processors/query/h2/opt/GridH2Table.java       |    12 +-
 .../query/h2/sys/view/SqlSystemView.java           |     7 +
 .../h2/twostep/msg/GridH2ValueMessageFactory.java  |    11 +
 .../query/stat/IgniteGlobalStatisticsManager.java  |  1021 +
 .../stat/IgniteStatisticsConfigurationManager.java |    23 +-
 .../query/stat/IgniteStatisticsHelper.java         |    60 +
 .../query/stat/IgniteStatisticsManagerImpl.java    |   102 +-
 .../query/stat/IgniteStatisticsRepository.java     |    86 +-
 .../query/stat/ObjectStatisticsEvent.java          |    75 +
 .../query/stat/ObjectStatisticsImpl.java           |    16 +
 .../StatisticsAddressedRequest.java}               |    51 +-
 .../processors/query/stat/StatisticsProcessor.java |     4 +-
 .../processors/query/stat/StatisticsType.java      |     5 +-
 .../processors/query/stat/StatisticsUtils.java     |    63 +-
 .../query/stat/messages/StatisticsKeyMessage.java  |     6 +
 .../query/stat/messages/StatisticsRequest.java     |   243 +
 ...ticsKeyMessage.java => StatisticsResponse.java} |   107 +-
 .../stat/view/ColumnLocalDataViewSupplier.java     |     4 +-
 ...ew.java => StatisticsColumnGlobalDataView.java} |     7 +-
 .../stat/view/StatisticsColumnLocalDataView.java   |     2 +-
 .../cache/CacheSqlQueryValueCopySelfTest.java      |     2 +-
 .../processors/cache/index/BasicIndexTest.java     |     4 +-
 .../cache/metric/SqlViewExporterSpiTest.java       |     6 +-
 .../query/h2/TableStatisticsAbstractTest.java      |     2 +-
 .../stat/IgniteStatisticsRepositoryStaticTest.java |    84 +
 .../query/stat/IgniteStatisticsRepositoryTest.java |    13 +-
 ...cValueDistributionTableStatisticsUsageTest.java |     2 +-
 .../PSUCompositeIndexTableStatisticsUsageTest.java |    10 +-
 .../stat/PSUStatisticPartialGatheringTest.java     |     4 +-
 .../query/stat/PSUStatisticsStorageTest.java       |    12 +-
 ...UValueDistributionTableStatisticsUsageTest.java |     2 +-
 .../query/stat/SqlStatisticsCommandTests.java      |     5 +-
 .../query/stat/StatisticsAbstractTest.java         |   274 +-
 .../processors/query/stat/StatisticsClearTest.java |     2 +-
 .../query/stat/StatisticsConfigurationTest.java    |    22 +-
 .../query/stat/StatisticsGatheringTest.java        |    70 +-
 ....java => StatisticsGlobalViewInMemoryTest.java} |    15 +-
 ...va => StatisticsGlobalViewPersistenceTest.java} |    15 +-
 .../query/stat/StatisticsGlobalViewTest.java       |   160 +
 .../query/stat/StatisticsObsolescenceTest.java     |     2 +-
 .../query/stat/StatisticsRestartAbstractTest.java  |     5 +-
 .../query/stat/StatisticsStorageTest.java          |    10 +-
 .../query/stat/StatisticsTypesAbstractTest.java    |     2 +-
 .../processors/query/stat/StatisticsUtilsTest.java |   158 +
 .../query/stat/StatisticsViewsInMemoryTest.java    |    11 +-
 .../query/stat/StatisticsViewsPersistenceTest.java |    11 +-
 .../processors/query/stat/StatisticsViewsTest.java |   141 +-
 .../testsuites/IgniteStatisticsTestSuite.java      |     8 +
 .../apache/ignite/util/KillCommandsMXBeanTest.java |     7 +-
 .../cpp/common/include/ignite/common/utils.h       |    23 +
 modules/platforms/cpp/odbc-test/CMakeLists.txt     |     4 +-
 .../cpp/odbc-test/config/queries-default.xml       |    15 +
 .../cpp/odbc-test/src/configuration_test.cpp       |     9 +
 .../cpp/odbc-test/src/cross_engine_test.cpp        |   126 +
 modules/platforms/cpp/odbc/CMakeLists.txt          |     1 +
 .../include/ignite/odbc/config/configuration.h     |    32 +
 .../ignite/odbc/config/connection_string_parser.h  |     3 +
 .../cpp/odbc/include/ignite/odbc/engine_mode.h     |    73 +
 .../odbc/include/ignite/odbc/protocol_version.h    |     3 +
 .../odbc/system/ui/dsn_configuration_window.h      |     8 +
 .../win/src/system/ui/dsn_configuration_window.cpp |    60 +-
 .../cpp/odbc/src/config/configuration.cpp          |    26 +-
 .../odbc/src/config/connection_string_parser.cpp   |    18 +
 modules/platforms/cpp/odbc/src/dsn_config.cpp      |     5 +
 modules/platforms/cpp/odbc/src/engine_mode.cpp     |    77 +
 modules/platforms/cpp/odbc/src/message.cpp         |    10 +
 .../platforms/cpp/odbc/src/protocol_version.cpp    |     6 +-
 parent/pom.xml                                     |    13 +-
 pom.xml                                            |     1 +
 960 files changed, 644856 insertions(+), 2824 deletions(-)

diff --git a/modules/benchmarks/pom.xml b/modules/benchmarks/pom.xml
index 4d55310..93b305d 100644
--- a/modules/benchmarks/pom.xml
+++ b/modules/benchmarks/pom.xml
@@ -52,6 +52,18 @@
         </dependency>
 
         <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>ignite-calcite</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.javassist</groupId>
+            <artifactId>javassist</artifactId>
+            <version>${javassist.version}</version>
+        </dependency>
+
+        <dependency>
             <groupId>org.openjdk.jol</groupId>
             <artifactId>jol-core</artifactId>
             <version>0.9</version>
diff --git a/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/sql/JmhSqlBenchmark.java b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/sql/JmhSqlBenchmark.java
new file mode 100644
index 0000000..a4d231e
--- /dev/null
+++ b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/sql/JmhSqlBenchmark.java
@@ -0,0 +1,285 @@
+/*
+ * 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.benchmarks.jmh.sql;
+
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.IgniteDataStreamer;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction;
+import org.apache.ignite.cache.query.SqlFieldsQuery;
+import org.apache.ignite.cache.query.annotations.QuerySqlField;
+import org.apache.ignite.calcite.CalciteQueryEngineConfiguration;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.configuration.SqlConfiguration;
+import org.apache.ignite.indexing.IndexingQueryEngineConfiguration;
+import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+/**
+ * Benchmark simple SQL queries.
+ */
+@State(Scope.Benchmark)
+@Fork(1)
+@BenchmarkMode(Mode.Throughput)
+@OutputTimeUnit(TimeUnit.SECONDS)
+@Warmup(iterations = 3, time = 5)
+@Measurement(iterations = 3, time = 5)
+public class JmhSqlBenchmark {
+    /** Count of server nodes. */
+    private static final int SRV_NODES_CNT = 3;
+
+    /** Keys count. */
+    private static final int KEYS_CNT = 100000;
+
+    /** Size of batch. */
+    private static final int BATCH_SIZE = 1000;
+
+    /** Partitions count. */
+    private static final int PARTS_CNT = 1024;
+
+    /** IP finder shared across nodes. */
+    private static final TcpDiscoveryVmIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true);
+
+    /** Query engine. */
+    @Param({"H2", "CALCITE"})
+    private String engine;
+
+    /** Ignite client. */
+    private Ignite client;
+
+    /** Servers. */
+    private final Ignite[] servers = new Ignite[SRV_NODES_CNT];
+
+    /** Cache. */
+    private IgniteCache<Integer, Item> cache;
+
+    /**
+     * Create Ignite configuration.
+     *
+     * @param igniteInstanceName Ignite instance name.
+     * @return Configuration.
+     */
+    private IgniteConfiguration configuration(String igniteInstanceName) {
+        IgniteConfiguration cfg = new IgniteConfiguration();
+
+        cfg.setIgniteInstanceName(igniteInstanceName);
+        cfg.setLocalHost("127.0.0.1");
+        cfg.setDiscoverySpi(new TcpDiscoverySpi().setIpFinder(IP_FINDER));
+        cfg.setSqlConfiguration(new SqlConfiguration().setQueryEnginesConfiguration(
+            "CALCITE".equals(engine) ? new CalciteQueryEngineConfiguration() : new IndexingQueryEngineConfiguration()
+        ));
+
+        return cfg;
+    }
+
+    /**
+     * Initiate Ignite and caches.
+     */
+    @Setup(Level.Trial)
+    public void setup() {
+        for (int i = 0; i < SRV_NODES_CNT; i++)
+            servers[i] = Ignition.start(configuration("server" + i));
+
+        client = Ignition.start(configuration("client").setClientMode(true));
+
+        cache = client.getOrCreateCache(new CacheConfiguration<Integer, Item>("CACHE")
+            .setIndexedTypes(Integer.class, Item.class)
+            .setAffinity(new RendezvousAffinityFunction(false, PARTS_CNT))
+        );
+
+        try (IgniteDataStreamer<Integer, Item> ds = client.dataStreamer("CACHE")) {
+            for (int i = 0; i < KEYS_CNT; i++)
+                ds.addData(i, new Item(i));
+        }
+    }
+
+    /**
+     * Stop Ignite instance.
+     */
+    @TearDown
+    public void tearDown() {
+        client.close();
+
+        for (Ignite ignite : servers)
+            ignite.close();
+    }
+
+    /**
+     * Query unique value (full scan).
+     */
+    @Benchmark
+    public void querySimpleUnique() {
+        int key = ThreadLocalRandom.current().nextInt(KEYS_CNT);
+
+        List<?> res = executeSql("SELECT name FROM Item WHERE fld=?", key);
+
+        assert res.size() == 1;
+    }
+
+    /**
+     * Query unique value (indexed).
+     */
+    @Benchmark
+    public void querySimpleUniqueIndexed() {
+        int key = ThreadLocalRandom.current().nextInt(KEYS_CNT);
+
+        List<?> res = executeSql("SELECT name FROM Item WHERE fldIdx=?", key);
+
+        assert res.size() == 1;
+    }
+
+    /**
+     * Query batch (full scan).
+     */
+    @Benchmark
+    public void querySimpleBatch() {
+        int key = ThreadLocalRandom.current().nextInt(KEYS_CNT);
+
+        List<?> res = executeSql("SELECT name FROM Item WHERE fldBatch=?", key / BATCH_SIZE);
+
+        assert res.size() == BATCH_SIZE;
+    }
+
+    /**
+     * Query batch (indexed).
+     */
+    @Benchmark
+    public void querySimpleBatchIndexed() {
+        int key = ThreadLocalRandom.current().nextInt(KEYS_CNT);
+
+        List<?> res = executeSql("SELECT name FROM Item WHERE fldIdxBatch=?", key / BATCH_SIZE);
+
+        assert res.size() == BATCH_SIZE;
+    }
+
+    /**
+     * Query with group by and aggregate.
+     */
+    @Benchmark
+    public void queryGroupBy() {
+        List<?> res = executeSql("SELECT fldBatch, AVG(fld) FROM Item GROUP BY fldBatch");
+
+        assert res.size() == KEYS_CNT / BATCH_SIZE;
+    }
+
+    /**
+     * Query with indexed field group by and aggregate.
+     */
+    @Benchmark
+    public void queryGroupByIndexed() {
+        List<?> res = executeSql("SELECT fldIdxBatch, AVG(fld) FROM Item GROUP BY fldIdxBatch");
+
+        assert res.size() == KEYS_CNT / BATCH_SIZE;
+    }
+
+    /**
+     * Query with sorting (full set).
+     */
+    @Benchmark
+    public void queryOrderByFull() {
+        List<?> res = executeSql("SELECT name, fld FROM Item ORDER BY fld DESC");
+
+        assert res.size() == KEYS_CNT;
+    }
+
+    /**
+     * Query with sorting (batch).
+     */
+    @Benchmark
+    public void queryOrderByBatch() {
+        int key = ThreadLocalRandom.current().nextInt(KEYS_CNT);
+
+        List<?> res = executeSql("SELECT name, fld FROM Item WHERE fldIdxBatch=? ORDER BY fld DESC", key / BATCH_SIZE);
+
+        assert res.size() == BATCH_SIZE;
+    }
+
+    /** */
+    private List<?> executeSql(String sql, Object... args) {
+        List<List<?>> res = cache.query(new SqlFieldsQuery(sql).setArgs(args)).getAll();
+
+        return res.get(0);
+    }
+
+    /**
+     * Run benchmarks.
+     *
+     * @param args Args.
+     * @throws Exception Exception.
+     */
+    public static void main(String[] args) throws Exception {
+        final Options options = new OptionsBuilder()
+            .include(JmhSqlBenchmark.class.getSimpleName())
+            .build();
+
+        new Runner(options).run();
+    }
+
+    /** */
+    private static class Item {
+        /** */
+        @QuerySqlField
+        private final String name;
+
+        /** */
+        @QuerySqlField
+        private final int fld;
+
+        /** */
+        @QuerySqlField
+        private final int fldBatch;
+
+        /** */
+        @QuerySqlField(index = true)
+        private final int fldIdx;
+
+        /** */
+        @QuerySqlField(index = true)
+        private final int fldIdxBatch;
+
+        /** */
+        public Item(int val) {
+            name = "name" + val;
+            fld = val;
+            fldBatch = val / BATCH_SIZE;
+            fldIdx = val;
+            fldIdxBatch = val / BATCH_SIZE;
+        }
+    }
+}
diff --git a/modules/calcite/README.txt b/modules/calcite/README.txt
new file mode 100644
index 0000000..8316403
--- /dev/null
+++ b/modules/calcite/README.txt
@@ -0,0 +1,38 @@
+Apache Ignite Calcite Module
+--------------------------
+
+Apache Ignite Calcite module provides experimental Apache Calcite based query engine.
+
+To enable Calcite based engine explicit `CalciteQueryEngineConfiguration` instance should be added to
+`SqlConfiguration.QueryEnginesConfiguration` property (see `SqlConfiguration.setQueryEnginesConfiguration()`).
+Also, Calcite module libraries should be in a classpath.
+
+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..dcb39b0
--- /dev/null
+++ b/modules/calcite/pom.xml
@@ -0,0 +1,312 @@
+<?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>
+
+    <!-- Module specific package versions -->
+    <properties>
+        <avatica.version>1.19.0</avatica.version>
+        <calcite.version>1.29.0</calcite.version>
+        <checker.version>3.10.0</checker.version>
+        <esri.geometry.version>2.2.0</esri.geometry.version>
+        <immutables.version>2.8.2</immutables.version>
+        <janino.version>3.1.6</janino.version>
+        <javacc-maven-plugin>2.4</javacc-maven-plugin>
+        <!--        <javassist.version>3.28.0-GA</javassist.version>-->
+        <jsonpath.version>2.4.0</jsonpath.version>
+        <reflections.version>0.10.2</reflections.version>
+    </properties>
+
+    <parent>
+        <groupId>org.apache.ignite</groupId>
+        <artifactId>ignite-parent</artifactId>
+        <version>1</version>
+        <relativePath>../../parent</relativePath>
+    </parent>
+
+    <artifactId>ignite-calcite</artifactId>
+    <version>${revision}</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.calcite</groupId>
+            <artifactId>calcite-linq4j</artifactId>
+            <version>${calcite.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>${guava.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.checkerframework</groupId>
+            <artifactId>checker-qual</artifactId>
+            <version>${checker.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.codehaus.janino</groupId>
+            <artifactId>commons-compiler</artifactId>
+            <version>${janino.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.codehaus.janino</groupId>
+            <artifactId>janino</artifactId>
+            <version>${janino.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.calcite.avatica</groupId>
+            <artifactId>avatica-core</artifactId>
+            <version>${avatica.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+            <version>${jackson.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <version>${jackson.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+            <version>${jackson.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.jayway.jsonpath</groupId>
+            <artifactId>json-path</artifactId>
+            <version>${jsonpath.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.reflections</groupId>
+            <artifactId>reflections</artifactId>
+            <version>${reflections.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.javassist</groupId>
+            <artifactId>javassist</artifactId>
+            <version>${javassist.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.esri.geometry</groupId>
+            <artifactId>esri-geometry-api</artifactId>
+            <version>${esri.geometry.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.immutables</groupId>
+            <artifactId>value</artifactId>
+            <version>${immutables.version}</version>
+            <scope>provided</scope>
+        </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>${project.groupId}</groupId>
+            <artifactId>ignite-tools</artifactId>
+            <version>${project.version}</version>
+            <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>
+
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-clients</artifactId>
+            <version>${project.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>
+            <plugin>
+                <artifactId>maven-resources-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-fmpp-resources</id>
+                        <phase>validate</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}/codegen</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>src/main/codegen</directory>
+                                    <filtering>false</filtering>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>unpack-parser-template</id>
+                        <phase>validate</phase>
+                        <goals>
+                            <goal>unpack</goal>
+                        </goals>
+                        <configuration>
+                            <artifactItems>
+                                <artifactItem>
+                                    <groupId>org.apache.calcite</groupId>
+                                    <artifactId>calcite-core</artifactId>
+                                    <type>jar</type>
+                                    <overWrite>true</overWrite>
+                                    <outputDirectory>${project.build.directory}/</outputDirectory>
+                                    <includes>codegen/templates/Parser.jj</includes>
+                                </artifactItem>
+                            </artifactItems>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>com.googlecode.fmpp-maven-plugin</groupId>
+                <artifactId>fmpp-maven-plugin</artifactId>
+                <version>1.0</version>
+                <configuration>
+                    <cfgFile>${project.build.directory}/codegen/config.fmpp</cfgFile>
+                    <templateDirectory>${project.build.directory}/codegen/templates</templateDirectory>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>generate-fmpp-sources</id>
+                        <phase>validate</phase>
+                        <goals>
+                            <goal>generate</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>javacc-maven-plugin</artifactId>
+                <version>${javacc-maven-plugin}</version>
+                <executions>
+                    <execution>
+                        <id>javacc</id>
+                        <goals>
+                            <goal>javacc</goal>
+                        </goals>
+                        <configuration>
+                            <sourceDirectory>${project.build.directory}/generated-sources/fmpp</sourceDirectory>
+                            <outputDirectory>${project.build.sourceDirectory}</outputDirectory>
+                            <includes>
+                                <include>**/Parser.jj</include>
+                            </includes>
+                            <lookAhead>2</lookAhead>
+                            <isStatic>false</isStatic>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/modules/calcite/src/main/codegen/config.fmpp b/modules/calcite/src/main/codegen/config.fmpp
new file mode 100644
index 0000000..f692ad7
--- /dev/null
+++ b/modules/calcite/src/main/codegen/config.fmpp
@@ -0,0 +1,691 @@
+# 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.
+
+data: {
+  # Data declarations for this parser.
+  #
+  # Default declarations are in default_config.fmpp; if you do not include a
+  # declaration ('imports' or 'nonReservedKeywords', for example) in this file,
+  # FMPP will use the declaration from default_config.fmpp.
+  parser: {
+    # Generated parser implementation class package and name
+    package: "org.apache.ignite.internal.processors.query.calcite.sql.generated",
+    class: "IgniteSqlParserImpl",
+
+    # List of additional classes and packages to import.
+    # Example: "org.apache.calcite.sql.*", "java.util.List".
+    imports: [
+      "java.util.UUID",
+      "org.apache.calcite.sql.SqlCreate",
+      "org.apache.calcite.sql.SqlDrop",
+      "org.apache.calcite.sql.SqlLiteral",
+      "org.apache.calcite.schema.ColumnStrategy",
+      "org.apache.ignite.internal.processors.query.calcite.util.IgniteResource",
+      "org.apache.ignite.lang.IgniteUuid",
+      "org.apache.calcite.sql.ddl.SqlDdlNodes",
+      "org.apache.ignite.internal.processors.query.calcite.sql.*",
+    ]
+
+    # List of new keywords. Example: "DATABASES", "TABLES". If the keyword is
+    # not a reserved keyword, add it to the 'nonReservedKeywords' section.
+    keywords: [
+      "SEMI"
+      "IF"
+      "TEMPLATE"
+      "BACKUPS"
+      "AFFINITY_KEY"
+      "ATOMICITY"
+      "WRITE_SYNCHRONIZATION_MODE"
+      "CACHE_GROUP"
+      "CACHE_NAME"
+      "DATA_REGION"
+#     "KEY_TYPE" // already presented in Calcite
+      "VALUE_TYPE"
+      "ENCRYPTED"
+      "INDEX"
+      "PARALLEL"
+      "INLINE_SIZE"
+      "LOGGING"
+      "NOLOGGING"
+      "PASSWORD"
+      "KILL"
+      "SCAN"
+      "CONTINUOUS"
+      "SERVICE"
+      "COMPUTE"
+      "ASYNC"
+      "QUERY"
+      "UUID"
+    ]
+
+    # List of non-reserved keywords to add;
+    # items in this list become non-reserved
+    nonReservedKeywords: [
+      "SEMI"
+      "TEMPLATE"
+      "BACKUPS"
+      "AFFINITY_KEY"
+      "ATOMICITY"
+      "WRITE_SYNCHRONIZATION_MODE"
+      "CACHE_GROUP"
+      "CACHE_NAME"
+      "DATA_REGION"
+#     "KEY_TYPE" // already presented in Calcite
+      "VALUE_TYPE"
+      "ENCRYPTED"
+      "PARALLEL"
+      "INLINE_SIZE"
+      "LOGGING"
+      "NOLOGGING"
+      "PASSWORD"
+      "KILL"
+      "SCAN"
+      "CONTINUOUS"
+      "SERVICE"
+      "COMPUTE"
+      "ASYNC"
+      "QUERY"
+      "UUID"
+
+      # The following keywords are reserved in core Calcite,
+      # are reserved in some version of SQL,
+      # but are not reserved in Babel.
+      #
+      # Words that are commented out (e.g. "AND") are still reserved.
+      # These are the most important reserved words, and SQL cannot be
+      # unambiguously parsed if they are not reserved. For example, if
+      # "INNER" is not reserved then in the query
+      #
+      #   select * from emp inner join dept using (deptno)"
+      #
+      # "inner" could be a table alias for "emp".
+      #
+      "A"
+      "ABS"
+      "ABSOLUTE"
+      "ACTION"
+      "ADD"
+      "AFTER"
+#     "ALL"
+      "ALLOCATE"
+      "ALLOW"
+      "ALTER"
+      "AND"
+#     "ANY"
+      "ARE"
+      "ARRAY"
+#     "ARRAY_AGG" # not a keyword in Calcite
+      "ARRAY_MAX_CARDINALITY"
+      "AS"
+      "ASC"
+      "ASENSITIVE"
+      "ASSERTION"
+      "ASYMMETRIC"
+      "AT"
+      "ATOMIC"
+      "AUTHORIZATION"
+      "AVG"
+      "BEFORE"
+      "BEGIN"
+      "BEGIN_FRAME"
+      "BEGIN_PARTITION"
+      "BETWEEN"
+      "BIGINT"
+      "BINARY"
+      "BIT"
+#     "BIT_LENGTH" # not a keyword in Calcite
+      "BLOB"
+      "BOOLEAN"
+      "BOTH"
+      "BREADTH"
+      "BY"
+      "C"
+#     "CALL"
+      "CALLED"
+      "CARDINALITY"
+      "CASCADE"
+      "CASCADED"
+#     "CASE"
+      "CAST"
+      "CATALOG"
+      "CEIL"
+      "CEILING"
+      "CHAR"
+      "CHARACTER"
+      "CHARACTER_LENGTH"
+      "CHAR_LENGTH"
+      "CHECK"
+      "CLASSIFIER"
+      "CLOB"
+      "CLOSE"
+      "COALESCE"
+      "COLLATE"
+      "COLLATION"
+      "COLLECT"
+      "COLUMN"
+      "COMMIT"
+      "CONDITION"
+      "CONNECT"
+      "CONNECTION"
+#     "CONSTRAINT"
+      "CONSTRAINTS"
+      "CONSTRUCTOR"
+      "CONTAINS"
+      "CONTINUE"
+      "CONVERT"
+      "CORR"
+      "CORRESPONDING"
+      "COUNT"
+      "COVAR_POP"
+      "COVAR_SAMP"
+#     "CREATE"
+#     "CROSS"
+      "CUBE"
+      "CUME_DIST"
+#     "CURRENT"
+      "CURRENT_CATALOG"
+      "CURRENT_DATE"
+      "CURRENT_DEFAULT_TRANSFORM_GROUP"
+      "CURRENT_PATH"
+      "CURRENT_ROLE"
+      "CURRENT_ROW"
+      "CURRENT_SCHEMA"
+      "CURRENT_TIME"
+      "CURRENT_TIMESTAMP"
+      "CURRENT_TRANSFORM_GROUP_FOR_TYPE"
+      "CURRENT_USER"
+#     "CURSOR"
+      "CYCLE"
+      "DATA"
+      "DATE"
+      "DAY"
+      "DEALLOCATE"
+      "DEC"
+      "DECIMAL"
+      "DECLARE"
+#     "DEFAULT"
+      "DEFERRABLE"
+      "DEFERRED"
+#     "DEFINE"
+#     "DELETE"
+      "DENSE_RANK"
+      "DEPTH"
+      "DEREF"
+      "DESC"
+#     "DESCRIBE" # must be reserved
+      "DESCRIPTOR"
+      "DETERMINISTIC"
+      "DIAGNOSTICS"
+      "DISALLOW"
+      "DISCONNECT"
+#     "DISTINCT"
+#     "DO"  # not a keyword in Calcite
+      "DOMAIN"
+      "DOUBLE"
+#     "DROP" # probably must be reserved
+      "DYNAMIC"
+      "EACH"
+      "ELEMENT"
+      "ELSE"
+#     "ELSEIF" # not a keyword in Calcite
+      "EMPTY"
+      "END"
+#     "END-EXEC" # not a keyword in Calcite, and contains '-'
+      "END_FRAME"
+      "END_PARTITION"
+      "EQUALS"
+      "ESCAPE"
+      "EVERY"
+#     "EXCEPT" # must be reserved
+      "EXCEPTION"
+      "EXEC"
+      "EXECUTE"
+      "EXISTS"
+#     "EXIT" # not a keyword in Calcite
+      "EXP"
+#     "EXPLAIN" # must be reserved
+      "EXTEND"
+      "EXTERNAL"
+      "EXTRACT"
+      "FALSE"
+#     "FETCH"
+      "FILTER"
+      "FIRST"
+      "FIRST_VALUE"
+      "FLOAT"
+      "FLOOR"
+      "FOR"
+      "FOREIGN"
+#     "FOREVER" # not a keyword in Calcite
+      "FOUND"
+      "FRAME_ROW"
+      "FREE"
+#     "FROM" # must be reserved
+#     "FULL" # must be reserved
+      "FUNCTION"
+      "FUSION"
+      "G"
+      "GENERAL"
+      "GET"
+      "GLOBAL"
+      "GO"
+      "GOTO"
+#     "GRANT"
+#     "GROUP"
+#     "GROUPING"
+      "GROUPS"
+#     "HANDLER" # not a keyword in Calcite
+#     "HAVING"
+      "HOLD"
+      "HOUR"
+      "IDENTITY"
+#     "IF" # not a keyword in Calcite
+      # "ILIKE"
+      "IMMEDIATE"
+      "IMMEDIATELY"
+      "IMPORT"
+#     "IN"
+      "INDICATOR"
+      "INITIAL"
+      "INITIALLY"
+#     "INNER"
+      "INOUT"
+      "INPUT"
+      "INSENSITIVE"
+#     "INSERT"
+      "INT"
+      "INTEGER"
+#     "INTERSECT"
+      "INTERSECTION"
+#     "INTERVAL"
+#     "INTO"
+      "IS"
+      "ISOLATION"
+#     "ITERATE" # not a keyword in Calcite
+#     "JOIN"
+      "JSON_ARRAY"
+      "JSON_ARRAYAGG"
+      "JSON_EXISTS"
+      "JSON_OBJECT"
+      "JSON_OBJECTAGG"
+      "JSON_QUERY"
+      "JSON_VALUE"
+      "K"
+#     "KEEP" # not a keyword in Calcite
+      "KEY"
+      "LAG"
+      "LANGUAGE"
+      "LARGE"
+      "LAST"
+      "LAST_VALUE"
+#     "LATERAL"
+      "LEAD"
+      "LEADING"
+#     "LEAVE" # not a keyword in Calcite
+#     "LEFT"
+      "LENGTH"
+      "LEVEL"
+      "LIKE"
+      "LIKE_REGEX"
+#     "LIMIT"
+      "LN"
+      "LOCAL"
+      "LOCALTIME"
+      "LOCALTIMESTAMP"
+      "LOCATOR"
+#     "LOOP" # not a keyword in Calcite
+      "LOWER"
+      "M"
+      "MAP"
+      "MATCH"
+      "MATCHES"
+      "MATCH_NUMBER"
+#     "MATCH_RECOGNIZE"
+      "MAX"
+#     "MAX_CARDINALITY" # not a keyword in Calcite
+      "MEASURES"
+      "MEMBER"
+#     "MERGE"
+      "METHOD"
+      "MIN"
+#     "MINUS"
+      "MINUTE"
+      "MOD"
+      "MODIFIES"
+      "MODULE"
+      "MONTH"
+      "MULTISET"
+      "NAME"
+      "NAMES"
+      "NATIONAL"
+#     "NATURAL"
+      "NCHAR"
+      "NCLOB"
+#     "NEW"
+#     "NEXT"
+      "NO"
+      "NONE"
+      "NORMALIZE"
+      "NOT"
+      "NTH_VALUE"
+      "NTILE"
+#     "NULL"
+      "NULLIF"
+      "NUMERIC"
+      "OBJECT"
+      "OCCURRENCES_REGEX"
+      "OCTET_LENGTH"
+      "OF"
+#     "OFFSET"
+      "OLD"
+      "OMIT"
+#     "ON"
+      "ONE"
+      "ONLY"
+      "OPEN"
+      "OPTION"
+      "OR"
+#     "ORDER"
+      "ORDINALITY"
+      "OUT"
+#     "OUTER"
+      "OUTPUT"
+#     "OVER"
+      "OVERLAPS"
+      "OVERLAY"
+      "PAD"
+      "PARAMETER"
+      "PARTIAL"
+#     "PARTITION"
+      "PATH"
+#     "PATTERN"
+      "PER"
+      "PERCENT"
+      "PERCENTILE_CONT"
+      "PERCENTILE_DISC"
+      "PERCENT_RANK"
+      "PERIOD"
+      "PERMUTE"
+      "PORTION"
+      "POSITION"
+      "POSITION_REGEX"
+      "POWER"
+      "PRECEDES"
+      "PRECISION"
+      "PREPARE"
+      "PRESERVE"
+      "PREV"
+#     "PRIMARY"
+      "PRIOR"
+      "PRIVILEGES"
+      "PROCEDURE"
+      "PUBLIC"
+      "QUARTER"
+#     "RANGE"
+      "RANK"
+      "READ"
+      "READS"
+      "REAL"
+      "RECURSIVE"
+      "REF"
+      "REFERENCES"
+      "REFERENCING"
+      "REGR_AVGX"
+      "REGR_AVGY"
+      "REGR_COUNT"
+      "REGR_INTERCEPT"
+      "REGR_R2"
+      "REGR_SLOPE"
+      "REGR_SXX"
+      "REGR_SXY"
+      "REGR_SYY"
+      "RELATIVE"
+      "RELEASE"
+#     "REPEAT" # not a keyword in Calcite
+      "REPLACE"
+      "RESET"
+#     "RESIGNAL" # not a keyword in Calcite
+      "RESTRICT"
+      "RESULT"
+      "RETURN"
+      "RETURNS"
+      "REVOKE"
+#     "RIGHT"
+      # "RLIKE"
+      "ROLE"
+      "ROLLBACK"
+#     "ROLLUP"
+      "ROUTINE"
+#     "ROW"
+#     "ROWS"
+      "ROW_NUMBER"
+      "RUNNING"
+      "SAVEPOINT"
+      "SCHEMA"
+      "SCOPE"
+      "SCROLL"
+      "SEARCH"
+      "SECOND"
+      "SECTION"
+      "SEEK"
+#     "SELECT"
+      "SENSITIVE"
+      "SESSION"
+      "SESSION_USER"
+#     "SET"
+#     "SETS"
+      "SHOW"
+#     "SIGNAL" # not a keyword in Calcite
+      "SIMILAR"
+      "SIZE"
+#     "SKIP" # messes with JavaCC's <SKIP> token
+      "SMALLINT"
+#     "SOME"
+      "SPACE"
+      "SPECIFIC"
+      "SPECIFICTYPE"
+      "SQL"
+#     "SQLCODE" # not a keyword in Calcite
+#     "SQLERROR" # not a keyword in Calcite
+      "SQLEXCEPTION"
+      "SQLSTATE"
+      "SQLWARNING"
+      "SQRT"
+      "START"
+      "STATE"
+      "STATIC"
+      "STDDEV_POP"
+      "STDDEV_SAMP"
+#     "STREAM"
+      "SUBMULTISET"
+      "SUBSET"
+      "SUBSTRING"
+      "SUBSTRING_REGEX"
+      "SUCCEEDS"
+      "SUM"
+      "SYMMETRIC"
+      "SYSTEM"
+      "SYSTEM_TIME"
+      "SYSTEM_USER"
+#     "TABLE"
+#     "TABLESAMPLE"
+      "TEMPORARY"
+#     "THEN"
+#     "TIME"
+#     "TIMESTAMP"
+      "TIMEZONE_HOUR"
+      "TIMEZONE_MINUTE"
+      "TINYINT"
+      "TO"
+      "TRAILING"
+      "TRANSACTION"
+      "TRANSLATE"
+      "TRANSLATE_REGEX"
+      "TRANSLATION"
+      "TREAT"
+      "TRIGGER"
+      "TRIM"
+      "TRIM_ARRAY"
+      "TRUE"
+      "TRUNCATE"
+      "UESCAPE"
+      "UNDER"
+#     "UNDO" # not a keyword in Calcite
+#     "UNION"
+      "UNIQUE"
+      "UNKNOWN"
+#     "UNNEST"
+#     "UNTIL" # not a keyword in Calcite
+#     "UPDATE"
+      "UPPER"
+      "UPSERT"
+      "USAGE"
+      "USER"
+#     "USING"
+      "VALUE"
+#     "VALUES"
+      "VALUE_OF"
+      "VARBINARY"
+      "VARCHAR"
+      "VARYING"
+      "VAR_POP"
+      "VAR_SAMP"
+      "VERSION"
+      "VERSIONING"
+#     "VERSIONS" # not a keyword in Calcite
+      "VIEW"
+      "WEEK"
+#     "WHEN"
+      "WHENEVER"
+#     "WHERE"
+#     "WHILE" # not a keyword in Calcite
+      "WIDTH_BUCKET"
+#     "WINDOW"
+#     "WITH"
+      "WITHIN"
+      "WITHOUT"
+      "WORK"
+      "WRITE"
+      "YEAR"
+      "ZONE"
+    ]
+
+    # List of non-reserved keywords to add;
+    # items in this list become non-reserved.
+    nonReservedKeywordsToAdd: [
+    ]
+
+    # List of non-reserved keywords to remove;
+    # items in this list become reserved.
+    nonReservedKeywordsToRemove: [
+    ]
+
+    # List of additional join types. Each is a method with no arguments.
+    # Example: "LeftSemiJoin".
+    joinTypes: [
+ #     "LeftSemiJoin"
+    ]
+
+    # List of methods for parsing builtin function calls.
+    # Return type of method implementation should be "SqlNode".
+    # Example: "DateFunctionCall()".
+    builtinFunctionCallMethods: [
+  #     "DateFunctionCall()"
+   #    "DateaddFunctionCall()"
+    ]
+
+    # List of methods for parsing custom SQL statements.
+    # Return type of method implementation should be 'SqlNode'.
+    # Example: "SqlShowDatabases()", "SqlShowTables()".
+    statementParserMethods: [
+      "SqlAlterTable()",
+      "SqlAlterUser()",
+      "SqlKillScanQuery()",
+      "SqlKillContinuousQuery()",
+      "SqlKillService()",
+      "SqlKillTransaction()",
+      "SqlKillComputeTask()",
+      "SqlKillQuery()",
+      "SqlCommitTransaction()",
+      "SqlRollbackTransaction()"
+    ]
+
+    # List of methods for parsing extensions to "CREATE [OR REPLACE]" calls.
+    # Each must accept arguments "(SqlParserPos pos, boolean replace)".
+    # Example: "SqlCreateForeignSchema".
+    createStatementParserMethods: [
+      "SqlCreateTable",
+      "SqlCreateIndex",
+      "SqlCreateUser"
+    ]
+
+    # List of methods for parsing extensions to "DROP" calls.
+    # Each must accept arguments "(SqlParserPos pos)".
+    # Example: "SqlDropSchema".
+    dropStatementParserMethods: [
+      "SqlDropTable",
+      "SqlDropIndex",
+      "SqlDropUser"
+    ]
+
+    # List of methods for parsing extensions to "ALTER <scope>" calls.
+    # Where scope is SYSTEM or SESSION.
+    # Each must accept arguments "(SqlParserPos pos, String scope)".
+    alterStatementParserMethods: [
+    ]
+
+    # List of methods for parsing custom literals.
+    # Return type of method implementation should be "SqlNode".
+    # Example: ParseJsonLiteral().
+    literalParserMethods: [
+    ]
+
+    # List of methods for parsing custom data types.
+    # Return type of method implementation should be "SqlTypeNameSpec".
+    # Example: SqlParseTimeStampZ().
+    dataTypeParserMethods: [
+    ]
+
+    # Binary operators tokens.
+    # Example: "< INFIX_CAST: \"::\" >".
+    binaryOperatorsTokens: [
+      "< INFIX_CAST: \"::\" >"
+    ]
+
+    # Binary operators initialization.
+    # Example: "InfixCast".
+    extraBinaryExpressions: [
+      "InfixCast"
+    ]
+
+    # List of files in @includes directory that have parser method
+    # implementations for parsing custom SQL statements, literals or types
+    # given as part of "statementParserMethods", "literalParserMethods" or
+    # "dataTypeParserMethods".
+    # Example: "parserImpls.ftl".
+    implementationFiles: [
+      "parserImpls.ftl"
+    ]
+
+    includePosixOperators: true
+    includeCompoundIdentifier: true
+    includeBraces: true
+    includeAdditionalDeclarations: false
+  }
+}
+
+freemarkerLinks: {
+  includes: includes/
+}
diff --git a/modules/calcite/src/main/codegen/includes/parserImpls.ftl b/modules/calcite/src/main/codegen/includes/parserImpls.ftl
new file mode 100644
index 0000000..221e903
--- /dev/null
+++ b/modules/calcite/src/main/codegen/includes/parserImpls.ftl
@@ -0,0 +1,620 @@
+<#--
+// 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.
+-->
+
+boolean IfNotExistsOpt() :
+{
+}
+{
+    <IF> <NOT> <EXISTS> { return true; }
+|
+    { return false; }
+}
+
+SqlNodeList WithCreateTableOptionList() :
+{
+    List<SqlNode> list = new ArrayList<SqlNode>();
+    final Span s;
+}
+{
+    [
+        <WITH> { s = span(); }
+        CreateTableOption(list)
+        (
+            <COMMA> { s.add(this); } CreateTableOption(list)
+        )*
+        {
+            return new SqlNodeList(list, s.end(this));
+        }
+    ]
+    { return null; }
+}
+
+SqlLiteral CreateTableOptionKey() :
+{
+}
+{
+    <TEMPLATE> { return SqlLiteral.createSymbol(IgniteSqlCreateTableOptionEnum.TEMPLATE, getPos()); }
+|
+    <BACKUPS> { return SqlLiteral.createSymbol(IgniteSqlCreateTableOptionEnum.BACKUPS, getPos()); }
+|
+    <AFFINITY_KEY> { return SqlLiteral.createSymbol(IgniteSqlCreateTableOptionEnum.AFFINITY_KEY, getPos()); }
+|
+    <ATOMICITY> { return SqlLiteral.createSymbol(IgniteSqlCreateTableOptionEnum.ATOMICITY, getPos()); }
+|
+    <WRITE_SYNCHRONIZATION_MODE> { return SqlLiteral.createSymbol(IgniteSqlCreateTableOptionEnum.WRITE_SYNCHRONIZATION_MODE, getPos()); }
+|
+    <CACHE_GROUP> { return SqlLiteral.createSymbol(IgniteSqlCreateTableOptionEnum.CACHE_GROUP, getPos()); }
+|
+    <CACHE_NAME> { return SqlLiteral.createSymbol(IgniteSqlCreateTableOptionEnum.CACHE_NAME, getPos()); }
+|
+    <DATA_REGION> { return SqlLiteral.createSymbol(IgniteSqlCreateTableOptionEnum.DATA_REGION, getPos()); }
+|
+    <KEY_TYPE> { return SqlLiteral.createSymbol(IgniteSqlCreateTableOptionEnum.KEY_TYPE, getPos()); }
+|
+    <VALUE_TYPE> { return SqlLiteral.createSymbol(IgniteSqlCreateTableOptionEnum.VALUE_TYPE, getPos()); }
+|
+    <ENCRYPTED> { return SqlLiteral.createSymbol(IgniteSqlCreateTableOptionEnum.ENCRYPTED, getPos()); }
+}
+
+void CreateTableOption(List<SqlNode> list) :
+{
+    final Span s;
+    final SqlLiteral key;
+    final SqlNode val;
+}
+{
+    key = CreateTableOptionKey() { s = span(); }
+    <EQ>
+    (
+        val = Literal()
+    |
+        val = SimpleIdentifier()
+    ) {
+        list.add(new IgniteSqlCreateTableOption(key, val, s.end(this)));
+    }
+}
+
+SqlDataTypeSpec DataTypeEx() :
+{
+    final SqlDataTypeSpec dt;
+}
+{
+    (
+        dt = DataType()
+    |
+        dt = IntervalType()
+    |
+        dt = UuidType()
+    )
+    {
+        return dt;
+    }
+}
+
+SqlDataTypeSpec IntervalType() :
+{
+    final Span s;
+    final SqlIntervalQualifier intervalQualifier;
+}
+{
+    <INTERVAL> { s = span(); } intervalQualifier = IntervalQualifier() {
+        return new SqlDataTypeSpec(new IgniteSqlIntervalTypeNameSpec(intervalQualifier, s.end(this)), s.pos());
+    }
+}
+
+SqlDataTypeSpec UuidType() :
+{
+        final Span s;
+}
+{
+    <UUID> { s = span(); }
+    {
+        return new SqlDataTypeSpec(new SqlUserDefinedTypeNameSpec("UUID", s.end(this)), s.pos());
+    }
+}
+
+void TableElement(List<SqlNode> list) :
+{
+    final SqlDataTypeSpec type;
+    final boolean nullable;
+    final SqlNodeList columnList;
+    final Span s = Span.of();
+    final ColumnStrategy strategy;
+    final SqlNode dflt;
+    SqlIdentifier id = null;
+}
+{
+    id = SimpleIdentifier() type = DataTypeEx() nullable = NullableOptDefaultTrue()
+    (
+        <DEFAULT_> { s.add(this); } dflt = Literal() {
+            strategy = ColumnStrategy.DEFAULT;
+        }
+    |
+        {
+            dflt = null;
+            strategy = nullable ? ColumnStrategy.NULLABLE
+                : ColumnStrategy.NOT_NULLABLE;
+        }
+    )
+    [
+        <PRIMARY> { s.add(this); } <KEY> {
+            columnList = SqlNodeList.of(id);
+            list.add(SqlDdlNodes.primary(s.end(columnList), null, columnList));
+        }
+    ]
+    {
+        list.add(
+            SqlDdlNodes.column(s.add(id).end(this), id,
+                type.withNullable(nullable), dflt, strategy));
+    }
+|
+    [ <CONSTRAINT> { s.add(this); } id = SimpleIdentifier() ]
+    <PRIMARY> { s.add(this); } <KEY>
+    columnList = ParenthesizedSimpleIdentifierList() {
+        list.add(SqlDdlNodes.primary(s.end(columnList), id, columnList));
+    }
+}
+
+SqlNodeList TableElementList() :
+{
+    final Span s;
+    final List<SqlNode> list = new ArrayList<SqlNode>();
+}
+{
+    <LPAREN> { s = span(); }
+    TableElement(list)
+    (
+        <COMMA> TableElement(list)
+    )*
+    <RPAREN> {
+        return new SqlNodeList(list, s.end(this));
+    }
+}
+
+SqlCreate SqlCreateTable(Span s, boolean replace) :
+{
+    final boolean ifNotExists;
+    final SqlIdentifier id;
+    final SqlNodeList columnList;
+    final SqlNodeList optionList;
+    final SqlNode query;
+}
+{
+    <TABLE>
+    ifNotExists = IfNotExistsOpt()
+    id = CompoundIdentifier()
+    (
+        LOOKAHEAD(3)
+        columnList = TableElementList()
+        optionList = WithCreateTableOptionList()
+        { query = null; }
+    |
+        (
+            columnList = ParenthesizedSimpleIdentifierList()
+        |
+            { columnList = null; }
+        )
+        optionList = WithCreateTableOptionList()
+        <AS> { s.add(this); } query = OrderedQueryOrExpr(ExprContext.ACCEPT_QUERY)
+    )
+    {
+        return new IgniteSqlCreateTable(s.end(this), ifNotExists, id, columnList, query, optionList);
+    }
+}
+
+SqlNode IndexedColumn() :
+{
+    final Span s;
+    SqlNode col;
+}
+{
+    col = SimpleIdentifier()
+    (
+        <ASC>
+    |   <DESC> {
+            col = SqlStdOperatorTable.DESC.createCall(getPos(), col);
+        }
+    )?
+    {
+        return col;
+    }
+}
+
+SqlNodeList IndexedColumnList() :
+{
+    final Span s;
+    final List<SqlNode> list = new ArrayList<SqlNode>();
+    SqlNode col = null;
+}
+{
+    <LPAREN> { s = span(); }
+    col = IndexedColumn() { list.add(col); }
+    (
+        <COMMA> col = IndexedColumn() { list.add(col); }
+    )*
+    <RPAREN> {
+        return new SqlNodeList(list, s.end(this));
+    }
+}
+
+SqlCreate SqlCreateIndex(Span s, boolean replace) :
+{
+    final boolean ifNotExists;
+    final SqlIdentifier idxId;
+    final SqlIdentifier tblId;
+    final SqlNodeList columnList;
+    SqlNumericLiteral parallel = null;
+    SqlNumericLiteral inlineSize = null;
+}
+{
+    <INDEX>
+    ifNotExists = IfNotExistsOpt()
+    idxId = SimpleIdentifier()
+    <ON>
+    tblId = CompoundIdentifier()
+    columnList = IndexedColumnList()
+    (
+        <PARALLEL> <UNSIGNED_INTEGER_LITERAL> {
+            if (parallel != null)
+                throw SqlUtil.newContextException(getPos(), IgniteResource.INSTANCE.optionAlreadyDefined("PARALLEL"));
+
+            parallel = SqlLiteral.createExactNumeric(token.image, getPos());
+        }
+    |
+        <INLINE_SIZE> <UNSIGNED_INTEGER_LITERAL> {
+            if (inlineSize != null)
+                throw SqlUtil.newContextException(getPos(), IgniteResource.INSTANCE.optionAlreadyDefined("INLINE_SIZE"));
+
+            inlineSize = SqlLiteral.createExactNumeric(token.image, getPos());
+        }
+    )*
+    {
+        return new IgniteSqlCreateIndex(s.end(this), ifNotExists, idxId, tblId, columnList, parallel, inlineSize);
+    }
+}
+
+boolean IfExistsOpt() :
+{
+}
+{
+    <IF> <EXISTS> { return true; }
+|
+    { return false; }
+}
+
+SqlDrop SqlDropTable(Span s, boolean replace) :
+{
+    final boolean ifExists;
+    final SqlIdentifier id;
+}
+{
+    <TABLE> ifExists = IfExistsOpt() id = CompoundIdentifier() {
+        return SqlDdlNodes.dropTable(s.end(this), ifExists, id);
+    }
+}
+
+SqlDrop SqlDropIndex(Span s, boolean replace) :
+{
+    final boolean ifExists;
+    final SqlIdentifier id;
+}
+{
+    <INDEX> ifExists = IfExistsOpt() id = CompoundIdentifier() {
+        return new IgniteSqlDropIndex(s.end(this), ifExists, id);
+    }
+}
+
+void InfixCast(List<Object> list, ExprContext exprContext, Span s) :
+{
+    final SqlDataTypeSpec dt;
+}
+{
+    <INFIX_CAST> {
+        checkNonQueryExpression(exprContext);
+    }
+    dt = DataTypeEx() {
+        list.add(
+            new SqlParserUtil.ToTreeListItem(SqlLibraryOperators.INFIX_CAST,
+                s.pos()));
+        list.add(dt);
+    }
+}
+
+SqlNodeList ColumnWithTypeList() :
+{
+    final Span s;
+    List<SqlNode> list = new ArrayList<SqlNode>();
+    SqlNode col;
+}
+{
+    <LPAREN> { s = span(); }
+    col = ColumnWithType() { list.add(col); }
+    (
+        <COMMA> col = ColumnWithType() { list.add(col); }
+    )*
+    <RPAREN> {
+        return new SqlNodeList(list, s.end(this));
+    }
+}
+
+SqlNode ColumnWithType() :
+{
+    SqlIdentifier id;
+    SqlDataTypeSpec type;
+    boolean nullable = true;
+    final Span s = Span.of();
+}
+{
+    id = SimpleIdentifier()
+    type = DataTypeEx()
+    [
+        <NOT> <NULL> {
+            nullable = false;
+        }
+    ]
+    {
+        return SqlDdlNodes.column(s.add(id).end(this), id, type.withNullable(nullable), null, null);
+    }
+}
+
+SqlNodeList ColumnWithTypeOrList() :
+{
+    SqlNode col;
+    SqlNodeList list;
+}
+{
+    col = ColumnWithType() { return new SqlNodeList(Collections.singletonList(col), col.getParserPosition()); }
+|
+    list = ColumnWithTypeList() { return list; }
+}
+
+SqlNode SqlAlterTable() :
+{
+    final Span s;
+    final boolean ifExists;
+    final SqlIdentifier id;
+    boolean colIgnoreErr;
+    SqlNode col;
+    SqlNodeList cols;
+}
+{
+    <ALTER> { s = span(); }
+    <TABLE> ifExists = IfExistsOpt() id = CompoundIdentifier()
+    (
+        <LOGGING> { return new IgniteSqlAlterTable(s.end(this), ifExists, id, true); }
+    |
+        <NOLOGGING>  { return new IgniteSqlAlterTable(s.end(this), ifExists, id, false); }
+    |
+        <ADD> [<COLUMN>] colIgnoreErr = IfNotExistsOpt() cols = ColumnWithTypeOrList() {
+            return new IgniteSqlAlterTableAddColumn(s.end(this), ifExists, id, colIgnoreErr, cols);
+        }
+    |
+        <DROP> [<COLUMN>] colIgnoreErr = IfExistsOpt() cols = SimpleIdentifierOrList() {
+            return new IgniteSqlAlterTableDropColumn(s.end(this), ifExists, id, colIgnoreErr, cols);
+        }
+    )
+}
+
+SqlCreate SqlCreateUser(Span s, boolean replace) :
+{
+    final SqlIdentifier user;
+    final SqlNode password;
+}
+{
+    <USER> user = SimpleIdentifier()
+    <WITH> <PASSWORD> password = StringLiteral() {
+        return new IgniteSqlCreateUser(s.end(this), user, SqlLiteral.unchain(password));
+    }
+}
+
+SqlNode SqlAlterUser() :
+{
+    final Span s;
+    final SqlIdentifier user;
+    final SqlNode password;
+}
+{
+    <ALTER> { s = span(); } <USER> user = SimpleIdentifier()
+    <WITH> <PASSWORD> password = StringLiteral() {
+        return new IgniteSqlAlterUser(s.end(this), user, SqlLiteral.unchain(password));
+    }
+}
+
+SqlDrop SqlDropUser(Span s, boolean replace) :
+{
+    final SqlIdentifier user;
+}
+{
+    <USER> user = SimpleIdentifier() {
+        return new IgniteSqlDropUser(s.end(this), user);
+    }
+}
+
+<DEFAULT, DQID, BTID> TOKEN :
+{
+    < NEGATE: "!" >
+|   < TILDE: "~" >
+}
+
+SqlNumericLiteral SignedIntegerLiteral() :
+{
+    final Span s;
+}
+{
+    <PLUS> <UNSIGNED_INTEGER_LITERAL> {
+        return SqlLiteral.createExactNumeric(token.image, getPos());
+    }
+|
+    <MINUS> { s = span(); } <UNSIGNED_INTEGER_LITERAL> {
+        return SqlLiteral.createNegative(SqlLiteral.createExactNumeric(token.image, getPos()), s.end(this));
+    }
+|
+    <UNSIGNED_INTEGER_LITERAL> {
+        return SqlLiteral.createExactNumeric(token.image, getPos());
+    }
+}
+
+SqlCharStringLiteral UuidLiteral():
+{
+    final Span s;
+    final String rawUuuid;
+}
+{
+    <QUOTED_STRING> {
+        String rawUuid = SqlParserUtil.parseString(token.image);
+        try {
+            java.util.UUID.fromString(rawUuid);
+            return SqlLiteral.createCharString(rawUuid, getPos());
+        }
+        catch (Exception e) {
+            throw SqlUtil.newContextException(getPos(), IgniteResource.INSTANCE.illegalUuid(rawUuid));
+        }
+    }
+}
+
+SqlCharStringLiteral IgniteUuidLiteral():
+{
+    final Span s;
+    final String rawUuuid;
+}
+{
+    <QUOTED_STRING> {
+        String rawUuid = SqlParserUtil.parseString(token.image);
+        try {
+            IgniteUuid.fromString(rawUuid);
+            return SqlLiteral.createCharString(rawUuid, getPos());
+        }
+        catch (Exception e) {
+            throw SqlUtil.newContextException(getPos(), IgniteResource.INSTANCE.illegalIgniteUuid(rawUuid));
+        }
+    }
+}
+
+SqlNode SqlKillScanQuery():
+{
+    final Span s;
+    final SqlCharStringLiteral originNodeId;
+    final SqlCharStringLiteral cacheName;
+    final SqlNumericLiteral queryId;
+}
+{
+    <KILL> { s = span(); } <SCAN>
+    originNodeId = UuidLiteral()
+    <QUOTED_STRING> {
+        cacheName = SqlLiteral.createCharString(SqlParserUtil.parseString(token.image), getPos());
+    }
+    queryId = SignedIntegerLiteral() {
+        return IgniteSqlKill.createScanQueryKill(s.end(this), originNodeId, cacheName, queryId);
+    }
+}
+
+SqlNode SqlKillContinuousQuery():
+{
+    final Span s;
+    final SqlCharStringLiteral originNodeId;
+    final SqlCharStringLiteral routineId;
+}
+{
+    <KILL> { s = span(); } <CONTINUOUS>
+    originNodeId = UuidLiteral()
+    routineId = UuidLiteral() {
+        return IgniteSqlKill.createContinuousQueryKill(s.end(this), originNodeId, routineId);
+    }
+}
+
+SqlNode SqlKillTransaction():
+{
+    final Span s;
+    final SqlCharStringLiteral xid;
+}
+{
+    <KILL> { s = span(); } <TRANSACTION>
+    xid = IgniteUuidLiteral() {
+        return IgniteSqlKill.createTransactionKill(s.end(this), xid);
+    }
+}
+
+SqlNode SqlKillService():
+{
+    final Span s;
+    final SqlCharStringLiteral srvName;
+}
+{
+    <KILL> { s = span(); } <SERVICE>
+    <QUOTED_STRING> {
+        srvName = SqlLiteral.createCharString(SqlParserUtil.parseString(token.image), getPos());
+        return IgniteSqlKill.createServiceKill(s.end(this), srvName);
+    }
+}
+
+SqlNode SqlKillComputeTask():
+{
+    final Span s;
+    final SqlCharStringLiteral sesId;
+}
+{
+    <KILL> { s = span(); } <COMPUTE>
+    sesId = IgniteUuidLiteral() {
+        return IgniteSqlKill.createComputeTaskKill(s.end(this), sesId);
+    }
+}
+
+boolean IsAsyncOpt() :
+{
+}
+{
+    <ASYNC> { return true; } | { return false; }
+}
+
+SqlNode SqlKillQuery():
+{
+    final Span s;
+    final boolean isAsync;
+}
+{
+    <KILL> { s = span(); } <QUERY>
+    isAsync= IsAsyncOpt()
+    <QUOTED_STRING> {
+        String rawQueryId = SqlParserUtil.parseString(token.image);
+        SqlCharStringLiteral queryIdLiteral = SqlLiteral.createCharString(rawQueryId, getPos());
+        Pair<UUID, Long> id = IgniteSqlKill.parseGlobalQueryId(rawQueryId);
+        if (id == null) {
+            throw SqlUtil.newContextException(getPos(), IgniteResource.INSTANCE.illegalGlobalQueryId(rawQueryId));
+        }
+        return IgniteSqlKill.createQueryKill(s.end(this), queryIdLiteral, id.getKey(), id.getValue(), isAsync);
+    }
+}
+
+SqlNode SqlCommitTransaction():
+{
+    final Span s;
+}
+{
+    <COMMIT> { s = span(); } (<TRANSACTION>)? {
+        return new IgniteSqlCommit(s.end(this));
+    }
+}
+
+SqlNode SqlRollbackTransaction():
+{
+    final Span s;
+}
+{
+    <ROLLBACK> { s = span(); } (<TRANSACTION>)? {
+        return new IgniteSqlRollback(s.end(this));
+    }
+}
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/stat/StatisticsType.java b/modules/calcite/src/main/java/org/apache/calcite/plan/volcano/VolcanoUtils.java
similarity index 54%
copy from modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/stat/StatisticsType.java
copy to modules/calcite/src/main/java/org/apache/calcite/plan/volcano/VolcanoUtils.java
index 38f3f67a..2b9293c 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/stat/StatisticsType.java
+++ b/modules/calcite/src/main/java/org/apache/calcite/plan/volcano/VolcanoUtils.java
@@ -15,30 +15,17 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.processors.query.stat;
+package org.apache.calcite.plan.volcano;
 
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Types of statistics width.
- */
-public enum StatisticsType {
-    /** Statistics by some particular partition. */
-    PARTITION,
-
-    /** Statistics by some data node. */
-    LOCAL;
-
-    /** Enumerated values. */
-    private static final StatisticsType[] VALUES = values();
+import org.apache.calcite.plan.RelOptCost;
 
+/** */
+public class VolcanoUtils {
     /**
-     * Efficiently gets enumerated value from its ordinal.
-     *
-     * @param ord Ordinal value.
-     * @return Enumerated value or {@code null} if ordinal out of range.
+     * @param relSubset Subset.
+     * @return Cost of best known plan.
      */
-    @Nullable public static StatisticsType fromOrdinal(int ord) {
-        return ord >= 0 && ord < VALUES.length ? VALUES[ord] : null;
+    public static RelOptCost bestCost(RelSubset relSubset) {
+        return relSubset.bestCost;
     }
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlDropUserCommand.java b/modules/calcite/src/main/java/org/apache/ignite/calcite/CalciteQueryEngineConfiguration.java
similarity index 50%
copy from modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlDropUserCommand.java
copy to modules/calcite/src/main/java/org/apache/ignite/calcite/CalciteQueryEngineConfiguration.java
index e49d251..206cf24 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlDropUserCommand.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/calcite/CalciteQueryEngineConfiguration.java
@@ -15,45 +15,43 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.sql.command;
+package org.apache.ignite.calcite;
 
-import org.apache.ignite.internal.sql.SqlLexer;
-import org.apache.ignite.internal.sql.SqlParserUtils;
-import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.internal.processors.query.QueryEngine;
+import org.apache.ignite.internal.processors.query.QueryEngineConfigurationEx;
+import org.apache.ignite.internal.processors.query.calcite.CalciteQueryProcessor;
+import org.apache.ignite.lang.IgniteExperimental;
 
 /**
- * DROP USER command.
+ * Query engine configuration for Calcite-based query engine.
  */
-public class SqlDropUserCommand implements SqlCommand {
-    /** User name. */
-    private String userName;
+@IgniteExperimental
+public class CalciteQueryEngineConfiguration implements QueryEngineConfigurationEx {
+    /** Query engine name. */
+    public static final String ENGINE_NAME = "calcite";
+
+    /** */
+    private boolean isDflt;
 
     /** {@inheritDoc} */
-    @Override public String schemaName() {
-        return null;
+    @Override public String engineName() {
+        return ENGINE_NAME;
     }
 
     /** {@inheritDoc} */
-    @Override public void schemaName(String schemaName) {
-        // No-op.
+    @Override public Class<? extends QueryEngine> engineClass() {
+        return CalciteQueryProcessor.class;
     }
 
-    /**
-     * @return User name.
-     */
-    public String userName() {
-        return userName;
+    /** {@inheritDoc} */
+    @Override public boolean isDefault() {
+        return isDflt;
     }
 
     /** {@inheritDoc} */
-    @Override public SqlCommand parse(SqlLexer lex) {
-        userName = SqlParserUtils.parseUsername(lex);
+    @Override public CalciteQueryEngineConfiguration setDefault(boolean isDflt) {
+        this.isDflt = isDflt;
 
         return this;
     }
-
-    /** {@inheritDoc} */
-    @Override public String toString() {
-        return S.toString(SqlDropUserCommand.class, this);
-    }
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/calcite/package-info.java b/modules/calcite/src/main/java/org/apache/ignite/calcite/package-info.java
new file mode 100644
index 0000000..3461ef1
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/calcite/package-info.java
@@ -0,0 +1,22 @@
+/*
+ *  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.
+ */
+
+/**
+ * Contains Calcite-based query engine classes and interfaces.
+ */
+
+package org.apache.ignite.calcite;
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..beb3836
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessor.java
@@ -0,0 +1,442 @@
+/*
+ * 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.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+import org.apache.calcite.DataContexts;
+import org.apache.calcite.config.Lex;
+import org.apache.calcite.config.NullCollation;
+import org.apache.calcite.plan.Contexts;
+import org.apache.calcite.plan.ConventionTraitDef;
+import org.apache.calcite.plan.RelTraitDef;
+import org.apache.calcite.rel.RelCollationTraitDef;
+import org.apache.calcite.rel.core.Aggregate;
+import org.apache.calcite.rel.hint.HintStrategyTable;
+import org.apache.calcite.schema.SchemaPlus;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlNodeList;
+import org.apache.calcite.sql.parser.SqlParser;
+import org.apache.calcite.sql.util.SqlOperatorTables;
+import org.apache.calcite.sql.validate.SqlValidator;
+import org.apache.calcite.sql2rel.SqlToRelConverter;
+import org.apache.calcite.tools.FrameworkConfig;
+import org.apache.calcite.tools.Frameworks;
+import org.apache.ignite.SystemProperty;
+import org.apache.ignite.cache.query.FieldsQueryCursor;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.processors.GridProcessorAdapter;
+import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
+import org.apache.ignite.internal.processors.failure.FailureProcessor;
+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.RunningQuery;
+import org.apache.ignite.internal.processors.query.calcite.exec.ArrayRowHandler;
+import org.apache.ignite.internal.processors.query.calcite.exec.ExchangeService;
+import org.apache.ignite.internal.processors.query.calcite.exec.ExchangeServiceImpl;
+import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionService;
+import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionServiceImpl;
+import org.apache.ignite.internal.processors.query.calcite.exec.MailboxRegistry;
+import org.apache.ignite.internal.processors.query.calcite.exec.MailboxRegistryImpl;
+import org.apache.ignite.internal.processors.query.calcite.exec.QueryTaskExecutor;
+import org.apache.ignite.internal.processors.query.calcite.exec.QueryTaskExecutorImpl;
+import org.apache.ignite.internal.processors.query.calcite.exec.exp.RexExecutorImpl;
+import org.apache.ignite.internal.processors.query.calcite.message.MessageService;
+import org.apache.ignite.internal.processors.query.calcite.message.MessageServiceImpl;
+import org.apache.ignite.internal.processors.query.calcite.metadata.AffinityService;
+import org.apache.ignite.internal.processors.query.calcite.metadata.AffinityServiceImpl;
+import org.apache.ignite.internal.processors.query.calcite.metadata.MappingService;
+import org.apache.ignite.internal.processors.query.calcite.metadata.MappingServiceImpl;
+import org.apache.ignite.internal.processors.query.calcite.metadata.cost.IgniteCostFactory;
+import org.apache.ignite.internal.processors.query.calcite.prepare.CacheKey;
+import org.apache.ignite.internal.processors.query.calcite.prepare.IgniteConvertletTable;
+import org.apache.ignite.internal.processors.query.calcite.prepare.IgniteTypeCoercion;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PrepareServiceImpl;
+import org.apache.ignite.internal.processors.query.calcite.prepare.QueryPlan;
+import org.apache.ignite.internal.processors.query.calcite.prepare.QueryPlanCache;
+import org.apache.ignite.internal.processors.query.calcite.prepare.QueryPlanCacheImpl;
+import org.apache.ignite.internal.processors.query.calcite.schema.SchemaHolder;
+import org.apache.ignite.internal.processors.query.calcite.schema.SchemaHolderImpl;
+import org.apache.ignite.internal.processors.query.calcite.sql.IgniteSqlConformance;
+import org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteOwnSqlOperatorTable;
+import org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteStdSqlOperatorTable;
+import org.apache.ignite.internal.processors.query.calcite.sql.generated.IgniteSqlParserImpl;
+import org.apache.ignite.internal.processors.query.calcite.trait.CorrelationTraitDef;
+import org.apache.ignite.internal.processors.query.calcite.trait.DistributionTraitDef;
+import org.apache.ignite.internal.processors.query.calcite.trait.RewindabilityTraitDef;
+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.query.calcite.util.LifecycleAware;
+import org.apache.ignite.internal.processors.query.calcite.util.Service;
+import org.jetbrains.annotations.Nullable;
+
+import static org.apache.ignite.IgniteSystemProperties.getLong;
+
+/** */
+public class CalciteQueryProcessor extends GridProcessorAdapter implements QueryEngine {
+    /**
+     * Default planner timeout, in ms.
+     */
+    private static final long DFLT_IGNITE_CALCITE_PLANNER_TIMEOUT = 15000;
+
+    /**
+     * Planner timeout property name.
+     */
+    @SystemProperty(value = "Timeout of calcite based sql engine's planner, in ms", type = Long.class,
+        defaults = "" + DFLT_IGNITE_CALCITE_PLANNER_TIMEOUT)
+    public static final String IGNITE_CALCITE_PLANNER_TIMEOUT = "IGNITE_CALCITE_PLANNER_TIMEOUT";
+
+    /** */
+    public static final FrameworkConfig FRAMEWORK_CONFIG = Frameworks.newConfigBuilder()
+        .executor(new RexExecutorImpl(DataContexts.EMPTY))
+        .sqlToRelConverterConfig(SqlToRelConverter.config()
+            .withTrimUnusedFields(true)
+            // currently SqlToRelConverter creates not optimal plan for both optimization and execution
+            // so it's better to disable such rewriting right now
+            // TODO: remove this after IGNITE-14277
+            .withInSubQueryThreshold(Integer.MAX_VALUE)
+            .withDecorrelationEnabled(true)
+            .withExpand(false)
+            .withHintStrategyTable(
+                HintStrategyTable.builder()
+                    .hintStrategy("DISABLE_RULE", (hint, rel) -> true)
+                    .hintStrategy("EXPAND_DISTINCT_AGG", (hint, rel) -> rel instanceof Aggregate)
+                    // QUERY_ENGINE hint preprocessed by regexp, but to avoid warnings should be also in HintStrategyTable.
+                    .hintStrategy("QUERY_ENGINE", (hint, rel) -> true)
+                    .build()
+            )
+        )
+        .convertletTable(IgniteConvertletTable.INSTANCE)
+        .parserConfig(
+            SqlParser.config()
+                .withParserFactory(IgniteSqlParserImpl.FACTORY)
+                .withLex(Lex.ORACLE)
+                .withConformance(IgniteSqlConformance.INSTANCE))
+        .sqlValidatorConfig(SqlValidator.Config.DEFAULT
+            .withIdentifierExpansion(true)
+            .withDefaultNullCollation(NullCollation.LOW)
+            .withSqlConformance(IgniteSqlConformance.INSTANCE)
+            .withTypeCoercionFactory(IgniteTypeCoercion::new))
+        // Dialects support.
+        .operatorTable(SqlOperatorTables.chain(IgniteStdSqlOperatorTable.INSTANCE, IgniteOwnSqlOperatorTable.instance()))
+        // Context provides a way to store data within the planner session that can be accessed in planner rules.
+        .context(Contexts.empty())
+        // Custom cost factory to use during optimization
+        .costFactory(new IgniteCostFactory())
+        .typeSystem(IgniteTypeSystem.INSTANCE)
+        .traitDefs(new RelTraitDef<?>[] {
+            ConventionTraitDef.INSTANCE,
+            RelCollationTraitDef.INSTANCE,
+            DistributionTraitDef.INSTANCE,
+            RewindabilityTraitDef.INSTANCE,
+            CorrelationTraitDef.INSTANCE,
+        })
+        .build();
+
+    /** Query planner timeout. */
+    private final long queryPlannerTimeout = getLong(IGNITE_CALCITE_PLANNER_TIMEOUT,
+        DFLT_IGNITE_CALCITE_PLANNER_TIMEOUT);
+
+    /** */
+    private final QueryPlanCache qryPlanCache;
+
+    /** */
+    private final QueryTaskExecutor taskExecutor;
+
+    /** */
+    private final FailureProcessor failureProcessor;
+
+    /** */
+    private final AffinityService partSvc;
+
+    /** */
+    private final SchemaHolder schemaHolder;
+
+    /** */
+    private final MessageService msgSvc;
+
+    /** */
+    private final ExchangeService exchangeSvc;
+
+    /** */
+    private final MappingService mappingSvc;
+
+    /** */
+    private final MailboxRegistry mailboxRegistry;
+
+    /** */
+    private final ExecutionService<Object[]> executionSvc;
+
+    /** */
+    private final PrepareServiceImpl prepareSvc;
+
+    /** */
+    private final QueryRegistry qryReg;
+
+    /**
+     * @param ctx Kernal context.
+     */
+    public CalciteQueryProcessor(GridKernalContext ctx) {
+        super(ctx);
+
+        failureProcessor = ctx.failure();
+        schemaHolder = new SchemaHolderImpl(ctx);
+        qryPlanCache = new QueryPlanCacheImpl(ctx);
+        mailboxRegistry = new MailboxRegistryImpl(ctx);
+        taskExecutor = new QueryTaskExecutorImpl(ctx);
+        executionSvc = new ExecutionServiceImpl<>(ctx, ArrayRowHandler.INSTANCE);
+        partSvc = new AffinityServiceImpl(ctx);
+        msgSvc = new MessageServiceImpl(ctx);
+        mappingSvc = new MappingServiceImpl(ctx);
+        exchangeSvc = new ExchangeServiceImpl(ctx);
+        prepareSvc = new PrepareServiceImpl(ctx);
+        qryReg = new QueryRegistryImpl(ctx);
+    }
+
+    /**
+     * @return Affinity service.
+     */
+    public AffinityService affinityService() {
+        return partSvc;
+    }
+
+    /**
+     * @return Query cache.
+     */
+    public QueryPlanCache queryPlanCache() {
+        return qryPlanCache;
+    }
+
+    /**
+     * @return Task executor.
+     */
+    public QueryTaskExecutor taskExecutor() {
+        return taskExecutor;
+    }
+
+    /**
+     * @return Schema holder.
+     */
+    public SchemaHolder schemaHolder() {
+        return schemaHolder;
+    }
+
+    /**
+     * @return Message service.
+     */
+    public MessageService messageService() {
+        return msgSvc;
+    }
+
+    /**
+     * @return Mapping service.
+     */
+    public MappingService mappingService() {
+        return mappingSvc;
+    }
+
+    /**
+     * @return Exchange service.
+     */
+    public ExchangeService exchangeService() {
+        return exchangeSvc;
+    }
+
+    /**
+     * @return Mailbox registry.
+     */
+    public MailboxRegistry mailboxRegistry() {
+        return mailboxRegistry;
+    }
+
+    /**
+     * @return Failure processor.
+     */
+    public FailureProcessor failureProcessor() {
+        return failureProcessor;
+    }
+
+    /** */
+    public PrepareServiceImpl prepareService() {
+        return prepareSvc;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void start() {
+        onStart(ctx,
+            executionSvc,
+            mailboxRegistry,
+            partSvc,
+            schemaHolder,
+            msgSvc,
+            taskExecutor,
+            mappingSvc,
+            qryPlanCache,
+            exchangeSvc,
+            qryReg
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public void stop(boolean cancel) {
+        onStop(
+            qryReg,
+            executionSvc,
+            mailboxRegistry,
+            partSvc,
+            schemaHolder,
+            msgSvc,
+            taskExecutor,
+            mappingSvc,
+            qryPlanCache,
+            exchangeSvc
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public List<FieldsQueryCursor<List<?>>> query(
+        @Nullable QueryContext qryCtx,
+        @Nullable String schemaName,
+        String sql,
+        Object... params
+    ) throws IgniteSQLException {
+        SchemaPlus schema = schemaHolder.schema(schemaName);
+
+        assert schema != null : "Schema not found: " + schemaName;
+
+        QueryPlan plan = queryPlanCache().queryPlan(new CacheKey(schema.getName(), sql));
+
+        if (plan != null) {
+            RootQuery<Object[]> qry = new RootQuery<>(
+                sql,
+                schema,
+                params,
+                qryCtx,
+                exchangeSvc,
+                (q) -> qryReg.unregister(q.id()),
+                log,
+                queryPlannerTimeout
+            );
+
+            qryReg.register(qry);
+
+            try {
+                return Collections.singletonList(executionSvc.executePlan(
+                    qry,
+                    plan
+                ));
+            }
+            catch (Exception e) {
+                boolean isCanceled = qry.isCancelled();
+
+                qry.cancel();
+
+                qryReg.unregister(qry.id());
+
+                if (isCanceled)
+                    throw new IgniteSQLException("The query was cancelled while planning", IgniteQueryErrorCode.QUERY_CANCELED, e);
+                else
+                    throw e;
+
+            }
+        }
+
+        SqlNodeList qryList = Commons.parse(sql, FRAMEWORK_CONFIG.getParserConfig());
+        List<FieldsQueryCursor<List<?>>> cursors = new ArrayList<>(qryList.size());
+
+        List<RootQuery<Object[]>> qrys = new ArrayList<>(qryList.size());
+
+        for (final SqlNode sqlNode: qryList) {
+            RootQuery<Object[]> qry = new RootQuery<>(
+                sqlNode.toString(),
+                schemaHolder.schema(schemaName), // Update schema for each query in multiple statements.
+                params,
+                qryCtx,
+                exchangeSvc,
+                (q) -> qryReg.unregister(q.id()),
+                log,
+                queryPlannerTimeout
+            );
+
+            qrys.add(qry);
+
+            qryReg.register(qry);
+
+            try {
+                if (qryList.size() == 1) {
+                    plan = queryPlanCache().queryPlan(
+                        new CacheKey(schemaName, sql), // Use source SQL to avoid redundant parsing next time.
+                        () -> prepareSvc.prepareSingle(sqlNode, qry.planningContext()));
+                }
+                else
+                    plan = prepareSvc.prepareSingle(sqlNode, qry.planningContext());
+
+                cursors.add(executionSvc.executePlan(qry, plan));
+            }
+            catch (Exception e) {
+                boolean isCanceled = qry.isCancelled();
+
+                qrys.forEach(RootQuery::cancel);
+
+                qryReg.unregister(qry.id());
+
+                if (isCanceled)
+                    throw new IgniteSQLException("The query was cancelled while planning", IgniteQueryErrorCode.QUERY_CANCELED, e);
+                else
+                    throw e;
+            }
+        }
+
+        return cursors;
+    }
+
+    /** */
+    private void onStart(GridKernalContext ctx, Service... services) {
+        for (Service service : services) {
+            if (service instanceof LifecycleAware)
+                ((LifecycleAware)service).onStart(ctx);
+        }
+    }
+
+    /** */
+    private void onStop(Service... services) {
+        for (Service service : services) {
+            if (service instanceof LifecycleAware)
+                ((LifecycleAware)service).onStop();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public RunningQuery runningQuery(UUID id) {
+        return qryReg.query(id);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Collection<? extends RunningQuery> runningQueries() {
+        return qryReg.runningQueries();
+    }
+
+    /** */
+    public QueryRegistry queryRegistry() {
+        return qryReg;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/Query.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/Query.java
new file mode 100644
index 0000000..09be27c
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/Query.java
@@ -0,0 +1,242 @@
+/*
+ * 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.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
+import org.apache.ignite.internal.processors.query.GridQueryCancel;
+import org.apache.ignite.internal.processors.query.IgniteSQLException;
+import org.apache.ignite.internal.processors.query.QueryState;
+import org.apache.ignite.internal.processors.query.RunningQuery;
+import org.apache.ignite.internal.processors.query.calcite.exec.ExchangeService;
+import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionCancelledException;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+/** */
+public class Query<RowT> implements RunningQuery {
+    /** */
+    private final UUID initNodeId;
+
+    /** */
+    private final UUID id;
+
+    /** */
+    protected final Object mux = new Object();
+
+    /** */
+    protected final Set<RunningFragment<RowT>> fragments;
+
+    /** */
+    protected final GridQueryCancel cancel;
+
+    /** */
+    protected final Consumer<Query<RowT>> unregister;
+
+    /** */
+    protected volatile QueryState state = QueryState.INITED;
+
+    /** */
+    protected final ExchangeService exch;
+
+    /** */
+    protected final int totalFragmentsCnt;
+
+    /** */
+    protected final AtomicInteger finishedFragmentsCnt = new AtomicInteger();
+
+    /** */
+    protected final Set<Long> initNodeStartedExchanges = new HashSet<>();
+
+    /** Logger. */
+    protected final IgniteLogger log;
+
+    /** */
+    public Query(
+        UUID id,
+        UUID initNodeId,
+        GridQueryCancel cancel,
+        ExchangeService exch,
+        Consumer<Query<RowT>> unregister,
+        IgniteLogger log,
+        int totalFragmentsCnt
+    ) {
+        this.id = id;
+        this.unregister = unregister;
+        this.initNodeId = initNodeId;
+        this.exch = exch;
+        this.log = log;
+
+        this.cancel = cancel != null ? cancel : new GridQueryCancel();
+
+        fragments = Collections.newSetFromMap(new ConcurrentHashMap<>());
+        this.totalFragmentsCnt = totalFragmentsCnt;
+    }
+
+    /** {@inheritDoc} */
+    @Override public UUID id() {
+        return id;
+    }
+
+    /** {@inheritDoc} */
+    @Override public QueryState state() {
+        return state;
+    }
+
+    /** */
+    public UUID initiatorNodeId() {
+        return initNodeId;
+    }
+
+    /** */
+    protected void tryClose() {
+        List<RunningFragment<RowT>> fragments = new ArrayList<>(this.fragments);
+
+        AtomicInteger cntDown = new AtomicInteger(fragments.size());
+
+        for (RunningFragment<RowT> frag : fragments) {
+            frag.context().execute(() -> {
+                frag.root().close();
+                frag.context().cancel();
+
+                if (cntDown.decrementAndGet() == 0)
+                    unregister.accept(this);
+
+            }, frag.root()::onError);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void cancel() {
+        synchronized (mux) {
+            if (state == QueryState.CLOSED)
+                return;
+
+            if (state == QueryState.INITED) {
+                state = QueryState.CLOSING;
+
+                try {
+                    exch.closeQuery(initNodeId, id);
+
+                    return;
+                }
+                catch (IgniteCheckedException e) {
+                    log.warning("Cannot send cancel request to query initiator", e);
+                }
+            }
+
+            if (state == QueryState.EXECUTING || state == QueryState.CLOSING)
+                state = QueryState.CLOSED;
+        }
+
+        for (RunningFragment<RowT> frag : fragments)
+            frag.context().execute(() -> frag.root().onError(new ExecutionCancelledException()), frag.root()::onError);
+
+        tryClose();
+    }
+
+    /** */
+    public void addFragment(RunningFragment<RowT> f) {
+        synchronized (mux) {
+            if (state == QueryState.INITED)
+                state = QueryState.EXECUTING;
+
+            if (state == QueryState.CLOSING || state == QueryState.CLOSED) {
+                throw new IgniteSQLException(
+                    "The query was cancelled",
+                    IgniteQueryErrorCode.QUERY_CANCELED,
+                    new ExecutionCancelledException()
+                );
+            }
+
+            fragments.add(f);
+        }
+    }
+
+    /** */
+    public boolean isCancelled() {
+        return cancel.isCanceled();
+    }
+
+    /** */
+    public void onNodeLeft(UUID nodeId) {
+        if (initNodeId.equals(nodeId))
+            cancel();
+    }
+
+    /**
+     * Callback after the first batch of the query fragment from the node is received.
+     */
+    public void onInboundExchangeStarted(UUID nodeId, long exchangeId) {
+        // No-op.
+    }
+
+    /**
+     * Callback after the last batch of the query fragment from the node is processed.
+     */
+    public void onInboundExchangeFinished(UUID nodeId, long exchangeId) {
+        // No-op.
+    }
+
+    /**
+     * Callback after the first batch of the query fragment from the node is sent.
+     */
+    public void onOutboundExchangeStarted(UUID nodeId, long exchangeId) {
+        if (initNodeId.equals(nodeId))
+            initNodeStartedExchanges.add(exchangeId);
+    }
+
+    /**
+     * Callback after the last batch of the query fragment is sent to all nodes.
+     */
+    public void onOutboundExchangeFinished(long exchangeId) {
+        if (finishedFragmentsCnt.incrementAndGet() == totalFragmentsCnt) {
+            QueryState state0;
+
+            synchronized (mux) {
+                state0 = state;
+
+                if (state0 == QueryState.EXECUTING)
+                    state = QueryState.CLOSED;
+            }
+
+            if (state0 == QueryState.EXECUTING)
+                tryClose();
+        }
+    }
+
+    /** */
+    public boolean isExchangeWithInitNodeStarted(long fragmentId) {
+        // On remote node exchange ID is the same as fragment ID.
+        return initNodeStartedExchanges.contains(fragmentId);
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(Query.class, this, "state", state, "fragments", fragments);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/QueryRegistry.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/QueryRegistry.java
new file mode 100644
index 0000000..f3a411d
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/QueryRegistry.java
@@ -0,0 +1,54 @@
+/*
+ * 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.Collection;
+import java.util.UUID;
+import org.apache.ignite.internal.processors.query.RunningQuery;
+import org.apache.ignite.internal.processors.query.calcite.util.Service;
+
+/**
+ * Registry of the running queries.
+ */
+public interface QueryRegistry extends Service {
+    /**
+     * Register the query or return the existing one with the same identifier.
+     *
+     * @param qry Query to register.
+     * @return Registered query.
+     */
+    RunningQuery register(RunningQuery qry);
+
+    /**
+     * Lookup query by identifier.
+     *
+     * @param id Query identified.
+     * @return Registered query or {@code null} if the query with specified identifier isn't found.
+     */
+    RunningQuery query(UUID id);
+
+    /**
+     * Unregister query by identifier.
+     *
+     * @param id Query identifier.
+     */
+    void unregister(UUID id);
+
+    /** */
+    Collection<? extends RunningQuery> runningQueries();
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/QueryRegistryImpl.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/QueryRegistryImpl.java
new file mode 100644
index 0000000..58c5df1
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/QueryRegistryImpl.java
@@ -0,0 +1,108 @@
+/*
+ * 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.Collection;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import org.apache.ignite.cache.query.QueryCancelledException;
+import org.apache.ignite.cache.query.SqlFieldsQuery;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.processors.cache.query.GridCacheQueryType;
+import org.apache.ignite.internal.processors.query.GridQueryCancel;
+import org.apache.ignite.internal.processors.query.RunningQuery;
+import org.apache.ignite.internal.processors.query.RunningQueryManager;
+import org.apache.ignite.internal.processors.query.calcite.util.AbstractService;
+import org.apache.ignite.internal.util.IgniteUtils;
+
+/**
+ * Registry of the running queries.
+ */
+public class QueryRegistryImpl extends AbstractService implements QueryRegistry {
+    /** */
+    private final ConcurrentMap<UUID, RunningQuery> runningQrys = new ConcurrentHashMap<>();
+
+    /** */
+    protected final GridKernalContext kctx;
+
+    /** */
+    public QueryRegistryImpl(GridKernalContext ctx) {
+        super(ctx);
+
+        kctx = ctx;
+    }
+
+    /** {@inheritDoc} */
+    @Override public RunningQuery register(RunningQuery qry) {
+        return runningQrys.computeIfAbsent(qry.id(), k -> {
+            if (!(qry instanceof RootQuery))
+                return qry;
+
+            RootQuery<?> rootQry = (RootQuery<?>)qry;
+
+            RunningQueryManager qryMgr = kctx.query().runningQueryManager();
+
+            SqlFieldsQuery fieldsQry = rootQry.context().unwrap(SqlFieldsQuery.class);
+
+            String initiatorId = fieldsQry != null ? fieldsQry.getQueryInitiatorId() : null;
+
+            long locId = qryMgr.register(rootQry.sql(), GridCacheQueryType.SQL_FIELDS, rootQry.context().schemaName(),
+                false, createCancelToken(qry), initiatorId);
+
+            rootQry.localQueryId(locId);
+
+            return qry;
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public RunningQuery query(UUID id) {
+        return runningQrys.get(id);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void unregister(UUID id) {
+        RunningQuery val = runningQrys.remove(id);
+        if (val instanceof RootQuery<?>)
+            kctx.query().runningQueryManager().unregister(((RootQuery<?>)val).localQueryId(), null);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Collection<? extends RunningQuery> runningQueries() {
+        return runningQrys.values();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void tearDown() {
+        runningQrys.values().forEach(q -> IgniteUtils.close(q::cancel, log));
+        runningQrys.clear();
+    }
+
+    /** */
+    private static GridQueryCancel createCancelToken(RunningQuery qry) {
+        GridQueryCancel token = new GridQueryCancel();
+        try {
+            token.add(qry::cancel);
+        }
+        catch (QueryCancelledException ignore) {
+            // Ignore, since it is impossible;
+        }
+        return token;
+    }
+}
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/KillQueryRun.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/RemoteFragmentKey.java
similarity index 50%
copy from modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/KillQueryRun.java
copy to modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/RemoteFragmentKey.java
index a6c0565..a2a0c64 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/KillQueryRun.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/RemoteFragmentKey.java
@@ -13,59 +13,54 @@
  * 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.h2;
+package org.apache.ignite.internal.processors.query.calcite;
 
 import java.util.UUID;
-import org.apache.ignite.internal.util.future.GridFutureAdapter;
 
-/**
- * Kill Query run context.
- */
-class KillQueryRun {
-    /** Node id. */
+/** */
+final class RemoteFragmentKey {
+    /** */
     private final UUID nodeId;
 
-    /** Node query id. */
-    private final long nodeQryId;
-
-    /** Cancellation query future. */
-    private final GridFutureAdapter<String> cancelFut;
-
-    /**
-     * Constructor.
-     *
-     * @param nodeId Node id.
-     * @param cancelFut Cancellation query future.
-     */
-    public KillQueryRun(UUID nodeId, long nodeQryId, GridFutureAdapter<String> cancelFut) {
-        assert nodeId != null;
+    /** */
+    private final long fragmentId;
 
+    /** */
+    RemoteFragmentKey(UUID nodeId, long fragmentId) {
         this.nodeId = nodeId;
-        this.nodeQryId = nodeQryId;
-        this.cancelFut = cancelFut;
+        this.fragmentId = fragmentId;
     }
 
-    /**
-     * @return Node id.
-     */
+    /** */
     public UUID nodeId() {
         return nodeId;
     }
 
-    /**
-     * @return Node query id.
-     */
-    public long nodeQryId() {
-        return nodeQryId;
+    /** */
+    public long fragmentId() {
+        return fragmentId;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+
+        RemoteFragmentKey that = (RemoteFragmentKey)o;
+
+        if (fragmentId != that.fragmentId)
+            return false;
+        return nodeId.equals(that.nodeId);
     }
 
-    /**
-     * @return Cancellation query future.
-     */
-    public GridFutureAdapter<String> cancelFuture() {
-        return cancelFut;
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        int res = nodeId.hashCode();
+        res = 31 * res + (int)(fragmentId ^ (fragmentId >>> 32));
+        return res;
     }
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/RootQuery.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/RootQuery.java
new file mode 100644
index 0000000..1b81f63
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/RootQuery.java
@@ -0,0 +1,405 @@
+/*
+ * 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.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import org.apache.calcite.plan.Context;
+import org.apache.calcite.schema.SchemaPlus;
+import org.apache.calcite.tools.Frameworks;
+import org.apache.calcite.util.CancelFlag;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.cache.query.QueryCancelledException;
+import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException;
+import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
+import org.apache.ignite.internal.processors.query.GridQueryCancel;
+import org.apache.ignite.internal.processors.query.IgniteSQLException;
+import org.apache.ignite.internal.processors.query.QueryContext;
+import org.apache.ignite.internal.processors.query.QueryState;
+import org.apache.ignite.internal.processors.query.calcite.exec.ExchangeService;
+import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.Node;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.RootNode;
+import org.apache.ignite.internal.processors.query.calcite.prepare.BaseQueryContext;
+import org.apache.ignite.internal.processors.query.calcite.prepare.Fragment;
+import org.apache.ignite.internal.processors.query.calcite.prepare.MultiStepPlan;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PlanningContext;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+import static org.apache.ignite.internal.processors.query.calcite.CalciteQueryProcessor.FRAMEWORK_CONFIG;
+
+/**
+ * The RootQuery is created on the query initiator (originator) node as the first step of a query run;
+ * It contains the information about query state, contexts, remote fragments;
+ * It provides 'cancel' functionality for running query like a base query class.
+ */
+public class RootQuery<RowT> extends Query<RowT> {
+    /** SQL query. */
+    private final String sql;
+
+    /** Parameters. */
+    private final Object[] params;
+
+    /** Remote nodes unfinished fragments count. AtomicInteger used just as int holder, there is no concurrency here. */
+    private final Map<UUID, AtomicInteger> remoteFragments;
+
+    /** Node to fragment. */
+    private final Set<RemoteFragmentKey> waiting;
+
+    /** */
+    private volatile RootNode<RowT> root;
+
+    /** */
+    private volatile PlanningContext pctx;
+
+    /** */
+    private final BaseQueryContext ctx;
+
+    /** */
+    private final long plannerTimeout;
+
+    /** */
+    private volatile long locQryId;
+
+    /** */
+    public RootQuery(
+        String sql,
+        SchemaPlus schema,
+        Object[] params,
+        QueryContext qryCtx,
+        ExchangeService exch,
+        Consumer<Query<RowT>> unregister,
+        IgniteLogger log,
+        long plannerTimeout
+    ) {
+        super(
+            UUID.randomUUID(),
+            null,
+            qryCtx != null ? qryCtx.unwrap(GridQueryCancel.class) : null,
+            exch,
+            unregister,
+            log,
+            0 // Total fragments count not used for RootQuery.
+        );
+
+        this.sql = sql;
+        this.params = params;
+
+        remoteFragments = new HashMap<>();
+        waiting = new HashSet<>();
+
+        this.plannerTimeout = plannerTimeout;
+
+        Context parent = Commons.convert(qryCtx);
+
+        ctx = BaseQueryContext.builder()
+            .parentContext(parent)
+            .frameworkConfig(
+                Frameworks.newConfigBuilder(FRAMEWORK_CONFIG)
+                    .defaultSchema(schema)
+                    .build()
+            )
+            .logger(log)
+            .build();
+    }
+
+    /**
+     * Creates the new root that inherits the query parameters from {@code this} query.
+     * Is used to execute DML query immediately after (inside) DDL.
+     * e.g.:
+     *      CREATE TABLE MY_TABLE AS SELECT ... FROM ...;
+     *
+     * @param schema new schema.
+     */
+    public RootQuery<RowT> childQuery(SchemaPlus schema) {
+        return new RootQuery<>(sql, schema, params, QueryContext.of(cancel), exch, unregister, log, plannerTimeout);
+    }
+
+    /** */
+    public BaseQueryContext context() {
+        return ctx;
+    }
+
+    /** */
+    public String sql() {
+        return sql;
+    }
+
+    /** */
+    public Object[] parameters() {
+        return params;
+    }
+
+    /**
+     * Starts maping phase for the query.
+     */
+    public void mapping() {
+        synchronized (mux) {
+            if (state == QueryState.CLOSED) {
+                throw new IgniteSQLException(
+                    "The query was cancelled while executing.",
+                    IgniteQueryErrorCode.QUERY_CANCELED
+                );
+            }
+
+            state = QueryState.MAPPING;
+        }
+    }
+
+    /**
+     * Starts execution phase for the query and setup remote fragments.
+     */
+    public void run(ExecutionContext<RowT> ctx, MultiStepPlan plan, Node<RowT> root) {
+        synchronized (mux) {
+            if (state == QueryState.CLOSED) {
+                throw new IgniteSQLException(
+                    "The query was cancelled while executing.",
+                    IgniteQueryErrorCode.QUERY_CANCELED
+                );
+            }
+
+            RootNode<RowT> rootNode = new RootNode<>(ctx, plan.fieldsMetadata().rowType(), this::tryClose);
+            rootNode.register(root);
+
+            addFragment(new RunningFragment<>(F.first(plan.fragments()).root(), rootNode, ctx));
+
+            this.root = rootNode;
+
+            for (int i = 1; i < plan.fragments().size(); i++) {
+                Fragment fragment = plan.fragments().get(i);
+                List<UUID> nodes = plan.mapping(fragment).nodeIds();
+
+                nodes.forEach(n -> remoteFragments.compute(n, (id, cnt) -> {
+                    if (cnt == null)
+                        return new AtomicInteger(1);
+                    else {
+                        cnt.incrementAndGet();
+
+                        return cnt;
+                    }
+                }));
+
+                for (UUID node : nodes)
+                    waiting.add(new RemoteFragmentKey(node, fragment.fragmentId()));
+            }
+
+            state = QueryState.EXECUTING;
+        }
+    }
+
+    /**
+     * Can be called multiple times after receive each error
+     * at {@link #onResponse(RemoteFragmentKey, Throwable)}.
+     */
+    @Override protected void tryClose() {
+        QueryState state0 = null;
+
+        synchronized (mux) {
+            if (state == QueryState.CLOSED)
+                return;
+
+            if (state == QueryState.INITED || state == QueryState.PLANNING || state == QueryState.MAPPING) {
+                state = QueryState.CLOSED;
+
+                return;
+            }
+
+            if (state == QueryState.EXECUTING) {
+                state0 = state = QueryState.CLOSING;
+
+                root.closeInternal();
+            }
+
+            if (state == QueryState.CLOSING && waiting.isEmpty())
+                state0 = state = QueryState.CLOSED;
+        }
+
+        if (state0 == QueryState.CLOSED) {
+            try {
+                IgniteException wrpEx = null;
+
+                for (Map.Entry<UUID, AtomicInteger> entry : remoteFragments.entrySet()) {
+                    try {
+                        // Don't send close message if all remote fragments are finished (query is self-closed on the
+                        // remote node in this case).
+                        if (!entry.getKey().equals(root.context().localNodeId()) && entry.getValue().get() > 0)
+                            exch.closeQuery(entry.getKey(), id());
+                    }
+                    catch (IgniteCheckedException e) {
+                        if (wrpEx == null)
+                            wrpEx = new IgniteException("Failed to send cancel message. [nodeId=" + entry.getKey() + ']', e);
+                        else
+                            wrpEx.addSuppressed(e);
+                    }
+                }
+
+                if (wrpEx != null)
+                    log.warning("An exception occures during the query cancel", wrpEx);
+            }
+            finally {
+                super.tryClose();
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void cancel() {
+        cancel.cancel();
+
+        tryClose();
+    }
+
+    /** */
+    public PlanningContext planningContext() {
+        synchronized (mux) {
+            if (state == QueryState.CLOSED || state == QueryState.CLOSING) {
+                throw new IgniteSQLException(
+                    "The query was cancelled while executing.",
+                    IgniteQueryErrorCode.QUERY_CANCELED
+                );
+            }
+
+            if (state == QueryState.EXECUTING || state == QueryState.MAPPING) {
+                throw new IgniteSQLException(
+                    "Invalid query flow",
+                    IgniteQueryErrorCode.UNKNOWN
+                );
+            }
+
+            if (pctx == null) {
+                state = QueryState.PLANNING;
+
+                pctx = PlanningContext.builder()
+                    .parentContext(ctx)
+                    .query(sql)
+                    .parameters(params)
+                    .plannerTimeout(plannerTimeout)
+                    .build();
+
+                try {
+                    cancel.add(() -> pctx.unwrap(CancelFlag.class).requestCancel());
+                }
+                catch (QueryCancelledException e) {
+                    throw new IgniteSQLException(e.getMessage(), IgniteQueryErrorCode.QUERY_CANCELED, e);
+                }
+            }
+
+            return pctx;
+        }
+    }
+
+    /** */
+    public Iterator<RowT> iterator() {
+        return root;
+    }
+
+    /** */
+    public long localQueryId() {
+        return locQryId;
+    }
+
+    /** */
+    public void localQueryId(long locQryId) {
+        this.locQryId = locQryId;
+    }
+
+    /** */
+    @Override public void onNodeLeft(UUID nodeId) {
+        List<RemoteFragmentKey> fragments = null;
+
+        synchronized (mux) {
+            fragments = waiting.stream().filter(f -> f.nodeId().equals(nodeId)).collect(Collectors.toList());
+        }
+
+        if (!F.isEmpty(fragments)) {
+            ClusterTopologyCheckedException ex = new ClusterTopologyCheckedException(
+                "Failed to start query, node left. nodeId=" + nodeId);
+
+            for (RemoteFragmentKey fragment : fragments)
+                onResponse(fragment, ex);
+        }
+    }
+
+    /** */
+    public void onResponse(UUID nodeId, long fragmentId, Throwable error) {
+        onResponse(new RemoteFragmentKey(nodeId, fragmentId), error);
+    }
+
+    /** */
+    private void onResponse(RemoteFragmentKey fragment, Throwable error) {
+        QueryState state;
+        synchronized (mux) {
+            waiting.remove(fragment);
+
+            state = this.state;
+        }
+
+        if (error != null)
+            onError(error);
+        else if (state == QueryState.CLOSING)
+            tryClose();
+    }
+
+    /** */
+    public void onError(Throwable error) {
+        root.onError(error);
+
+        tryClose();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onInboundExchangeStarted(UUID nodeId, long exchangeId) {
+        onResponse(nodeId, exchangeId, null);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onInboundExchangeFinished(UUID nodeId, long exchangeId) {
+        AtomicInteger cnt = remoteFragments.get(nodeId);
+
+        assert cnt != null : nodeId;
+
+        cnt.decrementAndGet();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onOutboundExchangeStarted(UUID nodeId, long exchangeId) {
+        // No-op.
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onOutboundExchangeFinished(long exchangeId) {
+        // No-op.
+    }
+
+    /** */
+    @Override public String toString() {
+        return S.toString(RootQuery.class, this);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/RunningFragment.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/RunningFragment.java
new file mode 100644
index 0000000..2627e022
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/RunningFragment.java
@@ -0,0 +1,79 @@
+/*
+ * 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.Objects;
+
+import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.AbstractNode;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+/** */
+public class RunningFragment<Row> {
+    /** Relation tree of the fragment is used to generate fragment human-readable description. */
+    private final IgniteRel rootRel;
+
+    /** */
+    private final AbstractNode<Row> root;
+
+    /** */
+    private final ExecutionContext<Row> ectx;
+
+    /** */
+    public RunningFragment(
+        IgniteRel rootRel,
+        AbstractNode<Row> root,
+        ExecutionContext<Row> ectx) {
+        this.rootRel = rootRel;
+        this.root = root;
+        this.ectx = ectx;
+    }
+
+    /** */
+    public ExecutionContext<Row> context() {
+        return ectx;
+    }
+
+    /** */
+    public AbstractNode<Row> root() {
+        return root;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+
+        RunningFragment<Row> fragment = (RunningFragment<Row>)o;
+
+        return Objects.equals(ectx, fragment.ectx);
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        return Objects.hash(ectx);
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(RunningFragment.class, this);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/AbstractIndexScan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/AbstractIndexScan.java
new file mode 100644
index 0000000..ca12e49
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/AbstractIndexScan.java
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ignite.internal.processors.query.calcite.exec;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.cache.query.index.sorted.inline.IndexQueryContext;
+import org.apache.ignite.internal.util.lang.GridCursor;
+import org.apache.ignite.internal.util.lang.GridIteratorAdapter;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Abstract index scan.
+ */
+public abstract class AbstractIndexScan<Row, IdxRow> implements Iterable<Row>, AutoCloseable {
+    /** */
+    private final TreeIndex<IdxRow> idx;
+
+    /** Additional filters. */
+    private final Predicate<Row> filters;
+
+    /** Lower index scan bound. */
+    private final Supplier<Row> lowerBound;
+
+    /** Upper index scan bound. */
+    private final Supplier<Row> upperBound;
+
+    /** */
+    private final Function<Row, Row> rowTransformer;
+
+    /** */
+    protected final ExecutionContext<Row> ectx;
+
+    /** */
+    protected final RelDataType rowType;
+
+    /**
+     * @param ectx Execution context.
+     * @param idx Physical index.
+     * @param filters Additional filters.
+     * @param lowerBound Lower index scan bound.
+     * @param upperBound Upper index scan bound.
+     */
+    protected AbstractIndexScan(
+        ExecutionContext<Row> ectx,
+        RelDataType rowType,
+        TreeIndex<IdxRow> idx,
+        Predicate<Row> filters,
+        Supplier<Row> lowerBound,
+        Supplier<Row> upperBound,
+        Function<Row, Row> rowTransformer
+    ) {
+        this.ectx = ectx;
+        this.rowType = rowType;
+        this.idx = idx;
+        this.filters = filters;
+        this.lowerBound = lowerBound;
+        this.upperBound = upperBound;
+        this.rowTransformer = rowTransformer;
+    }
+
+    /** {@inheritDoc} */
+    @Override public synchronized Iterator<Row> iterator() {
+        IdxRow lower = lowerBound == null ? null : row2indexRow(lowerBound.get());
+        IdxRow upper = upperBound == null ? null : row2indexRow(upperBound.get());
+
+        return new IteratorImpl(idx.find(lower, upper, indexQueryContext()));
+    }
+
+    /** */
+    protected abstract IdxRow row2indexRow(Row bound);
+
+    /** */
+    protected abstract Row indexRow2Row(IdxRow idxRow) throws IgniteCheckedException;
+
+    /** */
+    protected abstract IndexQueryContext indexQueryContext();
+
+    /** {@inheritDoc} */
+    @Override public void close() {
+        // No-op.
+    }
+
+    /** */
+    private class IteratorImpl extends GridIteratorAdapter<Row> {
+        /** */
+        private final GridCursor<IdxRow> cursor;
+
+        /** Next element. */
+        private Row next;
+
+        /** */
+        private IteratorImpl(@NotNull GridCursor<IdxRow> cursor) {
+            this.cursor = cursor;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean hasNextX() throws IgniteCheckedException {
+            advance();
+
+            return next != null;
+        }
+
+        /** {@inheritDoc} */
+        @Override public Row nextX() throws IgniteCheckedException {
+            advance();
+
+            if (next == null)
+                throw new NoSuchElementException();
+
+            Row res = next;
+
+            next = null;
+
+            return res;
+        }
+
+        /** {@inheritDoc} */
+        @Override public void removeX() {
+            throw new UnsupportedOperationException("Remove is not supported.");
+        }
+
+        /** */
+        private void advance() throws IgniteCheckedException {
+            assert cursor != null;
+
+            if (next != null)
+                return;
+
+            while (next == null && cursor.next()) {
+                IdxRow idxRow = cursor.get();
+
+                Row r = indexRow2Row(idxRow);
+
+                if (filters != null && !filters.test(r))
+                    continue;
+
+                if (rowTransformer != null)
+                    r = rowTransformer.apply(r);
+
+                next = r;
+            }
+        }
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ArrayRowHandler.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ArrayRowHandler.java
new file mode 100644
index 0000000..be2eeb6
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ArrayRowHandler.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.exec;
+
+import java.lang.reflect.Type;
+
+import org.apache.ignite.internal.util.typedef.F;
+
+/**
+ * Handler for rows that implemented as a simple objects array.
+ */
+public class ArrayRowHandler implements RowHandler<Object[]> {
+    /** */
+    public static final RowHandler<Object[]> INSTANCE = new ArrayRowHandler();
+
+    /** */
+    private ArrayRowHandler() {}
+
+    /** {@inheritDoc} */
+    @Override public Object get(int field, Object[] row) {
+        return row[field];
+    }
+
+    /** {@inheritDoc} */
+    @Override public void set(int field, Object[] row, Object val) {
+        row[field] = val;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Object[] concat(Object[] left, Object[] right) {
+        return F.concat(left, right);
+    }
+
+    /** {@inheritDoc} */
+    @Override public int columnCount(Object[] row) {
+        return row.length;
+    }
+
+    /** {@inheritDoc} */
+    @Override public RowFactory<Object[]> factory(Type... types) {
+        int rowLen = types.length;
+
+        return new RowFactory<Object[]>() {
+            /** {@inheritDoc} */
+            @Override public RowHandler<Object[]> handler() {
+                return ArrayRowHandler.this;
+            }
+
+            /** {@inheritDoc} */
+            @Override public Object[] create() {
+                return new Object[rowLen];
+            }
+
+            /** {@inheritDoc} */
+            @Override public Object[] create(Object... fields) {
+                assert fields.length == rowLen;
+
+                return fields;
+            }
+        };
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ClosableIteratorsHolder.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ClosableIteratorsHolder.java
new file mode 100644
index 0000000..caae30f
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ClosableIteratorsHolder.java
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.exec;
+
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+/**
+ */
+@SuppressWarnings({"rawtypes", "unchecked"})
+public class ClosableIteratorsHolder {
+    /** */
+    private final ReferenceQueue refQueue;
+
+    /** */
+    private final Map<Reference, Object> refMap;
+
+    /** */
+    private final IgniteLogger log;
+
+    /** */
+    private volatile boolean stopped;
+
+    /** */
+    private Thread cleanWorker;
+
+    /** */
+    public ClosableIteratorsHolder(IgniteLogger log) {
+        this.log = log;
+
+        refQueue = new ReferenceQueue<>();
+        refMap = new ConcurrentHashMap<>();
+    }
+
+    /**
+     * @param src Closeable iterator.
+     * @return Weak closable iterator wrapper.
+     */
+    public <T> Iterator<T> iterator(final Iterator<T> src) {
+        cleanUp(false);
+
+        return new DelegatingIterator<>(src);
+    }
+
+    /** */
+    public void init() {
+        cleanWorker = new Thread(() -> cleanUp(true), "ignite-calcite-iterators-cleanup");
+        cleanWorker.setDaemon(true);
+        cleanWorker.start();
+    }
+
+    /** */
+    public void tearDown() {
+        stopped = true;
+        refMap.clear();
+        U.interrupt(cleanWorker);
+    }
+
+    /** */
+    private void cleanUp(boolean blocking) {
+        for (Reference<?> ref = nextRef(blocking); !stopped && ref != null; ref = nextRef(blocking))
+            Commons.close(refMap.remove(ref), log);
+    }
+
+    /** */
+    private Reference nextRef(boolean blocking) {
+        try {
+            return !blocking ? refQueue.poll() : refQueue.remove();
+        }
+        catch (InterruptedException ignored) {
+            return null;
+        }
+    }
+
+    /** */
+    private AutoCloseable closeable(Object referent, Object resource) {
+        if (!(resource instanceof AutoCloseable))
+            return null;
+
+        return new CloseableReference(referent, resource);
+    }
+
+    /** */
+    private final class DelegatingIterator<T> implements Iterator<T>, AutoCloseable {
+        /** */
+        private final Iterator<T> delegate;
+
+        /** */
+        private final AutoCloseable closeable;
+
+        /** */
+        private DelegatingIterator(Iterator<T> delegate) {
+            closeable = closeable(this, this.delegate = delegate);
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean hasNext() {
+            return delegate.hasNext();
+        }
+
+        /** {@inheritDoc} */
+        @Override public T next() {
+            return delegate.next();
+        }
+
+        /** {@inheritDoc} */
+        @Override public void remove() {
+            delegate.remove();
+        }
+
+        /** {@inheritDoc} */
+        @Override public void forEachRemaining(Consumer<? super T> action) {
+            delegate.forEachRemaining(action);
+        }
+
+        /** {@inheritDoc} */
+        @Override public void close() throws Exception {
+            Commons.close(closeable);
+        }
+    }
+
+    /** */
+    private final class CloseableReference extends WeakReference implements AutoCloseable {
+        /** */
+        private CloseableReference(Object referent, Object resource) {
+            super(referent, refQueue);
+
+            refMap.put(this, resource);
+        }
+
+        /** {@inheritDoc} */
+        @Override public void close() throws Exception {
+            try {
+                Commons.close(refMap.remove(this));
+            }
+            finally {
+                clear();
+            }
+        }
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExchangeService.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExchangeService.java
new file mode 100644
index 0000000..5c618d2
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExchangeService.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.exec;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.processors.query.calcite.util.Service;
+
+/**
+ *
+ */
+public interface ExchangeService extends Service {
+    /**
+     * Sends a batch of data to remote node.
+     * @param nodeId Target node ID.
+     * @param qryId Query ID.
+     * @param fragmentId Target fragment ID.
+     * @param exchangeId Exchange ID.
+     * @param batchId Batch ID.
+     * @param last Last batch flag.
+     * @param rows Data rows.
+     */
+    <Row> void sendBatch(UUID nodeId, UUID qryId, long fragmentId, long exchangeId, int batchId, boolean last,
+        List<Row> rows) throws IgniteCheckedException;
+
+    /**
+     * Acknowledges a batch with given ID is processed.
+     * @param nodeId Node ID to notify.
+     * @param qryId Query ID.
+     * @param fragmentId Target fragment ID.
+     * @param exchangeId Exchange ID.
+     * @param batchId Batch ID.
+     */
+    void acknowledge(UUID nodeId, UUID qryId, long fragmentId, long exchangeId, int batchId) throws IgniteCheckedException;
+
+    /**
+     * Sends cancel request.
+     * @param nodeId Target node ID.
+     * @param qryId Query ID.
+     * @param fragmentId Target fragment ID.
+     * @param exchangeId Exchange ID.
+     */
+    void closeInbox(UUID nodeId, UUID qryId, long fragmentId, long exchangeId) throws IgniteCheckedException;
+
+    /**
+     * Sends cancel request.
+     * @param nodeId Target node ID.
+     * @param qryId Query ID.
+     */
+    void closeQuery(UUID nodeId, UUID qryId) throws IgniteCheckedException;
+
+    /**
+     * @param nodeId Target node ID.
+     * @param qryId Query ID.
+     * @param fragmentId Source fragment ID.
+     * @param err Exception to send.
+     * @throws IgniteCheckedException On error marshaling or send ErrorMessage.
+     */
+    void sendError(UUID nodeId, UUID qryId, long fragmentId, Throwable err) throws IgniteCheckedException;
+
+    /**
+     * @param nodeId Node ID.
+     * @return {@code true} if node is alive, {@code false} otherwise.
+     */
+    boolean alive(UUID nodeId);
+
+    /**
+     * Callback after the last batch of the query fragment is sent.
+     */
+    void onOutboundExchangeFinished(UUID qryId, long exchangeId);
+
+    /**
+     * Callback after the last batch of the query fragment from the node is processed.
+     */
+    void onInboundExchangeFinished(UUID nodeId, UUID qryId, long exchangeId);
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExchangeServiceImpl.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExchangeServiceImpl.java
new file mode 100644
index 0000000..2a2a246
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExchangeServiceImpl.java
@@ -0,0 +1,316 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.exec;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.processors.query.RunningQuery;
+import org.apache.ignite.internal.processors.query.calcite.CalciteQueryProcessor;
+import org.apache.ignite.internal.processors.query.calcite.Query;
+import org.apache.ignite.internal.processors.query.calcite.QueryRegistry;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.Inbox;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.Outbox;
+import org.apache.ignite.internal.processors.query.calcite.message.ErrorMessage;
+import org.apache.ignite.internal.processors.query.calcite.message.InboxCloseMessage;
+import org.apache.ignite.internal.processors.query.calcite.message.MessageService;
+import org.apache.ignite.internal.processors.query.calcite.message.MessageType;
+import org.apache.ignite.internal.processors.query.calcite.message.QueryBatchAcknowledgeMessage;
+import org.apache.ignite.internal.processors.query.calcite.message.QueryBatchMessage;
+import org.apache.ignite.internal.processors.query.calcite.message.QueryCloseMessage;
+import org.apache.ignite.internal.processors.query.calcite.metadata.FragmentDescription;
+import org.apache.ignite.internal.processors.query.calcite.prepare.BaseQueryContext;
+import org.apache.ignite.internal.processors.query.calcite.util.AbstractService;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.apache.ignite.internal.util.typedef.F;
+
+/**
+ *
+ */
+public class ExchangeServiceImpl extends AbstractService implements ExchangeService {
+    /** */
+    private final UUID locaNodeId;
+
+    /** */
+    private QueryTaskExecutor taskExecutor;
+
+    /** */
+    private MailboxRegistry mailboxRegistry;
+
+    /** */
+    private MessageService msgSvc;
+
+    /** */
+    private QueryRegistry qryRegistry;
+
+    /**
+     * @param ctx Kernal context.
+     */
+    public ExchangeServiceImpl(GridKernalContext ctx) {
+        super(ctx);
+
+        locaNodeId = ctx.localNodeId();
+    }
+
+    /**
+     * @param taskExecutor Task executor.
+     */
+    public void taskExecutor(QueryTaskExecutor taskExecutor) {
+        this.taskExecutor = taskExecutor;
+    }
+
+    /**
+     * @return Task executor.
+     */
+    public QueryTaskExecutor taskExecutor() {
+        return taskExecutor;
+    }
+
+    /**
+     * @param mailboxRegistry Mailbox registry.
+     */
+    public void mailboxRegistry(MailboxRegistry mailboxRegistry) {
+        this.mailboxRegistry = mailboxRegistry;
+    }
+
+    /**
+     * @return  Mailbox registry.
+     */
+    public MailboxRegistry mailboxRegistry() {
+        return mailboxRegistry;
+    }
+
+    /**
+     * @param msgSvc Message service.
+     */
+    public void messageService(MessageService msgSvc) {
+        this.msgSvc = msgSvc;
+    }
+
+    /**
+     * @return  Message service.
+     */
+    public MessageService messageService() {
+        return msgSvc;
+    }
+
+    /** */
+    public void queryRegistry(QueryRegistry qryRegistry) {
+        this.qryRegistry = qryRegistry;
+    }
+
+    /** {@inheritDoc} */
+    @Override public <Row> void sendBatch(UUID nodeId, UUID qryId, long fragmentId, long exchangeId, int batchId,
+        boolean last, List<Row> rows) throws IgniteCheckedException {
+        messageService().send(nodeId, new QueryBatchMessage(qryId, fragmentId, exchangeId, batchId, last, Commons.cast(rows)));
+
+        if (batchId == 0) {
+            Query<?> qry = (Query<?>)qryRegistry.query(qryId);
+
+            if (qry != null)
+                qry.onOutboundExchangeStarted(nodeId, exchangeId);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void acknowledge(UUID nodeId, UUID qryId, long fragmentId, long exchangeId, int batchId)
+        throws IgniteCheckedException {
+        messageService().send(nodeId, new QueryBatchAcknowledgeMessage(qryId, fragmentId, exchangeId, batchId));
+    }
+
+    /** {@inheritDoc} */
+    @Override public void closeQuery(UUID nodeId, UUID qryId) throws IgniteCheckedException {
+        messageService().send(nodeId, new QueryCloseMessage(qryId));
+    }
+
+    /** {@inheritDoc} */
+    @Override public void closeInbox(UUID nodeId, UUID qryId, long fragmentId, long exchangeId) throws IgniteCheckedException {
+        messageService().send(nodeId, new InboxCloseMessage(qryId, fragmentId, exchangeId));
+    }
+
+    /** {@inheritDoc} */
+    @Override public void sendError(UUID nodeId, UUID qryId, long fragmentId, Throwable err) throws IgniteCheckedException {
+        messageService().send(nodeId, new ErrorMessage(qryId, fragmentId, err));
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onStart(GridKernalContext ctx) {
+        CalciteQueryProcessor proc =
+            Objects.requireNonNull(Commons.lookupComponent(ctx, CalciteQueryProcessor.class));
+
+        taskExecutor(proc.taskExecutor());
+        mailboxRegistry(proc.mailboxRegistry());
+        messageService(proc.messageService());
+        queryRegistry(proc.queryRegistry());
+
+        init();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void init() {
+        messageService().register((n, m) -> onMessage(n, (InboxCloseMessage)m), MessageType.QUERY_INBOX_CANCEL_MESSAGE);
+        messageService().register((n, m) -> onMessage(n, (QueryBatchAcknowledgeMessage)m), MessageType.QUERY_ACKNOWLEDGE_MESSAGE);
+        messageService().register((n, m) -> onMessage(n, (QueryBatchMessage)m), MessageType.QUERY_BATCH_MESSAGE);
+        messageService().register((n, m) -> onMessage(n, (QueryCloseMessage)m), MessageType.QUERY_CLOSE_MESSAGE);
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean alive(UUID nodeId) {
+        return messageService().alive(nodeId);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onOutboundExchangeFinished(UUID qryId, long exchangeId) {
+        Query<?> qry = (Query<?>)qryRegistry.query(qryId);
+
+        if (qry != null)
+            qry.onOutboundExchangeFinished(exchangeId);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onInboundExchangeFinished(UUID nodeId, UUID qryId, long exchangeId) {
+        Query<?> qry = (Query<?>)qryRegistry.query(qryId);
+
+        if (qry != null)
+            qry.onInboundExchangeFinished(nodeId, exchangeId);
+    }
+
+    /** */
+    protected void onMessage(UUID nodeId, InboxCloseMessage msg) {
+        Collection<Inbox<?>> inboxes = mailboxRegistry().inboxes(msg.queryId(), msg.fragmentId(), msg.exchangeId());
+
+        if (!F.isEmpty(inboxes)) {
+            for (Inbox<?> inbox : inboxes)
+                inbox.context().execute(inbox::close, inbox::onError);
+        }
+        else if (log.isDebugEnabled()) {
+            log.debug("Stale inbox cancel message received: [" +
+                "nodeId=" + nodeId +
+                ", queryId=" + msg.queryId() +
+                ", fragmentId=" + msg.fragmentId() +
+                ", exchangeId=" + msg.exchangeId() + "]");
+        }
+    }
+
+    /** */
+    protected void onMessage(UUID nodeId, QueryCloseMessage msg) {
+        RunningQuery qry = qryRegistry.query(msg.queryId());
+
+        if (qry != null)
+            qry.cancel();
+        else {
+            if (log.isDebugEnabled()) {
+                log.debug("Stale query close message received: [" +
+                    "nodeId=" + nodeId +
+                    ", queryId=" + msg.queryId() + "]");
+            }
+        }
+    }
+
+    /** */
+    protected void onMessage(UUID nodeId, QueryBatchAcknowledgeMessage msg) {
+        Outbox<?> outbox = mailboxRegistry().outbox(msg.queryId(), msg.exchangeId());
+
+        if (outbox != null) {
+            try {
+                outbox.onAcknowledge(nodeId, msg.batchId());
+            }
+            catch (Throwable e) {
+                outbox.onError(e);
+
+                throw new IgniteException("Unexpected exception", e);
+            }
+        }
+        else if (log.isDebugEnabled()) {
+            log.debug("Stale acknowledge message received: [" +
+                "nodeId=" + nodeId + ", " +
+                "queryId=" + msg.queryId() + ", " +
+                "fragmentId=" + msg.fragmentId() + ", " +
+                "exchangeId=" + msg.exchangeId() + ", " +
+                "batchId=" + msg.batchId() + "]");
+        }
+    }
+
+    /** */
+    protected void onMessage(UUID nodeId, QueryBatchMessage msg) {
+        Inbox<?> inbox = mailboxRegistry().inbox(msg.queryId(), msg.exchangeId());
+
+        if (inbox == null && msg.batchId() == 0) {
+            // first message sent before a fragment is built
+            // note that an inbox source fragment id is also used as an exchange id
+            Inbox<?> newInbox = new Inbox<>(baseInboxContext(nodeId, msg.queryId(), msg.fragmentId()),
+                this, mailboxRegistry(), msg.exchangeId(), msg.exchangeId());
+
+            inbox = mailboxRegistry().register(newInbox);
+        }
+
+        if (inbox != null) {
+            try {
+                if (msg.batchId() == 0) {
+                    Query<?> qry = (Query<?>)qryRegistry.query(msg.queryId());
+
+                    if (qry != null)
+                        qry.onInboundExchangeStarted(nodeId, msg.exchangeId());
+                }
+
+                inbox.onBatchReceived(nodeId, msg.batchId(), msg.last(), Commons.cast(msg.rows()));
+            }
+            catch (Throwable e) {
+                inbox.onError(e);
+
+                throw new IgniteException("Unexpected exception", e);
+            }
+        }
+        else if (log.isDebugEnabled()) {
+            log.debug("Stale batch message received: [" +
+                "nodeId=" + nodeId + ", " +
+                "queryId=" + msg.queryId() + ", " +
+                "fragmentId=" + msg.fragmentId() + ", " +
+                "exchangeId=" + msg.exchangeId() + ", " +
+                "batchId=" + msg.batchId() + "]");
+        }
+    }
+
+    /**
+     * @return Minimal execution context to meet Inbox needs.
+     */
+    private ExecutionContext<?> baseInboxContext(UUID nodeId, UUID qryId, long fragmentId) {
+        return new ExecutionContext<>(
+            BaseQueryContext.builder()
+                .logger(log)
+                .build(),
+            taskExecutor(),
+            qryId,
+            locaNodeId,
+            nodeId,
+            null,
+            new FragmentDescription(
+                fragmentId,
+                null,
+                null,
+                null),
+            null,
+            ImmutableMap.of());
+    }
+}
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/stat/StatisticsType.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionCancelledException.java
similarity index 53%
copy from modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/stat/StatisticsType.java
copy to modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionCancelledException.java
index 38f3f67a..fb4a8d8 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/stat/StatisticsType.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionCancelledException.java
@@ -15,30 +15,10 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.processors.query.stat;
+package org.apache.ignite.internal.processors.query.calcite.exec;
 
-import org.jetbrains.annotations.Nullable;
+import org.apache.ignite.IgniteCheckedException;
 
-/**
- * Types of statistics width.
- */
-public enum StatisticsType {
-    /** Statistics by some particular partition. */
-    PARTITION,
-
-    /** Statistics by some data node. */
-    LOCAL;
-
-    /** Enumerated values. */
-    private static final StatisticsType[] VALUES = values();
-
-    /**
-     * Efficiently gets enumerated value from its ordinal.
-     *
-     * @param ord Ordinal value.
-     * @return Enumerated value or {@code null} if ordinal out of range.
-     */
-    @Nullable public static StatisticsType fromOrdinal(int ord) {
-        return ord >= 0 && ord < VALUES.length ? VALUES[ord] : null;
-    }
+/** */
+public class ExecutionCancelledException extends IgniteCheckedException {
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionContext.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionContext.java
new file mode 100644
index 0000000..fd0cf9e
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionContext.java
@@ -0,0 +1,327 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.exec;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+import org.apache.calcite.DataContext;
+import org.apache.calcite.linq4j.QueryProvider;
+import org.apache.calcite.schema.SchemaPlus;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
+import org.apache.ignite.internal.processors.query.calcite.exec.exp.ExpressionFactory;
+import org.apache.ignite.internal.processors.query.calcite.exec.exp.ExpressionFactoryImpl;
+import org.apache.ignite.internal.processors.query.calcite.metadata.ColocationGroup;
+import org.apache.ignite.internal.processors.query.calcite.metadata.FragmentDescription;
+import org.apache.ignite.internal.processors.query.calcite.prepare.AbstractQueryContext;
+import org.apache.ignite.internal.processors.query.calcite.prepare.BaseDataContext;
+import org.apache.ignite.internal.processors.query.calcite.prepare.BaseQueryContext;
+import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.apache.ignite.internal.processors.query.calcite.util.TypeUtils;
+import org.jetbrains.annotations.NotNull;
+
+import static org.apache.ignite.internal.processors.query.calcite.util.Commons.checkRange;
+
+/**
+ * Runtime context allowing access to the tables in a database.
+ */
+public class ExecutionContext<Row> extends AbstractQueryContext implements DataContext {
+    /** Placeholder for values, which expressions is not specified. */
+    private static final Object UNSPECIFIED_VALUE = new Object();
+
+    /** */
+    private final UUID qryId;
+
+    /** */
+    private final UUID locNodeId;
+
+    /** */
+    private final UUID originatingNodeId;
+
+    /** */
+    private final AffinityTopologyVersion topVer;
+
+    /** */
+    private final FragmentDescription fragmentDesc;
+
+    /** */
+    private final Map<String, Object> params;
+
+    /** */
+    private final QueryTaskExecutor executor;
+
+    /** */
+    private final RowHandler<Row> handler;
+
+    /** */
+    private final ExpressionFactory<Row> expressionFactory;
+
+    /** */
+    private final AtomicBoolean cancelFlag = new AtomicBoolean();
+
+    /** */
+    private final BaseDataContext baseDataContext;
+
+    /** */
+    private Object[] correlations = new Object[16];
+
+    /**
+     * @param qctx Parent base query context.
+     * @param qryId Query ID.
+     * @param fragmentDesc Partitions information.
+     * @param params Parameters.
+     */
+    @SuppressWarnings("AssignmentOrReturnOfFieldWithMutableType")
+    public ExecutionContext(
+        BaseQueryContext qctx,
+        QueryTaskExecutor executor,
+        UUID qryId,
+        UUID locNodeId,
+        UUID originatingNodeId,
+        AffinityTopologyVersion topVer,
+        FragmentDescription fragmentDesc,
+        RowHandler<Row> handler,
+        Map<String, Object> params
+    ) {
+        super(qctx);
+
+        this.executor = executor;
+        this.qryId = qryId;
+        this.locNodeId = locNodeId;
+        this.originatingNodeId = originatingNodeId;
+        this.topVer = topVer;
+        this.fragmentDesc = fragmentDesc;
+        this.handler = handler;
+        this.params = params;
+
+        baseDataContext = new BaseDataContext(qctx.typeFactory());
+
+        expressionFactory = new ExpressionFactoryImpl<>(
+            this,
+            qctx.typeFactory(),
+            qctx.config().getParserConfig().conformance(),
+            qctx.rexBuilder()
+        );
+    }
+
+    /**
+     * @return Query ID.
+     */
+    public UUID queryId() {
+        return qryId;
+    }
+
+    /**
+     * @return Fragment ID.
+     */
+    public long fragmentId() {
+        return fragmentDesc.fragmentId();
+    }
+
+    /**
+     * @return Target mapping.
+     */
+    public ColocationGroup target() {
+        return fragmentDesc.target();
+    }
+
+    /** */
+    public List<UUID> remotes(long exchangeId) {
+        return fragmentDesc.remotes().get(exchangeId);
+    }
+
+    /** */
+    public ColocationGroup group(long sourceId) {
+        return fragmentDesc.mapping().findGroup(sourceId);
+    }
+
+    /**
+     * @return Keep binary flag.
+     */
+    public boolean keepBinary() {
+        return true; // TODO
+    }
+
+    /**
+     * @return MVCC snapshot.
+     */
+    public MvccSnapshot mvccSnapshot() {
+        return null; // TODO
+    }
+
+    /**
+     * @return Handler to access row fields.
+     */
+    public RowHandler<Row> rowHandler() {
+        return handler;
+    }
+
+    /**
+     * @return Expression factory.
+     */
+    public ExpressionFactory<Row> expressionFactory() {
+        return expressionFactory;
+    }
+
+    /**
+     * @return Local node ID.
+     */
+    public UUID localNodeId() {
+        return locNodeId;
+    }
+
+    /**
+     * @return Originating node ID (the node, who started the execution).
+     */
+    public UUID originatingNodeId() {
+        return originatingNodeId;
+    }
+
+    /**
+     * @return Topology version.
+     */
+    public AffinityTopologyVersion topologyVersion() {
+        return topVer;
+    }
+
+    /** */
+    public IgniteLogger logger() {
+        return unwrap(BaseQueryContext.class).logger();
+    }
+
+    /** {@inheritDoc} */
+    @Override public SchemaPlus getRootSchema() {
+        return baseDataContext.getRootSchema();
+    }
+
+    /** {@inheritDoc} */
+    @Override public IgniteTypeFactory getTypeFactory() {
+        return baseDataContext.getTypeFactory();
+    }
+
+    /** {@inheritDoc} */
+    @Override public QueryProvider getQueryProvider() {
+        return baseDataContext.getQueryProvider();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Object get(String name) {
+        if (Variable.CANCEL_FLAG.camelName.equals(name))
+            return cancelFlag;
+        if (name.startsWith("?"))
+            return TypeUtils.toInternal(this, params.get(name));
+
+        return baseDataContext.get(name);
+    }
+
+    /**
+     * Gets correlated value.
+     *
+     * @param id Correlation ID.
+     * @return Correlated value.
+     */
+    public @NotNull Object getCorrelated(int id) {
+        checkRange(correlations, id);
+
+        return correlations[id];
+    }
+
+    /**
+     * Sets correlated value.
+     *
+     * @param id Correlation ID.
+     * @param value Correlated value.
+     */
+    public void setCorrelated(@NotNull Object value, int id) {
+        correlations = Commons.ensureCapacity(correlations, id + 1);
+
+        correlations[id] = value;
+    }
+
+    /**
+     * Executes a query task.
+     *
+     * @param task Query task.
+     */
+    public void execute(RunnableX task, Consumer<Throwable> onError) {
+        if (isCancelled())
+            return;
+
+        executor.execute(qryId, fragmentId(), () -> {
+            try {
+                if (!isCancelled())
+                    task.run();
+            }
+            catch (Throwable e) {
+                onError.accept(e);
+
+                throw new IgniteException("Unexpected exception", e);
+            }
+        });
+    }
+
+    /** */
+    @FunctionalInterface
+    public interface RunnableX {
+        /** */
+        void run() throws Exception;
+    }
+
+    /**
+     * Sets cancel flag, returns {@code true} if flag was changed by this call.
+     *
+     * @return {@code True} if flag was changed by this call.
+     */
+    public boolean cancel() {
+        return !cancelFlag.get() && cancelFlag.compareAndSet(false, true);
+    }
+
+    /** */
+    public boolean isCancelled() {
+        return cancelFlag.get();
+    }
+
+    /** */
+    public Object unspecifiedValue() {
+        return UNSPECIFIED_VALUE;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+
+        ExecutionContext<?> context = (ExecutionContext<?>)o;
+
+        return qryId.equals(context.qryId) && fragmentDesc.fragmentId() == context.fragmentDesc.fragmentId();
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        return Objects.hash(qryId, fragmentDesc.fragmentId());
+    }
+}
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/stat/StatisticsType.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionService.java
similarity index 53%
copy from modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/stat/StatisticsType.java
copy to modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionService.java
index 38f3f67a..a3fbf2c 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/stat/StatisticsType.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionService.java
@@ -15,30 +15,19 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.processors.query.stat;
+package org.apache.ignite.internal.processors.query.calcite.exec;
 
-import org.jetbrains.annotations.Nullable;
+import java.util.List;
+
+import org.apache.ignite.cache.query.FieldsQueryCursor;
+import org.apache.ignite.internal.processors.query.calcite.RootQuery;
+import org.apache.ignite.internal.processors.query.calcite.prepare.QueryPlan;
+import org.apache.ignite.internal.processors.query.calcite.util.Service;
 
 /**
- * Types of statistics width.
+ *
  */
-public enum StatisticsType {
-    /** Statistics by some particular partition. */
-    PARTITION,
-
-    /** Statistics by some data node. */
-    LOCAL;
-
-    /** Enumerated values. */
-    private static final StatisticsType[] VALUES = values();
-
-    /**
-     * Efficiently gets enumerated value from its ordinal.
-     *
-     * @param ord Ordinal value.
-     * @return Enumerated value or {@code null} if ordinal out of range.
-     */
-    @Nullable public static StatisticsType fromOrdinal(int ord) {
-        return ord >= 0 && ord < VALUES.length ? VALUES[ord] : null;
-    }
+public interface ExecutionService<Row> extends Service {
+    /** */
+    FieldsQueryCursor<List<?>> executePlan(RootQuery<Row> qry, QueryPlan plan);
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionServiceImpl.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionServiceImpl.java
new file mode 100644
index 0000000..60c5ee1
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionServiceImpl.java
@@ -0,0 +1,757 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.exec;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.apache.calcite.plan.Context;
+import org.apache.calcite.plan.Contexts;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.sql.SqlInsert;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.tools.Frameworks;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.cache.query.FieldsQueryCursor;
+import org.apache.ignite.events.EventType;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.managers.eventstorage.DiscoveryEventListener;
+import org.apache.ignite.internal.managers.eventstorage.GridEventStorageManager;
+import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.internal.processors.cache.GridCachePartitionExchangeManager;
+import org.apache.ignite.internal.processors.cache.QueryCursorImpl;
+import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
+import org.apache.ignite.internal.processors.failure.FailureProcessor;
+import org.apache.ignite.internal.processors.query.IgniteSQLException;
+import org.apache.ignite.internal.processors.query.QueryState;
+import org.apache.ignite.internal.processors.query.RunningQuery;
+import org.apache.ignite.internal.processors.query.calcite.CalciteQueryProcessor;
+import org.apache.ignite.internal.processors.query.calcite.Query;
+import org.apache.ignite.internal.processors.query.calcite.QueryRegistry;
+import org.apache.ignite.internal.processors.query.calcite.RootQuery;
+import org.apache.ignite.internal.processors.query.calcite.RunningFragment;
+import org.apache.ignite.internal.processors.query.calcite.exec.ddl.DdlCommandHandler;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.Inbox;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.Node;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.Outbox;
+import org.apache.ignite.internal.processors.query.calcite.message.ErrorMessage;
+import org.apache.ignite.internal.processors.query.calcite.message.MessageService;
+import org.apache.ignite.internal.processors.query.calcite.message.MessageType;
+import org.apache.ignite.internal.processors.query.calcite.message.QueryStartRequest;
+import org.apache.ignite.internal.processors.query.calcite.message.QueryStartResponse;
+import org.apache.ignite.internal.processors.query.calcite.metadata.AffinityService;
+import org.apache.ignite.internal.processors.query.calcite.metadata.FragmentDescription;
+import org.apache.ignite.internal.processors.query.calcite.metadata.FragmentMapping;
+import org.apache.ignite.internal.processors.query.calcite.metadata.MappingService;
+import org.apache.ignite.internal.processors.query.calcite.metadata.RemoteException;
+import org.apache.ignite.internal.processors.query.calcite.prepare.BaseQueryContext;
+import org.apache.ignite.internal.processors.query.calcite.prepare.CacheKey;
+import org.apache.ignite.internal.processors.query.calcite.prepare.DdlPlan;
+import org.apache.ignite.internal.processors.query.calcite.prepare.ExplainPlan;
+import org.apache.ignite.internal.processors.query.calcite.prepare.FieldsMetadata;
+import org.apache.ignite.internal.processors.query.calcite.prepare.FieldsMetadataImpl;
+import org.apache.ignite.internal.processors.query.calcite.prepare.Fragment;
+import org.apache.ignite.internal.processors.query.calcite.prepare.FragmentPlan;
+import org.apache.ignite.internal.processors.query.calcite.prepare.MappingQueryContext;
+import org.apache.ignite.internal.processors.query.calcite.prepare.MultiStepPlan;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PlanningContext;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PrepareServiceImpl;
+import org.apache.ignite.internal.processors.query.calcite.prepare.QueryPlan;
+import org.apache.ignite.internal.processors.query.calcite.prepare.QueryPlanCache;
+import org.apache.ignite.internal.processors.query.calcite.prepare.ddl.CreateTableCommand;
+import org.apache.ignite.internal.processors.query.calcite.schema.SchemaHolder;
+import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
+import org.apache.ignite.internal.processors.query.calcite.util.AbstractService;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.apache.ignite.internal.processors.query.calcite.util.ListFieldsQueryCursor;
+import org.apache.ignite.internal.processors.query.calcite.util.TypeUtils;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.X;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.jetbrains.annotations.Nullable;
+
+import static java.util.Collections.singletonList;
+import static org.apache.ignite.internal.processors.query.calcite.CalciteQueryProcessor.FRAMEWORK_CONFIG;
+import static org.apache.ignite.internal.processors.query.calcite.externalize.RelJsonReader.fromJson;
+
+/**
+ *
+ */
+@SuppressWarnings("TypeMayBeWeakened")
+public class ExecutionServiceImpl<Row> extends AbstractService implements ExecutionService<Row> {
+    /** */
+    private final DiscoveryEventListener discoLsnr;
+
+    /** */
+    private UUID locNodeId;
+
+    /** */
+    private GridEventStorageManager evtMgr;
+
+    /** */
+    private GridCachePartitionExchangeManager<?, ?> exchangeMgr;
+
+    /** */
+    private QueryPlanCache qryPlanCache;
+
+    /** */
+    private SchemaHolder schemaHolder;
+
+    /** */
+    private QueryTaskExecutor taskExecutor;
+
+    /** */
+    private FailureProcessor failureProcessor;
+
+    /** */
+    private AffinityService partSvc;
+
+    /** */
+    private MailboxRegistry mailboxRegistry;
+
+    /** */
+    private MappingService mappingSvc;
+
+    /** */
+    private MessageService msgSvc;
+
+    /** */
+    private ExchangeService exchangeSvc;
+
+    /** */
+    private PrepareServiceImpl prepareSvc;
+
+    /** */
+    private ClosableIteratorsHolder iteratorsHolder;
+
+    /** */
+    private QueryRegistry qryReg;
+
+    /** */
+    private final RowHandler<Row> handler;
+
+    /** */
+    private final DdlCommandHandler ddlCmdHnd;
+
+    /**
+     * @param ctx Kernal.
+     */
+    public ExecutionServiceImpl(GridKernalContext ctx, RowHandler<Row> handler) {
+        super(ctx);
+        this.handler = handler;
+
+        discoLsnr = (e, c) -> onNodeLeft(e.eventNode().id());
+
+        ddlCmdHnd = new DdlCommandHandler(
+            ctx::query, ctx.cache(), ctx.security(), () -> schemaHolder().schema(null)
+        );
+    }
+
+    /**
+     * @param locNodeId Local node ID.
+     */
+    public void localNodeId(UUID locNodeId) {
+        this.locNodeId = locNodeId;
+    }
+
+    /**
+     * @return Local node ID.
+     */
+    public UUID localNodeId() {
+        return locNodeId;
+    }
+
+    /**
+     * @param qryPlanCache Query cache.
+     */
+    public void queryPlanCache(QueryPlanCache qryPlanCache) {
+        this.qryPlanCache = qryPlanCache;
+    }
+
+    /**
+     * @return Query cache.
+     */
+    public QueryPlanCache queryPlanCache() {
+        return qryPlanCache;
+    }
+
+    /**
+     * @param schemaHolder Schema holder.
+     */
+    public void schemaHolder(SchemaHolder schemaHolder) {
+        this.schemaHolder = schemaHolder;
+    }
+
+    /**
+     * @return Schema holder.
+     */
+    public SchemaHolder schemaHolder() {
+        return schemaHolder;
+    }
+
+    /**
+     * @param taskExecutor Task executor.
+     */
+    public void taskExecutor(QueryTaskExecutor taskExecutor) {
+        this.taskExecutor = taskExecutor;
+    }
+
+    /**
+     * @return Task executor.
+     */
+    public QueryTaskExecutor taskExecutor() {
+        return taskExecutor;
+    }
+
+    /**
+     * @param failureProcessor Failure processor.
+     */
+    public void failureProcessor(FailureProcessor failureProcessor) {
+        this.failureProcessor = failureProcessor;
+    }
+
+    /**
+     * @return Failure processor.
+     */
+    public FailureProcessor failureProcessor() {
+        return failureProcessor;
+    }
+
+    /**
+     * @param partSvc Partition service.
+     */
+    public void partitionService(AffinityService partSvc) {
+        this.partSvc = partSvc;
+    }
+
+    /**
+     * @return Partition service.
+     */
+    public AffinityService partitionService() {
+        return partSvc;
+    }
+
+    /**
+     * @param mailboxRegistry Mailbox registry.
+     */
+    public void mailboxRegistry(MailboxRegistry mailboxRegistry) {
+        this.mailboxRegistry = mailboxRegistry;
+    }
+
+    /**
+     * @return Mailbox registry.
+     */
+    public MailboxRegistry mailboxRegistry() {
+        return mailboxRegistry;
+    }
+
+    /**
+     * @param mappingSvc Mapping service.
+     */
+    public void mappingService(MappingService mappingSvc) {
+        this.mappingSvc = mappingSvc;
+    }
+
+    /**
+     * @return Mapping service.
+     */
+    public MappingService mappingService() {
+        return mappingSvc;
+    }
+
+    /**
+     * @param msgSvc Message service.
+     */
+    public void messageService(MessageService msgSvc) {
+        this.msgSvc = msgSvc;
+    }
+
+    /**
+     * @return Message service.
+     */
+    public MessageService messageService() {
+        return msgSvc;
+    }
+
+    /**
+     * @param exchangeSvc Exchange service.
+     */
+    public void exchangeService(ExchangeService exchangeSvc) {
+        this.exchangeSvc = exchangeSvc;
+    }
+
+    /**
+     * @param prepareSvc Prepare service.
+     */
+    public void prepareService(PrepareServiceImpl prepareSvc) {
+        this.prepareSvc = prepareSvc;
+    }
+
+    /**
+     * @return Exchange service.
+     */
+    public ExchangeService exchangeService() {
+        return exchangeSvc;
+    }
+
+    /**
+     * @param evtMgr Event manager.
+     */
+    public void eventManager(GridEventStorageManager evtMgr) {
+        this.evtMgr = evtMgr;
+    }
+
+    /**
+     * @return Event manager.
+     */
+    public GridEventStorageManager eventManager() {
+        return evtMgr;
+    }
+
+    /**
+     * @param exchangeMgr Exchange manager.
+     */
+    public void exchangeManager(GridCachePartitionExchangeManager<?, ?> exchangeMgr) {
+        this.exchangeMgr = exchangeMgr;
+    }
+
+    /**
+     * @return Exchange manager.
+     */
+    public GridCachePartitionExchangeManager<?, ?> exchangeManager() {
+        return exchangeMgr;
+    }
+
+    /**
+     * @param iteratorsHolder Iterators holder.
+     */
+    public void iteratorsHolder(ClosableIteratorsHolder iteratorsHolder) {
+        this.iteratorsHolder = iteratorsHolder;
+    }
+
+    /**
+     * @return Iterators holder.
+     */
+    public ClosableIteratorsHolder iteratorsHolder() {
+        return iteratorsHolder;
+    }
+
+    /** */
+    public void queryRegistry(QueryRegistry qryReg) {
+        this.qryReg = qryReg;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onStart(GridKernalContext ctx) {
+        localNodeId(ctx.localNodeId());
+        exchangeManager(ctx.cache().context().exchange());
+        eventManager(ctx.event());
+        iteratorsHolder(new ClosableIteratorsHolder(log));
+
+        CalciteQueryProcessor proc = Objects.requireNonNull(
+            Commons.lookupComponent(ctx, CalciteQueryProcessor.class));
+
+        queryPlanCache(proc.queryPlanCache());
+        schemaHolder(proc.schemaHolder());
+        taskExecutor(proc.taskExecutor());
+        failureProcessor(proc.failureProcessor());
+        partitionService(proc.affinityService());
+        mailboxRegistry(proc.mailboxRegistry());
+        mappingService(proc.mappingService());
+        messageService(proc.messageService());
+        exchangeService(proc.exchangeService());
+        queryRegistry(proc.queryRegistry());
+        prepareService(proc.prepareService());
+
+        init();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void init() {
+        messageService().register((n, m) -> onMessage(n, (QueryStartRequest)m), MessageType.QUERY_START_REQUEST);
+        messageService().register((n, m) -> onMessage(n, (QueryStartResponse)m), MessageType.QUERY_START_RESPONSE);
+        messageService().register((n, m) -> onMessage(n, (ErrorMessage)m), MessageType.QUERY_ERROR_MESSAGE);
+
+        eventManager().addDiscoveryEventListener(discoLsnr, EventType.EVT_NODE_FAILED, EventType.EVT_NODE_LEFT);
+
+        iteratorsHolder().init();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void tearDown() {
+        eventManager().removeDiscoveryEventListener(discoLsnr, EventType.EVT_NODE_FAILED, EventType.EVT_NODE_LEFT);
+
+        iteratorsHolder().tearDown();
+    }
+
+    /** */
+    protected AffinityTopologyVersion topologyVersion() {
+        return exchangeManager().readyAffinityVersion();
+    }
+
+    /** */
+    private BaseQueryContext createQueryContext(Context parent, @Nullable String schema) {
+        return BaseQueryContext.builder()
+            .parentContext(parent)
+            .frameworkConfig(
+                Frameworks.newConfigBuilder(FRAMEWORK_CONFIG)
+                    .defaultSchema(schemaHolder().schema(schema))
+                    .build()
+            )
+            .logger(log)
+            .build();
+    }
+
+    /** */
+    private QueryPlan prepareFragment(BaseQueryContext ctx, String jsonFragment) {
+        return new FragmentPlan(fromJson(ctx, jsonFragment));
+    }
+
+    /** {@inheritDoc} */
+    @Override public FieldsQueryCursor<List<?>> executePlan(
+        RootQuery<Row> qry,
+        QueryPlan plan
+    ) {
+        switch (plan.type()) {
+            case DML:
+                ListFieldsQueryCursor<?> cur = mapAndExecutePlan(
+                    qry,
+                    (MultiStepPlan)plan
+                );
+
+                cur.iterator().hasNext();
+
+                return cur;
+
+            case QUERY:
+                return mapAndExecutePlan(
+                    qry,
+                    (MultiStepPlan)plan
+                );
+
+            case EXPLAIN:
+                return executeExplain(qry, (ExplainPlan)plan);
+
+            case DDL:
+                return executeDdl(qry, (DdlPlan)plan);
+
+            default:
+                throw new AssertionError("Unexpected plan type: " + plan);
+        }
+    }
+
+    /** */
+    private FieldsQueryCursor<List<?>> executeDdl(RootQuery<Row> qry, DdlPlan plan) {
+        try {
+            ddlCmdHnd.handle(qry.id(), plan.command());
+        }
+        catch (IgniteCheckedException e) {
+            throw new IgniteSQLException("Failed to execute DDL statement [stmt=" + qry.sql() +
+                ", err=" + e.getMessage() + ']', e);
+        }
+        finally {
+            qryReg.unregister(qry.id());
+        }
+
+        if (plan.command() instanceof CreateTableCommand
+            && ((CreateTableCommand)plan.command()).insertStatement() != null) {
+            RootQuery<Row> insQry = qry.childQuery(schemaHolder.schema(qry.context().schemaName()));
+
+            qryReg.register(insQry);
+
+            SqlInsert insertStmt = ((CreateTableCommand)plan.command()).insertStatement();
+
+            QueryPlan dmlPlan = prepareSvc.prepareSingle(insertStmt, insQry.planningContext());
+
+            return executePlan(insQry, dmlPlan);
+        }
+        else {
+            QueryCursorImpl<List<?>> resCur = new QueryCursorImpl<>(Collections.singletonList(
+                Collections.singletonList(0L)), null, false, false);
+
+            IgniteTypeFactory typeFactory = qry.context().typeFactory();
+
+            resCur.fieldsMeta(new FieldsMetadataImpl(RelOptUtil.createDmlRowType(
+                SqlKind.INSERT, typeFactory), null).queryFieldsMetadata(typeFactory));
+
+            return resCur;
+        }
+    }
+
+    /** */
+    private ListFieldsQueryCursor<?> mapAndExecutePlan(
+        RootQuery<Row> qry,
+        MultiStepPlan plan
+    ) {
+        qry.mapping();
+
+        MappingQueryContext mapCtx = Commons.mapContext(locNodeId, topologyVersion());
+        plan.init(mappingSvc, mapCtx);
+
+        List<Fragment> fragments = plan.fragments();
+
+        // Local execution
+        Fragment fragment = F.first(fragments);
+
+        if (U.assertionsEnabled()) {
+            assert fragment != null;
+
+            FragmentMapping mapping = plan.mapping(fragment);
+
+            assert mapping != null;
+
+            List<UUID> nodes = mapping.nodeIds();
+
+            assert nodes != null && nodes.size() == 1 && F.first(nodes).equals(localNodeId());
+        }
+
+        FragmentDescription fragmentDesc = new FragmentDescription(
+            fragment.fragmentId(),
+            plan.mapping(fragment),
+            plan.target(fragment),
+            plan.remotes(fragment));
+
+        ExecutionContext<Row> ectx = new ExecutionContext<>(
+            qry.context(),
+            taskExecutor(),
+            qry.id(),
+            locNodeId,
+            locNodeId,
+            mapCtx.topologyVersion(),
+            fragmentDesc,
+            handler,
+            Commons.parametersMap(qry.parameters()));
+
+        Node<Row> node = new LogicalRelImplementor<>(ectx, partitionService(), mailboxRegistry(),
+            exchangeService(), failureProcessor()).go(fragment.root());
+
+        qry.run(ectx, plan, node);
+
+        Map<UUID, Long> fragmentsPerNode = fragments.stream()
+            .skip(1)
+            .flatMap(f -> f.mapping().nodeIds().stream())
+            .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
+
+        // Start remote execution.
+        for (int i = 1; i < fragments.size(); i++) {
+            fragment = fragments.get(i);
+            fragmentDesc = new FragmentDescription(
+                fragment.fragmentId(),
+                plan.mapping(fragment),
+                plan.target(fragment),
+                plan.remotes(fragment));
+
+            Throwable ex = null;
+            byte[] parametersMarshalled = null;
+
+            for (UUID nodeId : fragmentDesc.nodeIds()) {
+                if (ex != null)
+                    qry.onResponse(nodeId, fragment.fragmentId(), ex);
+                else {
+                    try {
+                        QueryStartRequest req = new QueryStartRequest(
+                            qry.id(),
+                            qry.context().schemaName(),
+                            fragment.serialized(),
+                            ectx.topologyVersion(),
+                            fragmentDesc,
+                            fragmentsPerNode.get(nodeId).intValue(),
+                            qry.parameters(),
+                            parametersMarshalled
+                        );
+
+                        messageService().send(nodeId, req);
+
+                        // Avoid marshaling of the same parameters for other nodes.
+                        if (parametersMarshalled == null)
+                            parametersMarshalled = req.parametersMarshalled();
+                    }
+                    catch (Throwable e) {
+                        qry.onResponse(nodeId, fragment.fragmentId(), ex = e);
+                    }
+                }
+            }
+        }
+
+        return new ListFieldsQueryCursor<>(plan, iteratorsHolder().iterator(qry.iterator()), ectx);
+    }
+
+    /** */
+    private FieldsQueryCursor<List<?>> executeExplain(RootQuery<Row> qry, ExplainPlan plan) {
+        QueryCursorImpl<List<?>> cur = new QueryCursorImpl<>(singletonList(singletonList(plan.plan())));
+        cur.fieldsMeta(plan.fieldsMeta().queryFieldsMetadata(Commons.typeFactory()));
+
+        qryReg.unregister(qry.id());
+
+        return cur;
+    }
+
+    /** */
+    private void executeFragment(Query<Row> qry, FragmentPlan plan, ExecutionContext<Row> ectx) {
+        UUID origNodeId = ectx.originatingNodeId();
+
+        Outbox<Row> node = new LogicalRelImplementor<>(
+            ectx,
+            partitionService(),
+            mailboxRegistry(),
+            exchangeService(),
+            failureProcessor()
+        )
+            .go(plan.root());
+
+        qry.addFragment(new RunningFragment<>(plan.root(), node, ectx));
+
+        node.init();
+
+        if (!qry.isExchangeWithInitNodeStarted(ectx.fragmentId())) {
+            try {
+                messageService().send(origNodeId, new QueryStartResponse(qry.id(), ectx.fragmentId()));
+            }
+            catch (IgniteCheckedException e) {
+                IgniteException wrpEx = new IgniteException("Failed to send reply. [nodeId=" + origNodeId + ']', e);
+
+                throw wrpEx;
+            }
+        }
+    }
+
+    /** */
+    private FieldsMetadata queryFieldsMetadata(PlanningContext ctx, RelDataType sqlType,
+        @Nullable List<List<String>> origins) {
+        RelDataType resultType = TypeUtils.getResultType(
+            ctx.typeFactory(), ctx.catalogReader(), sqlType, origins);
+        return new FieldsMetadataImpl(resultType, origins);
+    }
+
+    /** */
+    private void onMessage(UUID nodeId, final QueryStartRequest msg) {
+        assert nodeId != null && msg != null;
+
+        try {
+            Query<Row> qry = (Query<Row>)qryReg.register(
+                new Query<>(
+                    msg.queryId(),
+                    nodeId,
+                    null,
+                    exchangeSvc,
+                    (q) -> qryReg.unregister(q.id()),
+                    log,
+                    msg.totalFragmentsCount()
+                )
+            );
+
+            final BaseQueryContext qctx = createQueryContext(Contexts.empty(), msg.schema());
+
+            QueryPlan qryPlan = queryPlanCache().queryPlan(
+                new CacheKey(msg.schema(), msg.root()),
+                () -> prepareFragment(qctx, msg.root())
+            );
+
+            assert qryPlan.type() == QueryPlan.Type.FRAGMENT;
+
+            ExecutionContext<Row> ectx = new ExecutionContext<>(
+                qctx,
+                taskExecutor(),
+                msg.queryId(),
+                locNodeId,
+                nodeId,
+                msg.topologyVersion(),
+                msg.fragmentDescription(),
+                handler,
+                Commons.parametersMap(msg.parameters())
+            );
+
+            executeFragment(qry, (FragmentPlan)qryPlan, ectx);
+        }
+        catch (Throwable ex) {
+            U.error(log, "Failed to start query fragment ", ex);
+
+            mailboxRegistry.outboxes(msg.queryId(), msg.fragmentId(), -1)
+                .forEach(Outbox::close);
+            mailboxRegistry.inboxes(msg.queryId(), msg.fragmentId(), -1)
+                .forEach(Inbox::close);
+
+            try {
+                messageService().send(nodeId, new QueryStartResponse(msg.queryId(), msg.fragmentId(), ex));
+            }
+            catch (IgniteCheckedException e) {
+                U.error(log, "Error occurred during send error message: " + X.getFullStackTrace(e));
+
+                IgniteException wrpEx = new IgniteException("Error occurred during send error message", e);
+
+                e.addSuppressed(ex);
+
+                Query<Row> qry = (Query<Row>)qryReg.query(msg.queryId());
+
+                qry.cancel();
+
+                throw wrpEx;
+            }
+
+            throw ex;
+        }
+    }
+
+    /** */
+    private void onMessage(UUID nodeId, QueryStartResponse msg) {
+        assert nodeId != null && msg != null;
+
+        RunningQuery qry = qryReg.query(msg.queryId());
+
+        if (qry != null) {
+            assert qry instanceof RootQuery : "Unexpected query object: " + qry;
+
+            ((RootQuery<Row>)qry).onResponse(nodeId, msg.fragmentId(), msg.error());
+        }
+    }
+
+    /** */
+    private void onMessage(UUID nodeId, ErrorMessage msg) {
+        assert nodeId != null && msg != null;
+
+        RunningQuery qry = qryReg.query(msg.queryId());
+
+        if (qry != null && qry.state() != QueryState.CLOSED) {
+            assert qry instanceof RootQuery : "Unexpected query object: " + qry;
+
+            Exception e = new RemoteException(nodeId, msg.queryId(), msg.fragmentId(), msg.error());
+
+            if (X.hasCause(msg.error(), ExecutionCancelledException.class)) {
+                e = new IgniteSQLException(
+                    "The query was cancelled while executing.",
+                    IgniteQueryErrorCode.QUERY_CANCELED,
+                    e
+                );
+            }
+
+            ((RootQuery<Row>)qry).onError(e);
+        }
+    }
+
+    /** */
+    private void onNodeLeft(UUID nodeId) {
+        qryReg.runningQueries()
+            .forEach((qry) -> ((Query<Row>)qry).onNodeLeft(nodeId));
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/IndexScan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/IndexScan.java
new file mode 100644
index 0000000..012070c
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/IndexScan.java
@@ -0,0 +1,318 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ignite.internal.processors.query.calcite.exec;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.calcite.util.ImmutableIntList;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.cluster.ClusterTopologyException;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.cache.query.index.sorted.IndexRow;
+import org.apache.ignite.internal.cache.query.index.sorted.IndexSearchRowImpl;
+import org.apache.ignite.internal.cache.query.index.sorted.InlineIndexRowHandler;
+import org.apache.ignite.internal.cache.query.index.sorted.inline.IndexQueryContext;
+import org.apache.ignite.internal.cache.query.index.sorted.inline.InlineIndex;
+import org.apache.ignite.internal.cache.query.index.sorted.keys.IndexKey;
+import org.apache.ignite.internal.cache.query.index.sorted.keys.IndexKeyFactory;
+import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.internal.processors.cache.CacheObjectContext;
+import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTopologyFuture;
+import org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtLocalPartition;
+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.cache.mvcc.MvccSnapshot;
+import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler.RowFactory;
+import org.apache.ignite.internal.processors.query.calcite.schema.CacheTableDescriptor;
+import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
+import org.apache.ignite.internal.processors.query.calcite.util.TypeUtils;
+import org.apache.ignite.internal.util.lang.GridCursor;
+import org.apache.ignite.spi.indexing.IndexingQueryFilter;
+import org.apache.ignite.spi.indexing.IndexingQueryFilterImpl;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Scan on index.
+ */
+public class IndexScan<Row> extends AbstractIndexScan<Row, IndexRow> {
+    /** */
+    private final GridKernalContext kctx;
+
+    /** */
+    private final GridCacheContext<?, ?> cctx;
+
+    /** */
+    private final CacheObjectContext coCtx;
+
+    /** */
+    private final CacheTableDescriptor desc;
+
+    /** */
+    private final RowFactory<Row> factory;
+
+    /** */
+    private final AffinityTopologyVersion topVer;
+
+    /** */
+    private final int[] parts;
+
+    /** */
+    private final MvccSnapshot mvccSnapshot;
+
+    /** */
+    private volatile List<GridDhtLocalPartition> reserved;
+
+    /** */
+    private final ImmutableBitSet requiredColumns;
+
+    /** */
+    private final InlineIndex idx;
+
+    /** Mapping from index keys to row fields. */
+    private final ImmutableIntList idxFieldMapping;
+
+    /** Types of key fields stored in index. */
+    private final Type[] fieldsStoreTypes;
+
+    /**
+     * @param ectx Execution context.
+     * @param desc Table descriptor.
+     * @param idxFieldMapping Mapping from index keys to row fields.
+     * @param idx Phisycal index.
+     * @param filters Additional filters.
+     * @param lowerBound Lower index scan bound.
+     * @param upperBound Upper index scan bound.
+     */
+    public IndexScan(
+        ExecutionContext<Row> ectx,
+        CacheTableDescriptor desc,
+        InlineIndex idx,
+        ImmutableIntList idxFieldMapping,
+        int[] parts,
+        Predicate<Row> filters,
+        Supplier<Row> lowerBound,
+        Supplier<Row> upperBound,
+        Function<Row, Row> rowTransformer,
+        @Nullable ImmutableBitSet requiredColumns
+    ) {
+        super(
+            ectx,
+            desc.rowType(ectx.getTypeFactory(), requiredColumns),
+            new TreeIndexWrapper(idx),
+            filters,
+            lowerBound,
+            upperBound,
+            rowTransformer
+        );
+
+        this.desc = desc;
+        this.idx = idx;
+        cctx = desc.cacheContext();
+        kctx = cctx.kernalContext();
+        coCtx = cctx.cacheObjectContext();
+
+        factory = ectx.rowHandler().factory(ectx.getTypeFactory(), rowType);
+        topVer = ectx.topologyVersion();
+        this.parts = parts;
+        mvccSnapshot = ectx.mvccSnapshot();
+        this.requiredColumns = requiredColumns;
+        this.idxFieldMapping = idxFieldMapping;
+
+        RelDataType srcRowType = desc.rowType(ectx.getTypeFactory(), null);
+        IgniteTypeFactory typeFactory = ectx.getTypeFactory();
+        fieldsStoreTypes = new Type[srcRowType.getFieldCount()];
+
+        for (int i = 0; i < srcRowType.getFieldCount(); i++)
+            fieldsStoreTypes[i] = typeFactory.getResultClass(srcRowType.getFieldList().get(i).getType());
+    }
+
+    /** {@inheritDoc} */
+    @Override public synchronized Iterator<Row> iterator() {
+        reserve();
+
+        try {
+            return super.iterator();
+        }
+        catch (Exception e) {
+            release();
+
+            throw e;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IndexRow row2indexRow(Row bound) {
+        if (bound == null)
+            return null;
+
+        InlineIndexRowHandler idxRowHnd = idx.segment(0).rowHandler();
+        RowHandler<Row> rowHnd = ectx.rowHandler();
+
+        IndexKey[] keys = new IndexKey[idxRowHnd.indexKeyDefinitions().size()];
+
+        assert keys.length >= idxFieldMapping.size() : "Unexpected index keys [keys.length=" + keys.length +
+            ", idxFieldMapping.size()=" + idxFieldMapping.size() + ']';
+
+        boolean nullSearchRow = true;
+
+        for (int i = 0; i < idxFieldMapping.size(); ++i) {
+            int fieldIdx = idxFieldMapping.getInt(i);
+            Object key = rowHnd.get(fieldIdx, bound);
+
+            if (key != ectx.unspecifiedValue()) {
+                key = TypeUtils.fromInternal(ectx, key, fieldsStoreTypes[fieldIdx]);
+
+                keys[i] = IndexKeyFactory.wrap(key, idxRowHnd.indexKeyDefinitions().get(i).idxType(),
+                    cctx.cacheObjectContext(), idxRowHnd.indexKeyTypeSettings());
+
+                nullSearchRow = false;
+            }
+        }
+
+        return nullSearchRow ? null : new IndexSearchRowImpl(keys, idxRowHnd);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected Row indexRow2Row(IndexRow row) throws IgniteCheckedException {
+        return desc.toRow(ectx, row.cacheDataRow(), factory, requiredColumns);
+    }
+
+    /** */
+    @Override public void close() {
+        release();
+    }
+
+    /** */
+    private synchronized void reserve() {
+        if (reserved != null)
+            return;
+
+        GridDhtPartitionTopology top = cctx.topology();
+        top.readLock();
+
+        GridDhtTopologyFuture topFut = top.topologyVersionFuture();
+
+        boolean done = topFut.isDone();
+
+        if (!done || !(topFut.topologyVersion().compareTo(topVer) >= 0
+            && cctx.shared().exchange().lastAffinityChangedTopologyVersion(topFut.initialVersion()).compareTo(topVer) <= 0)) {
+            top.readUnlock();
+
+            throw new ClusterTopologyException("Topology was changed. Please retry on stable topology.");
+        }
+
+        List<GridDhtLocalPartition> toReserve;
+
+        if (cctx.isReplicated()) {
+            int partsCnt = cctx.affinity().partitions();
+            toReserve = new ArrayList<>(partsCnt);
+            for (int i = 0; i < partsCnt; i++)
+                toReserve.add(top.localPartition(i));
+        }
+        else if (cctx.isPartitioned()) {
+            assert parts != null;
+
+            toReserve = new ArrayList<>(parts.length);
+            for (int i = 0; i < parts.length; i++)
+                toReserve.add(top.localPartition(parts[i]));
+        }
+        else {
+            assert cctx.isLocal();
+
+            toReserve = Collections.emptyList();
+        }
+
+        reserved = new ArrayList<>(toReserve.size());
+
+        try {
+            for (GridDhtLocalPartition part : toReserve) {
+                if (part == null || !part.reserve()) {
+                    throw new ClusterTopologyException(
+                        "Failed to reserve partition for query execution. Retry on stable topology."
+                    );
+                }
+                else if (part.state() != GridDhtPartitionState.OWNING) {
+                    part.release();
+
+                    throw new ClusterTopologyException(
+                        "Failed to reserve partition for query execution. Retry on stable topology."
+                    );
+                }
+
+                reserved.add(part);
+            }
+        }
+        catch (Exception e) {
+            release();
+
+            throw e;
+        }
+        finally {
+            top.readUnlock();
+        }
+    }
+
+    /** */
+    private synchronized void release() {
+        if (reserved == null)
+            return;
+
+        for (GridDhtLocalPartition part : reserved)
+            part.release();
+
+        reserved = null;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IndexQueryContext indexQueryContext() {
+        IndexingQueryFilter filter = new IndexingQueryFilterImpl(kctx, topVer, parts);
+        return new IndexQueryContext(filter, null, mvccSnapshot);
+    }
+
+    /** */
+    private static class TreeIndexWrapper implements TreeIndex<IndexRow> {
+        /** Underlying index. */
+        private final InlineIndex idx;
+
+        /** */
+        private TreeIndexWrapper(InlineIndex idx) {
+            this.idx = idx;
+        }
+
+        /** {@inheritDoc} */
+        @Override public GridCursor<IndexRow> find(IndexRow lower, IndexRow upper, IndexQueryContext qctx) {
+            try {
+                int seg = 0; // TODO segments support
+
+                return idx.find(lower, upper, true, true, seg, qctx);
+            }
+            catch (IgniteCheckedException e) {
+                throw new IgniteException("Failed to find index rows", e);
+            }
+        }
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java
new file mode 100644
index 0000000..17be8cb
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java
@@ -0,0 +1,775 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.exec;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.function.BiPredicate;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Intersect;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.Minus;
+import org.apache.calcite.rel.core.Spool;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.ignite.internal.processors.failure.FailureProcessor;
+import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler.RowFactory;
+import org.apache.ignite.internal.processors.query.calcite.exec.exp.ExpressionFactory;
+import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AccumulatorWrapper;
+import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AggregateType;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.AbstractSetOpNode;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.CorrelatedNestedLoopJoinNode;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.FilterNode;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.HashAggregateNode;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.Inbox;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.IndexSpoolNode;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.IntersectNode;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.LimitNode;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.MergeJoinNode;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.MinusNode;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.ModifyNode;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.NestedLoopJoinNode;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.Node;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.Outbox;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.ProjectNode;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.ScanNode;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.SortAggregateNode;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.SortNode;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.TableSpoolNode;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.UnionAllNode;
+import org.apache.ignite.internal.processors.query.calcite.metadata.AffinityService;
+import org.apache.ignite.internal.processors.query.calcite.metadata.ColocationGroup;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteCorrelatedNestedLoopJoin;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteHashIndexSpool;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteLimit;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteMergeJoin;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteNestedLoopJoin;
+import org.apache.ignite.internal.processors.query.calcite.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.IgniteSort;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSortedIndexSpool;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableFunctionScan;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableModify;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableSpool;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTrimExchange;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteUnionAll;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteValues;
+import org.apache.ignite.internal.processors.query.calcite.rel.agg.IgniteColocatedHashAggregate;
+import org.apache.ignite.internal.processors.query.calcite.rel.agg.IgniteColocatedSortAggregate;
+import org.apache.ignite.internal.processors.query.calcite.rel.agg.IgniteMapHashAggregate;
+import org.apache.ignite.internal.processors.query.calcite.rel.agg.IgniteMapSortAggregate;
+import org.apache.ignite.internal.processors.query.calcite.rel.agg.IgniteReduceHashAggregate;
+import org.apache.ignite.internal.processors.query.calcite.rel.agg.IgniteReduceSortAggregate;
+import org.apache.ignite.internal.processors.query.calcite.rel.set.IgniteSetOp;
+import org.apache.ignite.internal.processors.query.calcite.rule.LogicalScanConverterRule;
+import org.apache.ignite.internal.processors.query.calcite.schema.CacheTableDescriptor;
+import org.apache.ignite.internal.processors.query.calcite.schema.IgniteIndex;
+import org.apache.ignite.internal.processors.query.calcite.schema.IgniteTable;
+import org.apache.ignite.internal.processors.query.calcite.trait.Destination;
+import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
+import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils;
+import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.apache.ignite.internal.processors.query.calcite.util.RexUtils;
+import org.apache.ignite.internal.util.typedef.F;
+
+import static org.apache.calcite.rel.RelDistribution.Type.HASH_DISTRIBUTED;
+import static org.apache.ignite.internal.processors.query.calcite.util.TypeUtils.combinedRowType;
+
+/**
+ * Implements a query plan.
+ */
+@SuppressWarnings("TypeMayBeWeakened")
+public class LogicalRelImplementor<Row> implements IgniteRelVisitor<Node<Row>> {
+    /** */
+    public static final String CNLJ_NOT_SUPPORTED_JOIN_ASSERTION_MSG = "only INNER and LEFT join supported by IgniteCorrelatedNestedLoop";
+
+    /** */
+    private final ExecutionContext<Row> ctx;
+
+    /** */
+    private final AffinityService affSrvc;
+
+    /** */
+    private final ExchangeService exchangeSvc;
+
+    /** */
+    private final MailboxRegistry mailboxRegistry;
+
+    /** */
+    private final ExpressionFactory<Row> expressionFactory;
+
+    /**
+     * @param ctx Root context.
+     * @param affSrvc Affinity service.
+     * @param mailboxRegistry Mailbox registry.
+     * @param exchangeSvc Exchange service.
+     * @param failure Failure processor.
+     */
+    public LogicalRelImplementor(
+        ExecutionContext<Row> ctx,
+        AffinityService affSrvc,
+        MailboxRegistry mailboxRegistry,
+        ExchangeService exchangeSvc,
+        FailureProcessor failure
+    ) {
+        this.affSrvc = affSrvc;
+        this.mailboxRegistry = mailboxRegistry;
+        this.exchangeSvc = exchangeSvc;
+        this.ctx = ctx;
+
+        expressionFactory = ctx.expressionFactory();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteSender rel) {
+        IgniteDistribution distribution = rel.distribution();
+
+        Destination<Row> dest = distribution.destination(ctx, affSrvc, ctx.target());
+
+        // Outbox fragment ID is used as exchange ID as well.
+        Outbox<Row> outbox =
+            new Outbox<>(ctx, rel.getRowType(), exchangeSvc, mailboxRegistry, rel.exchangeId(), rel.targetFragmentId(), dest);
+
+        Node<Row> input = visit(rel.getInput());
+
+        outbox.register(input);
+
+        mailboxRegistry.register(outbox);
+
+        return outbox;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteFilter rel) {
+        Predicate<Row> pred = expressionFactory.predicate(rel.getCondition(), rel.getRowType());
+
+        FilterNode<Row> node = new FilterNode<>(ctx, rel.getRowType(), pred);
+
+        Node<Row> input = visit(rel.getInput());
+
+        node.register(input);
+
+        return node;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteTrimExchange rel) {
+        assert TraitUtils.distribution(rel).getType() == HASH_DISTRIBUTED;
+
+        IgniteDistribution distr = rel.distribution();
+        Destination<Row> dest = distr.destination(ctx, affSrvc, ctx.group(rel.sourceId()));
+        UUID localNodeId = ctx.localNodeId();
+
+        FilterNode<Row> node = new FilterNode<>(ctx, rel.getRowType(), r -> Objects.equals(localNodeId, F.first(dest.targets(r))));
+
+        Node<Row> input = visit(rel.getInput());
+
+        node.register(input);
+
+        return node;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteProject rel) {
+        Function<Row, Row> prj = expressionFactory.project(rel.getProjects(), rel.getInput().getRowType());
+
+        ProjectNode<Row> node = new ProjectNode<>(ctx, rel.getRowType(), prj);
+
+        Node<Row> input = visit(rel.getInput());
+
+        node.register(input);
+
+        return node;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteNestedLoopJoin rel) {
+        RelDataType outType = rel.getRowType();
+        RelDataType leftType = rel.getLeft().getRowType();
+        RelDataType rightType = rel.getRight().getRowType();
+        JoinRelType joinType = rel.getJoinType();
+
+        RelDataType rowType = combinedRowType(ctx.getTypeFactory(), leftType, rightType);
+
+        BiPredicate<Row, Row> cond = expressionFactory.biPredicate(rel.getCondition(), rowType);
+
+        Node<Row> node = NestedLoopJoinNode.create(ctx, outType, leftType, rightType, joinType, cond);
+
+        Node<Row> leftInput = visit(rel.getLeft());
+        Node<Row> rightInput = visit(rel.getRight());
+
+        node.register(F.asList(leftInput, rightInput));
+
+        return node;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteCorrelatedNestedLoopJoin rel) {
+        RelDataType outType = rel.getRowType();
+        RelDataType leftType = rel.getLeft().getRowType();
+        RelDataType rightType = rel.getRight().getRowType();
+
+        RelDataType rowType = combinedRowType(ctx.getTypeFactory(), leftType, rightType);
+        BiPredicate<Row, Row> cond = expressionFactory.biPredicate(rel.getCondition(), rowType);
+
+        assert rel.getJoinType() == JoinRelType.INNER || rel.getJoinType() == JoinRelType.LEFT
+            : CNLJ_NOT_SUPPORTED_JOIN_ASSERTION_MSG;
+
+        Node<Row> node = new CorrelatedNestedLoopJoinNode<>(ctx, outType, cond, rel.getVariablesSet(),
+            rel.getJoinType());
+
+        Node<Row> leftInput = visit(rel.getLeft());
+        Node<Row> rightInput = visit(rel.getRight());
+
+        node.register(F.asList(leftInput, rightInput));
+
+        return node;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteMergeJoin rel) {
+        RelDataType outType = rel.getRowType();
+        RelDataType leftType = rel.getLeft().getRowType();
+        RelDataType rightType = rel.getRight().getRowType();
+        JoinRelType joinType = rel.getJoinType();
+
+        int pairsCnt = rel.analyzeCondition().pairs().size();
+
+        Comparator<Row> comp = expressionFactory.comparator(
+            rel.leftCollation().getFieldCollations().subList(0, pairsCnt),
+            rel.rightCollation().getFieldCollations().subList(0, pairsCnt)
+        );
+
+        Node<Row> node = MergeJoinNode.create(ctx, outType, leftType, rightType, joinType, comp);
+
+        Node<Row> leftInput = visit(rel.getLeft());
+        Node<Row> rightInput = visit(rel.getRight());
+
+        node.register(F.asList(leftInput, rightInput));
+
+        return node;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteIndexScan rel) {
+        RexNode condition = rel.condition();
+        List<RexNode> projects = rel.projects();
+
+        IgniteTable tbl = rel.getTable().unwrap(IgniteTable.class);
+        IgniteTypeFactory typeFactory = ctx.getTypeFactory();
+
+        ImmutableBitSet requiredColumns = rel.requiredColumns();
+        List<RexNode> lowerCond = rel.lowerBound();
+        List<RexNode> upperCond = rel.upperBound();
+
+        RelDataType rowType = tbl.getRowType(typeFactory, requiredColumns);
+
+        Predicate<Row> filters = condition == null ? null : expressionFactory.predicate(condition, rowType);
+        Supplier<Row> lower = lowerCond == null ? null : expressionFactory.rowSource(lowerCond);
+        Supplier<Row> upper = upperCond == null ? null : expressionFactory.rowSource(upperCond);
+        Function<Row, Row> prj = projects == null ? null : expressionFactory.project(projects, rowType);
+
+        ColocationGroup grp = ctx.group(rel.sourceId());
+
+        IgniteIndex idx = tbl.getIndex(rel.indexName());
+
+        if (idx != null && !tbl.isIndexRebuildInProgress()) {
+            Iterable<Row> rowsIter = idx.scan(ctx, grp, filters, lower, upper, prj, requiredColumns);
+
+            return new ScanNode<>(ctx, rowType, rowsIter);
+        }
+        else {
+            // Index was invalidated after planning, workaround through table-scan -> sort -> index spool.
+            // If there are correlates in filter or project, spool node is required to provide ability to rewind input.
+            // Sort node is required if output should be sorted or if spool node required (to provide search by
+            // index conditions).
+            // Additionally, project node is required in case of spool inserted, since spool requires unmodified
+            // original input for filtering by index conditions.
+            boolean filterHasCorrelation = condition != null && RexUtils.hasCorrelation(condition);
+            boolean projectHasCorrelation = projects != null && RexUtils.hasCorrelation(projects);
+            boolean spoolNodeRequired = projectHasCorrelation || filterHasCorrelation;
+            boolean projNodeRequired = projects != null && spoolNodeRequired;
+
+            Iterable<Row> rowsIter = tbl.scan(
+                ctx,
+                grp,
+                filterHasCorrelation ? null : filters,
+                projNodeRequired ? null : prj,
+                requiredColumns
+            );
+
+            // If there are projects in the scan node - after the scan we already have target row type.
+            if (!spoolNodeRequired && projects != null)
+                rowType = rel.getRowType();
+
+            Node<Row> node = new ScanNode<>(ctx, rowType, rowsIter);
+
+            RelCollation collation = rel.collation();
+
+            if ((!spoolNodeRequired && projects != null) || requiredColumns != null) {
+                collation = collation.apply(LogicalScanConverterRule.createMapping(
+                    spoolNodeRequired ? null : projects,
+                    requiredColumns,
+                    tbl.getRowType(typeFactory).getFieldCount()
+                ));
+            }
+
+            boolean sortNodeRequired = !collation.getFieldCollations().isEmpty();
+
+            if (sortNodeRequired) {
+                SortNode<Row> sortNode = new SortNode<>(ctx, rowType, expressionFactory.comparator(collation));
+
+                sortNode.register(node);
+
+                node = sortNode;
+            }
+
+            if (spoolNodeRequired) {
+                if (lowerCond != null || upperCond != null) {
+                    if (requiredColumns != null) {
+                        // Remap index find predicate according to rowType of the spool.
+                        int cardinality = requiredColumns.cardinality();
+                        List<RexNode> remappedLowerCond = lowerCond != null ? new ArrayList<>(cardinality) : null;
+                        List<RexNode> remappedUpperCond = upperCond != null ? new ArrayList<>(cardinality) : null;
+
+                        for (int i = requiredColumns.nextSetBit(0); i != -1; i = requiredColumns.nextSetBit(i + 1)) {
+                            if (remappedLowerCond != null)
+                                remappedLowerCond.add(lowerCond.get(i));
+
+                            if (remappedUpperCond != null)
+                                remappedUpperCond.add(upperCond.get(i));
+                        }
+
+                        lower = remappedLowerCond == null ? null : expressionFactory.rowSource(remappedLowerCond);
+                        upper = remappedUpperCond == null ? null : expressionFactory.rowSource(remappedUpperCond);
+                    }
+                }
+
+                IndexSpoolNode<Row> spoolNode = IndexSpoolNode.createTreeSpool(
+                    ctx,
+                    rowType,
+                    collation,
+                    expressionFactory.comparator(collation),
+                    filterHasCorrelation ? filters : null, // Not correlated filter included into table scan.
+                    lower,
+                    upper
+                );
+
+                spoolNode.register(node);
+
+                node = spoolNode;
+            }
+
+            if (projNodeRequired) {
+                ProjectNode<Row> projectNode = new ProjectNode<>(ctx, rel.getRowType(), prj);
+
+                projectNode.register(node);
+
+                node = projectNode;
+            }
+
+            return node;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteTableScan rel) {
+        RexNode condition = rel.condition();
+        List<RexNode> projects = rel.projects();
+        ImmutableBitSet requiredColunms = rel.requiredColumns();
+
+        IgniteTable tbl = rel.getTable().unwrap(IgniteTable.class);
+        IgniteTypeFactory typeFactory = ctx.getTypeFactory();
+
+        RelDataType rowType = tbl.getRowType(typeFactory, requiredColunms);
+
+        Predicate<Row> filters = condition == null ? null : expressionFactory.predicate(condition, rowType);
+        Function<Row, Row> prj = projects == null ? null : expressionFactory.project(projects, rowType);
+
+        ColocationGroup group = ctx.group(rel.sourceId());
+
+        Iterable<Row> rowsIter = tbl.scan(ctx, group, filters, prj, requiredColunms);
+
+        return new ScanNode<>(ctx, rowType, rowsIter);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteValues rel) {
+        List<RexLiteral> vals = Commons.flat(Commons.cast(rel.getTuples()));
+
+        RelDataType rowType = rel.getRowType();
+
+        return new ScanNode<>(ctx, rowType, expressionFactory.values(vals, rowType));
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteUnionAll rel) {
+        UnionAllNode<Row> node = new UnionAllNode<>(ctx, rel.getRowType());
+
+        List<Node<Row>> inputs = Commons.transform(rel.getInputs(), this::visit);
+
+        node.register(inputs);
+
+        return node;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteLimit rel) {
+        Supplier<Integer> offset = (rel.offset() == null) ? null : expressionFactory.execute(rel.offset());
+        Supplier<Integer> fetch = (rel.fetch() == null) ? null : expressionFactory.execute(rel.fetch());
+
+        LimitNode<Row> node = new LimitNode<>(ctx, rel.getRowType(), offset, fetch);
+
+        Node<Row> input = visit(rel.getInput());
+
+        node.register(input);
+
+        return node;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteSort rel) {
+        RelCollation collation = rel.getCollation();
+
+        SortNode<Row> node = new SortNode<>(ctx, rel.getRowType(), expressionFactory.comparator(collation));
+
+        Node<Row> input = visit(rel.getInput());
+
+        node.register(input);
+
+        return node;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteTableSpool rel) {
+        TableSpoolNode<Row> node = new TableSpoolNode<>(ctx, rel.getRowType(), rel.readType == Spool.Type.LAZY);
+
+        Node<Row> input = visit(rel.getInput());
+
+        node.register(input);
+
+        return node;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteSortedIndexSpool rel) {
+        RelCollation collation = rel.collation();
+
+        assert rel.indexCondition() != null : rel;
+
+        List<RexNode> lowerBound = rel.indexCondition().lowerBound();
+        List<RexNode> upperBound = rel.indexCondition().upperBound();
+
+        Predicate<Row> filter = expressionFactory.predicate(rel.condition(), rel.getRowType());
+        Supplier<Row> lower = lowerBound == null ? null : expressionFactory.rowSource(lowerBound);
+        Supplier<Row> upper = upperBound == null ? null : expressionFactory.rowSource(upperBound);
+
+        IndexSpoolNode<Row> node = IndexSpoolNode.createTreeSpool(
+            ctx,
+            rel.getRowType(),
+            collation,
+            expressionFactory.comparator(collation),
+            filter,
+            lower,
+            upper
+        );
+
+        Node<Row> input = visit(rel.getInput());
+
+        node.register(input);
+
+        return node;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteHashIndexSpool rel) {
+        Supplier<Row> searchRow = expressionFactory.rowSource(rel.searchRow());
+
+        Predicate<Row> filter = expressionFactory.predicate(rel.condition(), rel.getRowType());
+
+        IndexSpoolNode<Row> node = IndexSpoolNode.createHashSpool(
+            ctx,
+            rel.getRowType(),
+            ImmutableBitSet.of(rel.keys()),
+            filter,
+            searchRow
+        );
+
+        Node<Row> input = visit(rel.getInput());
+
+        node.register(input);
+
+        return node;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteSetOp rel) {
+        RelDataType rowType = rel.getRowType();
+
+        RowFactory<Row> rowFactory = ctx.rowHandler().factory(ctx.getTypeFactory(), rowType);
+
+        List<Node<Row>> inputs = Commons.transform(rel.getInputs(), this::visit);
+
+        AbstractSetOpNode<Row> node;
+
+        if (rel instanceof Minus)
+            node = new MinusNode<>(ctx, rowType, rel.aggregateType(), rel.all(), rowFactory);
+        else if (rel instanceof Intersect)
+            node = new IntersectNode<>(ctx, rowType, rel.aggregateType(), rel.all(), rowFactory, rel.getInputs().size());
+        else
+            throw new AssertionError();
+
+        node.register(inputs);
+
+        return node;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteTableFunctionScan rel) {
+        Supplier<Iterable<Object[]>> dataSupplier = expressionFactory.execute(rel.getCall());
+
+        RelDataType rowType = rel.getRowType();
+
+        RowFactory<Row> rowFactory = ctx.rowHandler().factory(ctx.getTypeFactory(), rowType);
+
+        return new ScanNode<>(ctx, rowType, new TableFunctionScan<>(dataSupplier, rowFactory));
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteTableModify rel) {
+        switch (rel.getOperation()) {
+            case INSERT:
+            case UPDATE:
+            case DELETE:
+            case MERGE:
+                ModifyNode<Row> node = new ModifyNode<>(ctx, rel.getRowType(), rel.getTable().unwrap(CacheTableDescriptor.class),
+                    rel.getOperation(), rel.getUpdateColumnList());
+
+                Node<Row> input = visit(rel.getInput());
+
+                node.register(input);
+
+                return node;
+            default:
+                throw new AssertionError();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteReceiver rel) {
+        Inbox<Row> inbox = (Inbox<Row>)mailboxRegistry.register(
+            new Inbox<>(ctx, exchangeSvc, mailboxRegistry, rel.exchangeId(), rel.sourceFragmentId()));
+
+        // here may be an already created (to consume rows from remote nodes) inbox
+        // without proper context, we need to init it with a right one.
+        inbox.init(ctx, rel.getRowType(), ctx.remotes(rel.exchangeId()), expressionFactory.comparator(rel.collation()));
+
+        return inbox;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteColocatedHashAggregate rel) {
+        AggregateType type = AggregateType.SINGLE;
+
+        RelDataType rowType = rel.getRowType();
+        RelDataType inputType = rel.getInput().getRowType();
+
+        Supplier<List<AccumulatorWrapper<Row>>> accFactory = expressionFactory.accumulatorsFactory(
+            type, rel.getAggCallList(), inputType);
+        RowFactory<Row> rowFactory = ctx.rowHandler().factory(ctx.getTypeFactory(), rowType);
+
+        HashAggregateNode<Row> node = new HashAggregateNode<>(ctx, rowType, type, rel.getGroupSets(), accFactory, rowFactory);
+
+        Node<Row> input = visit(rel.getInput());
+
+        node.register(input);
+
+        return node;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteMapHashAggregate rel) {
+        AggregateType type = AggregateType.MAP;
+
+        RelDataType rowType = rel.getRowType();
+        RelDataType inputType = rel.getInput().getRowType();
+
+        Supplier<List<AccumulatorWrapper<Row>>> accFactory = expressionFactory.accumulatorsFactory(
+            type, rel.getAggCallList(), inputType);
+        RowFactory<Row> rowFactory = ctx.rowHandler().factory(ctx.getTypeFactory(), rowType);
+
+        HashAggregateNode<Row> node = new HashAggregateNode<>(ctx, rowType, type, rel.getGroupSets(), accFactory, rowFactory);
+
+        Node<Row> input = visit(rel.getInput());
+
+        node.register(input);
+
+        return node;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteReduceHashAggregate rel) {
+        AggregateType type = AggregateType.REDUCE;
+
+        RelDataType rowType = rel.getRowType();
+
+        Supplier<List<AccumulatorWrapper<Row>>> accFactory = expressionFactory.accumulatorsFactory(
+            type, rel.getAggregateCalls(), null);
+        RowFactory<Row> rowFactory = ctx.rowHandler().factory(ctx.getTypeFactory(), rowType);
+
+        HashAggregateNode<Row> node = new HashAggregateNode<>(ctx, rowType, type, rel.getGroupSets(), accFactory, rowFactory);
+
+        Node<Row> input = visit(rel.getInput());
+
+        node.register(input);
+
+        return node;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteColocatedSortAggregate rel) {
+        AggregateType type = AggregateType.SINGLE;
+
+        RelDataType rowType = rel.getRowType();
+        RelDataType inputType = rel.getInput().getRowType();
+
+        Supplier<List<AccumulatorWrapper<Row>>> accFactory = expressionFactory.accumulatorsFactory(
+            type,
+            rel.getAggCallList(),
+            inputType
+        );
+
+        RowFactory<Row> rowFactory = ctx.rowHandler().factory(ctx.getTypeFactory(), rowType);
+
+        SortAggregateNode<Row> node = new SortAggregateNode<>(
+            ctx,
+            rowType,
+            type,
+            rel.getGroupSet(),
+            accFactory,
+            rowFactory,
+            expressionFactory.comparator(rel.collation())
+        );
+
+        Node<Row> input = visit(rel.getInput());
+
+        node.register(input);
+
+        return node;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteMapSortAggregate rel) {
+        AggregateType type = AggregateType.MAP;
+
+        RelDataType rowType = rel.getRowType();
+        RelDataType inputType = rel.getInput().getRowType();
+
+        Supplier<List<AccumulatorWrapper<Row>>> accFactory = expressionFactory.accumulatorsFactory(
+            type,
+            rel.getAggCallList(),
+            inputType
+        );
+
+        RowFactory<Row> rowFactory = ctx.rowHandler().factory(ctx.getTypeFactory(), rowType);
+
+        SortAggregateNode<Row> node = new SortAggregateNode<>(
+            ctx,
+            rowType,
+            type,
+            rel.getGroupSet(),
+            accFactory,
+            rowFactory,
+            expressionFactory.comparator(rel.collation())
+        );
+
+        Node<Row> input = visit(rel.getInput());
+
+        node.register(input);
+
+        return node;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteReduceSortAggregate rel) {
+        AggregateType type = AggregateType.REDUCE;
+
+        RelDataType rowType = rel.getRowType();
+
+        Supplier<List<AccumulatorWrapper<Row>>> accFactory = expressionFactory.accumulatorsFactory(
+            type,
+            rel.getAggregateCalls(),
+            null
+        );
+
+        RowFactory<Row> rowFactory = ctx.rowHandler().factory(ctx.getTypeFactory(), rowType);
+
+        SortAggregateNode<Row> node = new SortAggregateNode<>(
+            ctx,
+            rowType,
+            type,
+            rel.getGroupSet(),
+            accFactory,
+            rowFactory,
+            expressionFactory.comparator(rel.collation())
+            );
+
+        Node<Row> input = visit(rel.getInput());
+
+        node.register(input);
+
+        return node;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteRel rel) {
+        return rel.accept(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node<Row> visit(IgniteExchange rel) {
+        throw new AssertionError();
+    }
+
+    /** */
+    private Node<Row> visit(RelNode rel) {
+        return visit((IgniteRel)rel);
+    }
+
+    /** */
+    public <T extends Node<Row>> T go(IgniteRel rel) {
+        return (T)visit(rel);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/MailboxRegistry.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/MailboxRegistry.java
new file mode 100644
index 0000000..8c7c2d4
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/MailboxRegistry.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.exec;
+
+import java.util.Collection;
+import java.util.UUID;
+
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.Inbox;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.Outbox;
+import org.apache.ignite.internal.processors.query.calcite.util.Service;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ *
+ */
+public interface MailboxRegistry extends Service {
+    /**
+     * Tries to register and inbox node and returns it if success or returns previously registered inbox otherwise.
+     *
+     * @param inbox Inbox.
+     * @return Registered inbox.
+     */
+    Inbox<?> register(Inbox<?> inbox);
+
+    /**
+     * Unregisters an inbox.
+     *
+     * @param inbox Inbox to unregister.
+     */
+    void unregister(Inbox<?> inbox);
+
+    /**
+     * Registers an outbox.
+     *
+     * @param outbox Outbox to register.
+     */
+    void register(Outbox<?> outbox);
+
+    /**
+     * Unregisters an outbox.
+     *
+     * @param outbox Outbox to unregister.
+     */
+    void unregister(Outbox<?> outbox);
+
+    /**
+     * Returns a registered outbox by provided query ID, exchange ID pair.
+     *
+     * @param qryId Query ID.
+     * @param exchangeId Exchange ID.
+     *
+     * @return Registered outbox. May be {@code null} if execution was cancelled.
+     */
+    Outbox<?> outbox(UUID qryId, long exchangeId);
+
+    /**
+     * Returns a registered inbox by provided query ID, exchange ID pair.
+     *
+     * @param qryId Query ID.
+     * @param exchangeId Exchange ID.
+     *
+     * @return Registered inbox. May be {@code null} if execution was cancelled.
+     */
+    Inbox<?> inbox(UUID qryId, long exchangeId);
+
+    /**
+     * Returns all registered inboxes for provided query ID.
+     *
+     * @param qryId Query ID. {@code null} means return inboxes with any query id.
+     * @param fragmentId Fragment Id. {@code -1} means return inboxes with any fragment id.
+     * @param exchangeId Exchange Id. {@code -1} means return inboxes with any exchange id.
+     * @return Registered inboxes.
+     */
+    Collection<Inbox<?>> inboxes(@Nullable UUID qryId, long fragmentId, long exchangeId);
+
+    /**
+     * Returns all registered outboxes for provided query ID.
+     *
+     * @param qryId Query ID. {@code null} means return outboxes with any query id.
+     * @param fragmentId Fragment Id. {@code -1} means return outboxes with any fragment id.
+     * @param exchangeId Exchange Id. {@code -1} means return outboxes with any exchange id.
+     * @return Registered outboxes.
+     */
+    Collection<Outbox<?>> outboxes(@Nullable UUID qryId, long fragmentId, long exchangeId);
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/MailboxRegistryImpl.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/MailboxRegistryImpl.java
new file mode 100644
index 0000000..538e7b0
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/MailboxRegistryImpl.java
@@ -0,0 +1,216 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.exec;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import org.apache.ignite.events.EventType;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.managers.eventstorage.DiscoveryEventListener;
+import org.apache.ignite.internal.managers.eventstorage.GridEventStorageManager;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.Inbox;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.Mailbox;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.Outbox;
+import org.apache.ignite.internal.processors.query.calcite.util.AbstractService;
+import org.apache.ignite.internal.util.tostring.GridToStringExclude;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ *
+ */
+public class MailboxRegistryImpl extends AbstractService implements MailboxRegistry {
+    /** */
+    private static final Predicate<Mailbox<?>> ALWAYS_TRUE = o -> true;
+
+    /** */
+    private final Map<MailboxKey, Outbox<?>> locals;
+
+    /** */
+    private final Map<MailboxKey, Inbox<?>> remotes;
+
+    /** */
+    @GridToStringExclude
+    private final DiscoveryEventListener discoLsnr;
+
+    /** */
+    @GridToStringExclude
+    private GridEventStorageManager evtMgr;
+
+    /**
+     * @param ctx Kernal.
+     */
+    public MailboxRegistryImpl(GridKernalContext ctx) {
+        super(ctx);
+
+        locals = new ConcurrentHashMap<>();
+        remotes = new ConcurrentHashMap<>();
+
+        discoLsnr = (e, c) -> onNodeLeft(e.eventNode().id());
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onStart(GridKernalContext ctx) {
+        eventManager(ctx.event());
+
+        init();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void init() {
+        eventManager().addDiscoveryEventListener(discoLsnr, EventType.EVT_NODE_FAILED, EventType.EVT_NODE_LEFT);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void tearDown() {
+        eventManager().removeDiscoveryEventListener(discoLsnr, EventType.EVT_NODE_FAILED, EventType.EVT_NODE_LEFT);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Inbox<?> register(Inbox<?> inbox) {
+        Inbox<?> old = remotes.putIfAbsent(new MailboxKey(inbox.queryId(), inbox.exchangeId()), inbox);
+
+        return old != null ? old : inbox;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void unregister(Inbox<?> inbox) {
+        remotes.remove(new MailboxKey(inbox.queryId(), inbox.exchangeId()), inbox);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void register(Outbox<?> outbox) {
+        Outbox<?> res = locals.put(new MailboxKey(outbox.queryId(), outbox.exchangeId()), outbox);
+
+        assert res == null : res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void unregister(Outbox<?> outbox) {
+        locals.remove(new MailboxKey(outbox.queryId(), outbox.exchangeId()), outbox);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Outbox<?> outbox(UUID qryId, long exchangeId) {
+        return locals.get(new MailboxKey(qryId, exchangeId));
+    }
+
+    /** {@inheritDoc} */
+    @Override public Inbox<?> inbox(UUID qryId, long exchangeId) {
+        return remotes.get(new MailboxKey(qryId, exchangeId));
+    }
+
+    /** {@inheritDoc} */
+    @Override public Collection<Inbox<?>> inboxes(@Nullable UUID qryId, long fragmentId, long exchangeId) {
+        return remotes.values().stream()
+            .filter(makeFilter(qryId, fragmentId, exchangeId))
+            .collect(Collectors.toList());
+    }
+
+    /** {@inheritDoc} */
+    @Override public Collection<Outbox<?>> outboxes(@Nullable UUID qryId, long fragmentId, long exchangeId) {
+        return locals.values().stream()
+            .filter(makeFilter(qryId, fragmentId, exchangeId))
+            .collect(Collectors.toList());
+    }
+
+    /**
+     * @param evtMgr Event manager.
+     */
+    public void eventManager(GridEventStorageManager evtMgr) {
+        this.evtMgr = evtMgr;
+    }
+
+    /**
+     * @return Event manager.
+     */
+    public GridEventStorageManager eventManager() {
+        return evtMgr;
+    }
+
+    /** */
+    private void onNodeLeft(UUID nodeId) {
+        locals.values().forEach(n -> n.onNodeLeft(nodeId));
+        remotes.values().forEach(n -> n.onNodeLeft(nodeId));
+    }
+
+    /** */
+    private static Predicate<Mailbox<?>> makeFilter(@Nullable UUID qryId, long fragmentId, long exchangeId) {
+        Predicate<Mailbox<?>> filter = ALWAYS_TRUE;
+        if (qryId != null)
+            filter = filter.and(mailbox -> Objects.equals(mailbox.queryId(), qryId));
+        if (fragmentId != -1)
+            filter = filter.and(mailbox -> mailbox.fragmentId() == fragmentId);
+        if (exchangeId != -1)
+            filter = filter.and(mailbox -> mailbox.exchangeId() == exchangeId);
+
+        return filter;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(MailboxRegistryImpl.class, this);
+    }
+
+    /** */
+    private static class MailboxKey {
+        /** */
+        private final UUID qryId;
+
+        /** */
+        private final long exchangeId;
+
+        /** */
+        private MailboxKey(UUID qryId, long exchangeId) {
+            this.qryId = qryId;
+            this.exchangeId = exchangeId;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean equals(Object o) {
+            if (this == o)
+                return true;
+            if (o == null || getClass() != o.getClass())
+                return false;
+
+            MailboxKey that = (MailboxKey)o;
+
+            if (exchangeId != that.exchangeId)
+                return false;
+            return qryId.equals(that.qryId);
+        }
+
+        /** {@inheritDoc} */
+        @Override public int hashCode() {
+            int res = qryId.hashCode();
+            res = 31 * res + (int)(exchangeId ^ (exchangeId >>> 32));
+            return res;
+        }
+
+        /** {@inheritDoc} */
+        @Override public String toString() {
+            return S.toString(MailboxKey.class, this);
+        }
+    }
+}
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/stat/StatisticsType.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/QueryTaskExecutor.java
similarity index 55%
copy from modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/stat/StatisticsType.java
copy to modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/QueryTaskExecutor.java
index 38f3f67a..6891cde 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/stat/StatisticsType.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/QueryTaskExecutor.java
@@ -15,30 +15,21 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.processors.query.stat;
+package org.apache.ignite.internal.processors.query.calcite.exec;
 
-import org.jetbrains.annotations.Nullable;
+import java.util.UUID;
+import org.apache.ignite.internal.processors.query.calcite.util.Service;
 
 /**
- * Types of statistics width.
+ *
  */
-public enum StatisticsType {
-    /** Statistics by some particular partition. */
-    PARTITION,
-
-    /** Statistics by some data node. */
-    LOCAL;
-
-    /** Enumerated values. */
-    private static final StatisticsType[] VALUES = values();
-
+public interface QueryTaskExecutor extends Service {
     /**
-     * Efficiently gets enumerated value from its ordinal.
+     * Executes a query task in a thread, responsible for particular query fragment.
      *
-     * @param ord Ordinal value.
-     * @return Enumerated value or {@code null} if ordinal out of range.
+     * @param qryId Query ID.
+     * @param fragmentId Fragment ID.
+     * @param qryTask Query task.
      */
-    @Nullable public static StatisticsType fromOrdinal(int ord) {
-        return ord >= 0 && ord < VALUES.length ? VALUES[ord] : null;
-    }
+    void execute(UUID qryId, long fragmentId, Runnable qryTask);
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/QueryTaskExecutorImpl.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/QueryTaskExecutorImpl.java
new file mode 100644
index 0000000..c572901
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/QueryTaskExecutorImpl.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.exec;
+
+import java.util.Objects;
+import java.util.UUID;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.processors.query.calcite.CalciteQueryProcessor;
+import org.apache.ignite.internal.processors.query.calcite.util.AbstractService;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.apache.ignite.internal.util.StripedExecutor;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.thread.IgniteStripedThreadPoolExecutor;
+
+/**
+ * TODO use {@link StripedExecutor}, registered in core pols.
+ */
+public class QueryTaskExecutorImpl extends AbstractService implements QueryTaskExecutor, Thread.UncaughtExceptionHandler {
+    /** */
+    private IgniteStripedThreadPoolExecutor stripedThreadPoolExecutor;
+
+    /** */
+    private Thread.UncaughtExceptionHandler eHnd;
+
+    /** */
+    public QueryTaskExecutorImpl(GridKernalContext ctx) {
+        super(ctx);
+    }
+
+    /**
+     * @param stripedThreadPoolExecutor Executor.
+     */
+    public void stripedThreadPoolExecutor(IgniteStripedThreadPoolExecutor stripedThreadPoolExecutor) {
+        this.stripedThreadPoolExecutor = stripedThreadPoolExecutor;
+    }
+
+    /**
+     * @param eHnd Uncaught exception handler.
+     */
+    public void exceptionHandler(Thread.UncaughtExceptionHandler eHnd) {
+        this.eHnd = eHnd;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void execute(UUID qryId, long fragmentId, Runnable qryTask) {
+        stripedThreadPoolExecutor.execute(
+            () -> {
+                try {
+                    qryTask.run();
+                }
+                catch (Throwable e) {
+                    U.warn(log, "Uncaught exception", e);
+
+                    /*
+                     * No exceptions are rethrown here to preserve the current thread from being destroyed,
+                     * because other queries may be pinned to the current thread id.
+                     * However, unrecoverable errors must be processed by FailureHandler.
+                     */
+                    uncaughtException(Thread.currentThread(), e);
+                }
+            },
+            hash(qryId, fragmentId)
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onStart(GridKernalContext ctx) {
+        exceptionHandler(ctx.uncaughtExceptionHandler());
+
+        CalciteQueryProcessor proc = Objects.requireNonNull(Commons.lookupComponent(ctx, CalciteQueryProcessor.class));
+
+        stripedThreadPoolExecutor(new IgniteStripedThreadPoolExecutor(
+            ctx.config().getQueryThreadPoolSize(),
+            ctx.igniteInstanceName(),
+            "calciteQry",
+            this,
+            false,
+            0
+        ));
+    }
+
+    /** {@inheritDoc} */
+    @Override public void tearDown() {
+        U.shutdownNow(getClass(), stripedThreadPoolExecutor, log);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void uncaughtException(Thread t, Throwable e) {
+        if (eHnd != null)
+            eHnd.uncaughtException(t, e);
+    }
+
+    /** */
+    private static int hash(UUID qryId, long fragmentId) {
+        // inlined Objects.hash(...)
+        return U.safeAbs(31 * (31 + (qryId != null ? qryId.hashCode() : 0)) + Long.hashCode(fragmentId));
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/RowHandler.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/RowHandler.java
new file mode 100644
index 0000000..ad03cb6
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/RowHandler.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.exec;
+
+import java.lang.reflect.Type;
+import java.util.List;
+
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
+
+/**
+ * Universal accessor and mutator for rows. It also has factory methods.
+ */
+public interface RowHandler<Row> {
+    /** */
+    Object get(int field, Row row);
+
+    /** */
+    void set(int field, Row row, Object val);
+
+    /** */
+    Row concat(Row left, Row right);
+
+    /** */
+    int columnCount(Row row);
+
+    /** */
+    default RowFactory<Row> factory(IgniteTypeFactory typeFactory, RelDataType rowType) {
+        if (rowType.isStruct())
+            return factory(typeFactory, RelOptUtil.getFieldTypeList(rowType));
+
+        return factory(typeFactory.getJavaClass(rowType));
+    }
+
+    /** */
+    default RowFactory<Row> factory(IgniteTypeFactory typeFactory, List<RelDataType> fieldTypes) {
+        Type[] types = new Type[fieldTypes.size()];
+        for (int i = 0; i < fieldTypes.size(); i++)
+            types[i] = typeFactory.getJavaClass(fieldTypes.get(i));
+
+        return factory(types);
+    }
+
+    /** */
+    RowFactory<Row> factory(Type... types);
+
+    /** */
+    @SuppressWarnings("PublicInnerClass")
+    interface RowFactory<Row> {
+        /** */
+        RowHandler<Row> handler();
+
+        /** */
+        Row create();
+
+        /** */
+        Row create(Object... fields);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/RuntimeHashIndex.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/RuntimeHashIndex.java
new file mode 100644
index 0000000..8329486
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/RuntimeHashIndex.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ignite.internal.processors.query.calcite.exec;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.GroupKey;
+import org.apache.ignite.internal.util.lang.GridFilteredIterator;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.X;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Runtime hash index based on on-heap hash map.
+ */
+public class RuntimeHashIndex<Row> implements RuntimeIndex<Row> {
+    /**
+     * Placeholder for keys containing NULL values. Used to skip rows with such keys, since condition NULL=NULL
+     * should not satisfy the filter.
+     */
+    private static final GroupKey NULL_KEY = new GroupKey(X.EMPTY_OBJECT_ARRAY);
+
+    /** */
+    protected final ExecutionContext<Row> ectx;
+
+    /** */
+    private final ImmutableBitSet keys;
+
+    /** Rows. */
+    private HashMap<GroupKey, List<Row>> rows;
+
+    /**
+     *
+     */
+    public RuntimeHashIndex(
+        ExecutionContext<Row> ectx,
+        ImmutableBitSet keys
+    ) {
+        this.ectx = ectx;
+
+        assert !F.isEmpty(keys);
+
+        this.keys = keys;
+        rows = new HashMap<>();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void push(Row r) {
+        GroupKey key = key(r);
+
+        if (key == NULL_KEY)
+            return;
+
+        List<Row> eqRows = rows.computeIfAbsent(key, k -> new ArrayList<>());
+
+        eqRows.add(r);
+    }
+
+    /** */
+    @Override public void close() {
+        rows.clear();
+    }
+
+    /** */
+    public Iterable<Row> scan(Supplier<Row> searchRow, @Nullable Predicate<Row> filter) {
+        return new IndexScan(searchRow, filter);
+    }
+
+    /** */
+    private GroupKey key(Row r) {
+        GroupKey.Builder b = GroupKey.builder(keys.cardinality());
+
+        for (Integer field : keys) {
+            Object fieldVal = ectx.rowHandler().get(field, r);
+
+            if (fieldVal == null)
+                return NULL_KEY;
+
+            b.add(fieldVal);
+        }
+
+        return b.build();
+    }
+
+    /**
+     *
+     */
+    private class IndexScan implements Iterable<Row>, AutoCloseable {
+        /** Search row. */
+        private final Supplier<Row> searchRow;
+
+        /** Row filter. */
+        private final Predicate<Row> filter;
+
+        /**
+         * @param searchRow Search row.
+         * @param filter Scan condition.
+         */
+        IndexScan(Supplier<Row> searchRow, @Nullable Predicate<Row> filter) {
+            this.searchRow = searchRow;
+            this.filter = filter;
+        }
+
+        /** {@inheritDoc} */
+        @Override public void close() {
+            // No-op.
+        }
+
+        /** {@inheritDoc} */
+        @NotNull @Override public Iterator<Row> iterator() {
+            GroupKey key = key(searchRow.get());
+
+            if (key == NULL_KEY)
+                return Collections.emptyIterator();
+
+            List<Row> eqRows = rows.get(key);
+
+            if (eqRows == null)
+                return Collections.emptyIterator();
+
+            return filter == null ? eqRows.iterator() : new GridFilteredIterator<Row>(eqRows.iterator()) {
+                @Override protected boolean accept(Row row) {
+                    return filter.test(row);
+                }
+            };
+        }
+    }
+}
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/stat/StatisticsType.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/RuntimeIndex.java
similarity index 54%
copy from modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/stat/StatisticsType.java
copy to modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/RuntimeIndex.java
index 38f3f67a..01d257c 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/stat/StatisticsType.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/RuntimeIndex.java
@@ -14,31 +14,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-package org.apache.ignite.internal.processors.query.stat;
-
-import org.jetbrains.annotations.Nullable;
+package org.apache.ignite.internal.processors.query.calcite.exec;
 
 /**
- * Types of statistics width.
+ * Runtime index interface.
+ * The temporary index is built and available only on query execution. Not stored at the schema.
  */
-public enum StatisticsType {
-    /** Statistics by some particular partition. */
-    PARTITION,
-
-    /** Statistics by some data node. */
-    LOCAL;
-
-    /** Enumerated values. */
-    private static final StatisticsType[] VALUES = values();
-
+public interface RuntimeIndex<Row> extends AutoCloseable {
     /**
-     * Efficiently gets enumerated value from its ordinal.
-     *
-     * @param ord Ordinal value.
-     * @return Enumerated value or {@code null} if ordinal out of range.
+     * Add row to index.
      */
-    @Nullable public static StatisticsType fromOrdinal(int ord) {
-        return ord >= 0 && ord < VALUES.length ? VALUES[ord] : null;
-    }
+    void push(Row r);
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/RuntimeSortedIndex.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/RuntimeSortedIndex.java
new file mode 100644
index 0000000..88df20a
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/RuntimeSortedIndex.java
@@ -0,0 +1,210 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ignite.internal.processors.query.calcite.exec;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.ignite.internal.cache.query.index.sorted.inline.IndexQueryContext;
+import org.apache.ignite.internal.util.lang.GridCursor;
+import org.apache.ignite.internal.util.typedef.F;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Runtime sorted index.
+ */
+public class RuntimeSortedIndex<Row> implements RuntimeIndex<Row>, TreeIndex<Row> {
+    /** */
+    protected final ExecutionContext<Row> ectx;
+
+    /** */
+    protected final Comparator<Row> comp;
+
+    /** Collation. */
+    private final RelCollation collation;
+
+    /** Rows. */
+    private final ArrayList<Row> rows = new ArrayList<>();
+
+    /**
+     *
+     */
+    public RuntimeSortedIndex(
+        ExecutionContext<Row> ectx,
+        RelCollation collation,
+        Comparator<Row> comp
+    ) {
+        this.ectx = ectx;
+        this.comp = comp;
+
+        assert Objects.nonNull(collation);
+
+        this.collation = collation;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void push(Row r) {
+        assert rows.isEmpty() || comp.compare(r, rows.get(rows.size() - 1)) >= 0 : "Not sorted input";
+
+        rows.add(r);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void close() {
+        rows.clear();
+    }
+
+    /** {@inheritDoc} */
+    @Override public GridCursor<Row> find(Row lower, Row upper, IndexQueryContext qctx) {
+        assert qctx == null;
+
+        int firstCol = F.first(collation.getKeys());
+
+        Object lowerBound = (lower == null) ? null : ectx.rowHandler().get(firstCol, lower);
+        Object upperBound = (upper == null) ? null : ectx.rowHandler().get(firstCol, upper);
+
+        Row lowerRow = (lowerBound == null) ? null : lower;
+        Row upperRow = (upperBound == null) ? null : upper;
+
+        return new Cursor(rows, lowerRow, upperRow);
+    }
+
+    /**
+     * Creates iterable on the index.
+     */
+    public Iterable<Row> scan(
+        ExecutionContext<Row> ectx,
+        RelDataType rowType,
+        Predicate<Row> filter,
+        Supplier<Row> lowerBound,
+        Supplier<Row> upperBound
+    ) {
+        return new IndexScan(rowType, this, filter, lowerBound, upperBound);
+    }
+
+    /**
+     * Cursor to navigate through a sorted list with duplicates.
+     */
+    private class Cursor implements GridCursor<Row> {
+        /** List of rows. */
+        private final List<Row> rows;
+
+        /** Upper bound. */
+        private final Row upper;
+
+        /** Current row. */
+        private Row row;
+
+        /** Current index of list element. */
+        private int idx;
+
+        /**
+         * @param rows List of rows.
+         * @param lower Lower bound (inclusive).
+         * @param upper Upper bound (inclusive).
+         */
+        Cursor(List<Row> rows, @Nullable Row lower, @Nullable Row upper) {
+            this.rows = rows;
+            this.upper = upper;
+
+            idx = lower == null ? 0 : lowerBound(rows, lower);
+        }
+
+        /**
+         * Searches the lower bound (skipping duplicates) using a binary search.
+         *
+         * @param rows List of rows.
+         * @param bound Lower bound.
+         * @return Lower bound position in the list.
+         */
+        private int lowerBound(List<Row> rows, Row bound) {
+            int low = 0, high = rows.size() - 1, idx = -1;
+
+            while (low <= high) {
+                int mid = (high - low) / 2 + low;
+                int compRes = comp.compare(rows.get(mid), bound);
+
+                if (compRes > 0)
+                    high = mid - 1;
+                else if (compRes == 0) {
+                    idx = mid;
+                    high = mid - 1;
+                }
+                else
+                    low = mid + 1;
+            }
+
+            return idx == -1 ? low : idx;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean next() {
+            if (idx == rows.size() || (upper != null && comp.compare(upper, rows.get(idx)) < 0))
+                return false;
+
+            row = rows.get(idx++);
+
+            return true;
+        }
+
+        /** {@inheritDoc} */
+        @Override public Row get() {
+            return row;
+        }
+    }
+
+    /**
+     *
+     */
+    private class IndexScan extends AbstractIndexScan<Row, Row> {
+        /**
+         * @param rowType Row type.
+         * @param idx Physical index.
+         * @param filter Additional filters.
+         * @param lowerBound Lower index scan bound.
+         * @param upperBound Upper index scan bound.
+         */
+        IndexScan(
+            RelDataType rowType,
+            TreeIndex<Row> idx,
+            Predicate<Row> filter,
+            Supplier<Row> lowerBound,
+            Supplier<Row> upperBound) {
+            super(RuntimeSortedIndex.this.ectx, rowType, idx, filter, lowerBound, upperBound, null);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected Row row2indexRow(Row bound) {
+            return bound;
+        }
+
+        /** {@inheritDoc} */
+        @Override protected Row indexRow2Row(Row row) {
+            return row;
+        }
+
+        /** */
+        @Override protected IndexQueryContext indexQueryContext() {
+            return null;
+        }
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/SystemViewScan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/SystemViewScan.java
new file mode 100644
index 0000000..89392be
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/SystemViewScan.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.exec;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler.RowFactory;
+import org.apache.ignite.internal.processors.query.calcite.schema.SystemViewColumnDescriptor;
+import org.apache.ignite.internal.processors.query.calcite.schema.SystemViewTableDescriptorImpl;
+import org.apache.ignite.internal.processors.query.calcite.util.TypeUtils;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.lang.IgniteClosure;
+import org.apache.ignite.lang.IgnitePredicate;
+import org.apache.ignite.spi.systemview.view.FiltrableSystemView;
+import org.apache.ignite.spi.systemview.view.SystemView;
+import org.jetbrains.annotations.Nullable;
+
+/** */
+public class SystemViewScan<Row, ViewRow> implements Iterable<Row> {
+    /** */
+    private final ExecutionContext<Row> ectx;
+
+    /** */
+    private final SystemViewTableDescriptorImpl<ViewRow> desc;
+
+    /** */
+    private final RowFactory<Row> factory;
+
+    /** */
+    private final Supplier<Row> searchRow;
+
+    /** */
+    private final Predicate<Row> filters;
+
+    /** */
+    private final Function<Row, Row> rowTransformer;
+
+    /** Participating colunms. */
+    private final ImmutableBitSet requiredColumns;
+
+    /** System view field names (for filtering). */
+    private final String[] filterableFieldNames;
+
+    /** System view field types (for filtering). */
+    private final Class<?>[] filterableFieldTypes;
+
+    /** */
+    public SystemViewScan(
+        ExecutionContext<Row> ectx,
+        SystemViewTableDescriptorImpl<ViewRow> desc,
+        @Nullable Supplier<Row> searchRow,
+        Predicate<Row> filters,
+        Function<Row, Row> rowTransformer,
+        @Nullable ImmutableBitSet requiredColumns
+    ) {
+        this.ectx = ectx;
+        this.desc = desc;
+        this.searchRow = searchRow;
+        this.filters = filters;
+        this.rowTransformer = rowTransformer;
+        this.requiredColumns = requiredColumns;
+
+        RelDataType rowType = desc.rowType(ectx.getTypeFactory(), requiredColumns);
+
+        factory = ectx.rowHandler().factory(ectx.getTypeFactory(), rowType);
+
+        filterableFieldNames = new String[desc.columnDescriptors().size()];
+        filterableFieldTypes = new Class<?>[desc.columnDescriptors().size()];
+
+        if (desc.isFiltrable()) {
+            for (SystemViewColumnDescriptor col : desc.columnDescriptors()) {
+                if (col.isFiltrable()) {
+                    filterableFieldNames[col.fieldIndex()] = col.originalName();
+                    filterableFieldTypes[col.fieldIndex()] = col.storageType();
+                }
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public Iterator<Row> iterator() {
+        SystemView<ViewRow> view = desc.systemView();
+
+        Iterator<ViewRow> viewIter;
+
+        if (searchRow != null) {
+            assert view instanceof FiltrableSystemView : view;
+
+            Row searchValues = searchRow.get();
+
+            RowHandler<Row> rowHnd = ectx.rowHandler();
+            Map<String, Object> filterMap = null;
+
+            for (int i = 0; i < filterableFieldNames.length; i++) {
+                if (filterableFieldNames[i] == null)
+                    continue;
+
+                Object val = rowHnd.get(i, searchValues);
+
+                if (val != ectx.unspecifiedValue()) {
+                    if (filterMap == null)
+                        filterMap = new HashMap<>();
+
+                    filterMap.put(filterableFieldNames[i], TypeUtils.fromInternal(ectx, val, filterableFieldTypes[i]));
+                }
+            }
+
+            viewIter = F.isEmpty(filterMap) ? view.iterator() : ((FiltrableSystemView<ViewRow>)view).iterator(filterMap);
+        }
+        else
+            viewIter = view.iterator();
+
+        Iterator<Row> iter = F.iterator(
+            viewIter,
+            row -> desc.toRow(ectx, row, factory, requiredColumns),
+            true);
+
+        if (rowTransformer != null || filters != null) {
+            IgniteClosure<Row, Row> trans = rowTransformer == null ? F.identity() : rowTransformer::apply;
+            IgnitePredicate<Row> filter = filters == null ? F.alwaysTrue() : filters::test;
+
+            iter = F.iterator(iter, trans, true, filter);
+        }
+
+        return iter;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableFunctionScan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableFunctionScan.java
new file mode 100644
index 0000000..a853184
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableFunctionScan.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.exec;
+
+import java.util.Iterator;
+import java.util.function.Supplier;
+import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler.RowFactory;
+import org.apache.ignite.internal.util.typedef.F;
+
+/** */
+public class TableFunctionScan<Row> implements Iterable<Row> {
+    /** */
+    private final Supplier<Iterable<Object[]>> dataSupplier;
+
+    /** */
+    private final RowFactory<Row> rowFactory;
+
+    /** */
+    public TableFunctionScan(
+        Supplier<Iterable<Object[]>> dataSupplier,
+        RowFactory<Row> rowFactory
+    ) {
+        this.dataSupplier = dataSupplier;
+        this.rowFactory = rowFactory;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Iterator<Row> iterator() {
+        return F.iterator(dataSupplier.get(), rowFactory::create, true);
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableScan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableScan.java
new file mode 100644
index 0000000..2953053
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableScan.java
@@ -0,0 +1,282 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.exec;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Queue;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.cluster.ClusterTopologyException;
+import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTopologyFuture;
+import org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtLocalPartition;
+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.cache.mvcc.MvccSnapshot;
+import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
+import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler.RowFactory;
+import org.apache.ignite.internal.processors.query.calcite.schema.CacheTableDescriptor;
+import org.apache.ignite.internal.util.lang.GridCursor;
+import org.apache.ignite.internal.util.lang.GridIteratorAdapter;
+import org.apache.ignite.internal.util.typedef.F;
+import org.jetbrains.annotations.Nullable;
+
+/** */
+public class TableScan<Row> implements Iterable<Row>, AutoCloseable {
+    /** */
+    private final GridCacheContext<?, ?> cctx;
+
+    /** */
+    private final Predicate<Row> filters;
+
+    /** */
+    private final ExecutionContext<Row> ectx;
+
+    /** */
+    private final CacheTableDescriptor desc;
+
+    /** */
+    private final RowFactory<Row> factory;
+
+    /** */
+    private final AffinityTopologyVersion topVer;
+
+    /** */
+    private final int[] parts;
+
+    /** */
+    private final MvccSnapshot mvccSnapshot;
+
+    /** */
+    private volatile List<GridDhtLocalPartition> reserved;
+
+    /** */
+    private final Function<Row, Row> rowTransformer;
+
+    /** Participating colunms. */
+    private final ImmutableBitSet requiredColunms;
+
+    /** */
+    public TableScan(
+        ExecutionContext<Row> ectx,
+        CacheTableDescriptor desc,
+        int[] parts,
+        Predicate<Row> filters,
+        Function<Row, Row> rowTransformer,
+        @Nullable ImmutableBitSet requiredColunms
+    ) {
+        this.ectx = ectx;
+        cctx = desc.cacheContext();
+        this.desc = desc;
+        this.parts = parts;
+        this.filters = filters;
+        this.rowTransformer = rowTransformer;
+        this.requiredColunms = requiredColunms;
+
+        RelDataType rowType = desc.rowType(this.ectx.getTypeFactory(), requiredColunms);
+
+        factory = this.ectx.rowHandler().factory(this.ectx.getTypeFactory(), rowType);
+        topVer = ectx.topologyVersion();
+        mvccSnapshot = ectx.mvccSnapshot();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Iterator<Row> iterator() {
+        reserve();
+        try {
+            return new IteratorImpl();
+        }
+        catch (Exception e) {
+            release();
+
+            throw e;
+        }
+    }
+
+    /** */
+    @Override public void close() {
+        release();
+    }
+
+    /** */
+    private synchronized void reserve() {
+        if (reserved != null)
+            return;
+
+        GridDhtPartitionTopology top = cctx.topology();
+        top.readLock();
+
+        GridDhtTopologyFuture topFut = top.topologyVersionFuture();
+
+        boolean done = topFut.isDone();
+
+        if (!done || !(topFut.topologyVersion().compareTo(topVer) >= 0
+            && cctx.shared().exchange().lastAffinityChangedTopologyVersion(topFut.initialVersion()).compareTo(topVer) <= 0)) {
+            top.readUnlock();
+
+            throw new ClusterTopologyException("Topology was changed. Please retry on stable topology.");
+        }
+
+        List<GridDhtLocalPartition> toReserve;
+        if (cctx.isReplicated()) {
+            int partsCnt = cctx.affinity().partitions();
+            toReserve = new ArrayList<>(partsCnt);
+            for (int i = 0; i < partsCnt; i++)
+                toReserve.add(top.localPartition(i));
+        }
+        else if (cctx.isPartitioned()) {
+            assert parts != null;
+
+            toReserve = new ArrayList<>(parts.length);
+            for (int i = 0; i < parts.length; i++)
+                toReserve.add(top.localPartition(parts[i]));
+        }
+        else {
+            assert cctx.isLocal();
+
+            toReserve = Collections.emptyList();
+        }
+
+        reserved = new ArrayList<>(toReserve.size());
+
+        try {
+            for (GridDhtLocalPartition part : toReserve) {
+                if (part == null || !part.reserve())
+                    throw new ClusterTopologyException("Failed to reserve partition for query execution. Retry on stable topology.");
+                else if (part.state() != GridDhtPartitionState.OWNING) {
+                    part.release();
+
+                    throw new ClusterTopologyException("Failed to reserve partition for query execution. Retry on stable topology.");
+                }
+
+                reserved.add(part);
+            }
+        }
+        catch (Exception e) {
+            release();
+
+            throw e;
+        }
+        finally {
+            top.readUnlock();
+        }
+    }
+
+    /** */
+    private synchronized void release() {
+        if (F.isEmpty(reserved))
+            return;
+
+        reserved.forEach(GridDhtLocalPartition::release);
+
+        reserved = null;
+    }
+
+    /**
+     * Table scan iterator.
+     */
+    private class IteratorImpl extends GridIteratorAdapter<Row> {
+        /** */
+        private final Queue<GridDhtLocalPartition> parts;
+
+        /** */
+        private GridCursor<? extends CacheDataRow> cur;
+
+        /** */
+        private Row next;
+
+        /** */
+        private IteratorImpl() {
+            assert reserved != null;
+
+            parts = new ArrayDeque<>(reserved);
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean hasNextX() throws IgniteCheckedException {
+            advance();
+
+            return next != null;
+        }
+
+        /** {@inheritDoc} */
+        @Override public Row nextX() throws IgniteCheckedException {
+            advance();
+
+            if (next == null)
+                throw new NoSuchElementException();
+
+            Row next = this.next;
+
+            this.next = null;
+
+            return next;
+        }
+
+        /** {@inheritDoc} */
+        @Override public void removeX() {
+            throw new UnsupportedOperationException("Remove is not supported.");
+        }
+
+        /** */
+        private void advance() throws IgniteCheckedException {
+            assert parts != null;
+
+            if (next != null)
+                return;
+
+            while (true) {
+                if (cur == null) {
+                    GridDhtLocalPartition part = parts.poll();
+                    if (part == null)
+                        break;
+
+                    cur = part.dataStore().cursor(cctx.cacheId(), mvccSnapshot);
+                }
+
+                if (cur.next()) {
+                    CacheDataRow row = cur.get();
+
+                    if (!desc.match(row))
+                        continue;
+
+                    Row r = desc.toRow(ectx, row, factory, requiredColunms);
+
+                    if (filters != null && !filters.test(r))
+                        continue;
+
+                    if (rowTransformer != null)
+                        r = rowTransformer.apply(r);
+
+                    next = r;
+                    break;
+                } else
+                    cur = null;
+            }
+        }
+    }
+}
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/stat/StatisticsType.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TreeIndex.java
similarity index 55%
copy from modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/stat/StatisticsType.java
copy to modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TreeIndex.java
index 38f3f67a..91cb912 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/stat/StatisticsType.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TreeIndex.java
@@ -14,31 +14,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.ignite.internal.processors.query.calcite.exec;
 
-package org.apache.ignite.internal.processors.query.stat;
-
-import org.jetbrains.annotations.Nullable;
+import org.apache.ignite.internal.cache.query.index.sorted.inline.IndexQueryContext;
+import org.apache.ignite.internal.util.lang.GridCursor;
 
 /**
- * Types of statistics width.
+ * Tree index interface.
+ *
+ * @param <R> Indexing row type.
  */
-public enum StatisticsType {
-    /** Statistics by some particular partition. */
-    PARTITION,
-
-    /** Statistics by some data node. */
-    LOCAL;
-
-    /** Enumerated values. */
-    private static final StatisticsType[] VALUES = values();
-
+public interface TreeIndex<R> {
     /**
-     * Efficiently gets enumerated value from its ordinal.
+     * Index lookup method.
      *
-     * @param ord Ordinal value.
-     * @return Enumerated value or {@code null} if ordinal out of range.
+     * @param lower Lower bound.
+     * @param upper Upper bound.
+     * @param qctx Index query context.
+     * @return Cursor over the rows within bounds.
      */
-    @Nullable public static StatisticsType fromOrdinal(int ord) {
-        return ord >= 0 && ord < VALUES.length ? VALUES[ord] : null;
-    }
+    public GridCursor<R> find(R lower, R upper, IndexQueryContext qctx);
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ddl/DdlCommandHandler.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ddl/DdlCommandHandler.java
new file mode 100644
index 0000000..1789eb1
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ddl/DdlCommandHandler.java
@@ -0,0 +1,403 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.exec.ddl;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.function.Supplier;
+import org.apache.calcite.schema.SchemaPlus;
+import org.apache.calcite.schema.Table;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.cache.QueryEntity;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.cache.GridCacheContextInfo;
+import org.apache.ignite.internal.processors.cache.GridCacheProcessor;
+import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
+import org.apache.ignite.internal.processors.query.GridQueryProcessor;
+import org.apache.ignite.internal.processors.query.GridQuerySchemaManager;
+import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
+import org.apache.ignite.internal.processors.query.IgniteSQLException;
+import org.apache.ignite.internal.processors.query.QueryEntityEx;
+import org.apache.ignite.internal.processors.query.QueryField;
+import org.apache.ignite.internal.processors.query.QueryUtils;
+import org.apache.ignite.internal.processors.query.calcite.prepare.ddl.AlterTableAddCommand;
+import org.apache.ignite.internal.processors.query.calcite.prepare.ddl.AlterTableDropCommand;
+import org.apache.ignite.internal.processors.query.calcite.prepare.ddl.ColumnDefinition;
+import org.apache.ignite.internal.processors.query.calcite.prepare.ddl.CreateTableCommand;
+import org.apache.ignite.internal.processors.query.calcite.prepare.ddl.DdlCommand;
+import org.apache.ignite.internal.processors.query.calcite.prepare.ddl.DropTableCommand;
+import org.apache.ignite.internal.processors.query.calcite.prepare.ddl.NativeCommandWrapper;
+import org.apache.ignite.internal.processors.query.calcite.prepare.ddl.TransactionCommand;
+import org.apache.ignite.internal.processors.query.calcite.schema.IgniteCacheTable;
+import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.apache.ignite.internal.processors.query.schema.SchemaOperationException;
+import org.apache.ignite.internal.processors.security.IgniteSecurity;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.lang.IgniteUuid;
+import org.apache.ignite.plugin.security.SecurityPermission;
+
+import static org.apache.ignite.internal.processors.query.QueryUtils.convert;
+import static org.apache.ignite.internal.processors.query.QueryUtils.isDdlOnSchemaSupported;
+
+/** */
+public class DdlCommandHandler {
+    /** */
+    private final Supplier<GridQueryProcessor> qryProcessorSupp;
+
+    /** */
+    private final GridCacheProcessor cacheProcessor;
+
+    /** */
+    private final IgniteSecurity security;
+
+    /** */
+    private final Supplier<SchemaPlus> schemaSupp;
+
+    /** */
+    private final NativeCommandHandler nativeCmdHnd;
+
+    /** */
+    private final GridQuerySchemaManager schemaMgr;
+
+    /** */
+    public DdlCommandHandler(Supplier<GridQueryProcessor> qryProcessorSupp, GridCacheProcessor cacheProcessor,
+        IgniteSecurity security, Supplier<SchemaPlus> schemaSupp) {
+        this.qryProcessorSupp = qryProcessorSupp;
+        this.cacheProcessor = cacheProcessor;
+        this.security = security;
+        this.schemaSupp = schemaSupp;
+        schemaMgr = new SchemaManager(schemaSupp);
+        nativeCmdHnd = new NativeCommandHandler(cacheProcessor.context().kernalContext(), schemaMgr);
+    }
+
+    /** */
+    public void handle(UUID qryId, DdlCommand cmd) throws IgniteCheckedException {
+        try {
+            if (cmd instanceof TransactionCommand)
+                return;
+
+            if (cmd instanceof CreateTableCommand)
+                handle0((CreateTableCommand)cmd);
+            else if (cmd instanceof DropTableCommand)
+                handle0((DropTableCommand)cmd);
+            else if (cmd instanceof AlterTableAddCommand)
+                handle0((AlterTableAddCommand)cmd);
+            else if (cmd instanceof AlterTableDropCommand)
+                handle0((AlterTableDropCommand)cmd);
+            else if (cmd instanceof NativeCommandWrapper)
+                nativeCmdHnd.handle(qryId, (NativeCommandWrapper)cmd);
+            else {
+                throw new IgniteSQLException("Unsupported DDL operation [" +
+                    "cmdName=" + (cmd == null ? null : cmd.getClass().getSimpleName()) + "; " +
+                    "cmd=\"" + cmd + "\"]", IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+            }
+        }
+        catch (SchemaOperationException e) {
+            throw convert(e);
+        }
+    }
+
+    /** */
+    private void handle0(CreateTableCommand cmd) throws IgniteCheckedException {
+        security.authorize(cmd.cacheName(), SecurityPermission.CACHE_CREATE);
+
+        isDdlOnSchemaSupported(cmd.schemaName());
+
+        if (schemaSupp.get().getSubSchema(cmd.schemaName()).getTable(cmd.tableName()) != null) {
+            if (cmd.ifNotExists())
+                return;
+
+            throw new SchemaOperationException(SchemaOperationException.CODE_TABLE_EXISTS, cmd.tableName());
+        }
+
+        CacheConfiguration<?, ?> ccfg = new CacheConfiguration<>(cmd.tableName());
+
+        QueryEntity e = toQueryEntity(cmd);
+
+        ccfg.setQueryEntities(Collections.singleton(e));
+        ccfg.setSqlSchema(cmd.schemaName());
+
+        SchemaOperationException err =
+            QueryUtils.checkQueryEntityConflicts(ccfg, cacheProcessor.cacheDescriptors().values());
+
+        if (err != null)
+            throw convert(err);
+
+        if (!F.isEmpty(cmd.cacheName()) && cacheProcessor.cacheDescriptor(cmd.cacheName()) != null) {
+            qryProcessorSupp.get().dynamicAddQueryEntity(
+                cmd.cacheName(),
+                cmd.schemaName(),
+                e,
+                null,
+                true
+            ).get();
+        }
+        else {
+            qryProcessorSupp.get().dynamicTableCreate(
+                cmd.schemaName(),
+                e,
+                cmd.templateName(),
+                cmd.cacheName(),
+                cmd.cacheGroup(),
+                cmd.dataRegionName(),
+                cmd.affinityKey(),
+                cmd.atomicityMode(),
+                cmd.writeSynchronizationMode(),
+                cmd.backups(),
+                cmd.ifNotExists(),
+                cmd.encrypted(),
+                null
+            );
+        }
+    }
+
+    /** */
+    private void handle0(DropTableCommand cmd) throws IgniteCheckedException {
+        isDdlOnSchemaSupported(cmd.schemaName());
+
+        Table tbl = schemaSupp.get().getSubSchema(cmd.schemaName()).getTable(cmd.tableName());
+
+        if (tbl == null) {
+            if (!cmd.ifExists())
+                throw new SchemaOperationException(SchemaOperationException.CODE_TABLE_NOT_FOUND, cmd.tableName());
+
+            return;
+        }
+
+        assert tbl instanceof IgniteCacheTable : tbl;
+
+        String cacheName = ((IgniteCacheTable)tbl).descriptor().cacheInfo().name();
+
+        security.authorize(cacheName, SecurityPermission.CACHE_DESTROY);
+
+        qryProcessorSupp.get().dynamicTableDrop(cacheName, cmd.tableName(), cmd.ifExists());
+    }
+
+    /** */
+    private void handle0(AlterTableAddCommand cmd) throws IgniteCheckedException {
+        isDdlOnSchemaSupported(cmd.schemaName());
+
+        GridQueryTypeDescriptor typeDesc = schemaMgr.typeDescriptorForTable(cmd.schemaName(), cmd.tableName());
+
+        if (typeDesc == null) {
+            if (!cmd.ifTableExists())
+                throw new SchemaOperationException(SchemaOperationException.CODE_TABLE_NOT_FOUND, cmd.tableName());
+        }
+        else {
+            if (QueryUtils.isSqlType(typeDesc.valueClass())) {
+                throw new SchemaOperationException("Cannot add column(s) because table was created " +
+                    "with WRAP_VALUE=false option.");
+            }
+
+            List<QueryField> cols = new ArrayList<>(cmd.columns().size());
+
+            boolean allFieldsNullable = true;
+
+            for (ColumnDefinition col : cmd.columns()) {
+                if (typeDesc.fields().containsKey(col.name())) {
+                    if (!cmd.ifColumnNotExists())
+                        throw new SchemaOperationException(SchemaOperationException.CODE_COLUMN_EXISTS, col.name());
+                    else
+                        continue;
+                }
+
+                Type javaType = Commons.typeFactory().getResultClass(col.type());
+
+                String typeName = javaType instanceof Class ? ((Class<?>)javaType).getName() : javaType.getTypeName();
+
+                Integer precession = col.precision();
+                Integer scale = col.scale();
+
+                QueryField field = new QueryField(col.name(), typeName,
+                    col.type().isNullable(), col.defaultValue(),
+                    precession == null ? -1 : precession, scale == null ? -1 : scale);
+
+                cols.add(field);
+
+                allFieldsNullable &= field.isNullable();
+            }
+
+            if (!F.isEmpty(cols)) {
+                GridCacheContextInfo<?, ?> ctxInfo = schemaMgr.cacheInfoForTable(cmd.schemaName(), cmd.tableName());
+
+                assert ctxInfo != null;
+
+                if (!allFieldsNullable)
+                    QueryUtils.checkNotNullAllowed(ctxInfo.config());
+
+                qryProcessorSupp.get().dynamicColumnAdd(ctxInfo.name(), cmd.schemaName(),
+                    typeDesc.tableName(), cols, cmd.ifTableExists(), cmd.ifColumnNotExists()).get();
+            }
+        }
+    }
+
+    /** */
+    private void handle0(AlterTableDropCommand cmd) throws IgniteCheckedException {
+        isDdlOnSchemaSupported(cmd.schemaName());
+
+        GridQueryTypeDescriptor typeDesc = schemaMgr.typeDescriptorForTable(cmd.schemaName(), cmd.tableName());
+
+        if (typeDesc == null) {
+            if (!cmd.ifTableExists())
+                throw new SchemaOperationException(SchemaOperationException.CODE_TABLE_NOT_FOUND, cmd.tableName());
+        }
+        else {
+            GridCacheContextInfo<?, ?> ctxInfo = schemaMgr.cacheInfoForTable(cmd.schemaName(), cmd.tableName());
+
+            GridCacheContext<?, ?> cctx = ctxInfo.cacheContext();
+
+            assert cctx != null;
+
+            if (cctx.mvccEnabled()) {
+                throw new IgniteSQLException("Cannot drop column(s) with enabled MVCC. " +
+                    "Operation is unsupported at the moment.", IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+            }
+
+            if (QueryUtils.isSqlType(typeDesc.valueClass())) {
+                throw new SchemaOperationException("Cannot drop column(s) because table was created " +
+                    "with WRAP_VALUE=false option.");
+            }
+
+            List<String> cols = new ArrayList<>(cmd.columns().size());
+
+            for (String colName : cmd.columns()) {
+                if (!typeDesc.fields().containsKey(colName)) {
+                    if (!cmd.ifColumnExists())
+                        throw new SchemaOperationException(SchemaOperationException.CODE_COLUMN_NOT_FOUND, colName);
+                    else
+                        continue;
+                }
+
+                SchemaOperationException err = QueryUtils.validateDropColumn(typeDesc, colName);
+
+                if (err != null)
+                    throw err;
+
+                cols.add(colName);
+            }
+
+            if (!F.isEmpty(cols)) {
+                qryProcessorSupp.get().dynamicColumnRemove(ctxInfo.name(), cmd.schemaName(),
+                    typeDesc.tableName(), cols, cmd.ifTableExists(), cmd.ifColumnExists()).get();
+            }
+        }
+    }
+
+    /** */
+    private QueryEntity toQueryEntity(CreateTableCommand cmd) {
+        QueryEntity res = new QueryEntity();
+
+        res.setTableName(cmd.tableName());
+
+        Set<String> notNullFields = null;
+
+        HashMap<String, Object> dfltValues = new HashMap<>();
+
+        Map<String, Integer> precision = new HashMap<>();
+        Map<String, Integer> scale = new HashMap<>();
+
+        IgniteTypeFactory tf = Commons.typeFactory();
+
+        for (ColumnDefinition col : cmd.columns()) {
+            String name = col.name();
+
+            Type javaType = tf.getResultClass(col.type());
+
+            String typeName = javaType instanceof Class ? ((Class<?>)javaType).getName() : javaType.getTypeName();
+
+            res.addQueryField(name, typeName, null);
+
+            if (!col.nullable()) {
+                if (notNullFields == null)
+                    notNullFields = new HashSet<>();
+
+                notNullFields.add(name);
+            }
+
+            if (col.defaultValue() != null)
+                dfltValues.put(name, col.defaultValue());
+
+            if (col.precision() != null)
+                precision.put(name, col.precision());
+
+            if (col.scale() != null)
+                scale.put(name, col.scale());
+        }
+
+        if (!F.isEmpty(dfltValues))
+            res.setDefaultFieldValues(dfltValues);
+
+        if (!F.isEmpty(precision))
+            res.setFieldsPrecision(precision);
+
+        if (!F.isEmpty(scale))
+            res.setFieldsScale(scale);
+
+        String valTypeName = QueryUtils.createTableValueTypeName(cmd.schemaName(), cmd.tableName());
+
+        String keyTypeName;
+        if ((!F.isEmpty(cmd.primaryKeyColumns()) && cmd.primaryKeyColumns().size() > 1) || !F.isEmpty(cmd.keyTypeName())) {
+            keyTypeName = cmd.keyTypeName();
+
+            if (F.isEmpty(keyTypeName))
+                keyTypeName = QueryUtils.createTableKeyTypeName(valTypeName);
+
+            if (!F.isEmpty(cmd.primaryKeyColumns())) {
+                res.setKeyFields(new LinkedHashSet<>(cmd.primaryKeyColumns()));
+
+                res = new QueryEntityEx(res).setPreserveKeysOrder(true);
+            }
+        }
+        else if (!F.isEmpty(cmd.primaryKeyColumns()) && cmd.primaryKeyColumns().size() == 1) {
+            String pkFieldName = cmd.primaryKeyColumns().get(0);
+
+            keyTypeName = res.getFields().get(pkFieldName);
+
+            res.setKeyFieldName(pkFieldName);
+        }
+        else {
+            // if pk is not explicitly set, we create it ourselves
+            keyTypeName = IgniteUuid.class.getName();
+
+            res = new QueryEntityEx(res).implicitPk(true);
+        }
+
+        res.setValueType(F.isEmpty(cmd.valueTypeName()) ? valTypeName : cmd.valueTypeName());
+        res.setKeyType(keyTypeName);
+
+        if (!F.isEmpty(notNullFields)) {
+            QueryEntityEx res0 = new QueryEntityEx(res);
+
+            res0.setNotNullFields(notNullFields);
+
+            res = res0;
+        }
+
+        return res;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ddl/NativeCommandHandler.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ddl/NativeCommandHandler.java
new file mode 100644
index 0000000..3cca9d6
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ddl/NativeCommandHandler.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.exec.ddl;
+
+import java.util.List;
+import java.util.UUID;
+import org.apache.ignite.cache.query.FieldsQueryCursor;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.processors.query.GridQuerySchemaManager;
+import org.apache.ignite.internal.processors.query.calcite.prepare.ddl.NativeCommandWrapper;
+import org.apache.ignite.internal.sql.SqlCommandProcessor;
+
+/**
+ * Handler for Ignite native (core module) commands.
+ */
+public class NativeCommandHandler {
+    /** Command processor. */
+    private final SqlCommandProcessor proc;
+
+    /**
+     * @param ctx Context.
+     */
+    public NativeCommandHandler(GridKernalContext ctx, GridQuerySchemaManager schemaMgr) {
+        proc = new SqlCommandProcessor(ctx, schemaMgr);
+    }
+
+    /**
+     * @param qryId Query id.
+     * @param cmd   Native command.
+     */
+    public FieldsQueryCursor<List<?>> handle(UUID qryId, NativeCommandWrapper cmd) {
+        assert proc.isCommandSupported(cmd.command()) : cmd.command();
+
+        return proc.runCommand(cmd.command());
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ddl/SchemaManager.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ddl/SchemaManager.java
new file mode 100644
index 0000000..9e2b243
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ddl/SchemaManager.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.exec.ddl;
+
+import java.util.function.Supplier;
+import org.apache.calcite.schema.SchemaPlus;
+import org.apache.calcite.schema.Table;
+import org.apache.ignite.internal.processors.cache.GridCacheContextInfo;
+import org.apache.ignite.internal.processors.query.GridQuerySchemaManager;
+import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
+import org.apache.ignite.internal.processors.query.calcite.schema.IgniteCacheTable;
+import org.apache.ignite.internal.processors.query.calcite.schema.IgniteTable;
+
+/**
+ * Schema manager.
+ */
+class SchemaManager implements GridQuerySchemaManager {
+    /** Schema holder. */
+    private final Supplier<SchemaPlus> schemaSupp;
+
+    /**
+     * @param schemaSupp Schema supplier.
+     */
+    SchemaManager(Supplier<SchemaPlus> schemaSupp) {
+        this.schemaSupp = schemaSupp;
+    }
+
+    /** {@inheritDoc} */
+    @Override public GridQueryTypeDescriptor typeDescriptorForTable(String schemaName, String tableName) {
+        SchemaPlus schema = schemaSupp.get().getSubSchema(schemaName);
+
+        if (schema == null)
+            return null;
+
+        IgniteCacheTable tbl = (IgniteCacheTable)schema.getTable(tableName);
+
+        return tbl == null ? null : tbl.descriptor().typeDescription();
+    }
+
+    /** {@inheritDoc} */
+    @Override public GridQueryTypeDescriptor typeDescriptorForIndex(String schemaName, String idxName) {
+        SchemaPlus schema = schemaSupp.get().getSubSchema(schemaName);
+
+        if (schema == null)
+            return null;
+
+        for (String tableName : schema.getTableNames()) {
+            Table tbl = schema.getTable(tableName);
+
+            if (tbl instanceof IgniteCacheTable && ((IgniteTable)tbl).getIndex(idxName) != null)
+                return ((IgniteCacheTable)tbl).descriptor().typeDescription();
+        }
+
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public <K, V> GridCacheContextInfo<K, V> cacheInfoForTable(String schemaName, String tableName) {
+        SchemaPlus schema = schemaSupp.get().getSubSchema(schemaName);
+
+        if (schema == null)
+            return null;
+
+        Table tbl = schema.getTable(tableName);
+
+        IgniteCacheTable cachetbl = tbl instanceof IgniteCacheTable ? (IgniteCacheTable)tbl : null;
+
+        return cachetbl == null ? null : (GridCacheContextInfo<K, V>)cachetbl.descriptor().cacheInfo();
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/BiScalar.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/BiScalar.java
new file mode 100644
index 0000000..f7b92cb
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/BiScalar.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.exec.exp;
+
+import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
+
+/** */
+@FunctionalInterface
+public interface BiScalar extends Scalar {
+    /** Multi input and single output. */
+    void execute(ExecutionContext ctx, Object in1, Object in2, Object out);
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/CallImplementor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/CallImplementor.java
new file mode 100644
index 0000000..6328938
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/CallImplementor.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.exec.exp;
+
+import org.apache.calcite.linq4j.tree.Expression;
+import org.apache.calcite.rex.RexCall;
+
+/**
+ * Implements a call via given translator.
+ *
+ * @see org.apache.calcite.schema.ScalarFunction
+ * @see org.apache.calcite.schema.TableFunction
+ * @see RexImpTable
+ */
+public interface CallImplementor {
+    /**
+     * Implements a call.
+     *
+     * @param translator Translator for the call
+     * @param call Call that should be implemented
+     * @param nullAs The desired mode of {@code null} translation
+     * @return Translated call
+     */
+    Expression implement(
+        RexToLixTranslator translator,
+        RexCall call,
+        RexImpTable.NullAs nullAs);
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ConverterUtils.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ConverterUtils.java
new file mode 100644
index 0000000..671fb42
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ConverterUtils.java
@@ -0,0 +1,458 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ignite.internal.processors.query.calcite.exec.exp;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.apache.calcite.adapter.enumerable.RexImpTable;
+import org.apache.calcite.linq4j.tree.ConstantExpression;
+import org.apache.calcite.linq4j.tree.ConstantUntypedNull;
+import org.apache.calcite.linq4j.tree.Expression;
+import org.apache.calcite.linq4j.tree.ExpressionType;
+import org.apache.calcite.linq4j.tree.Expressions;
+import org.apache.calcite.linq4j.tree.Primitive;
+import org.apache.calcite.linq4j.tree.Types;
+import org.apache.calcite.linq4j.tree.UnaryExpression;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.runtime.SqlFunctions;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.util.BuiltInMethod;
+import org.apache.calcite.util.Util;
+
+/** */
+public class ConverterUtils {
+    /** */
+    private ConverterUtils() {
+    }
+
+    /**
+     * In Calcite, {@code java.sql.Date} and {@code java.sql.Time} are stored as {@code Integer} type, {@code
+     * java.sql.Timestamp} is stored as {@code Long} type.
+     */
+    static Expression toInternal(Expression operand, Type targetType) {
+        return toInternal(operand, operand.getType(), targetType);
+    }
+
+    /** */
+    private static Expression toInternal(Expression operand,
+        Type fromType, Type targetType) {
+        if (fromType == java.sql.Date.class) {
+            if (targetType == int.class)
+                return Expressions.call(BuiltInMethod.DATE_TO_INT.method, operand);
+            else if (targetType == Integer.class)
+                return Expressions.call(BuiltInMethod.DATE_TO_INT_OPTIONAL.method, operand);
+        }
+        else if (fromType == java.sql.Time.class) {
+            if (targetType == int.class)
+                return Expressions.call(BuiltInMethod.TIME_TO_INT.method, operand);
+            else if (targetType == Integer.class)
+                return Expressions.call(BuiltInMethod.TIME_TO_INT_OPTIONAL.method, operand);
+        }
+        else if (fromType == java.sql.Timestamp.class) {
+            if (targetType == long.class)
+                return Expressions.call(BuiltInMethod.TIMESTAMP_TO_LONG.method, operand);
+            else if (targetType == Long.class)
+                return Expressions.call(BuiltInMethod.TIMESTAMP_TO_LONG_OPTIONAL.method, operand);
+        }
... 648473 lines suppressed ...