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 2021/06/22 11:00:38 UTC

[ignite] branch sql-calcite updated: IGNITE-13549 CREATE INDEX/DROP INDEX commands - Fixes #9176.

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

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


The following commit(s) were added to refs/heads/sql-calcite by this push:
     new 22a4aa1  IGNITE-13549 CREATE INDEX/DROP INDEX commands - Fixes #9176.
22a4aa1 is described below

commit 22a4aa1fe94d74f46cdef92226ca8aa42994d0cc
Author: Aleksey Plekhanov <pl...@gmail.com>
AuthorDate: Tue Jun 22 13:55:22 2021 +0300

    IGNITE-13549 CREATE INDEX/DROP INDEX commands - Fixes #9176.
    
    Signed-off-by: Aleksey Plekhanov <pl...@gmail.com>
---
 modules/calcite/src/main/codegen/config.fmpp       |  14 +-
 .../src/main/codegen/includes/parserImpls.ftl      |  82 +++++
 .../query/calcite/exec/ExecutionServiceImpl.java   |  19 +-
 .../query/calcite/exec/ddl/DdlCommandHandler.java  |  12 +-
 .../calcite/exec/ddl/NativeCommandHandler.java     | 116 +++++++
 .../prepare/ddl/DdlSqlToCommandConverter.java      |  51 +--
 .../calcite/prepare/ddl/NativeCommandWrapper.java  |  39 +++
 .../prepare/ddl/SqlToNativeCommandConverter.java   | 117 +++++++
 .../query/calcite/sql/IgniteSqlCreateIndex.java    | 162 ++++++++++
 .../query/calcite/sql/IgniteSqlDropIndex.java      |  76 +++++
 .../query/calcite/util/IgniteResource.java         |   3 +
 .../processors/query/calcite/util/PlanUtils.java   |  71 +++++
 .../integration/AbstractDdlIntegrationTest.java    |  88 +++++
 .../integration/IndexDdlIntegrationTest.java       | 203 ++++++++++++
 .../integration/TableDdlIntegrationTest.java       |  69 +---
 .../query/calcite/sql/SqlDdlParserTest.java        | 144 +++++++++
 .../ignite/testsuites/IntegrationTestSuite.java    |   2 +
 .../processors/odbc/jdbc/JdbcRequestHandler.java   |   6 +-
 .../processors/query/GridQueryProcessor.java       |   8 +-
 .../processors/query/GridQuerySchemaManager.java   |  47 +++
 .../ignite/internal/sql/SqlCommandProcessor.java   | 355 +++++++++++++++++++++
 .../sql/command/SqlCreateIndexCommand.java         |  36 +++
 .../internal/sql/command/SqlDropIndexCommand.java  |  17 +
 .../processors/query/h2/CommandProcessor.java      | 309 +++---------------
 .../processors/query/h2/IgniteH2Indexing.java      |   2 +-
 .../internal/processors/query/h2/QueryParser.java  |  44 +--
 .../processors/query/h2/SchemaManager.java         |  24 +-
 27 files changed, 1684 insertions(+), 432 deletions(-)

diff --git a/modules/calcite/src/main/codegen/config.fmpp b/modules/calcite/src/main/codegen/config.fmpp
index df0c7a8..f40d158 100644
--- a/modules/calcite/src/main/codegen/config.fmpp
+++ b/modules/calcite/src/main/codegen/config.fmpp
@@ -31,8 +31,7 @@ data: {
       "org.apache.calcite.sql.SqlDrop",
       "org.apache.calcite.sql.SqlLiteral",
       "org.apache.calcite.schema.ColumnStrategy",
-      "org.apache.ignite.internal.processors.query.calcite.sql.IgniteSqlCreateTable",
-      "org.apache.ignite.internal.processors.query.calcite.sql.IgniteSqlCreateTableOptionEnum",
+      "org.apache.ignite.internal.processors.query.calcite.util.IgniteResource",
       "org.apache.calcite.sql.ddl.SqlDdlNodes",
     ]
 
@@ -52,6 +51,9 @@ data: {
 #     "KEY_TYPE" // already presented in Calcite
       "VALUE_TYPE"
       "ENCRYPTED"
+      "INDEX"
+      "PARALLEL"
+      "INLINE_SIZE"
     ]
 
     # List of non-reserved keywords to add;
@@ -69,6 +71,8 @@ data: {
 #     "KEY_TYPE" // already presented in Calcite
       "VALUE_TYPE"
       "ENCRYPTED"
+      "PARALLEL"
+      "INLINE_SIZE"
 
       # The following keywords are reserved in core Calcite,
       # are reserved in some version of SQL,
@@ -584,14 +588,16 @@ data: {
     # Each must accept arguments "(SqlParserPos pos, boolean replace)".
     # Example: "SqlCreateForeignSchema".
     createStatementParserMethods: [
-      "SqlCreateTable"
+      "SqlCreateTable",
+      "SqlCreateIndex"
     ]
 
     # List of methods for parsing extensions to "DROP" calls.
     # Each must accept arguments "(SqlParserPos pos)".
     # Example: "SqlDropSchema".
     dropStatementParserMethods: [
-      "SqlDropTable"
+      "SqlDropTable",
+      "SqlDropIndex"
     ]
 
     # List of methods for parsing extensions to "DROP" calls.
diff --git a/modules/calcite/src/main/codegen/includes/parserImpls.ftl b/modules/calcite/src/main/codegen/includes/parserImpls.ftl
index 7b43a6d..aad7cfb 100644
--- a/modules/calcite/src/main/codegen/includes/parserImpls.ftl
+++ b/modules/calcite/src/main/codegen/includes/parserImpls.ftl
@@ -164,6 +164,77 @@ SqlCreate SqlCreateTable(Span s, boolean replace) :
     }
 }
 
+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() :
 {
 }
@@ -184,6 +255,17 @@ SqlDrop SqlDropTable(Span s, boolean replace) :
     }
 }
 
+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;
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionServiceImpl.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionServiceImpl.java
index c161e89..ac2ed48 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionServiceImpl.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionServiceImpl.java
@@ -27,7 +27,6 @@ import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
-
 import com.google.common.collect.ImmutableList;
 import org.apache.calcite.plan.Context;
 import org.apache.calcite.plan.Contexts;
@@ -39,6 +38,7 @@ import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.sql.SqlDdl;
 import org.apache.calcite.sql.SqlExplain;
 import org.apache.calcite.sql.SqlExplainLevel;
+import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlNodeList;
 import org.apache.calcite.sql.parser.SqlParseException;
@@ -545,6 +545,9 @@ public class ExecutionServiceImpl<Row> extends AbstractService implements Execut
 
         ctx.planner().reset();
 
+        if (SqlKind.DDL.contains(sqlNode.getKind()))
+            return prepareDdl(sqlNode, ctx);
+
         switch (sqlNode.getKind()) {
             case SELECT:
             case ORDER_BY:
@@ -563,10 +566,6 @@ public class ExecutionServiceImpl<Row> extends AbstractService implements Execut
             case EXPLAIN:
                 return prepareExplain(sqlNode, ctx);
 
-            case CREATE_TABLE:
-            case DROP_TABLE:
-                return prepareDdl(sqlNode, ctx);
-
             default:
                 throw new IgniteSQLException("Unsupported operation [" +
                     "sqlNodeKind=" + sqlNode.getKind() + "; " +
@@ -615,9 +614,7 @@ public class ExecutionServiceImpl<Row> extends AbstractService implements Execut
     private QueryPlan prepareDdl(SqlNode sqlNode, PlanningContext ctx) {
         assert sqlNode instanceof SqlDdl : sqlNode == null ? "null" : sqlNode.getClass().getName();
 
-        SqlDdl ddlNode = (SqlDdl)sqlNode;
-
-        return new DdlPlan(ddlConverter.convert(ddlNode, ctx));
+        return new DdlPlan(ddlConverter.convert((SqlDdl)sqlNode, ctx));
     }
 
     /** */
@@ -658,7 +655,7 @@ public class ExecutionServiceImpl<Row> extends AbstractService implements Execut
             case EXPLAIN:
                 return executeExplain((ExplainPlan)plan, pctx);
             case DDL:
-                return executeDdl((DdlPlan)plan, pctx);
+                return executeDdl(qryId, (DdlPlan)plan, pctx);
 
             default:
                 throw new AssertionError("Unexpected plan type: " + plan);
@@ -666,9 +663,9 @@ public class ExecutionServiceImpl<Row> extends AbstractService implements Execut
     }
 
     /** */
-    private FieldsQueryCursor<List<?>> executeDdl(DdlPlan plan, PlanningContext pctx) {
+    private FieldsQueryCursor<List<?>> executeDdl(UUID qryId, DdlPlan plan, PlanningContext pctx) {
         try {
-            ddlCmdHnd.handle(pctx, plan.command());
+            ddlCmdHnd.handle(qryId, plan.command(), pctx);
         }
         catch (IgniteCheckedException e) {
             throw new IgniteSQLException("Failed to execute DDL statement [stmt=" + pctx.query() +
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
index befb58f..405be78 100644
--- 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
@@ -24,8 +24,8 @@ import java.util.HashSet;
 import java.util.LinkedHashSet;
 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;
@@ -42,6 +42,7 @@ import org.apache.ignite.internal.processors.query.calcite.prepare.ddl.ColumnDef
 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.schema.IgniteTable;
 import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
 import org.apache.ignite.internal.processors.query.schema.SchemaOperationException;
@@ -68,22 +69,29 @@ public class DdlCommandHandler {
     private final Supplier<SchemaPlus> schemaSupp;
 
     /** */
+    private final NativeCommandHandler nativeCmdHandler;
+
+    /** */
     public DdlCommandHandler(Supplier<GridQueryProcessor> qryProcessorSupp, GridCacheProcessor cacheProcessor,
         IgniteSecurity security, Supplier<SchemaPlus> schemaSupp) {
         this.qryProcessorSupp = qryProcessorSupp;
         this.cacheProcessor = cacheProcessor;
         this.security = security;
         this.schemaSupp = schemaSupp;
+        nativeCmdHandler = new NativeCommandHandler(cacheProcessor.context().kernalContext(), schemaSupp);
     }
 
     /** */
-    public void handle(PlanningContext pctx, DdlCommand cmd) throws IgniteCheckedException {
+    public void handle(UUID qryId, DdlCommand cmd, PlanningContext pctx) throws IgniteCheckedException {
         if (cmd instanceof CreateTableCommand)
             handle0(pctx, (CreateTableCommand)cmd);
 
         else if (cmd instanceof DropTableCommand)
             handle0(pctx, (DropTableCommand)cmd);
 
+        else if (cmd instanceof NativeCommandWrapper)
+            nativeCmdHandler.handle(qryId, (NativeCommandWrapper)cmd, pctx);
+
         else {
             throw new IgniteSQLException("Unsupported DDL operation [" +
                 "cmdName=" + (cmd == null ? null : cmd.getClass().getSimpleName()) + "; " +
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..0fc2f1c
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ddl/NativeCommandHandler.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.exec.ddl;
+
+import java.util.List;
+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.cache.query.FieldsQueryCursor;
+import org.apache.ignite.internal.GridKernalContext;
+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.SqlClientContext;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PlanningContext;
+import org.apache.ignite.internal.processors.query.calcite.prepare.ddl.NativeCommandWrapper;
+import org.apache.ignite.internal.processors.query.calcite.schema.IgniteTable;
+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, Supplier<SchemaPlus> schemaSupp) {
+        proc = new SqlCommandProcessor(ctx, new SchemaManager(schemaSupp));
+    }
+
+    /**
+     * @param qryId Query id.
+     * @param cmd   Native command.
+     * @param pctx  Planning context.
+     */
+    public FieldsQueryCursor<List<?>> handle(UUID qryId, NativeCommandWrapper cmd, PlanningContext pctx) {
+        assert proc.isCommandSupported(cmd.command()) : cmd.command();
+
+        return proc.runCommand(pctx.query(), cmd.command(), pctx.unwrap(SqlClientContext.class));
+    }
+
+    /**
+     * Schema manager.
+     */
+    private static class SchemaManager implements GridQuerySchemaManager {
+        /** Schema holder. */
+        private final Supplier<SchemaPlus> schemaSupp;
+
+        /**
+         * @param schemaSupp Schema supplier.
+         */
+        private 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;
+
+            IgniteTable tbl = (IgniteTable)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 IgniteTable && ((IgniteTable)tbl).getIndex(idxName) != null)
+                    return ((IgniteTable)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;
+
+            IgniteTable tbl = (IgniteTable)schema.getTable(tableName);
+
+            return tbl == null ? null : (GridCacheContextInfo<K, V>)tbl.descriptor().cacheInfo();
+        }
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/ddl/DdlSqlToCommandConverter.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/ddl/DdlSqlToCommandConverter.java
index f0c43ba..b5be013 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/ddl/DdlSqlToCommandConverter.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/ddl/DdlSqlToCommandConverter.java
@@ -28,7 +28,6 @@ import java.util.function.BiFunction;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
-
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.sql.SqlDdl;
 import org.apache.calcite.sql.SqlIdentifier;
@@ -63,6 +62,8 @@ import static org.apache.ignite.internal.processors.query.calcite.sql.IgniteSqlC
 import static org.apache.ignite.internal.processors.query.calcite.sql.IgniteSqlCreateTableOptionEnum.TEMPLATE;
 import static org.apache.ignite.internal.processors.query.calcite.sql.IgniteSqlCreateTableOptionEnum.VALUE_TYPE;
 import static org.apache.ignite.internal.processors.query.calcite.sql.IgniteSqlCreateTableOptionEnum.WRITE_SYNCHRONIZATION_MODE;
+import static org.apache.ignite.internal.processors.query.calcite.util.PlanUtils.deriveObjectName;
+import static org.apache.ignite.internal.processors.query.calcite.util.PlanUtils.deriveSchemaName;
 
 /** */
 public class DdlSqlToCommandConverter {
@@ -124,6 +125,9 @@ public class DdlSqlToCommandConverter {
         if (ddlNode instanceof SqlDropTable)
             return convertDropTable((SqlDropTable)ddlNode, ctx);
 
+        if (SqlToNativeCommandConverter.isSupported(ddlNode))
+            return SqlToNativeCommandConverter.convert(ddlNode, ctx);
+
         throw new IgniteSQLException("Unsupported operation [" +
             "sqlNodeKind=" + ddlNode.getKind() + "; " +
             "querySql=\"" + ctx.query() + "\"]", IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
@@ -222,51 +226,6 @@ public class DdlSqlToCommandConverter {
         return dropTblCmd;
     }
 
-    /** Derives a schema name from the compound identifier. */
-    private String deriveSchemaName(SqlIdentifier id, PlanningContext ctx) {
-        String schemaName;
-        if (id.isSimple())
-            schemaName = ctx.schemaName();
-        else {
-            SqlIdentifier schemaId = id.skipLast(1);
-
-            if (!schemaId.isSimple()) {
-                throw new IgniteSQLException("Unexpected value of schemaName [" +
-                    "expected a simple identifier, but was " + schemaId + "; " +
-                    "querySql=\"" + ctx.query() + "\"]", IgniteQueryErrorCode.PARSING);
-            }
-
-            schemaName = schemaId.getSimple();
-        }
-
-        ensureSchemaExists(ctx, schemaName);
-
-        return schemaName;
-    }
-
-    /** Derives an object(a table, an index, etc) name from the compound identifier. */
-    private String deriveObjectName(SqlIdentifier id, PlanningContext ctx, String objDesc) {
-        if (id.isSimple())
-            return id.getSimple();
-
-        SqlIdentifier objId = id.getComponent(id.skipLast(1).names.size());
-
-        if (!objId.isSimple()) {
-            throw new IgniteSQLException("Unexpected value of " + objDesc + " [" +
-                "expected a simple identifier, but was " + objId + "; " +
-                "querySql=\"" + ctx.query() + "\"]", IgniteQueryErrorCode.PARSING);
-        }
-
-        return objId.getSimple();
-    }
-
-    /** */
-    private void ensureSchemaExists(PlanningContext ctx, String schemaName) {
-        if (ctx.catalogReader().getRootSchema().getSubSchema(schemaName, true) == null)
-            throw new IgniteSQLException("Schema with name " + schemaName + " not found",
-                IgniteQueryErrorCode.SCHEMA_NOT_FOUND);
-    }
-
     /**
      * Short cut for validating that option value is a simple identifier.
      *
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/ddl/NativeCommandWrapper.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/ddl/NativeCommandWrapper.java
new file mode 100644
index 0000000..85e8ef8
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/ddl/NativeCommandWrapper.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.prepare.ddl;
+
+import java.util.Objects;
+import org.apache.ignite.internal.sql.command.SqlCommand;
+
+/**
+ * Wrapper for Ignite core SqlCommand.
+ */
+public class NativeCommandWrapper implements DdlCommand {
+    /** */
+    private final SqlCommand cmd;
+
+    /** */
+    public NativeCommandWrapper(SqlCommand cmd) {
+        this.cmd = Objects.requireNonNull(cmd);
+    }
+
+    /** */
+    public SqlCommand command() {
+        return cmd;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/ddl/SqlToNativeCommandConverter.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/ddl/SqlToNativeCommandConverter.java
new file mode 100644
index 0000000..bec218d
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/ddl/SqlToNativeCommandConverter.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.prepare.ddl;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.calcite.sql.SqlCall;
+import org.apache.calcite.sql.SqlDdl;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.ignite.cache.QueryIndex;
+import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
+import org.apache.ignite.internal.processors.query.IgniteSQLException;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PlanningContext;
+import org.apache.ignite.internal.processors.query.calcite.sql.IgniteSqlCreateIndex;
+import org.apache.ignite.internal.processors.query.calcite.sql.IgniteSqlDropIndex;
+import org.apache.ignite.internal.sql.command.SqlCommand;
+import org.apache.ignite.internal.sql.command.SqlCreateIndexCommand;
+import org.apache.ignite.internal.sql.command.SqlDropIndexCommand;
+import org.apache.ignite.internal.sql.command.SqlIndexColumn;
+
+import static org.apache.ignite.internal.processors.query.calcite.util.PlanUtils.deriveObjectName;
+import static org.apache.ignite.internal.processors.query.calcite.util.PlanUtils.deriveSchemaName;
+
+/** */
+public class SqlToNativeCommandConverter {
+    /**
+     * Is a given AST can be converted by this class.
+     *
+     * @param sqlCmd Root node of the given AST.
+     */
+    public static boolean isSupported(SqlNode sqlCmd) {
+        return sqlCmd instanceof IgniteSqlCreateIndex
+            || sqlCmd instanceof IgniteSqlDropIndex;
+    }
+
+    /**
+     * Converts a given AST to a native command.
+     *
+     * @param sqlCmd Root node of the given AST.
+     * @param pctx Planning context.
+     */
+    public static NativeCommandWrapper convert(SqlDdl sqlCmd, PlanningContext pctx) {
+        return new NativeCommandWrapper(convertSqlCmd(sqlCmd, pctx));
+    }
+
+    /**
+     * Converts SqlNode to SqlCommand.
+     */
+    private static SqlCommand convertSqlCmd(SqlDdl cmd, PlanningContext pctx) {
+        if (cmd instanceof IgniteSqlCreateIndex)
+            return convertCreateIndex((IgniteSqlCreateIndex)cmd, pctx);
+        else if (cmd instanceof IgniteSqlDropIndex)
+            return convertDropIndex((IgniteSqlDropIndex)cmd, pctx);
+
+        throw new IgniteSQLException("Unsupported native operation [" +
+            "cmdName=" + (cmd == null ? null : cmd.getClass().getSimpleName()) + "; " +
+            "querySql=\"" + pctx.query() + "\"]", IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+    }
+
+    /**
+     * Converts CREATE INDEX command.
+     */
+    private static SqlCreateIndexCommand convertCreateIndex(IgniteSqlCreateIndex sqlCmd, PlanningContext ctx) {
+        String schemaName = deriveSchemaName(sqlCmd.tableName(), ctx);
+        String tblName = deriveObjectName(sqlCmd.tableName(), ctx, "table name");
+        String idxName = sqlCmd.indexName().getSimple();
+
+        List<SqlIndexColumn> cols = new ArrayList<>(sqlCmd.columnList().size());
+
+        for (SqlNode col : sqlCmd.columnList().getList()) {
+            boolean desc = false;
+
+            if (col.getKind() == SqlKind.DESCENDING) {
+                col = ((SqlCall)col).getOperandList().get(0);
+
+                desc = true;
+            }
+
+            cols.add(new SqlIndexColumn(((SqlIdentifier)col).getSimple(), desc));
+        }
+
+        int parallel = sqlCmd.parallel() == null ? 0 : sqlCmd.parallel().intValue(true);
+
+        int inlineSize = sqlCmd.inlineSize() == null ? QueryIndex.DFLT_INLINE_SIZE :
+            sqlCmd.inlineSize().intValue(true);
+
+        return new SqlCreateIndexCommand(schemaName, tblName, idxName, sqlCmd.ifNotExists(), cols, false,
+            parallel, inlineSize);
+    }
+
+    /**
+     * Converts DROP INDEX command.
+     */
+    private static SqlDropIndexCommand convertDropIndex(IgniteSqlDropIndex sqlCmd, PlanningContext ctx) {
+        String schemaName = deriveSchemaName(sqlCmd.name(), ctx);
+        String idxName = deriveObjectName(sqlCmd.name(), ctx, "index name");
+
+        return new SqlDropIndexCommand(schemaName, idxName, sqlCmd.ifExists());
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/IgniteSqlCreateIndex.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/IgniteSqlCreateIndex.java
new file mode 100644
index 0000000..ca472d0
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/IgniteSqlCreateIndex.java
@@ -0,0 +1,162 @@
+/*
+ * 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.sql;
+
+import java.util.List;
+import java.util.Objects;
+import org.apache.calcite.sql.SqlCall;
+import org.apache.calcite.sql.SqlCreate;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlNodeList;
+import org.apache.calcite.sql.SqlNumericLiteral;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.SqlSpecialOperator;
+import org.apache.calcite.sql.SqlWriter;
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.util.ImmutableNullableList;
+
+/**
+ * Parse tree for {@code CREATE INDEX} statement
+ */
+public class IgniteSqlCreateIndex extends SqlCreate {
+    /** */
+    private final SqlIdentifier idxName;
+
+    /** */
+    private final SqlIdentifier tblName;
+
+    /** */
+    private final SqlNodeList columnList;
+
+    /** */
+    private final SqlNumericLiteral parallel;
+
+    /** */
+    private final SqlNumericLiteral inlineSize;
+
+    /** */
+    private static final SqlOperator OPERATOR =
+        new SqlSpecialOperator("CREATE INDEX", SqlKind.CREATE_INDEX);
+
+    /** Creates a SqlCreateIndex. */
+    protected IgniteSqlCreateIndex(SqlParserPos pos, boolean ifNotExists, SqlIdentifier idxName, SqlIdentifier tblName,
+        SqlNodeList columnList, SqlNumericLiteral parallel, SqlNumericLiteral inlineSize) {
+        super(OPERATOR, pos, false, ifNotExists);
+        this.idxName = Objects.requireNonNull(idxName, "index name");
+        this.tblName = Objects.requireNonNull(tblName, "table name");
+        this.columnList = columnList;
+        this.parallel = parallel;
+        this.inlineSize = inlineSize;
+    }
+
+    /** {@inheritDoc} */
+    @Override public List<SqlNode> getOperandList() {
+        return ImmutableNullableList.of(idxName, tblName, columnList, parallel, inlineSize);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
+        writer.keyword("CREATE");
+
+        writer.keyword("INDEX");
+
+        if (ifNotExists)
+            writer.keyword("IF NOT EXISTS");
+
+        idxName.unparse(writer, 0, 0);
+
+        writer.keyword("ON");
+
+        tblName.unparse(writer, 0, 0);
+
+        SqlWriter.Frame frame = writer.startList("(", ")");
+
+        for (SqlNode c : columnList) {
+            writer.sep(",");
+
+            boolean desc = false;
+
+            if (c.getKind() == SqlKind.DESCENDING) {
+                c = ((SqlCall)c).getOperandList().get(0);
+                desc = true;
+            }
+
+            c.unparse(writer, 0, 0);
+
+            if (desc)
+                writer.keyword("DESC");
+        }
+
+        writer.endList(frame);
+
+        if (parallel != null) {
+            writer.keyword("PARALLEL");
+
+            parallel.unparse(writer, 0, 0);
+        }
+
+        if (inlineSize != null) {
+            writer.keyword("INLINE_SIZE");
+
+            inlineSize.unparse(writer, 0, 0);
+        }
+    }
+
+    /**
+     * @return Name of the index.
+     */
+    public SqlIdentifier indexName() {
+        return idxName;
+    }
+
+    /**
+     * @return Name of the table.
+     */
+    public SqlIdentifier tableName() {
+        return tblName;
+    }
+
+    /**
+     * @return List of the specified columns and constraints.
+     */
+    public SqlNodeList columnList() {
+        return columnList;
+    }
+
+    /**
+     * @return PARALLEL clause.
+     */
+    public SqlNumericLiteral parallel() {
+        return parallel;
+    }
+
+    /**
+     * @return INLINE_SIZE clause.
+     */
+    public SqlNumericLiteral inlineSize() {
+        return inlineSize;
+    }
+
+    /**
+     * @return Whether the IF NOT EXISTS is specified.
+     */
+    public boolean ifNotExists() {
+        return ifNotExists;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/IgniteSqlDropIndex.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/IgniteSqlDropIndex.java
new file mode 100644
index 0000000..57afc9c
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/IgniteSqlDropIndex.java
@@ -0,0 +1,76 @@
+/*
+ * 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.sql;
+
+import java.util.List;
+import java.util.Objects;
+import org.apache.calcite.sql.SqlDrop;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.SqlSpecialOperator;
+import org.apache.calcite.sql.SqlWriter;
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.util.ImmutableNullableList;
+
+/**
+ * Parse tree for {@code DROP INDEX} statement
+ */
+public class IgniteSqlDropIndex extends SqlDrop {
+    /** */
+    private final SqlIdentifier name;
+
+    /** */
+    private static final SqlOperator OPERATOR =
+        new SqlSpecialOperator("DROP INDEX", SqlKind.DROP_INDEX);
+
+    /** */
+    protected IgniteSqlDropIndex(SqlParserPos pos, boolean ifExists, SqlIdentifier idxName) {
+        super(OPERATOR, pos, ifExists);
+        name = Objects.requireNonNull(idxName, "index name");
+    }
+
+    /** {@inheritDoc} */
+    @Override public List<SqlNode> getOperandList() {
+        return ImmutableNullableList.of(name);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
+        writer.keyword(getOperator().getName()); // "DROP ..."
+
+        if (ifExists)
+            writer.keyword("IF EXISTS");
+
+        name.unparse(writer, leftPrec, rightPrec);
+    }
+
+    /**
+     * @return Name of the object.
+     */
+    public SqlIdentifier name() {
+        return name;
+    }
+
+    /**
+     * @return Whether the IF EXISTS is specified.
+     */
+    public boolean ifExists() {
+        return ifExists;
+    }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteResource.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteResource.java
index a0dfd8e..ae24367 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteResource.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteResource.java
@@ -43,4 +43,7 @@ public interface IgniteResource {
     @Resources.BaseMessage("Illegal value of {0}. The value must be positive and less than Integer.MAX_VALUE " +
         "(" + Integer.MAX_VALUE + ")." )
     Resources.ExInst<SqlValidatorException> correctIntegerLimit(String a0);
+
+    @Resources.BaseMessage("Option ''{0}'' has already been defined")
+    Resources.ExInst<SqlValidatorException> optionAlreadyDefined(String optName);
 }
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/PlanUtils.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/PlanUtils.java
new file mode 100644
index 0000000..d444c9e
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/PlanUtils.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.util;
+
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
+import org.apache.ignite.internal.processors.query.IgniteSQLException;
+import org.apache.ignite.internal.processors.query.calcite.prepare.PlanningContext;
+
+/** */
+public class PlanUtils {
+    /** Derives a schema name from the compound identifier. */
+    public static String deriveSchemaName(SqlIdentifier id, PlanningContext ctx) {
+        String schemaName;
+        if (id.isSimple())
+            schemaName = ctx.schemaName();
+        else {
+            SqlIdentifier schemaId = id.skipLast(1);
+
+            if (!schemaId.isSimple()) {
+                throw new IgniteSQLException("Unexpected value of schemaName [" +
+                    "expected a simple identifier, but was " + schemaId + "; " +
+                    "querySql=\"" + ctx.query() + "\"]", IgniteQueryErrorCode.PARSING);
+            }
+
+            schemaName = schemaId.getSimple();
+        }
+
+        ensureSchemaExists(ctx, schemaName);
+
+        return schemaName;
+    }
+
+    /** Derives an object(a table, an index, etc) name from the compound identifier. */
+    public static String deriveObjectName(SqlIdentifier id, PlanningContext ctx, String objDesc) {
+        if (id.isSimple())
+            return id.getSimple();
+
+        SqlIdentifier objId = id.getComponent(id.skipLast(1).names.size());
+
+        if (!objId.isSimple()) {
+            throw new IgniteSQLException("Unexpected value of " + objDesc + " [" +
+                "expected a simple identifier, but was " + objId + "; " +
+                "querySql=\"" + ctx.query() + "\"]", IgniteQueryErrorCode.PARSING);
+        }
+
+        return objId.getSimple();
+    }
+
+    /** */
+    private static void ensureSchemaExists(PlanningContext ctx, String schemaName) {
+        if (ctx.catalogReader().getRootSchema().getSubSchema(schemaName, true) == null)
+            throw new IgniteSQLException("Schema with name " + schemaName + " not found",
+                IgniteQueryErrorCode.SCHEMA_NOT_FOUND);
+    }
+}
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/AbstractDdlIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/AbstractDdlIntegrationTest.java
new file mode 100644
index 0000000..1bd3d35
--- /dev/null
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/AbstractDdlIntegrationTest.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ignite.internal.processors.query.calcite.integration;
+
+import java.util.List;
+import org.apache.ignite.cache.query.FieldsQueryCursor;
+import org.apache.ignite.cache.query.QueryCursor;
+import org.apache.ignite.configuration.DataRegionConfiguration;
+import org.apache.ignite.configuration.DataStorageConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.configuration.SqlConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.processors.query.calcite.CalciteQueryProcessor;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.After;
+import org.junit.Before;
+
+/** */
+public class AbstractDdlIntegrationTest extends GridCommonAbstractTest {
+    /** */
+    private static final String CLIENT_NODE_NAME = "client";
+
+    /** */
+    protected static final String DATA_REGION_NAME = "test_data_region";
+
+    /** */
+    protected IgniteEx client;
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTestsStarted() throws Exception {
+        startGrids(1);
+
+        client = startClientGrid(CLIENT_NODE_NAME);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+        return super.getConfiguration(igniteInstanceName)
+            .setSqlConfiguration(
+                new SqlConfiguration().setSqlSchemas("MY_SCHEMA")
+            )
+            .setDataStorageConfiguration(
+                new DataStorageConfiguration()
+                    .setDataRegionConfigurations(new DataRegionConfiguration().setName(DATA_REGION_NAME))
+            );
+    }
+
+    /** */
+    @Before
+    public void init() {
+        client = grid(CLIENT_NODE_NAME);
+    }
+
+    /** */
+    @After
+    public void cleanUp() {
+        client.destroyCaches(client.cacheNames());
+    }
+
+    /** */
+    protected List<List<?>> executeSql(String sql) {
+        List<FieldsQueryCursor<List<?>>> cur = queryProcessor().query(null, "PUBLIC", sql);
+
+        try (QueryCursor<List<?>> srvCursor = cur.get(0)) {
+            return srvCursor.getAll();
+        }
+    }
+
+    /** */
+    private CalciteQueryProcessor queryProcessor() {
+        return Commons.lookupComponent(client.context(), CalciteQueryProcessor.class);
+    }
+}
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/IndexDdlIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/IndexDdlIntegrationTest.java
new file mode 100644
index 0000000..cff74c5
--- /dev/null
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/IndexDdlIntegrationTest.java
@@ -0,0 +1,203 @@
+/*
+ * 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.integration;
+
+import java.util.List;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.cache.query.index.Index;
+import org.apache.ignite.internal.cache.query.index.SortOrder;
+import org.apache.ignite.internal.cache.query.index.sorted.IndexKeyDefinition;
+import org.apache.ignite.internal.cache.query.index.sorted.inline.InlineIndex;
+import org.apache.ignite.internal.processors.cache.IgniteInternalCache;
+import org.apache.ignite.internal.processors.query.IgniteSQLException;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.lang.IgnitePredicate;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.junit.Test;
+
+/** */
+public class IndexDdlIntegrationTest extends AbstractDdlIntegrationTest {
+    /** Cache name. */
+    private static final String CACHE_NAME = "my_cache";
+
+    /** {@inheritDoc} */
+    @Override public void init() {
+        super.init();
+
+        executeSql("create table my_table(id int, val_int int, val_str varchar) with cache_name=\"" + CACHE_NAME + "\"");
+    }
+
+    /**
+     * Creates and drops index.
+     */
+    @Test
+    public void createDropIndexSimpleCase() {
+        assertNull(findIndex(CACHE_NAME, "my_index"));
+
+        executeSql("create index my_index on my_table(id)");
+
+        assertNotNull(findIndex(CACHE_NAME, "my_index"));
+
+        executeSql("drop index my_index");
+
+        assertNull(findIndex(CACHE_NAME, "my_index"));
+    }
+
+    /**
+     * Creates and drops index on not default schema.
+     */
+    @Test
+    public void createDropIndexWithSchema() {
+        String cacheName = "cache2";
+
+        executeSql("create table my_schema.my_table2(id int) with cache_name=\"" + cacheName + "\"");
+
+        assertNull(findIndex(cacheName, "my_index2"));
+
+        executeSql("create index my_index2 on my_schema.my_table2(id)");
+
+        assertNotNull(findIndex(cacheName, "my_index2"));
+
+        GridTestUtils.assertThrowsAnyCause(log, () -> executeSql("drop index my_index2"), IgniteSQLException.class,
+            "Index doesn't exist");
+
+        assertNotNull(findIndex(cacheName, "my_index2"));
+
+        executeSql("drop index my_schema.my_index2");
+
+        assertNull(findIndex(cacheName, "my_index2"));
+    }
+
+    /**
+     * Creates index with "if not exists" clause.
+     */
+    @Test
+    public void createIndexWithIfNotExistsClause() {
+        assertNull(findIndex(CACHE_NAME, "my_index"));
+
+        executeSql("create index if not exists my_index on my_table(id)");
+
+        GridTestUtils.assertThrowsAnyCause(log, () -> executeSql("create index my_index on my_table(val_int)"),
+            IgniteSQLException.class, "Index already exists");
+
+        assertNotNull(findIndex(CACHE_NAME, "my_index"));
+
+        executeSql("create index if not exists my_index on my_table(val_str)");
+
+        Index idx = findIndex(CACHE_NAME, "my_index");
+
+        assertNotNull(idx);
+
+        List<IndexKeyDefinition> keyDefs = indexKeyDefinitions(idx);
+
+        assertEquals("ID", keyDefs.get(0).name());
+    }
+
+    /**
+     * Creates drops index with "if exists" clause.
+     */
+    @Test
+    public void dropIndexWithIfExistsClause() {
+        assertNull(findIndex(CACHE_NAME, "my_index"));
+
+        executeSql("create index my_index on my_table(id)");
+
+        assertNotNull(findIndex(CACHE_NAME, "my_index"));
+
+        executeSql("drop index if exists my_index");
+
+        assertNull(findIndex(CACHE_NAME, "my_index"));
+
+        executeSql("drop index if exists my_index");
+
+        GridTestUtils.assertThrowsAnyCause(log, () -> executeSql("drop index my_index"), IgniteSQLException.class,
+            "Index doesn't exist");
+    }
+
+    /**
+     * Creates index with different columns ordering.
+     */
+    @Test
+    public void createIndexWithColumnsOrdering() {
+        assertNull(findIndex(CACHE_NAME, "my_index"));
+
+        executeSql("create index my_index on my_table(id, val_int asc, val_str desc)");
+
+        Index idx = findIndex(CACHE_NAME, "my_index");
+
+        assertNotNull(idx);
+
+        List<IndexKeyDefinition> keyDefs = indexKeyDefinitions(idx);
+
+        assertEquals("ID", keyDefs.get(0).name());
+        assertEquals(SortOrder.ASC, keyDefs.get(0).order().sortOrder());
+        assertEquals("VAL_INT", keyDefs.get(1).name());
+        assertEquals(SortOrder.ASC, keyDefs.get(1).order().sortOrder());
+        assertEquals("VAL_STR", keyDefs.get(2).name());
+        assertEquals(SortOrder.DESC, keyDefs.get(2).order().sortOrder());
+    }
+
+    /**
+     * Creates index with inline size.
+     */
+    @Test
+    public void createIndexWithInlineSize() {
+        assertNull(findIndex(CACHE_NAME, "my_index"));
+
+        executeSql("create index my_index on my_table(val_str) inline_size 10");
+
+        Index idx = findIndex(CACHE_NAME, "my_index");
+
+        assertNotNull(idx);
+
+        InlineIndex inlineIdx = idx.unwrap(InlineIndex.class);
+
+        assertNotNull(inlineIdx);
+        assertEquals(10, inlineIdx.inlineSize());
+    }
+
+    /**
+     * Creates index with inline size.
+     */
+    @Test
+    public void createIndexWithParallel() {
+        assertNull(findIndex(CACHE_NAME, "my_index"));
+
+        executeSql("create index my_index on my_table(val_str) parallel 10");
+
+        assertNotNull(findIndex(CACHE_NAME, "my_index"));
+    }
+
+    /** */
+    private Index findIndex(String cacheName, String idxName) {
+        IgniteEx node = grid(0);
+
+        IgniteInternalCache<?, ?> cache = node.cachex(cacheName);
+
+        return F.find(node.context().indexProcessor().indexes(cache.context()), null,
+            (IgnitePredicate<Index>)i -> idxName.equalsIgnoreCase(i.name()));
+    }
+
+    /** */
+    private static List<IndexKeyDefinition> indexKeyDefinitions(Index idx) {
+        InlineIndex inlineIdx = idx.unwrap(InlineIndex.class);
+
+        assertNotNull(inlineIdx);
+
+        return inlineIdx.segment(0).rowHandler().indexKeyDefinitions();
+    }
+}
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/TableDdlIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/TableDdlIntegrationTest.java
index 4bbfa89..771e485 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/TableDdlIntegrationTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/TableDdlIntegrationTest.java
@@ -27,32 +27,19 @@ import java.util.List;
 import java.util.Set;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
-
 import org.apache.ignite.IgniteCache;
 import org.apache.ignite.cache.CacheAtomicityMode;
 import org.apache.ignite.cache.CacheMode;
 import org.apache.ignite.cache.CachePeekMode;
 import org.apache.ignite.cache.CacheWriteSynchronizationMode;
 import org.apache.ignite.cache.QueryEntity;
-import org.apache.ignite.cache.query.FieldsQueryCursor;
-import org.apache.ignite.cache.query.QueryCursor;
 import org.apache.ignite.configuration.CacheConfiguration;
-import org.apache.ignite.configuration.DataRegionConfiguration;
-import org.apache.ignite.configuration.DataStorageConfiguration;
-import org.apache.ignite.configuration.IgniteConfiguration;
-import org.apache.ignite.configuration.SqlConfiguration;
-import org.apache.ignite.internal.IgniteEx;
 import org.apache.ignite.internal.processors.cache.IgniteInternalCache;
 import org.apache.ignite.internal.processors.query.IgniteSQLException;
-import org.apache.ignite.internal.processors.query.calcite.CalciteQueryProcessor;
-import org.apache.ignite.internal.processors.query.calcite.util.Commons;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.testframework.GridTestUtils;
-import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
 import org.hamcrest.CustomMatcher;
 import org.hamcrest.Matcher;
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
 
 import static org.apache.ignite.internal.util.IgniteUtils.map;
@@ -63,47 +50,7 @@ import static org.hamcrest.CoreMatchers.nullValue;
 import static org.junit.Assert.assertThat;
 
 /** */
-public class TableDdlIntegrationTest extends GridCommonAbstractTest {
-    /** */
-    private static final String CLIENT_NODE_NAME = "client";
-
-    /** */
-    private static final String DATA_REGION_NAME = "test_data_region";
-
-    /** */
-    private IgniteEx client;
-
-    /** {@inheritDoc} */
-    @Override protected void beforeTestsStarted() throws Exception {
-        startGrids(1);
-
-        client = startClientGrid(CLIENT_NODE_NAME);
-    }
-
-    /** {@inheritDoc} */
-    @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
-        return super.getConfiguration(igniteInstanceName)
-            .setSqlConfiguration(
-                new SqlConfiguration().setSqlSchemas("MY_SCHEMA")
-            )
-            .setDataStorageConfiguration(
-                new DataStorageConfiguration()
-                    .setDataRegionConfigurations(new DataRegionConfiguration().setName(DATA_REGION_NAME))
-            );
-    }
-
-    /** */
-    @Before
-    public void init() {
-        client = grid(CLIENT_NODE_NAME);
-    }
-
-    /** */
-    @After
-    public void cleanUp() {
-        client.destroyCaches(client.cacheNames());
-    }
-
+public class TableDdlIntegrationTest extends AbstractDdlIntegrationTest {
     /**
      * Creates table with two columns, where the first column is PK,
      * and verifies created cache.
@@ -401,20 +348,6 @@ public class TableDdlIntegrationTest extends GridCommonAbstractTest {
         executeSql("drop table if exists my_schema.my_table");
     }
 
-    /** */
-    private List<List<?>> executeSql(String sql) {
-        List<FieldsQueryCursor<List<?>>> cur = queryProcessor().query(null, "PUBLIC", sql);
-
-        try (QueryCursor<List<?>> srvCursor = cur.get(0)) {
-            return srvCursor.getAll();
-        }
-    }
-
-    /** */
-    private CalciteQueryProcessor queryProcessor() {
-        return Commons.lookupComponent(client.context(), CalciteQueryProcessor.class);
-    }
-
     /**
      * Matcher to verify that an object of the expected type and matches the given predicat.
      *
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/sql/SqlDdlParserTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/sql/SqlDdlParserTest.java
index 428dc6c..61a1767 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/sql/SqlDdlParserTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/sql/SqlDdlParserTest.java
@@ -21,6 +21,7 @@ import java.util.Objects;
 import java.util.function.Predicate;
 
 import com.google.common.collect.ImmutableList;
+import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlIdentifier;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlLiteral;
@@ -30,6 +31,8 @@ import org.apache.calcite.sql.ddl.SqlColumnDeclaration;
 import org.apache.calcite.sql.ddl.SqlKeyConstraint;
 import org.apache.calcite.sql.parser.SqlParseException;
 import org.apache.calcite.sql.parser.SqlParser;
+import org.apache.calcite.sql.validate.SqlValidatorException;
+import org.apache.ignite.testframework.GridTestUtils;
 import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
 import org.hamcrest.CustomMatcher;
 import org.hamcrest.Matcher;
@@ -238,6 +241,121 @@ public class SqlDdlParserTest extends GridCommonAbstractTest {
     }
 
     /**
+     * Create index with list of indexed columns.
+     */
+    @Test
+    public void createIndexColumns() throws SqlParseException {
+        String qry = "create index my_index on my_table(id, val1 asc, val2 desc)";
+
+        IgniteSqlCreateIndex createIdx = (IgniteSqlCreateIndex) parse(qry);
+
+        assertThat(createIdx.indexName().names, is(ImmutableList.of("MY_INDEX")));
+        assertThat(createIdx.tableName().names, is(ImmutableList.of("MY_TABLE")));
+        assertThat(createIdx.ifNotExists, is(false));
+        assertThat(createIdx.columnList(), hasItem(indexedColumn("ID", false)));
+        assertThat(createIdx.columnList(), hasItem(indexedColumn("VAL1", false)));
+        assertThat(createIdx.columnList(), hasItem(indexedColumn("VAL2", true)));
+    }
+
+    /**
+     * Create index on table with schema.
+     */
+    @Test
+    public void createIndexOnTableWithSchema() throws SqlParseException {
+        String qry = "create index my_index on my_schema.my_table(id)";
+
+        IgniteSqlCreateIndex createIdx = (IgniteSqlCreateIndex)parse(qry);
+
+        assertThat(createIdx.indexName().names, is(ImmutableList.of("MY_INDEX")));
+        assertThat(createIdx.tableName().names, is(ImmutableList.of("MY_SCHEMA", "MY_TABLE")));
+    }
+
+    /**
+     * Create index with "if not exists" clause"
+     */
+    @Test
+    public void createIndexIfNotExists() throws SqlParseException {
+        String qry = "create index if not exists my_index on my_table(id)";
+
+        IgniteSqlCreateIndex createIdx = (IgniteSqlCreateIndex)parse(qry);
+
+        assertThat(createIdx.indexName().names, is(ImmutableList.of("MY_INDEX")));
+        assertThat(createIdx.tableName().names, is(ImmutableList.of("MY_TABLE")));
+        assertThat(createIdx.ifNotExists, is(true));
+    }
+
+    /**
+     * Create index with parallel and inline_size options"
+     */
+    @Test
+    public void createIndexWithOptions() throws SqlParseException {
+        String qry = "create index my_index on my_table(id) parallel 10 inline_size 20";
+
+        IgniteSqlCreateIndex createIdx = (IgniteSqlCreateIndex)parse(qry);
+
+        assertThat(createIdx.indexName().names, is(ImmutableList.of("MY_INDEX")));
+        assertThat(createIdx.tableName().names, is(ImmutableList.of("MY_TABLE")));
+        assertEquals(10, createIdx.parallel().intValue(true));
+        assertEquals(20, createIdx.inlineSize().intValue(true));
+
+        qry = "create index my_index on my_table(id) parallel 10";
+
+        createIdx = (IgniteSqlCreateIndex)parse(qry);
+
+        assertEquals(10, createIdx.parallel().intValue(true));
+        assertNull(createIdx.inlineSize());
+
+        qry = "create index my_index on my_table(id) inline_size 20";
+
+        createIdx = (IgniteSqlCreateIndex)parse(qry);
+
+        assertNull(createIdx.parallel());
+        assertEquals(20, createIdx.inlineSize().intValue(true));
+
+        qry = "create index my_index on my_table(id) inline_size 20 parallel 10";
+
+        createIdx = (IgniteSqlCreateIndex)parse(qry);
+
+        assertEquals(20, createIdx.inlineSize().intValue(true));
+        assertEquals(10, createIdx.parallel().intValue(true));
+    }
+
+    /**
+     * Create index with malformed statements.
+     */
+    @Test
+    public void createIndexMalformed() {
+        assertParserThrows("create index my_index on my_table(id) parallel 10 inline_size 20 parallel 10",
+            SqlValidatorException.class, "Option 'PARALLEL' has already been defined");
+
+        assertParserThrows("create index my_index on my_table(id) inline_size -1", SqlParseException.class);
+
+        assertParserThrows("create index my_index on my_table(id) inline_size = 1", SqlParseException.class);
+
+        assertParserThrows("create index my_index on my_table(id) inline_size", SqlParseException.class);
+
+        assertParserThrows("create index if exists my_index on my_table(id)", SqlParseException.class);
+
+        assertParserThrows("create index my_index on my_table(id asc desc)", SqlParseException.class);
+
+        assertParserThrows("create index my_index on my_table(id nulls first)", SqlParseException.class);
+
+        assertParserThrows("create index my_scheme.my_index on my_table(id)", SqlParseException.class);
+
+        assertParserThrows("create index my_index on my_table(id.id2)", SqlParseException.class);
+    }
+
+    /** */
+    private void assertParserThrows(String sql, Class<? extends Exception> cls) {
+        assertParserThrows(sql, cls, "");
+    }
+
+    /** */
+    private void assertParserThrows(String sql, Class<? extends Exception> cls, String msg) {
+        GridTestUtils.assertThrowsAnyCause(log, () -> parse(sql), cls, msg);
+    }
+
+    /**
      * Parses a given statement and returns a resulting AST.
      *
      * @param stmt Statement to parse.
@@ -322,4 +440,30 @@ public class SqlDdlParserTest extends GridCommonAbstractTest {
             }
         };
     }
+
+    /**
+     * Matcher to verify name and direction of indexed column.
+     *
+     * @param name Expected name.
+     * @param desc Descending order.
+     * @return {@code true} in case name and order of the indexed column equal to expected values.
+     */
+    private static <T extends SqlColumnDeclaration> Matcher<T> indexedColumn(String name, boolean desc) {
+        return new CustomMatcher<T>("column with name=" + name) {
+            @Override public boolean matches(Object item) {
+                SqlNode node = (SqlNode)item;
+
+                if (desc) {
+                    if (node.getKind() != SqlKind.DESCENDING)
+                        return false;
+
+                    SqlIdentifier ident = ((SqlIdentifier)((SqlCall)node).getOperandList().get(0));
+
+                    return ident.names.get(0).equals(name);
+                }
+                else
+                    return item instanceof SqlIdentifier && ((SqlIdentifier)node).names.get(0).equals(name);
+            }
+        };
+    }
 }
diff --git a/modules/calcite/src/test/java/org/apache/ignite/testsuites/IntegrationTestSuite.java b/modules/calcite/src/test/java/org/apache/ignite/testsuites/IntegrationTestSuite.java
index 03fe094..aa9919c 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/testsuites/IntegrationTestSuite.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/testsuites/IntegrationTestSuite.java
@@ -28,6 +28,7 @@ import org.apache.ignite.internal.processors.query.calcite.SqlFieldsQueryUsageTe
 import org.apache.ignite.internal.processors.query.calcite.UnstableTopologyTest;
 import org.apache.ignite.internal.processors.query.calcite.integration.AggregatesIntegrationTest;
 import org.apache.ignite.internal.processors.query.calcite.integration.CalciteErrorHandlilngIntegrationTest;
+import org.apache.ignite.internal.processors.query.calcite.integration.IndexDdlIntegrationTest;
 import org.apache.ignite.internal.processors.query.calcite.integration.IndexSpoolIntegrationTest;
 import org.apache.ignite.internal.processors.query.calcite.integration.MetadataIntegrationTest;
 import org.apache.ignite.internal.processors.query.calcite.integration.SetOpIntegrationTest;
@@ -59,6 +60,7 @@ import org.junit.runners.Suite;
     MetadataIntegrationTest.class,
     SortAggregateIntegrationTest.class,
     TableDdlIntegrationTest.class,
+    IndexDdlIntegrationTest.class,
     FunctionsTest.class,
     TableDmlIntegrationTest.class,
     DataTypesTest.class,
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcRequestHandler.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcRequestHandler.java
index 4c0f1e1..a3d9fc0 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcRequestHandler.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcRequestHandler.java
@@ -775,8 +775,10 @@ public class JdbcRequestHandler implements ClientListenerRequestHandler {
     /** */
     private List<FieldsQueryCursor<List<?>>> querySqlFields(SqlFieldsQueryEx qry, GridQueryCancel cancel) {
         if (experimentalQueryEngine != null) {
-            if (executeWithExperimentalEngine(qry.getSql()))
-                return experimentalQueryEngine.query(QueryContext.of(qry, cancel), qry.getSchema(), qry.getSql(), qry.getArgs());
+            if (executeWithExperimentalEngine(qry.getSql())) {
+                return experimentalQueryEngine.query(QueryContext.of(qry, cliCtx, cancel), qry.getSchema(),
+                    qry.getSql(), qry.getArgs());
+            }
         }
 
         return connCtx.kernalContext().query().querySqlFields(null, qry,
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
index 1f37129..46227b5 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
@@ -173,7 +173,7 @@ public class GridQueryProcessor extends GridProcessorAdapter {
 
     /** Pattern to test incoming query to decide whether this query should be executed with Calcite or H2. */
     public static final Pattern H2_REDIRECTION_RULES =
-        Pattern.compile("\\s*(alter\\s*table|create\\s*index|drop\\s*index)", CASE_INSENSITIVE);
+        Pattern.compile("\\s*(alter\\s+table)", CASE_INSENSITIVE);
 
     /** For tests. */
     public static Class<? extends GridQueryIndexing> idxCls;
@@ -2850,8 +2850,10 @@ public class GridQueryProcessor extends GridProcessorAdapter {
             throw new CacheException("Execution of local SqlFieldsQuery on client node disallowed.");
 
         if (experimentalQueryEngine != null && useExperimentalSqlEngine) {
-            if (executeWithExperimentalEngine(qry.getSql()))
-                return experimentalQueryEngine.query(QueryContext.of(qry), qry.getSchema(), qry.getSql(), X.EMPTY_OBJECT_ARRAY);
+            if (executeWithExperimentalEngine(qry.getSql())) {
+                return experimentalQueryEngine.query(QueryContext.of(qry, cliCtx), qry.getSchema(), qry.getSql(),
+                    X.EMPTY_OBJECT_ARRAY);
+            }
         }
 
         return executeQuerySafe(cctx, () -> {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQuerySchemaManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQuerySchemaManager.java
new file mode 100644
index 0000000..b89d2b8
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQuerySchemaManager.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query;
+
+import org.apache.ignite.internal.processors.cache.GridCacheContextInfo;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Provides information about query engine schemas.
+ */
+public interface GridQuerySchemaManager {
+    /**
+     * Find type descriptor by schema and table name.
+     *
+     * @return Query type descriptor or {@code null} if descriptor was not found.
+     */
+    public @Nullable GridQueryTypeDescriptor typeDescriptorForTable(String schemaName, String tableName);
+
+    /**
+     * Find type descriptor by schema and index name.
+     *
+     * @return Query type descriptor or {@code null} if descriptor was not found.
+     */
+    public @Nullable GridQueryTypeDescriptor typeDescriptorForIndex(String schemaName, String idxName);
+
+    /**
+     * Find cache info by schema and table name.
+     *
+     * @return Cache info or {@code null} if cache info was not found.
+     */
+    public @Nullable <K, V> GridCacheContextInfo<K, V> cacheInfoForTable(String schemaName, String tableName);
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlCommandProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlCommandProcessor.java
new file mode 100644
index 0000000..f82f022
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlCommandProcessor.java
@@ -0,0 +1,355 @@
+/*
+ * 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.sql;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteCluster;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.cache.CacheMode;
+import org.apache.ignite.cache.QueryIndex;
+import org.apache.ignite.cache.QueryIndexType;
+import org.apache.ignite.cache.query.FieldsQueryCursor;
+import org.apache.ignite.internal.ComputeMXBeanImpl;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.QueryMXBeanImpl;
+import org.apache.ignite.internal.ServiceMXBeanImpl;
+import org.apache.ignite.internal.TransactionsMXBeanImpl;
+import org.apache.ignite.internal.processors.cache.GridCacheContextInfo;
+import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxLocal;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccUtils;
+import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
+import org.apache.ignite.internal.processors.query.GridQueryProperty;
+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.SqlClientContext;
+import org.apache.ignite.internal.processors.query.schema.SchemaOperationException;
+import org.apache.ignite.internal.sql.command.SqlAlterTableCommand;
+import org.apache.ignite.internal.sql.command.SqlAlterUserCommand;
+import org.apache.ignite.internal.sql.command.SqlCommand;
+import org.apache.ignite.internal.sql.command.SqlCreateIndexCommand;
+import org.apache.ignite.internal.sql.command.SqlCreateUserCommand;
+import org.apache.ignite.internal.sql.command.SqlDropIndexCommand;
+import org.apache.ignite.internal.sql.command.SqlDropUserCommand;
+import org.apache.ignite.internal.sql.command.SqlIndexColumn;
+import org.apache.ignite.internal.sql.command.SqlKillComputeTaskCommand;
+import org.apache.ignite.internal.sql.command.SqlKillContinuousQueryCommand;
+import org.apache.ignite.internal.sql.command.SqlKillScanQueryCommand;
+import org.apache.ignite.internal.sql.command.SqlKillServiceCommand;
+import org.apache.ignite.internal.sql.command.SqlKillTransactionCommand;
+import org.apache.ignite.internal.util.future.GridFinishedFuture;
+import org.jetbrains.annotations.Nullable;
+
+import static org.apache.ignite.internal.processors.query.QueryUtils.convert;
+import static org.apache.ignite.internal.processors.query.QueryUtils.isDdlOnSchemaSupported;
+
+/**
+ * Processor responsible for execution of native Ignite commands.
+ */
+public class SqlCommandProcessor {
+    /** Kernal context. */
+    protected final GridKernalContext ctx;
+
+    /** Logger. */
+    protected final IgniteLogger log;
+
+    /** Schema manager. */
+    protected final GridQuerySchemaManager schemaMgr;
+
+    /**
+     * Constructor.
+     *
+     * @param ctx Kernal context.
+     */
+    public SqlCommandProcessor(GridKernalContext ctx, GridQuerySchemaManager schemaMgr) {
+        this.ctx = ctx;
+        this.schemaMgr = schemaMgr;
+        log = ctx.log(getClass());
+    }
+
+    /**
+     * Execute command.
+     *
+     * @param sql SQL.
+     * @param cmdNative Native command.
+     * @param cliCtx Client context.
+     * @return Result.
+     */
+    @Nullable public FieldsQueryCursor<List<?>> runCommand(String sql, SqlCommand cmdNative,
+        @Nullable SqlClientContext cliCtx) {
+        assert cmdNative != null;
+
+        if (isDdl(cmdNative))
+            runCommandNativeDdl(sql, cmdNative);
+        else if (cmdNative instanceof SqlKillComputeTaskCommand)
+            processKillComputeTaskCommand((SqlKillComputeTaskCommand) cmdNative);
+        else if (cmdNative instanceof SqlKillTransactionCommand)
+            processKillTxCommand((SqlKillTransactionCommand) cmdNative);
+        else if (cmdNative instanceof SqlKillServiceCommand)
+            processKillServiceTaskCommand((SqlKillServiceCommand) cmdNative);
+        else if (cmdNative instanceof SqlKillScanQueryCommand)
+            processKillScanQueryCommand((SqlKillScanQueryCommand) cmdNative);
+        else if (cmdNative instanceof SqlKillContinuousQueryCommand)
+            processKillContinuousQueryCommand((SqlKillContinuousQueryCommand) cmdNative);
+
+        return null;
+    }
+
+    /**
+     * @return {@code True} if command is supported by this command processor.
+     */
+    public boolean isCommandSupported(SqlCommand cmd) {
+        return cmd instanceof SqlCreateIndexCommand
+            || cmd instanceof SqlDropIndexCommand
+            || cmd instanceof SqlAlterTableCommand
+            || cmd instanceof SqlCreateUserCommand
+            || cmd instanceof SqlAlterUserCommand
+            || cmd instanceof SqlDropUserCommand
+            || cmd instanceof SqlKillComputeTaskCommand
+            || cmd instanceof SqlKillServiceCommand
+            || cmd instanceof SqlKillTransactionCommand
+            || cmd instanceof SqlKillScanQueryCommand
+            || cmd instanceof SqlKillContinuousQueryCommand;
+    }
+
+    /**
+     * @param cmd Command.
+     * @return {@code True} if this is supported DDL command.
+     */
+    private static boolean isDdl(SqlCommand cmd) {
+        return cmd instanceof SqlCreateIndexCommand
+            || cmd instanceof SqlDropIndexCommand
+            || cmd instanceof SqlAlterTableCommand
+            || cmd instanceof SqlCreateUserCommand
+            || cmd instanceof SqlAlterUserCommand
+            || cmd instanceof SqlDropUserCommand;
+    }
+
+    /**
+     * Process kill scan query cmd.
+     *
+     * @param cmd Command.
+     */
+    private void processKillScanQueryCommand(SqlKillScanQueryCommand cmd) {
+        new QueryMXBeanImpl(ctx)
+            .cancelScan(cmd.getOriginNodeId(), cmd.getCacheName(), cmd.getQryId());
+    }
+
+    /**
+     * Process kill compute task command.
+     *
+     * @param cmd Command.
+     */
+    private void processKillComputeTaskCommand(SqlKillComputeTaskCommand cmd) {
+        new ComputeMXBeanImpl(ctx).cancel(cmd.getSessionId());
+    }
+
+    /**
+     * Process kill transaction cmd.
+     *
+     * @param cmd Command.
+     */
+    private void processKillTxCommand(SqlKillTransactionCommand cmd) {
+        new TransactionsMXBeanImpl(ctx).cancel(cmd.getXid());
+    }
+
+    /**
+     * Process kill service command.
+     *
+     * @param cmd Command.
+     */
+    private void processKillServiceTaskCommand(SqlKillServiceCommand cmd) {
+        new ServiceMXBeanImpl(ctx).cancel(cmd.getName());
+    }
+
+    /**
+     * Process kill continuous query cmd.
+     *
+     * @param cmd Command.
+     */
+    private void processKillContinuousQueryCommand(SqlKillContinuousQueryCommand cmd) {
+        new QueryMXBeanImpl(ctx).cancelContinuous(cmd.getOriginNodeId(), cmd.getRoutineId());
+    }
+
+    /**
+     * Run DDL statement.
+     *
+     * @param sql Original SQL.
+     * @param cmd Command.
+     */
+    private void runCommandNativeDdl(String sql, SqlCommand cmd) {
+        IgniteInternalFuture<?> fut = null;
+
+        try {
+            isDdlOnSchemaSupported(cmd.schemaName());
+
+            finishActiveTxIfNecessary();
+
+            if (cmd instanceof SqlCreateIndexCommand) {
+                SqlCreateIndexCommand cmd0 = (SqlCreateIndexCommand)cmd;
+
+                GridQueryTypeDescriptor typeDesc = schemaMgr.typeDescriptorForTable(cmd0.schemaName(), cmd0.tableName());
+                GridCacheContextInfo<?, ?> cacheInfo = schemaMgr.cacheInfoForTable(cmd0.schemaName(), cmd0.tableName());
+
+                if (typeDesc == null)
+                    throw new SchemaOperationException(SchemaOperationException.CODE_TABLE_NOT_FOUND, cmd0.tableName());
+
+                ensureDdlSupported(cacheInfo);
+
+                QueryIndex newIdx = new QueryIndex();
+
+                newIdx.setName(cmd0.indexName());
+
+                newIdx.setIndexType(cmd0.spatial() ? QueryIndexType.GEOSPATIAL : QueryIndexType.SORTED);
+
+                LinkedHashMap<String, Boolean> flds = new LinkedHashMap<>();
+
+                for (SqlIndexColumn col : cmd0.columns()) {
+                    GridQueryProperty prop = typeDesc.property(col.name());
+
+                    if (prop == null)
+                        throw new SchemaOperationException(SchemaOperationException.CODE_COLUMN_NOT_FOUND, col.name());
+
+                    flds.put(prop.name(), !col.descending());
+                }
+
+                newIdx.setFields(flds);
+                newIdx.setInlineSize(cmd0.inlineSize());
+
+                fut = ctx.query().dynamicIndexCreate(cacheInfo.name(), cmd.schemaName(), typeDesc.tableName(),
+                    newIdx, cmd0.ifNotExists(), cmd0.parallel());
+            }
+            else if (cmd instanceof SqlDropIndexCommand) {
+                SqlDropIndexCommand cmd0 = (SqlDropIndexCommand)cmd;
+
+                GridQueryTypeDescriptor typeDesc = schemaMgr.typeDescriptorForIndex(cmd0.schemaName(), cmd0.indexName());
+
+                if (typeDesc != null) {
+                    GridCacheContextInfo<?, ?> cacheInfo = schemaMgr.cacheInfoForTable(typeDesc.schemaName(),
+                        typeDesc.tableName());
+
+                    ensureDdlSupported(cacheInfo);
+
+                    fut = ctx.query().dynamicIndexDrop(cacheInfo.name(), cmd0.schemaName(), cmd0.indexName(),
+                        cmd0.ifExists());
+                }
+                else {
+                    if (cmd0.ifExists())
+                        fut = new GridFinishedFuture<>();
+                    else
+                        throw new SchemaOperationException(SchemaOperationException.CODE_INDEX_NOT_FOUND,
+                            cmd0.indexName());
+                }
+            }
+            else if (cmd instanceof SqlAlterTableCommand) {
+                SqlAlterTableCommand cmd0 = (SqlAlterTableCommand)cmd;
+
+                GridCacheContextInfo<?, ?> cacheInfo = schemaMgr.cacheInfoForTable(cmd0.schemaName(), cmd0.tableName());
+
+                if (cacheInfo == null) {
+                    throw new SchemaOperationException(SchemaOperationException.CODE_TABLE_NOT_FOUND,
+                        cmd0.tableName());
+                }
+
+                Boolean logging = cmd0.logging();
+
+                assert logging != null : "Only LOGGING/NOLOGGING are supported at the moment.";
+
+                IgniteCluster cluster = ctx.grid().cluster();
+
+                if (logging) {
+                    boolean res = cluster.enableWal(cacheInfo.name());
+
+                    if (!res)
+                        throw new IgniteSQLException("Logging already enabled for table: " + cmd0.tableName());
+                }
+                else {
+                    boolean res = cluster.disableWal(cacheInfo.name());
+
+                    if (!res)
+                        throw new IgniteSQLException("Logging already disabled for table: " + cmd0.tableName());
+                }
+
+                fut = new GridFinishedFuture<>();
+            }
+            else if (cmd instanceof SqlCreateUserCommand) {
+                SqlCreateUserCommand addCmd = (SqlCreateUserCommand)cmd;
+
+                ctx.security().createUser(addCmd.userName(), addCmd.password().toCharArray());
+            }
+            else if (cmd instanceof SqlAlterUserCommand) {
+                SqlAlterUserCommand altCmd = (SqlAlterUserCommand)cmd;
+
+                ctx.security().alterUser(altCmd.userName(), altCmd.password().toCharArray());
+            }
+            else if (cmd instanceof SqlDropUserCommand) {
+                SqlDropUserCommand dropCmd = (SqlDropUserCommand)cmd;
+
+                ctx.security().dropUser(dropCmd.userName());
+            }
+            else
+                throw new IgniteSQLException("Unsupported DDL operation: " + sql,
+                    IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+
+            if (fut != null)
+                fut.get();
+        }
+        catch (SchemaOperationException e) {
+            throw convert(e);
+        }
+        catch (IgniteSQLException e) {
+            throw e;
+        }
+        catch (Exception e) {
+            throw new IgniteSQLException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Commits active transaction if exists.
+     *
+     * @throws IgniteCheckedException If failed.
+     */
+    protected void finishActiveTxIfNecessary() throws IgniteCheckedException {
+        try (GridNearTxLocal tx = MvccUtils.tx(ctx)) {
+            if (tx == null)
+                return;
+
+            if (!tx.isRollbackOnly())
+                tx.commit();
+            else
+                tx.rollback();
+        }
+    }
+
+    /**
+     * Check if cache supports DDL statement.
+     *
+     * @param cctxInfo Cache context info.
+     * @throws IgniteSQLException If failed.
+     */
+    protected static void ensureDdlSupported(GridCacheContextInfo<?, ?> cctxInfo) throws IgniteSQLException {
+        if (cctxInfo.config().getCacheMode() == CacheMode.LOCAL) {
+            throw new IgniteSQLException("DDL statements are not supported on LOCAL caches",
+                IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+        }
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlCreateIndexCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlCreateIndexCommand.java
index 7a2b48b..3190641 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlCreateIndexCommand.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlCreateIndexCommand.java
@@ -83,6 +83,42 @@ public class SqlCreateIndexCommand implements SqlCommand {
     /** Inline size. Zero effectively disables inlining. */
     private int inlineSize = QueryIndex.DFLT_INLINE_SIZE;
 
+    /**
+     * Default constructor.
+     */
+    public SqlCreateIndexCommand() {
+    }
+
+    /**
+     * @param schemaName Schema name.
+     * @param tblName Table name.
+     * @param idxName Index name.
+     * @param ifNotExists "If not exists" clause.
+     * @param cols Indexed columns.
+     * @param spatial Spatial flag.
+     * @param parallel Count of threads to rebuild.
+     * @param inlineSize Inline size.
+     */
+    @SuppressWarnings("AssignmentOrReturnOfFieldWithMutableType")
+    public SqlCreateIndexCommand(String schemaName, String tblName, String idxName, boolean ifNotExists,
+        Collection<SqlIndexColumn> cols, boolean spatial, int parallel, int inlineSize) {
+        this.schemaName = schemaName;
+        this.tblName = tblName;
+        this.idxName = idxName;
+        this.ifNotExists = ifNotExists;
+        this.spatial = spatial;
+        this.parallel = parallel;
+        this.inlineSize = inlineSize;
+        this.cols = cols;
+
+        colNames = new HashSet<>();
+
+        for (SqlIndexColumn col : cols) {
+            if (!colNames.add(col.name()))
+                throw new IllegalArgumentException("Column already defined: " + col.name());
+        }
+    }
+
     /** {@inheritDoc} */
     @Override public String schemaName() {
         return schemaName;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlDropIndexCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlDropIndexCommand.java
index 1a1ea87..eed0977 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlDropIndexCommand.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlDropIndexCommand.java
@@ -37,6 +37,23 @@ public class SqlDropIndexCommand implements SqlCommand {
     /** IF EXISTS flag. */
     private boolean ifExists;
 
+    /**
+     * Default constructor.
+     */
+    public SqlDropIndexCommand() {
+    }
+
+    /**
+     * @param schemaName Schema name.
+     * @param idxName Index name.
+     * @param ifExists If exists clause.
+     */
+    public SqlDropIndexCommand(String schemaName, String idxName, boolean ifExists) {
+        this.schemaName = schemaName;
+        this.idxName = idxName;
+        this.ifExists = ifExists;
+    }
+
     /** {@inheritDoc} */
     @Override public String schemaName() {
         return schemaName;
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/CommandProcessor.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/CommandProcessor.java
index a1500fa..db5f715 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/CommandProcessor.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/CommandProcessor.java
@@ -32,16 +32,11 @@ import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
-
 import org.apache.ignite.IgniteCheckedException;
-import org.apache.ignite.IgniteCluster;
 import org.apache.ignite.IgniteDataStreamer;
-import org.apache.ignite.IgniteLogger;
 import org.apache.ignite.IgniteSystemProperties;
-import org.apache.ignite.cache.CacheMode;
 import org.apache.ignite.cache.QueryEntity;
 import org.apache.ignite.cache.QueryIndex;
-import org.apache.ignite.cache.QueryIndexType;
 import org.apache.ignite.cache.query.BulkLoadContextCursor;
 import org.apache.ignite.cache.query.FieldsQueryCursor;
 import org.apache.ignite.cluster.ClusterNode;
@@ -49,13 +44,9 @@ import org.apache.ignite.configuration.CacheConfiguration;
 import org.apache.ignite.events.DiscoveryEvent;
 import org.apache.ignite.events.Event;
 import org.apache.ignite.events.EventType;
-import org.apache.ignite.internal.ComputeMXBeanImpl;
 import org.apache.ignite.internal.GridKernalContext;
 import org.apache.ignite.internal.GridTopic;
 import org.apache.ignite.internal.IgniteInternalFuture;
-import org.apache.ignite.internal.QueryMXBeanImpl;
-import org.apache.ignite.internal.ServiceMXBeanImpl;
-import org.apache.ignite.internal.TransactionsMXBeanImpl;
 import org.apache.ignite.internal.managers.communication.GridIoPolicy;
 import org.apache.ignite.internal.managers.eventstorage.GridLocalEventListener;
 import org.apache.ignite.internal.processors.bulkload.BulkLoadAckClientParameters;
@@ -65,7 +56,6 @@ import org.apache.ignite.internal.processors.bulkload.BulkLoadProcessor;
 import org.apache.ignite.internal.processors.bulkload.BulkLoadStreamerWriter;
 import org.apache.ignite.internal.processors.cache.GridCacheContext;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxLocal;
-import org.apache.ignite.internal.processors.cache.mvcc.MvccUtils;
 import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
 import org.apache.ignite.internal.processors.query.GridQueryProperty;
 import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
@@ -91,6 +81,7 @@ import org.apache.ignite.internal.processors.query.h2.sql.GridSqlStatement;
 import org.apache.ignite.internal.processors.query.messages.GridQueryKillRequest;
 import org.apache.ignite.internal.processors.query.messages.GridQueryKillResponse;
 import org.apache.ignite.internal.processors.query.schema.SchemaOperationException;
+import org.apache.ignite.internal.sql.SqlCommandProcessor;
 import org.apache.ignite.internal.sql.command.SqlAlterTableCommand;
 import org.apache.ignite.internal.sql.command.SqlAlterUserCommand;
 import org.apache.ignite.internal.sql.command.SqlBeginTransactionCommand;
@@ -101,13 +92,7 @@ import org.apache.ignite.internal.sql.command.SqlCreateIndexCommand;
 import org.apache.ignite.internal.sql.command.SqlCreateUserCommand;
 import org.apache.ignite.internal.sql.command.SqlDropIndexCommand;
 import org.apache.ignite.internal.sql.command.SqlDropUserCommand;
-import org.apache.ignite.internal.sql.command.SqlIndexColumn;
-import org.apache.ignite.internal.sql.command.SqlKillComputeTaskCommand;
-import org.apache.ignite.internal.sql.command.SqlKillContinuousQueryCommand;
 import org.apache.ignite.internal.sql.command.SqlKillQueryCommand;
-import org.apache.ignite.internal.sql.command.SqlKillScanQueryCommand;
-import org.apache.ignite.internal.sql.command.SqlKillServiceCommand;
-import org.apache.ignite.internal.sql.command.SqlKillTransactionCommand;
 import org.apache.ignite.internal.sql.command.SqlRollbackTransactionCommand;
 import org.apache.ignite.internal.sql.command.SqlSetStreamingCommand;
 import org.apache.ignite.internal.util.future.GridFinishedFuture;
@@ -143,19 +128,13 @@ import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlQueryPar
 /**
  * Processor responsible for execution of all non-SELECT and non-DML commands.
  */
-public class CommandProcessor {
-    /** Kernal context. */
-    private final GridKernalContext ctx;
-
+public class CommandProcessor extends SqlCommandProcessor {
     /** Schema manager. */
     private final SchemaManager schemaMgr;
 
     /** H2 Indexing. */
     private final IgniteH2Indexing idx;
 
-    /** Logger. */
-    private final IgniteLogger log;
-
     /** Is backward compatible handling of UUID through DDL enabled. */
     private static final boolean handleUuidAsByte =
             IgniteSystemProperties.getBoolean(IgniteSystemProperties.IGNITE_SQL_UUID_DDL_BYTE_FORMAT, false);
@@ -189,11 +168,10 @@ public class CommandProcessor {
      * @param schemaMgr Schema manager.
      */
     public CommandProcessor(GridKernalContext ctx, SchemaManager schemaMgr, IgniteH2Indexing idx) {
-        this.ctx = ctx;
+        super(ctx, schemaMgr);
+
         this.schemaMgr = schemaMgr;
         this.idx = idx;
-
-        log = ctx.log(CommandProcessor.class);
     }
 
     /**
@@ -410,29 +388,16 @@ public class CommandProcessor {
         if (cmdNative != null) {
             assert cmdH2 == null;
 
-            if (isDdl(cmdNative))
-                runCommandNativeDdl(sql, cmdNative);
-            else if (cmdNative instanceof SqlBulkLoadCommand) {
-                res = processBulkLoadCommand((SqlBulkLoadCommand) cmdNative, qryId);
+            if (isCommandSupported(cmdNative)) {
+                FieldsQueryCursor<List<?>> resNative = runNativeCommand(sql, cmdNative, params, cliCtx, qryId);
 
-                unregister = false;
+                if (resNative != null) {
+                    res = resNative;
+                    unregister = true;
+                }
             }
-            else if (cmdNative instanceof SqlSetStreamingCommand)
-                processSetStreamingCommand((SqlSetStreamingCommand)cmdNative, cliCtx);
-            else if (cmdNative instanceof SqlKillQueryCommand)
-                processKillQueryCommand((SqlKillQueryCommand) cmdNative);
-            else if (cmdNative instanceof SqlKillComputeTaskCommand)
-                processKillComputeTaskCommand((SqlKillComputeTaskCommand) cmdNative);
-            else if (cmdNative instanceof SqlKillTransactionCommand)
-                processKillTxCommand((SqlKillTransactionCommand) cmdNative);
-            else if (cmdNative instanceof SqlKillServiceCommand)
-                processKillServiceTaskCommand((SqlKillServiceCommand) cmdNative);
-            else if (cmdNative instanceof SqlKillScanQueryCommand)
-                processKillScanQueryCommand((SqlKillScanQueryCommand) cmdNative);
-            else if (cmdNative instanceof SqlKillContinuousQueryCommand)
-                processKillContinuousQueryCommand((SqlKillContinuousQueryCommand) cmdNative);
             else
-                processTxCommand(cmdNative, params);
+                throw new UnsupportedOperationException("Unsupported command: " + cmdNative);
         }
         else {
             assert cmdH2 != null;
@@ -444,6 +409,44 @@ public class CommandProcessor {
     }
 
     /**
+     * Execute native command.
+     *
+     * @param sql SQL.
+     * @param cmdNative Native command.
+     * @param params Parameters.
+     * @param cliCtx Client context.
+     * @param qryId Running query ID.
+     * @return Result.
+     */
+    public FieldsQueryCursor<List<?>> runNativeCommand(String sql, SqlCommand cmdNative,
+        QueryParameters params, @Nullable SqlClientContext cliCtx, Long qryId) throws IgniteCheckedException {
+        if (super.isCommandSupported(cmdNative))
+            return runCommand(sql, cmdNative, cliCtx);
+
+        if (cmdNative instanceof SqlBulkLoadCommand)
+            return processBulkLoadCommand((SqlBulkLoadCommand) cmdNative, qryId);
+        else if (cmdNative instanceof SqlSetStreamingCommand)
+            processSetStreamingCommand((SqlSetStreamingCommand)cmdNative, cliCtx);
+        else if (cmdNative instanceof SqlKillQueryCommand)
+            processKillQueryCommand((SqlKillQueryCommand) cmdNative);
+        else
+            processTxCommand(cmdNative, params);
+
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isCommandSupported(SqlCommand cmd) {
+        return super.isCommandSupported(cmd)
+            || cmd instanceof SqlBeginTransactionCommand
+            || cmd instanceof SqlCommitTransactionCommand
+            || cmd instanceof SqlRollbackTransactionCommand
+            || cmd instanceof SqlBulkLoadCommand
+            || cmd instanceof SqlSetStreamingCommand
+            || cmd instanceof SqlKillQueryCommand;
+    }
+
+    /**
      * Process kill query command
      *
      * @param cmd Command.
@@ -510,187 +513,6 @@ public class CommandProcessor {
     }
 
     /**
-     * Process kill scan query cmd.
-     *
-     * @param cmd Command.
-     */
-    private void processKillScanQueryCommand(SqlKillScanQueryCommand cmd) {
-        new QueryMXBeanImpl(ctx)
-            .cancelScan(cmd.getOriginNodeId(), cmd.getCacheName(), cmd.getQryId());
-    }
-
-    /**
-     * Process kill compute task command.
-     *
-     * @param cmd Command.
-     */
-    private void processKillComputeTaskCommand(SqlKillComputeTaskCommand cmd) {
-        new ComputeMXBeanImpl(ctx).cancel(cmd.getSessionId());
-    }
-
-    /**
-     * Process kill transaction cmd.
-     *
-     * @param cmd Command.
-     */
-    private void processKillTxCommand(SqlKillTransactionCommand cmd) {
-        new TransactionsMXBeanImpl(ctx).cancel(cmd.getXid());
-    }
-
-    /**
-     * Process kill service command.
-     *
-     * @param cmd Command.
-     */
-    private void processKillServiceTaskCommand(SqlKillServiceCommand cmd) {
-        new ServiceMXBeanImpl(ctx).cancel(cmd.getName());
-    }
-
-    /**
-     * Process kill continuous query cmd.
-     *
-     * @param cmd Command.
-     */
-    private void processKillContinuousQueryCommand(SqlKillContinuousQueryCommand cmd) {
-        new QueryMXBeanImpl(ctx).cancelContinuous(cmd.getOriginNodeId(), cmd.getRoutineId());
-    }
-
-    /**
-     * Run DDL statement.
-     *
-     * @param sql Original SQL.
-     * @param cmd Command.
-     */
-    private void runCommandNativeDdl(String sql, SqlCommand cmd) {
-        IgniteInternalFuture fut = null;
-
-        try {
-            isDdlOnSchemaSupported(cmd.schemaName());
-
-            finishActiveTxIfNecessary();
-
-            if (cmd instanceof SqlCreateIndexCommand) {
-                SqlCreateIndexCommand cmd0 = (SqlCreateIndexCommand)cmd;
-
-                GridH2Table tbl = schemaMgr.dataTable(cmd0.schemaName(), cmd0.tableName());
-
-                if (tbl == null)
-                    throw new SchemaOperationException(SchemaOperationException.CODE_TABLE_NOT_FOUND, cmd0.tableName());
-
-                assert tbl.rowDescriptor() != null;
-
-                ensureDdlSupported(tbl);
-
-                QueryIndex newIdx = new QueryIndex();
-
-                newIdx.setName(cmd0.indexName());
-
-                newIdx.setIndexType(cmd0.spatial() ? QueryIndexType.GEOSPATIAL : QueryIndexType.SORTED);
-
-                LinkedHashMap<String, Boolean> flds = new LinkedHashMap<>();
-
-                // Let's replace H2's table and property names by those operated by GridQueryProcessor.
-                GridQueryTypeDescriptor typeDesc = tbl.rowDescriptor().type();
-
-                for (SqlIndexColumn col : cmd0.columns()) {
-                    GridQueryProperty prop = typeDesc.property(col.name());
-
-                    if (prop == null)
-                        throw new SchemaOperationException(SchemaOperationException.CODE_COLUMN_NOT_FOUND, col.name());
-
-                    flds.put(prop.name(), !col.descending());
-                }
-
-                newIdx.setFields(flds);
-                newIdx.setInlineSize(cmd0.inlineSize());
-
-                fut = ctx.query().dynamicIndexCreate(tbl.cacheName(), cmd.schemaName(), typeDesc.tableName(),
-                    newIdx, cmd0.ifNotExists(), cmd0.parallel());
-            }
-            else if (cmd instanceof SqlDropIndexCommand) {
-                SqlDropIndexCommand cmd0 = (SqlDropIndexCommand)cmd;
-
-                GridH2Table tbl = schemaMgr.dataTableForIndex(cmd0.schemaName(), cmd0.indexName());
-
-                if (tbl != null) {
-                    ensureDdlSupported(tbl);
-
-                    fut = ctx.query().dynamicIndexDrop(tbl.cacheName(), cmd0.schemaName(), cmd0.indexName(),
-                        cmd0.ifExists());
-                }
-                else {
-                    if (cmd0.ifExists())
-                        fut = new GridFinishedFuture();
-                    else
-                        throw new SchemaOperationException(SchemaOperationException.CODE_INDEX_NOT_FOUND,
-                            cmd0.indexName());
-                }
-            }
-            else if (cmd instanceof SqlAlterTableCommand) {
-                SqlAlterTableCommand cmd0 = (SqlAlterTableCommand)cmd;
-
-                GridH2Table tbl = schemaMgr.dataTable(cmd0.schemaName(), cmd0.tableName());
-
-                if (tbl == null) {
-                    throw new SchemaOperationException(SchemaOperationException.CODE_TABLE_NOT_FOUND,
-                        cmd0.tableName());
-                }
-
-                Boolean logging = cmd0.logging();
-
-                assert logging != null : "Only LOGGING/NOLOGGING are supported at the moment.";
-
-                IgniteCluster cluster = ctx.grid().cluster();
-
-                if (logging) {
-                    boolean res = cluster.enableWal(tbl.cacheName());
-
-                    if (!res)
-                        throw new IgniteSQLException("Logging already enabled for table: " + cmd0.tableName());
-                }
-                else {
-                    boolean res = cluster.disableWal(tbl.cacheName());
-
-                    if (!res)
-                        throw new IgniteSQLException("Logging already disabled for table: " + cmd0.tableName());
-                }
-
-                fut = new GridFinishedFuture();
-            }
-            else if (cmd instanceof SqlCreateUserCommand) {
-                SqlCreateUserCommand addCmd = (SqlCreateUserCommand)cmd;
-
-                ctx.security().createUser(addCmd.userName(), addCmd.password().toCharArray());
-            }
-            else if (cmd instanceof SqlAlterUserCommand) {
-                SqlAlterUserCommand altCmd = (SqlAlterUserCommand)cmd;
-
-                ctx.security().alterUser(altCmd.userName(), altCmd.password().toCharArray());
-            }
-            else if (cmd instanceof SqlDropUserCommand) {
-                SqlDropUserCommand dropCmd = (SqlDropUserCommand)cmd;
-
-                ctx.security().dropUser(dropCmd.userName());
-            }
-            else
-                throw new IgniteSQLException("Unsupported DDL operation: " + sql,
-                    IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
-
-            if (fut != null)
-                fut.get();
-        }
-        catch (SchemaOperationException e) {
-            throw convert(e);
-        }
-        catch (IgniteSQLException e) {
-            throw e;
-        }
-        catch (Exception e) {
-            throw new IgniteSQLException(e.getMessage(), e);
-        }
-    }
-
-    /**
      * Execute DDL statement.
      *
      * @param sql SQL.
@@ -714,7 +536,7 @@ public class CommandProcessor {
 
                 assert tbl.rowDescriptor() != null;
 
-                ensureDdlSupported(tbl);
+                ensureDdlSupported(tbl.cacheInfo());
 
                 QueryIndex newIdx = new QueryIndex();
 
@@ -749,7 +571,7 @@ public class CommandProcessor {
                 GridH2Table tbl = schemaMgr.dataTableForIndex(cmd.schemaName(), cmd.indexName());
 
                 if (tbl != null) {
-                    ensureDdlSupported(tbl);
+                    ensureDdlSupported(tbl.cacheInfo());
 
                     fut = ctx.query().dynamicIndexDrop(tbl.cacheName(), cmd.schemaName(), cmd.indexName(),
                         cmd.ifExists());
@@ -969,35 +791,6 @@ public class CommandProcessor {
     }
 
     /**
-     * Check if table supports DDL statement.
-     *
-     * @param tbl Table.
-     * @throws IgniteSQLException If failed.
-     */
-    private static void ensureDdlSupported(GridH2Table tbl) throws IgniteSQLException {
-        if (tbl.cacheInfo().config().getCacheMode() == CacheMode.LOCAL)
-            throw new IgniteSQLException("DDL statements are not supported on LOCAL caches",
-                IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
-    }
-
-    /**
-     * Commits active transaction if exists.
-     *
-     * @throws IgniteCheckedException If failed.
-     */
-    private void finishActiveTxIfNecessary() throws IgniteCheckedException {
-        try (GridNearTxLocal tx = MvccUtils.tx(ctx)) {
-            if (tx == null)
-                return;
-
-            if (!tx.isRollbackOnly())
-                tx.commit();
-            else
-                tx.rollback();
-        }
-    }
-
-    /**
      * Convert this statement to query entity and do Ignite specific sanity checks on the way.
      * @return Query entity mimicking this SQL statement.
      */
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
index 7543c27..d1185e3 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
@@ -2101,7 +2101,7 @@ public class IgniteH2Indexing implements GridQueryIndexing {
 
         longRunningQryMgr = new LongRunningQueryManager(ctx);
 
-        parser = new QueryParser(this, connMgr);
+        parser = new QueryParser(this, connMgr, cmd -> cmdProc.isCommandSupported(cmd));
 
         schemaMgr = new SchemaManager(ctx, connMgr);
         schemaMgr.start(ctx.config().getSqlConfiguration().getSqlSchemas());
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/QueryParser.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/QueryParser.java
index 9f01516..2eec586 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/QueryParser.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/QueryParser.java
@@ -23,6 +23,7 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Predicate;
 import java.util.regex.Pattern;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteLogger;
@@ -59,24 +60,7 @@ import org.apache.ignite.internal.processors.tracing.MTC.TraceSurroundings;
 import org.apache.ignite.internal.sql.SqlParseException;
 import org.apache.ignite.internal.sql.SqlParser;
 import org.apache.ignite.internal.sql.SqlStrictParseException;
-import org.apache.ignite.internal.sql.command.SqlAlterTableCommand;
-import org.apache.ignite.internal.sql.command.SqlAlterUserCommand;
-import org.apache.ignite.internal.sql.command.SqlBeginTransactionCommand;
-import org.apache.ignite.internal.sql.command.SqlBulkLoadCommand;
 import org.apache.ignite.internal.sql.command.SqlCommand;
-import org.apache.ignite.internal.sql.command.SqlCommitTransactionCommand;
-import org.apache.ignite.internal.sql.command.SqlCreateIndexCommand;
-import org.apache.ignite.internal.sql.command.SqlCreateUserCommand;
-import org.apache.ignite.internal.sql.command.SqlDropIndexCommand;
-import org.apache.ignite.internal.sql.command.SqlDropUserCommand;
-import org.apache.ignite.internal.sql.command.SqlKillComputeTaskCommand;
-import org.apache.ignite.internal.sql.command.SqlKillContinuousQueryCommand;
-import org.apache.ignite.internal.sql.command.SqlKillQueryCommand;
-import org.apache.ignite.internal.sql.command.SqlKillScanQueryCommand;
-import org.apache.ignite.internal.sql.command.SqlKillServiceCommand;
-import org.apache.ignite.internal.sql.command.SqlKillTransactionCommand;
-import org.apache.ignite.internal.sql.command.SqlRollbackTransactionCommand;
-import org.apache.ignite.internal.sql.command.SqlSetStreamingCommand;
 import org.apache.ignite.internal.util.GridBoundedConcurrentLinkedHashMap;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.U;
@@ -112,6 +96,9 @@ public class QueryParser {
     /** Query parser metrics holder. */
     private final QueryParserMetricsHolder metricsHolder;
 
+    /** Predicate to filter supported native commands. */
+    private final Predicate<SqlCommand> nativeCmdPredicate;
+
     /** */
     private volatile GridBoundedConcurrentLinkedHashMap<QueryDescriptor, QueryParserCacheEntry> cache =
         new GridBoundedConcurrentLinkedHashMap<>(CACHE_SIZE);
@@ -121,10 +108,12 @@ public class QueryParser {
      *
      * @param idx Indexing instance.
      * @param connMgr Connection manager.
+     * @param nativeCmdPredicate Predicate to filter supported native commands.
      */
-    public QueryParser(IgniteH2Indexing idx, ConnectionManager connMgr) {
+    public QueryParser(IgniteH2Indexing idx, ConnectionManager connMgr, Predicate<SqlCommand> nativeCmdPredicate) {
         this.idx = idx;
         this.connMgr = connMgr;
+        this.nativeCmdPredicate = nativeCmdPredicate;
 
         this.log = idx.kernalContext().log(QueryParser.class);
         this.metricsHolder = new QueryParserMetricsHolder(idx.kernalContext().metric());
@@ -264,24 +253,7 @@ public class QueryParser {
 
             assert nativeCmd != null : "Empty query. Parser met end of data";
 
-            if (!(nativeCmd instanceof SqlCreateIndexCommand
-                || nativeCmd instanceof SqlDropIndexCommand
-                || nativeCmd instanceof SqlBeginTransactionCommand
-                || nativeCmd instanceof SqlCommitTransactionCommand
-                || nativeCmd instanceof SqlRollbackTransactionCommand
-                || nativeCmd instanceof SqlBulkLoadCommand
-                || nativeCmd instanceof SqlAlterTableCommand
-                || nativeCmd instanceof SqlSetStreamingCommand
-                || nativeCmd instanceof SqlCreateUserCommand
-                || nativeCmd instanceof SqlAlterUserCommand
-                || nativeCmd instanceof SqlDropUserCommand
-                || nativeCmd instanceof SqlKillQueryCommand
-                || nativeCmd instanceof SqlKillComputeTaskCommand
-                || nativeCmd instanceof SqlKillServiceCommand
-                || nativeCmd instanceof SqlKillTransactionCommand
-                || nativeCmd instanceof SqlKillScanQueryCommand
-                || nativeCmd instanceof SqlKillContinuousQueryCommand)
-            )
+            if (!nativeCmdPredicate.test(nativeCmd))
                 return null;
 
             SqlFieldsQuery newQry = cloneFieldsQuery(qry).setSql(parser.lastCommandSql());
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/SchemaManager.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/SchemaManager.java
index 89498ee..67ffad5 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/SchemaManager.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/SchemaManager.java
@@ -49,6 +49,7 @@ import org.apache.ignite.internal.processors.cache.GridCacheContextInfo;
 import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
 import org.apache.ignite.internal.processors.cache.query.QueryTable;
 import org.apache.ignite.internal.processors.query.GridQueryIndexDescriptor;
+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.QueryField;
@@ -86,7 +87,7 @@ import static org.apache.ignite.internal.processors.metric.impl.MetricUtils.metr
 /**
  * Schema manager. Responsible for all manipulations on schema objects.
  */
-public class SchemaManager {
+public class SchemaManager implements GridQuerySchemaManager {
     /** */
     public static final String SQL_SCHEMA_VIEW = "schemas";
 
@@ -890,6 +891,27 @@ public class SchemaManager {
         return null;
     }
 
+    /** {@inheritDoc} */
+    @Override public GridQueryTypeDescriptor typeDescriptorForTable(String schemaName, String tableName) {
+        GridH2Table dataTable = dataTable(schemaName, tableName);
+
+        return dataTable == null ? null : dataTable.rowDescriptor().type();
+    }
+
+    /** {@inheritDoc} */
+    @Override public GridQueryTypeDescriptor typeDescriptorForIndex(String schemaName, String idxName) {
+        GridH2Table dataTable = dataTableForIndex(schemaName, idxName);
+
+        return dataTable == null ? null : dataTable.rowDescriptor().type();
+    }
+
+    /** {@inheritDoc} */
+    @Override public <K, V> GridCacheContextInfo<K, V> cacheInfoForTable(String schemaName, String tableName) {
+        GridH2Table dataTable = dataTable(schemaName, tableName);
+
+        return dataTable == null ? null : (GridCacheContextInfo<K, V>)dataTable.cacheInfo();
+    }
+
     /** */
     private SchemaChangeListener schemaChangeListener(GridKernalContext ctx) {
         List<SchemaChangeListener> subscribers = new ArrayList<>(ctx.internalSubscriptionProcessor().getSchemaChangeSubscribers());