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 11ef1ab  IGNITE-14857 ALTER TABLE command - Fixes #9185.
11ef1ab is described below

commit 11ef1abda9852db2f4f11c7233a54b09b57e7a95
Author: Aleksey Plekhanov <[email protected]>
AuthorDate: Tue Jul 20 10:58:29 2021 +0300

    IGNITE-14857 ALTER TABLE command - Fixes #9185.
    
    Signed-off-by: Aleksey Plekhanov <[email protected]>
---
 modules/calcite/src/main/codegen/config.fmpp       |  11 +-
 .../src/main/codegen/includes/parserImpls.ftl      |  75 +++++
 .../query/calcite/exec/ddl/DdlCommandHandler.java  | 158 +++++++++-
 .../calcite/exec/ddl/NativeCommandHandler.java     |  66 +----
 .../query/calcite/exec/ddl/SchemaManager.java      |  82 ++++++
 .../prepare/ddl/AbstractAlterTableCommand.java     |  74 +++++
 .../calcite/prepare/ddl/AlterTableAddCommand.java  |  61 ++++
 .../calcite/prepare/ddl/AlterTableDropCommand.java |  61 ++++
 .../prepare/ddl/DdlSqlToCommandConverter.java      |  66 +++++
 .../prepare/ddl/SqlToNativeCommandConverter.java   |  17 +-
 .../calcite/sql/IgniteAbstractSqlAlterTable.java   |  84 ++++++
 .../query/calcite/sql/IgniteSqlAlterTable.java     |  55 ++++
 .../calcite/sql/IgniteSqlAlterTableAddColumn.java  |  75 +++++
 .../calcite/sql/IgniteSqlAlterTableDropColumn.java |  75 +++++
 .../integration/AbstractDdlIntegrationTest.java    |  20 +-
 .../integration/TableDdlIntegrationTest.java       | 323 ++++++++++++++++++++-
 .../query/calcite/sql/SqlDdlParserTest.java        | 210 +++++++++++++-
 .../processors/odbc/jdbc/JdbcRequestHandler.java   |   8 +-
 .../processors/query/GridQueryProcessor.java       |  20 +-
 .../internal/sql/command/SqlAlterTableCommand.java |  18 ++
 20 files changed, 1440 insertions(+), 119 deletions(-)

diff --git a/modules/calcite/src/main/codegen/config.fmpp 
b/modules/calcite/src/main/codegen/config.fmpp
index f40d158..376aa2f 100644
--- a/modules/calcite/src/main/codegen/config.fmpp
+++ b/modules/calcite/src/main/codegen/config.fmpp
@@ -54,6 +54,8 @@ data: {
       "INDEX"
       "PARALLEL"
       "INLINE_SIZE"
+      "LOGGING"
+      "NOLOGGING"
     ]
 
     # List of non-reserved keywords to add;
@@ -73,6 +75,8 @@ data: {
       "ENCRYPTED"
       "PARALLEL"
       "INLINE_SIZE"
+      "LOGGING"
+      "NOLOGGING"
 
       # The following keywords are reserved in core Calcite,
       # are reserved in some version of SQL,
@@ -582,6 +586,7 @@ data: {
     # Return type of method implementation should be 'SqlNode'.
     # Example: "SqlShowDatabases()", "SqlShowTables()".
     statementParserMethods: [
+      "SqlAlterTable()"
     ]
 
     # List of methods for parsing extensions to "CREATE [OR REPLACE]" calls.
@@ -600,9 +605,9 @@ data: {
       "SqlDropIndex"
     ]
 
-    # List of methods for parsing extensions to "DROP" calls.
-    # Each must accept arguments "(SqlParserPos pos)".
-    # Example: "SqlDropSchema".
+    # List of methods for parsing extensions to "ALTER <scope>" calls.
+    # Where scope is SYSTEM or SESSION.
+    # Each must accept arguments "(SqlParserPos pos, String scope)".
     alterStatementParserMethods: [
     ]
 
diff --git a/modules/calcite/src/main/codegen/includes/parserImpls.ftl 
b/modules/calcite/src/main/codegen/includes/parserImpls.ftl
index aad7cfb..811f4f7 100644
--- a/modules/calcite/src/main/codegen/includes/parserImpls.ftl
+++ b/modules/calcite/src/main/codegen/includes/parserImpls.ftl
@@ -281,3 +281,78 @@ void InfixCast(List<Object> list, ExprContext exprContext, 
Span s) :
         list.add(dt);
     }
 }
+
+SqlNodeList ColumnWithTypeList() :
+{
+    final Span s;
+    List<SqlNode> list = new ArrayList<SqlNode>();
+    SqlNode col;
+}
+{
+    <LPAREN> { s = span(); }
+    col = ColumnWithType() { list.add(col); }
+    (
+        <COMMA> col = ColumnWithType() { list.add(col); }
+    )*
+    <RPAREN> {
+        return new SqlNodeList(list, s.end(this));
+    }
+}
+
+SqlNode ColumnWithType() :
+{
+    SqlIdentifier id;
+    SqlDataTypeSpec type;
+    boolean nullable = true;
+    final Span s = Span.of();
+}
+{
+    id = SimpleIdentifier()
+    type = DataType()
+    [
+        <NOT> <NULL> {
+            nullable = false;
+        }
+    ]
+    {
+        return SqlDdlNodes.column(s.add(id).end(this), id, 
type.withNullable(nullable), null, null);
+    }
+}
+
+SqlNodeList ColumnWithTypeOrList() :
+{
+    SqlNode col;
+    SqlNodeList list;
+}
+{
+    col = ColumnWithType() { return new 
SqlNodeList(Collections.singletonList(col), col.getParserPosition()); }
+|
+    list = ColumnWithTypeList() { return list; }
+}
+
+SqlNode SqlAlterTable() :
+{
+    final Span s;
+    final boolean ifExists;
+    final SqlIdentifier id;
+    boolean colIgnoreErr;
+    SqlNode col;
+    SqlNodeList cols;
+}
+{
+    <ALTER> { s = span(); }
+    <TABLE> ifExists = IfExistsOpt() id = CompoundIdentifier()
+    (
+        <LOGGING> { return new IgniteSqlAlterTable(s.end(this), ifExists, id, 
true); }
+    |
+        <NOLOGGING>  { return new IgniteSqlAlterTable(s.end(this), ifExists, 
id, false); }
+    |
+        <ADD> [<COLUMN>] colIgnoreErr = IfNotExistsOpt() cols = 
ColumnWithTypeOrList() {
+            return new IgniteSqlAlterTableAddColumn(s.end(this), ifExists, id, 
colIgnoreErr, cols);
+        }
+    |
+        <DROP> [<COLUMN>] colIgnoreErr = IfExistsOpt() cols = 
SimpleIdentifierOrList() {
+            return new IgniteSqlAlterTableDropColumn(s.end(this), ifExists, 
id, colIgnoreErr, cols);
+        }
+    )
+}
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 405be78..eedd0bc 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
@@ -18,10 +18,12 @@
 package org.apache.ignite.internal.processors.query.calcite.exec.ddl;
 
 import java.lang.reflect.Type;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
@@ -31,13 +33,20 @@ import org.apache.calcite.schema.Table;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.cache.QueryEntity;
 import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.cache.GridCacheContextInfo;
 import org.apache.ignite.internal.processors.cache.GridCacheProcessor;
 import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
 import org.apache.ignite.internal.processors.query.GridQueryProcessor;
+import org.apache.ignite.internal.processors.query.GridQuerySchemaManager;
+import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
 import org.apache.ignite.internal.processors.query.IgniteSQLException;
 import org.apache.ignite.internal.processors.query.QueryEntityEx;
+import org.apache.ignite.internal.processors.query.QueryField;
 import org.apache.ignite.internal.processors.query.QueryUtils;
 import 
org.apache.ignite.internal.processors.query.calcite.prepare.PlanningContext;
+import 
org.apache.ignite.internal.processors.query.calcite.prepare.ddl.AlterTableAddCommand;
+import 
org.apache.ignite.internal.processors.query.calcite.prepare.ddl.AlterTableDropCommand;
 import 
org.apache.ignite.internal.processors.query.calcite.prepare.ddl.ColumnDefinition;
 import 
org.apache.ignite.internal.processors.query.calcite.prepare.ddl.CreateTableCommand;
 import 
org.apache.ignite.internal.processors.query.calcite.prepare.ddl.DdlCommand;
@@ -69,7 +78,10 @@ public class DdlCommandHandler {
     private final Supplier<SchemaPlus> schemaSupp;
 
     /** */
-    private final NativeCommandHandler nativeCmdHandler;
+    private final NativeCommandHandler nativeCmdHnd;
+
+    /** */
+    private final GridQuerySchemaManager schemaMgr;
 
     /** */
     public DdlCommandHandler(Supplier<GridQueryProcessor> qryProcessorSupp, 
GridCacheProcessor cacheProcessor,
@@ -78,24 +90,36 @@ public class DdlCommandHandler {
         this.cacheProcessor = cacheProcessor;
         this.security = security;
         this.schemaSupp = schemaSupp;
-        nativeCmdHandler = new 
NativeCommandHandler(cacheProcessor.context().kernalContext(), schemaSupp);
+        schemaMgr = new SchemaManager(schemaSupp);
+        nativeCmdHnd = new 
NativeCommandHandler(cacheProcessor.context().kernalContext(), schemaMgr);
     }
 
     /** */
     public void handle(UUID qryId, DdlCommand cmd, PlanningContext pctx) 
throws IgniteCheckedException {
-        if (cmd instanceof CreateTableCommand)
-            handle0(pctx, (CreateTableCommand)cmd);
+        try {
+            if (cmd instanceof CreateTableCommand)
+                handle0(pctx, (CreateTableCommand)cmd);
 
-        else if (cmd instanceof DropTableCommand)
-            handle0(pctx, (DropTableCommand)cmd);
+            else if (cmd instanceof DropTableCommand)
+                handle0(pctx, (DropTableCommand)cmd);
 
-        else if (cmd instanceof NativeCommandWrapper)
-            nativeCmdHandler.handle(qryId, (NativeCommandWrapper)cmd, pctx);
+            else if (cmd instanceof AlterTableAddCommand)
+                handle0(pctx, (AlterTableAddCommand)cmd);
 
-        else {
-            throw new IgniteSQLException("Unsupported DDL operation [" +
-                "cmdName=" + (cmd == null ? null : 
cmd.getClass().getSimpleName()) + "; " +
-                "querySql=\"" + pctx.query() + "\"]", 
IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+            else if (cmd instanceof AlterTableDropCommand)
+                handle0(pctx, (AlterTableDropCommand)cmd);
+
+            else if (cmd instanceof NativeCommandWrapper)
+                nativeCmdHnd.handle(qryId, (NativeCommandWrapper)cmd, pctx);
+
+            else {
+                throw new IgniteSQLException("Unsupported DDL operation [" +
+                    "cmdName=" + (cmd == null ? null : 
cmd.getClass().getSimpleName()) + "; " +
+                    "querySql=\"" + pctx.query() + "\"]", 
IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+            }
+        }
+        catch (SchemaOperationException e) {
+            throw convert(e);
         }
     }
 
@@ -174,6 +198,116 @@ public class DdlCommandHandler {
     }
 
     /** */
+    private void handle0(PlanningContext pctx, AlterTableAddCommand cmd) 
throws IgniteCheckedException {
+        isDdlOnSchemaSupported(cmd.schemaName());
+
+        GridQueryTypeDescriptor typeDesc = 
schemaMgr.typeDescriptorForTable(cmd.schemaName(), cmd.tableName());
+
+        if (typeDesc == null) {
+            if (!cmd.ifTableExists())
+                throw new 
SchemaOperationException(SchemaOperationException.CODE_TABLE_NOT_FOUND, 
cmd.tableName());
+        }
+        else {
+            if (QueryUtils.isSqlType(typeDesc.valueClass())) {
+                throw new SchemaOperationException("Cannot add column(s) 
because table was created " +
+                    "with WRAP_VALUE=false option.");
+            }
+
+            List<QueryField> cols = new ArrayList<>(cmd.columns().size());
+
+            boolean allFieldsNullable = true;
+
+            for (ColumnDefinition col : cmd.columns()) {
+                if (typeDesc.fields().containsKey(col.name())) {
+                    if (!cmd.ifColumnNotExists())
+                        throw new 
SchemaOperationException(SchemaOperationException.CODE_COLUMN_EXISTS, 
col.name());
+                    else
+                        continue;
+                }
+
+                Type javaType = pctx.typeFactory().getResultClass(col.type());
+
+                String typeName = javaType instanceof Class ? 
((Class<?>)javaType).getName() : javaType.getTypeName();
+
+                Integer precession = col.precision();
+                Integer scale = col.scale();
+
+                QueryField field = new QueryField(col.name(), typeName,
+                    col.type().isNullable(), col.defaultValue(),
+                    precession == null ? -1 : precession, scale == null ? -1 : 
scale);
+
+                cols.add(field);
+
+                allFieldsNullable &= field.isNullable();
+            }
+
+            if (!F.isEmpty(cols)) {
+                GridCacheContextInfo<?, ?> ctxInfo = 
schemaMgr.cacheInfoForTable(cmd.schemaName(), cmd.tableName());
+
+                assert ctxInfo != null;
+
+                if (!allFieldsNullable)
+                    QueryUtils.checkNotNullAllowed(ctxInfo.config());
+
+                qryProcessorSupp.get().dynamicColumnAdd(ctxInfo.name(), 
cmd.schemaName(),
+                    typeDesc.tableName(), cols, cmd.ifTableExists(), 
cmd.ifColumnNotExists()).get();
+            }
+        }
+    }
+
+    /** */
+    private void handle0(PlanningContext pctx, AlterTableDropCommand cmd) 
throws IgniteCheckedException {
+        isDdlOnSchemaSupported(cmd.schemaName());
+
+        GridQueryTypeDescriptor typeDesc = 
schemaMgr.typeDescriptorForTable(cmd.schemaName(), cmd.tableName());
+
+        if (typeDesc == null) {
+            if (!cmd.ifTableExists())
+                throw new 
SchemaOperationException(SchemaOperationException.CODE_TABLE_NOT_FOUND, 
cmd.tableName());
+        }
+        else {
+            GridCacheContextInfo<?, ?> ctxInfo = 
schemaMgr.cacheInfoForTable(cmd.schemaName(), cmd.tableName());
+
+            GridCacheContext<?, ?> cctx = ctxInfo.cacheContext();
+
+            assert cctx != null;
+
+            if (cctx.mvccEnabled()) {
+                throw new IgniteSQLException("Cannot drop column(s) with 
enabled MVCC. " +
+                    "Operation is unsupported at the moment.", 
IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+            }
+
+            if (QueryUtils.isSqlType(typeDesc.valueClass())) {
+                throw new SchemaOperationException("Cannot drop column(s) 
because table was created " +
+                    "with WRAP_VALUE=false option.");
+            }
+
+            List<String> cols = new ArrayList<>(cmd.columns().size());
+
+            for (String colName : cmd.columns()) {
+                if (!typeDesc.fields().containsKey(colName)) {
+                    if (!cmd.ifColumnExists())
+                        throw new 
SchemaOperationException(SchemaOperationException.CODE_COLUMN_NOT_FOUND, 
colName);
+                    else
+                        continue;
+                }
+
+                SchemaOperationException err = 
QueryUtils.validateDropColumn(typeDesc, colName);
+
+                if (err != null)
+                    throw err;
+
+                cols.add(colName);
+            }
+
+            if (!F.isEmpty(cols)) {
+                qryProcessorSupp.get().dynamicColumnRemove(ctxInfo.name(), 
cmd.schemaName(),
+                    typeDesc.tableName(), cols, cmd.ifTableExists(), 
cmd.ifColumnExists()).get();
+            }
+        }
+    }
+
+    /** */
     private QueryEntity toQueryEntity(CreateTableCommand cmd, PlanningContext 
pctx) {
         QueryEntity res = new QueryEntity();
 
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
index 0fc2f1c..d2b0738 100644
--- 
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
@@ -19,18 +19,12 @@ 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;
 
 /**
@@ -43,8 +37,8 @@ public class NativeCommandHandler {
     /**
      * @param ctx Context.
      */
-    public NativeCommandHandler(GridKernalContext ctx, Supplier<SchemaPlus> 
schemaSupp) {
-        proc = new SqlCommandProcessor(ctx, new SchemaManager(schemaSupp));
+    public NativeCommandHandler(GridKernalContext ctx, GridQuerySchemaManager 
schemaMgr) {
+        proc = new SqlCommandProcessor(ctx, schemaMgr);
     }
 
     /**
@@ -57,60 +51,4 @@ public class NativeCommandHandler {
 
         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/exec/ddl/SchemaManager.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ddl/SchemaManager.java
new file mode 100644
index 0000000..cbf375f
--- /dev/null
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ddl/SchemaManager.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.exec.ddl;
+
+import java.util.function.Supplier;
+import org.apache.calcite.schema.SchemaPlus;
+import org.apache.calcite.schema.Table;
+import org.apache.ignite.internal.processors.cache.GridCacheContextInfo;
+import org.apache.ignite.internal.processors.query.GridQuerySchemaManager;
+import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
+import org.apache.ignite.internal.processors.query.calcite.schema.IgniteTable;
+
+/**
+ * Schema manager.
+ */
+class SchemaManager implements GridQuerySchemaManager {
+    /** Schema holder. */
+    private final Supplier<SchemaPlus> schemaSupp;
+
+    /**
+     * @param schemaSupp Schema supplier.
+     */
+    SchemaManager(Supplier<SchemaPlus> schemaSupp) {
+        this.schemaSupp = schemaSupp;
+    }
+
+    /** {@inheritDoc} */
+    @Override public GridQueryTypeDescriptor typeDescriptorForTable(String 
schemaName, String tableName) {
+        SchemaPlus schema = schemaSupp.get().getSubSchema(schemaName);
+
+        if (schema == null)
+            return null;
+
+        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/AbstractAlterTableCommand.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/ddl/AbstractAlterTableCommand.java
new file mode 100644
index 0000000..f721385
--- /dev/null
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/ddl/AbstractAlterTableCommand.java
@@ -0,0 +1,74 @@
+/*
+ * 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;
+
+/**
+ * ALTER TABLE ... ADD/DROP COLUMN statement.
+ */
+public abstract class AbstractAlterTableCommand implements DdlCommand {
+    /** Schema name. */
+    protected String schemaName;
+
+    /** Table name. */
+    protected String tblName;
+
+    /** Quietly ignore this command if table is not exists. */
+    protected boolean ifTableExists;
+
+    /**
+     * @return Schema name.
+     */
+    public String schemaName() {
+        return schemaName;
+    }
+
+    /**
+     * @param schemaName Schema name.
+     */
+    public void schemaName(String schemaName) {
+        this.schemaName = schemaName;
+    }
+
+    /**
+     * @return Table name.
+     */
+    public String tableName() {
+        return tblName;
+    }
+
+    /**
+     * @param tblName Table name.
+     */
+    public void tableName(String tblName) {
+        this.tblName = tblName;
+    }
+
+    /**
+     * @return Quietly ignore this command if table is not exists.
+     */
+    public boolean ifTableExists() {
+        return ifTableExists;
+    }
+
+    /**
+     * @param ifTableExists Quietly ignore this command if table is not exists.
+     */
+    public void ifTableExists(boolean ifTableExists) {
+        this.ifTableExists = ifTableExists;
+    }
+}
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/ddl/AlterTableAddCommand.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/ddl/AlterTableAddCommand.java
new file mode 100644
index 0000000..78a4ff7
--- /dev/null
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/ddl/AlterTableAddCommand.java
@@ -0,0 +1,61 @@
+/*
+ * 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.Collections;
+import java.util.List;
+
+/**
+ * ALTER TABLE ... ADD COLUMN statement.
+ */
+@SuppressWarnings("AssignmentOrReturnOfFieldWithMutableType")
+public class AlterTableAddCommand extends AbstractAlterTableCommand {
+    /** Quietly ignore this command if column already exists. */
+    private boolean ifColumnNotExists;
+
+    /** Columns. */
+    private List<ColumnDefinition> cols;
+
+    /**
+     * @return Columns.
+     */
+    public List<ColumnDefinition> columns() {
+        return Collections.unmodifiableList(cols);
+    }
+
+    /**
+     * @param cols Columns.
+     */
+    public void columns(List<ColumnDefinition> cols) {
+        this.cols = cols;
+    }
+
+    /**
+     * @return Quietly ignore this command if column already exists.
+     */
+    public boolean ifColumnNotExists() {
+        return ifColumnNotExists;
+    }
+
+    /**
+     * @param ifColumnNotExists Quietly ignore this command if column already 
exists.
+     */
+    public void ifColumnNotExists(boolean ifColumnNotExists) {
+        this.ifColumnNotExists = ifColumnNotExists;
+    }
+}
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/ddl/AlterTableDropCommand.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/ddl/AlterTableDropCommand.java
new file mode 100644
index 0000000..5bf2b3e
--- /dev/null
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/ddl/AlterTableDropCommand.java
@@ -0,0 +1,61 @@
+/*
+ * 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.Collections;
+import java.util.List;
+
+/**
+ * ALTER TABLE ... DROP COLUMN statement.
+ */
+@SuppressWarnings("AssignmentOrReturnOfFieldWithMutableType")
+public class AlterTableDropCommand extends AbstractAlterTableCommand {
+    /** Quietly ignore this command if column is not exists. */
+    private boolean ifColumnExists;
+
+    /** Columns. */
+    private List<String> cols;
+
+    /**
+     * @return Columns.
+     */
+    public List<String> columns() {
+        return Collections.unmodifiableList(cols);
+    }
+
+    /**
+     * @param cols Columns.
+     */
+    public void columns(List<String> cols) {
+        this.cols = cols;
+    }
+
+    /**
+     * @return Quietly ignore this command if column is not exists.
+     */
+    public boolean ifColumnExists() {
+        return ifColumnExists;
+    }
+
+    /**
+     * @param ifColumnExists Quietly ignore this command if column is not 
exists.
+     */
+    public void ifColumnExists(boolean ifColumnExists) {
+        this.ifColumnExists = ifColumnExists;
+    }
+}
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 b5be013..9d1f981 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
@@ -45,6 +45,8 @@ import 
org.apache.ignite.internal.processors.query.IgniteSQLException;
 import org.apache.ignite.internal.processors.query.QueryUtils;
 import 
org.apache.ignite.internal.processors.query.calcite.prepare.IgnitePlanner;
 import 
org.apache.ignite.internal.processors.query.calcite.prepare.PlanningContext;
+import 
org.apache.ignite.internal.processors.query.calcite.sql.IgniteSqlAlterTableAddColumn;
+import 
org.apache.ignite.internal.processors.query.calcite.sql.IgniteSqlAlterTableDropColumn;
 import 
org.apache.ignite.internal.processors.query.calcite.sql.IgniteSqlCreateTable;
 import 
org.apache.ignite.internal.processors.query.calcite.sql.IgniteSqlCreateTableOption;
 import 
org.apache.ignite.internal.processors.query.calcite.sql.IgniteSqlCreateTableOptionEnum;
@@ -125,6 +127,12 @@ public class DdlSqlToCommandConverter {
         if (ddlNode instanceof SqlDropTable)
             return convertDropTable((SqlDropTable)ddlNode, ctx);
 
+        if (ddlNode instanceof IgniteSqlAlterTableAddColumn)
+            return convertAlterTableAdd((IgniteSqlAlterTableAddColumn)ddlNode, 
ctx);
+
+        if (ddlNode instanceof IgniteSqlAlterTableDropColumn)
+            return 
convertAlterTableDrop((IgniteSqlAlterTableDropColumn)ddlNode, ctx);
+
         if (SqlToNativeCommandConverter.isSupported(ddlNode))
             return SqlToNativeCommandConverter.convert(ddlNode, ctx);
 
@@ -227,6 +235,64 @@ public class DdlSqlToCommandConverter {
     }
 
     /**
+     * Converts a given IgniteSqlAlterTableAddColumn AST to a 
AlterTableAddCommand.
+     *
+     * @param alterTblNode Root node of the given AST.
+     * @param ctx Planning context.
+     */
+    private AlterTableAddCommand 
convertAlterTableAdd(IgniteSqlAlterTableAddColumn alterTblNode, PlanningContext 
ctx) {
+        AlterTableAddCommand alterTblCmd = new AlterTableAddCommand();
+
+        alterTblCmd.schemaName(deriveSchemaName(alterTblNode.name(), ctx));
+        alterTblCmd.tableName(deriveObjectName(alterTblNode.name(), ctx, 
"table name"));
+        alterTblCmd.ifTableExists(alterTblNode.ifExists());
+        alterTblCmd.ifColumnNotExists(alterTblNode.ifNotExistsColumn());
+
+        List<ColumnDefinition> cols = new 
ArrayList<>(alterTblNode.columns().size());
+
+        for (SqlNode colNode : alterTblNode.columns()) {
+            assert colNode instanceof SqlColumnDeclaration : 
colNode.getClass();
+
+            SqlColumnDeclaration col = (SqlColumnDeclaration)colNode;
+
+            assert col.name.isSimple();
+
+            String name = col.name.getSimple();
+            RelDataType type = ctx.planner().convert(col.dataType);
+
+            assert col.expression == null : "Unexpected column default value" 
+ col.expression;
+
+            cols.add(new ColumnDefinition(name, type, null));
+        }
+
+        alterTblCmd.columns(cols);
+
+        return alterTblCmd;
+    }
+
+    /**
+     * Converts a given IgniteSqlAlterTableDropColumn AST to a 
AlterTableDropCommand.
+     *
+     * @param alterTblNode Root node of the given AST.
+     * @param ctx Planning context.
+     */
+    private AlterTableDropCommand 
convertAlterTableDrop(IgniteSqlAlterTableDropColumn alterTblNode, 
PlanningContext ctx) {
+        AlterTableDropCommand alterTblCmd = new AlterTableDropCommand();
+
+        alterTblCmd.schemaName(deriveSchemaName(alterTblNode.name(), ctx));
+        alterTblCmd.tableName(deriveObjectName(alterTblNode.name(), ctx, 
"table name"));
+        alterTblCmd.ifTableExists(alterTblNode.ifExists());
+        alterTblCmd.ifColumnExists(alterTblNode.ifExistsColumn());
+
+        List<String> cols = new ArrayList<>(alterTblNode.columns().size());
+        alterTblNode.columns().forEach(c -> 
cols.add(((SqlIdentifier)c).getSimple()));
+
+        alterTblCmd.columns(cols);
+
+        return alterTblCmd;
+    }
+
+    /**
      * Short cut for validating that option value is a simple identifier.
      *
      * @param opt An option to validate.
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
index bec218d..4f6d0ad 100644
--- 
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
@@ -28,8 +28,10 @@ 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.IgniteSqlAlterTable;
 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.SqlAlterTableCommand;
 import org.apache.ignite.internal.sql.command.SqlCommand;
 import org.apache.ignite.internal.sql.command.SqlCreateIndexCommand;
 import org.apache.ignite.internal.sql.command.SqlDropIndexCommand;
@@ -47,7 +49,8 @@ public class SqlToNativeCommandConverter {
      */
     public static boolean isSupported(SqlNode sqlCmd) {
         return sqlCmd instanceof IgniteSqlCreateIndex
-            || sqlCmd instanceof IgniteSqlDropIndex;
+            || sqlCmd instanceof IgniteSqlDropIndex
+            || sqlCmd instanceof IgniteSqlAlterTable;
     }
 
     /**
@@ -68,6 +71,8 @@ public class SqlToNativeCommandConverter {
             return convertCreateIndex((IgniteSqlCreateIndex)cmd, pctx);
         else if (cmd instanceof IgniteSqlDropIndex)
             return convertDropIndex((IgniteSqlDropIndex)cmd, pctx);
+        else if (cmd instanceof IgniteSqlAlterTable)
+            return convertAlterTable((IgniteSqlAlterTable)cmd, pctx);
 
         throw new IgniteSQLException("Unsupported native operation [" +
             "cmdName=" + (cmd == null ? null : cmd.getClass().getSimpleName()) 
+ "; " +
@@ -114,4 +119,14 @@ public class SqlToNativeCommandConverter {
 
         return new SqlDropIndexCommand(schemaName, idxName, sqlCmd.ifExists());
     }
+
+    /**
+     * Converts ALTER TABLE ... LOGGING/NOLOGGING command.
+     */
+    private static SqlAlterTableCommand convertAlterTable(IgniteSqlAlterTable 
sqlCmd, PlanningContext ctx) {
+        String schemaName = deriveSchemaName(sqlCmd.name(), ctx);
+        String tblName = deriveObjectName(sqlCmd.name(), ctx, "table name");
+
+        return new SqlAlterTableCommand(schemaName, tblName, 
sqlCmd.ifExists(), sqlCmd.logging());
+    }
 }
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/IgniteAbstractSqlAlterTable.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/IgniteAbstractSqlAlterTable.java
new file mode 100644
index 0000000..630e671
--- /dev/null
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/IgniteAbstractSqlAlterTable.java
@@ -0,0 +1,84 @@
+/*
+ * 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.Objects;
+import org.apache.calcite.sql.SqlDdl;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlKind;
+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;
+
+/**
+ * Parse tree for {@code ALTER TABLE } statement
+ */
+public abstract class IgniteAbstractSqlAlterTable extends SqlDdl {
+    /** */
+    protected final SqlIdentifier name;
+
+    /** */
+    protected final boolean ifExists;
+
+    /** */
+    private static final SqlOperator OPERATOR =
+        new SqlSpecialOperator("ALTER TABLE", SqlKind.ALTER_TABLE);
+
+    /** */
+    protected IgniteAbstractSqlAlterTable(SqlParserPos pos, boolean ifExists, 
SqlIdentifier tblName) {
+        super(OPERATOR, pos);
+        this.ifExists = ifExists;
+        name = Objects.requireNonNull(tblName, "table name");
+    }
+
+    /** {@inheritDoc} */
+    @Override public SqlOperator getOperator() {
+        return OPERATOR;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void unparse(SqlWriter writer, int leftPrec, int 
rightPrec) {
+        writer.keyword(getOperator().getName());
+
+        if (ifExists)
+            writer.keyword("IF EXISTS");
+
+        name.unparse(writer, leftPrec, rightPrec);
+
+        unparseAlterTableOperation(writer, leftPrec, rightPrec);
+    }
+
+    /**
+     * Unparse rest of the ALTER TABLE command.
+     */
+    protected abstract void unparseAlterTableOperation(SqlWriter writer, int 
leftPrec, int 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/sql/IgniteSqlAlterTable.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/IgniteSqlAlterTable.java
new file mode 100644
index 0000000..67e7031
--- /dev/null
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/IgniteSqlAlterTable.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ignite.internal.processors.query.calcite.sql;
+
+import java.util.List;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlWriter;
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.util.ImmutableNullableList;
+
+/**
+ * Parse tree for {@code ALTER TABLE ... LOGGING/NOLOGGING} statement
+ */
+public class IgniteSqlAlterTable extends IgniteAbstractSqlAlterTable {
+    /** */
+    private final boolean logging;
+
+    /** */
+    protected IgniteSqlAlterTable(SqlParserPos pos, boolean ifExists, 
SqlIdentifier tblName, boolean logging) {
+        super(pos, ifExists, tblName);
+        this.logging = logging;
+    }
+
+    /** {@inheritDoc} */
+    @Override public List<SqlNode> getOperandList() {
+        return ImmutableNullableList.of(name);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void unparseAlterTableOperation(SqlWriter writer, int 
leftPrec, int rightPrec) {
+        writer.keyword(logging ? "LOGGING" : "NOLOGGING");
+    }
+
+    /**
+     * @return If LOGGING or NOLOGGING clause specified.
+     */
+    public boolean logging() {
+        return logging;
+    }
+}
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/IgniteSqlAlterTableAddColumn.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/IgniteSqlAlterTableAddColumn.java
new file mode 100644
index 0000000..0687bbb
--- /dev/null
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/IgniteSqlAlterTableAddColumn.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ignite.internal.processors.query.calcite.sql;
+
+import java.util.List;
+import java.util.Objects;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlNodeList;
+import org.apache.calcite.sql.SqlWriter;
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.util.ImmutableNullableList;
+
+/**
+ * Parse tree for {@code ALTER TABLE ... ADD COLUMN} statement
+ */
+public class IgniteSqlAlterTableAddColumn extends IgniteAbstractSqlAlterTable {
+    /** */
+    private final boolean ifNotExistsColumn;
+
+    /** */
+    private final SqlNodeList columns;
+
+    /** */
+    protected IgniteSqlAlterTableAddColumn(SqlParserPos pos, boolean ifExists, 
SqlIdentifier tblName,
+        boolean ifNotExistsColumn, SqlNodeList columns) {
+        super(pos, ifExists, tblName);
+        this.ifNotExistsColumn = ifNotExistsColumn;
+        this.columns = Objects.requireNonNull(columns, "columns list");
+    }
+
+    /** {@inheritDoc} */
+    @Override public List<SqlNode> getOperandList() {
+        return ImmutableNullableList.of(name, columns);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void unparseAlterTableOperation(SqlWriter writer, int 
leftPrec, int rightPrec) {
+        writer.keyword("ADD");
+        writer.keyword("COLUMN");
+
+        if (ifNotExistsColumn)
+            writer.keyword("IF NOT EXISTS");
+
+        columns.unparse(writer, leftPrec, rightPrec);
+    }
+
+    /**
+     * @return Whether the IF NOT EXISTS is specified for columns.
+     */
+    public boolean ifNotExistsColumn() {
+        return ifNotExistsColumn;
+    }
+
+    /**
+     * @return Columns list.
+     */
+    public SqlNodeList columns() {
+        return columns;
+    }
+}
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/IgniteSqlAlterTableDropColumn.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/IgniteSqlAlterTableDropColumn.java
new file mode 100644
index 0000000..e7a1b7e
--- /dev/null
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/IgniteSqlAlterTableDropColumn.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ignite.internal.processors.query.calcite.sql;
+
+import java.util.List;
+import java.util.Objects;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlNodeList;
+import org.apache.calcite.sql.SqlWriter;
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.util.ImmutableNullableList;
+
+/**
+ * Parse tree for {@code ALTER TABLE ... DROP COLUMN} statement
+ */
+public class IgniteSqlAlterTableDropColumn extends IgniteAbstractSqlAlterTable 
{
+    /** */
+    private final boolean ifExistsColumn;
+
+    /** */
+    private final SqlNodeList columns;
+
+    /** */
+    protected IgniteSqlAlterTableDropColumn(SqlParserPos pos, boolean 
ifExists, SqlIdentifier tblName,
+        boolean ifExistsColumn, SqlNodeList columns) {
+        super(pos, ifExists, tblName);
+        this.ifExistsColumn = ifExistsColumn;
+        this.columns = Objects.requireNonNull(columns, "columns list");
+    }
+
+    /** {@inheritDoc} */
+    @Override public List<SqlNode> getOperandList() {
+        return ImmutableNullableList.of(name, columns);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void unparseAlterTableOperation(SqlWriter writer, int 
leftPrec, int rightPrec) {
+        writer.keyword("DROP");
+        writer.keyword("COLUMN");
+
+        if (ifExistsColumn)
+            writer.keyword("IF EXISTS");
+
+        columns.unparse(writer, leftPrec, rightPrec);
+    }
+
+    /**
+     * @return Whether the IF EXISTS is specified for columns.
+     */
+    public boolean ifExistsColumn() {
+        return ifExistsColumn;
+    }
+
+    /**
+     * @return Columns list.
+     */
+    public SqlNodeList columns() {
+        return columns;
+    }
+}
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
index 1bd3d35..f851b67 100644
--- 
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
@@ -19,6 +19,7 @@ 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.cluster.ClusterState;
 import org.apache.ignite.configuration.DataRegionConfiguration;
 import org.apache.ignite.configuration.DataStorageConfiguration;
 import org.apache.ignite.configuration.IgniteConfiguration;
@@ -39,6 +40,9 @@ public class AbstractDdlIntegrationTest extends 
GridCommonAbstractTest {
     protected static final String DATA_REGION_NAME = "test_data_region";
 
     /** */
+    protected static final String PERSISTENT_DATA_REGION = "pds_data_region";
+
+    /** */
     protected IgniteEx client;
 
     /** {@inheritDoc} */
@@ -46,6 +50,8 @@ public class AbstractDdlIntegrationTest extends 
GridCommonAbstractTest {
         startGrids(1);
 
         client = startClientGrid(CLIENT_NODE_NAME);
+
+        client.cluster().state(ClusterState.ACTIVE);
     }
 
     /** {@inheritDoc} */
@@ -56,7 +62,8 @@ public class AbstractDdlIntegrationTest extends 
GridCommonAbstractTest {
             )
             .setDataStorageConfiguration(
                 new DataStorageConfiguration()
-                    .setDataRegionConfigurations(new 
DataRegionConfiguration().setName(DATA_REGION_NAME))
+                    .setDataRegionConfigurations(new 
DataRegionConfiguration().setName(DATA_REGION_NAME),
+                        new 
DataRegionConfiguration().setName(PERSISTENT_DATA_REGION).setPersistenceEnabled(true))
             );
     }
 
@@ -74,7 +81,12 @@ public class AbstractDdlIntegrationTest extends 
GridCommonAbstractTest {
 
     /** */
     protected List<List<?>> executeSql(String sql) {
-        List<FieldsQueryCursor<List<?>>> cur = queryProcessor().query(null, 
"PUBLIC", sql);
+        return executeSql(client, sql);
+    }
+
+    /** */
+    protected List<List<?>> executeSql(IgniteEx ignite, String sql) {
+        List<FieldsQueryCursor<List<?>>> cur = 
queryProcessor(ignite).query(null, "PUBLIC", sql);
 
         try (QueryCursor<List<?>> srvCursor = cur.get(0)) {
             return srvCursor.getAll();
@@ -82,7 +94,7 @@ public class AbstractDdlIntegrationTest extends 
GridCommonAbstractTest {
     }
 
     /** */
-    private CalciteQueryProcessor queryProcessor() {
-        return Commons.lookupComponent(client.context(), 
CalciteQueryProcessor.class);
+    private CalciteQueryProcessor queryProcessor(IgniteEx ignite) {
+        return Commons.lookupComponent(ignite.context(), 
CalciteQueryProcessor.class);
     }
 }
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 771e485..9fb0c2c 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
@@ -43,6 +43,7 @@ import org.hamcrest.Matcher;
 import org.junit.Test;
 
 import static org.apache.ignite.internal.util.IgniteUtils.map;
+import static 
org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause;
 import static org.apache.ignite.testframework.GridTestUtils.hasSize;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.hasItem;
@@ -337,18 +338,336 @@ public class TableDdlIntegrationTest extends 
AbstractDdlIntegrationTest {
 
         GridTestUtils.assertThrows(log,
             () -> executeSql("drop table my_table"),
-            IgniteSQLException.class, "Table doesn't exist: MY_TABLE]");
+            IgniteSQLException.class, "Table doesn't exist: MY_TABLE");
 
         executeSql("drop table my_schema.my_table");
 
         GridTestUtils.assertThrows(log,
             () -> executeSql("drop table my_schema.my_table"),
-            IgniteSQLException.class, "Table doesn't exist: MY_TABLE]");
+            IgniteSQLException.class, "Table doesn't exist: MY_TABLE");
 
         executeSql("drop table if exists my_schema.my_table");
     }
 
     /**
+     * Add/drop column simple case.
+     */
+    @Test
+    public void alterTableAddDropSimpleCase() {
+        executeSql("create table my_table (id int primary key, val varchar)");
+
+        executeSql("alter table my_table add column val2 varchar");
+
+        executeSql("insert into my_table (id, val, val2) values (0, '1', 
'2')");
+
+        List<List<?>> res = executeSql("select * from my_table ");
+
+        assertEquals(1, res.size());
+        assertEquals("2", res.get(0).get(2));
+
+        executeSql("alter table my_table drop column val2");
+
+        res = executeSql("select * from my_table ");
+
+        assertEquals(1, res.size());
+        assertEquals(2, res.get(0).size());
+    }
+
+    /**
+     * Add/drop two columns at the same time.
+     */
+    @Test
+    public void alterTableAddDropTwoColumns() {
+        executeSql("create table my_table (id int primary key, val varchar)");
+
+        executeSql("alter table my_table add column (val2 varchar, val3 int)");
+
+        executeSql("insert into my_table (id, val, val2, val3) values (0, '1', 
'2', 3)");
+
+        List<List<?>> res = executeSql("select * from my_table ");
+
+        assertEquals(1, res.size());
+        assertEquals("2", res.get(0).get(2));
+        assertEquals(3, res.get(0).get(3));
+
+        executeSql("alter table my_table drop column (val2, val3)");
+
+        res = executeSql("select * from my_table ");
+
+        assertEquals(1, res.size());
+        assertEquals(2, res.get(0).size());
+    }
+
+    /**
+     * Add/drop column for table in custom schema.
+     */
+    @Test
+    public void alterTableAddDropCustomSchema() {
+        executeSql("create table my_schema.my_table (id int primary key, val 
varchar)");
+
+        executeSql("alter table my_schema.my_table add column val2 varchar");
+
+        executeSql("insert into my_schema.my_table (id, val, val2) values (0, 
'1', '2')");
+
+        List<List<?>> res = executeSql("select * from my_schema.my_table ");
+
+        assertEquals(1, res.size());
+        assertEquals("2", res.get(0).get(2));
+
+        executeSql("alter table my_schema.my_table drop column val2");
+
+        res = executeSql("select * from my_schema.my_table ");
+
+        assertEquals(1, res.size());
+        assertEquals(2, res.get(0).size());
+    }
+
+    /**
+     * Add/drop column if table exists.
+     */
+    @Test
+    public void alterTableAddDropIfTableExists() {
+        assertThrows("alter table my_table add val2 varchar", 
IgniteSQLException.class, "Table doesn't exist");
+
+        executeSql("alter table if exists my_table add column val2 varchar");
+
+        assertThrows("alter table my_table drop column val2", 
IgniteSQLException.class, "Table doesn't exist");
+
+        executeSql("alter table if exists my_table drop column val2");
+
+        executeSql("create table my_table (id int primary key, val varchar)");
+
+        executeSql("alter table if exists my_table add column val3 varchar");
+
+        executeSql("insert into my_table (id, val, val3) values (0, '1', 
'2')");
+
+        List<List<?>> res = executeSql("select * from my_table ");
+
+        assertEquals(1, res.size());
+        assertEquals("2", res.get(0).get(2));
+
+        executeSql("alter table if exists my_table drop column val3");
+
+        assertThrows("alter table if exists my_table drop column val3", 
IgniteSQLException.class,
+            "Column doesn't exist");
+
+        res = executeSql("select * from my_table ");
+
+        assertEquals(1, res.size());
+        assertEquals(2, res.get(0).size());
+    }
+
+    /**
+     * Add/drop column if column not exists/exists.
+     */
+    @Test
+    public void alterTableAddDropIfColumnExists() {
+        executeSql("create table my_table (id int primary key, val varchar)");
+
+        executeSql("insert into my_table (id, val) values (0, '1')");
+
+        assertThrows("alter table my_table add column val varchar", 
IgniteSQLException.class,
+            "Column already exist");
+
+        executeSql("alter table my_table add column if not exists val 
varchar");
+
+        List<List<?>> res = executeSql("select * from my_table ");
+        assertEquals(1, res.size());
+        assertEquals(2, res.get(0).size());
+
+        assertThrows("alter table my_table drop column val2", 
IgniteSQLException.class,
+            "Column doesn't exist");
+
+        executeSql("alter table my_table drop column if exists val2");
+
+        res = executeSql("select * from my_table ");
+        assertEquals(1, res.size());
+        assertEquals(2, res.get(0).size());
+
+        executeSql("alter table my_table add column if not exists val3 
varchar");
+
+        res = executeSql("select * from my_table ");
+        assertEquals(1, res.size());
+        assertEquals(3, res.get(0).size());
+
+        executeSql("alter table my_table drop column if exists val3");
+
+        res = executeSql("select * from my_table ");
+        assertEquals(1, res.size());
+        assertEquals(2, res.get(0).size());
+
+        // Mixing existsing and not existsing columns
+        executeSql("alter table my_table add column if not exists (val 
varchar, val4 varchar)");
+
+        res = executeSql("select * from my_table ");
+        assertEquals(1, res.size());
+        assertEquals(3, res.get(0).size());
+
+        executeSql("alter table my_table drop column if exists (val4, val5)");
+
+        res = executeSql("select * from my_table ");
+        assertEquals(1, res.size());
+        assertEquals(2, res.get(0).size());
+    }
+
+    /**
+     * Add not null column.
+     */
+    @Test
+    public void alterTableAddNotNullColumn() {
+        executeSql("create table my_table (id int primary key, val varchar)");
+
+        executeSql("alter table my_table add column val2 varchar not null");
+
+        assertThrows("insert into my_table (id, val, val2) values (0, '1', 
null)", IgniteSQLException.class,
+            "Null value is not allowed");
+
+        executeSql("insert into my_table (id, val, val2) values (0, '1', 
'2')");
+
+        List<List<?>> res = executeSql("select * from my_table ");
+
+        assertEquals(1, res.size());
+        assertEquals("2", res.get(0).get(2));
+    }
+
+    /**
+     * Drop forbidden column.
+     */
+    @Test
+    public void alterTableDropForbiddenColumn() {
+        executeSql("create table my_table (id int primary key, val varchar, 
val2 varchar)");
+
+        executeSql("create index my_index on my_table(val)");
+
+        executeSql("insert into my_table (id, val, val2) values (0, '1', 
'2')");
+
+        assertThrows("alter table my_table drop column id", 
IgniteSQLException.class,
+            "Cannot drop column");
+
+        assertThrows("alter table my_table drop column val", 
IgniteSQLException.class,
+            "Cannot drop column");
+
+        List<List<?>> res = executeSql("select * from my_table ");
+
+        assertEquals(1, res.size());
+        assertEquals(3, res.get(0).size());
+
+        executeSql("alter table my_table drop column val2");
+
+        res = executeSql("select * from my_table ");
+
+        assertEquals(1, res.size());
+        assertEquals(2, res.get(0).size());
+    }
+
+    /**
+     * Alter table from server and client nodes.
+     */
+    @Test
+    public void alterTableServerAndClient() throws Exception {
+        executeSql(grid(0), "create table my_table (id int primary key, val 
varchar)");
+
+        executeSql(grid(0), "alter table my_table add column val2 varchar");
+
+        executeSql(grid(0), "insert into my_table (id, val, val2) values (0, 
'1', '2')");
+
+        List<List<?>> res = executeSql(grid(0), "select * from my_table ");
+
+        assertEquals(1, res.size());
+        assertEquals(3, res.get(0).size());
+
+        executeSql(grid(0), "drop table my_table");
+
+        awaitPartitionMapExchange();
+
+        executeSql("create table my_table (id int primary key, val varchar)");
+
+        executeSql("alter table my_table add column val2 varchar");
+
+        executeSql("insert into my_table (id, val, val2) values (0, '1', 
'2')");
+
+        res = executeSql("select * from my_table ");
+
+        assertEquals(1, res.size());
+        assertEquals(3, res.get(0).size());
+
+        awaitPartitionMapExchange();
+
+        executeSql(grid(0), "alter table my_table drop column val2");
+
+        awaitPartitionMapExchange();
+
+        res = executeSql("select * from my_table ");
+
+        assertEquals(1, res.size());
+        assertEquals(2, res.get(0).size());
+    }
+
+    /**
+     * Drop and add the same column with different NOT NULL modificator.
+     */
+    @Test
+    public void alterTableDropAddColumn() {
+        executeSql("create table my_table (id int primary key, val varchar, 
val2 varchar)");
+
+        executeSql("insert into my_table (id, val, val2) values (0, '1', 
'2')");
+
+        executeSql("alter table my_table drop column val2");
+
+        List<List<?>> res = executeSql("select * from my_table ");
+
+        assertEquals(1, res.size());
+        assertEquals(2, res.get(0).size());
+
+        executeSql("alter table my_table add column val2 varchar not null");
+
+        res = executeSql("select * from my_table ");
+        assertEquals(1, res.size());
+        assertEquals(3, res.get(0).size());
+        // The command DROP COLUMN does not remove actual data from the 
cluster, it's a known and documented limitation.
+        assertEquals("2", res.get(0).get(2));
+
+        assertThrows("insert into my_table (id, val, val2) values (1, '2', 
null)", IgniteSQLException.class,
+            "Null value is not allowed");
+
+        executeSql("insert into my_table (id, val, val2) values (1, '2', 
'3')");
+
+        assertEquals(2, executeSql("select * from my_table").size());
+    }
+
+    /**
+     * Alter table logging/nologing.
+     */
+    @Test
+    public void alterTableLogging() {
+        String cacheName = "cache";
+
+        IgniteCache<Object, Object> cache = client.getOrCreateCache(new 
CacheConfiguration<>(cacheName)
+            
.setDataRegionName(PERSISTENT_DATA_REGION).setIndexedTypes(Integer.class, 
Integer.class));
+
+        assertTrue(client.cluster().isWalEnabled(cacheName));
+
+        executeSql("alter table \"" + cacheName + "\".Integer nologging");
+
+        assertFalse(client.cluster().isWalEnabled(cacheName));
+
+        executeSql("alter table \"" + cacheName + "\".Integer logging");
+
+        assertTrue(client.cluster().isWalEnabled(cacheName));
+    }
+
+    /**
+     * Asserts that executeSql throws an exception.
+     *
+     * @param sql Query.
+     * @param cls Exception class.
+     * @param msg Error message.
+     */
+    private void assertThrows(String sql, Class<? extends Exception> cls, 
String msg) {
+        assertThrowsAnyCause(log, () -> executeSql(sql), cls, msg);
+    }
+
+    /**
      * Matcher to verify that an object of the expected type and matches the 
given predicat.
      *
      * @param desc Description for this matcher.
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 61a1767..7e4843e 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
@@ -247,7 +247,7 @@ public class SqlDdlParserTest extends 
GridCommonAbstractTest {
     public void createIndexColumns() throws SqlParseException {
         String qry = "create index my_index on my_table(id, val1 asc, val2 
desc)";
 
-        IgniteSqlCreateIndex createIdx = (IgniteSqlCreateIndex) parse(qry);
+        IgniteSqlCreateIndex createIdx = parse(qry);
 
         assertThat(createIdx.indexName().names, 
is(ImmutableList.of("MY_INDEX")));
         assertThat(createIdx.tableName().names, 
is(ImmutableList.of("MY_TABLE")));
@@ -264,7 +264,7 @@ public class SqlDdlParserTest extends 
GridCommonAbstractTest {
     public void createIndexOnTableWithSchema() throws SqlParseException {
         String qry = "create index my_index on my_schema.my_table(id)";
 
-        IgniteSqlCreateIndex createIdx = (IgniteSqlCreateIndex)parse(qry);
+        IgniteSqlCreateIndex createIdx = parse(qry);
 
         assertThat(createIdx.indexName().names, 
is(ImmutableList.of("MY_INDEX")));
         assertThat(createIdx.tableName().names, 
is(ImmutableList.of("MY_SCHEMA", "MY_TABLE")));
@@ -277,7 +277,7 @@ public class SqlDdlParserTest extends 
GridCommonAbstractTest {
     public void createIndexIfNotExists() throws SqlParseException {
         String qry = "create index if not exists my_index on my_table(id)";
 
-        IgniteSqlCreateIndex createIdx = (IgniteSqlCreateIndex)parse(qry);
+        IgniteSqlCreateIndex createIdx = parse(qry);
 
         assertThat(createIdx.indexName().names, 
is(ImmutableList.of("MY_INDEX")));
         assertThat(createIdx.tableName().names, 
is(ImmutableList.of("MY_TABLE")));
@@ -291,7 +291,7 @@ public class SqlDdlParserTest extends 
GridCommonAbstractTest {
     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);
+        IgniteSqlCreateIndex createIdx = parse(qry);
 
         assertThat(createIdx.indexName().names, 
is(ImmutableList.of("MY_INDEX")));
         assertThat(createIdx.tableName().names, 
is(ImmutableList.of("MY_TABLE")));
@@ -300,21 +300,21 @@ public class SqlDdlParserTest extends 
GridCommonAbstractTest {
 
         qry = "create index my_index on my_table(id) parallel 10";
 
-        createIdx = (IgniteSqlCreateIndex)parse(qry);
+        createIdx = 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);
+        createIdx = 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);
+        createIdx = parse(qry);
 
         assertEquals(20, createIdx.inlineSize().intValue(true));
         assertEquals(10, createIdx.parallel().intValue(true));
@@ -345,6 +345,171 @@ public class SqlDdlParserTest extends 
GridCommonAbstractTest {
         assertParserThrows("create index my_index on my_table(id.id2)", 
SqlParseException.class);
     }
 
+    /**
+     * Alter table with LOGGING/NOLOGING clause.
+     */
+    @Test
+    public void alterTableLoggingNologging() throws SqlParseException {
+        IgniteSqlAlterTable alterTbl = parse("alter table my_table logging");
+
+        assertThat(alterTbl.name().names, is(ImmutableList.of("MY_TABLE")));
+        assertEquals(false, alterTbl.ifExists());
+        assertEquals(true, alterTbl.logging());
+
+        alterTbl = parse("alter table if exists my_table nologging");
+
+        assertThat(alterTbl.name().names, is(ImmutableList.of("MY_TABLE")));
+        assertEquals(true, alterTbl.ifExists());
+        assertEquals(false, alterTbl.logging());
+    }
+
+    /**
+     * Alter table with schema.
+     */
+    @Test
+    public void alterTableWithSchema() throws SqlParseException {
+        IgniteSqlAlterTable alterTbl1 = parse("alter table my_schema.my_table 
logging");
+        assertThat(alterTbl1.name().names, is(ImmutableList.of("MY_SCHEMA", 
"MY_TABLE")));
+
+        IgniteSqlAlterTableAddColumn alterTbl2 = parse("alter table 
my_schema.my_table add column a int");
+
+        assertThat(alterTbl2.name().names, is(ImmutableList.of("MY_SCHEMA", 
"MY_TABLE")));
+
+        IgniteSqlAlterTableDropColumn alterTbl3 = parse("alter table 
my_schema.my_table drop column a");
+
+        assertThat(alterTbl3.name().names, is(ImmutableList.of("MY_SCHEMA", 
"MY_TABLE")));
+    }
+
+    /**
+     * Alter table add column.
+     */
+    @Test
+    public void alterTableAddColumn() throws SqlParseException {
+        IgniteSqlAlterTableAddColumn alterTbl;
+
+        alterTbl = parse("alter table my_table add column a int");
+
+        assertThat(alterTbl.name().names, is(ImmutableList.of("MY_TABLE")));
+        assertEquals(false, alterTbl.ifNotExistsColumn());
+        assertEquals(1, alterTbl.columns().size());
+        assertThat(alterTbl.columns(), hasItem(columnWithName("A")));
+
+        alterTbl = parse("alter table my_table add column if not exists a 
int");
+
+        assertEquals(true, alterTbl.ifNotExistsColumn());
+        assertEquals(1, alterTbl.columns().size());
+        assertThat(alterTbl.columns(), hasItem(columnWithName("A")));
+
+        alterTbl = parse("alter table my_table add a int");
+
+        assertEquals(false, alterTbl.ifNotExistsColumn());
+        assertEquals(1, alterTbl.columns().size());
+        assertThat(alterTbl.columns(), hasItem(columnWithName("A")));
+
+        alterTbl = parse("alter table my_table add if not exists a int");
+
+        assertEquals(true, alterTbl.ifNotExistsColumn());
+        assertEquals(1, alterTbl.columns().size());
+        assertThat(alterTbl.columns(), hasItem(columnWithName("A")));
+
+        alterTbl = parse("alter table my_table add column (a int)");
+
+        assertEquals(false, alterTbl.ifNotExistsColumn());
+        assertEquals(1, alterTbl.columns().size());
+        assertThat(alterTbl.columns(), hasItem(columnWithName("A")));
+
+        alterTbl = parse("alter table my_table add column if not exists (a 
int)");
+
+        assertEquals(true, alterTbl.ifNotExistsColumn());
+        assertEquals(1, alterTbl.columns().size());
+        assertThat(alterTbl.columns(), hasItem(columnWithName("A")));
+
+        alterTbl = parse("alter table my_table add column (a int, \"b\" 
varchar, c date not null)");
+
+        assertEquals(false, alterTbl.ifNotExistsColumn());
+        assertEquals(3, alterTbl.columns().size());
+        assertThat(alterTbl.columns(), hasItem(columnDeclaration("A", 
"INTEGER", true)));
+        assertThat(alterTbl.columns(), hasItem(columnDeclaration("b", 
"VARCHAR", true)));
+        assertThat(alterTbl.columns(), hasItem(columnDeclaration("C", "DATE", 
false)));
+    }
+
+    /**
+     * Alter table drop column.
+     */
+    @Test
+    public void alterTableDropColumn() throws SqlParseException {
+        IgniteSqlAlterTableDropColumn alterTbl;
+
+        alterTbl = parse("alter table my_table drop column a");
+
+        assertThat(alterTbl.name().names, is(ImmutableList.of("MY_TABLE")));
+        assertEquals(false, alterTbl.ifExistsColumn());
+        assertEquals(1, alterTbl.columns().size());
+        assertThat(alterTbl.columns(), hasItem(identifierWithName("A")));
+
+        alterTbl = parse("alter table my_table drop column if exists a");
+
+        assertEquals(true, alterTbl.ifExistsColumn());
+        assertEquals(1, alterTbl.columns().size());
+        assertThat(alterTbl.columns(), hasItem(identifierWithName("A")));
+
+        alterTbl = parse("alter table my_table drop a");
+
+        assertEquals(false, alterTbl.ifExistsColumn());
+        assertEquals(1, alterTbl.columns().size());
+        assertThat(alterTbl.columns(), hasItem(identifierWithName("A")));
+
+        alterTbl = parse("alter table my_table drop if exists a");
+
+        assertEquals(true, alterTbl.ifExistsColumn());
+        assertEquals(1, alterTbl.columns().size());
+        assertThat(alterTbl.columns(), hasItem(identifierWithName("A")));
+
+        alterTbl = parse("alter table my_table drop column (a)");
+
+        assertEquals(false, alterTbl.ifExistsColumn());
+        assertEquals(1, alterTbl.columns().size());
+        assertThat(alterTbl.columns(), hasItem(identifierWithName("A")));
+
+        alterTbl = parse("alter table my_table drop column if exists (a)");
+
+        assertEquals(true, alterTbl.ifExistsColumn());
+        assertEquals(1, alterTbl.columns().size());
+        assertThat(alterTbl.columns(), hasItem(identifierWithName("A")));
+
+        alterTbl = parse("alter table my_table drop column (a, \"b\", c)");
+
+        assertEquals(false, alterTbl.ifExistsColumn());
+        assertEquals(3, alterTbl.columns().size());
+        assertThat(alterTbl.columns(), hasItem(identifierWithName("A")));
+        assertThat(alterTbl.columns(), hasItem(identifierWithName("b")));
+        assertThat(alterTbl.columns(), hasItem(identifierWithName("C")));
+    }
+
+    /**
+     * Malformed alter table statements.
+     */
+    @Test
+    public void alterTableMalformed() {
+        assertParserThrows("alter table my_table logging nologging", 
SqlParseException.class);
+
+        assertParserThrows("alter table my_table logging add column a int", 
SqlParseException.class);
+
+        assertParserThrows("alter table if not exists my_table logging", 
SqlParseException.class);
+
+        assertParserThrows("alter table my_table add column if exists (a 
int)", SqlParseException.class);
+
+        assertParserThrows("alter table my_table add column (a)", 
SqlParseException.class);
+
+        assertParserThrows("alter table my_table drop column if not exists 
(a)", SqlParseException.class);
+
+        assertParserThrows("alter table my_table drop column (a int)", 
SqlParseException.class);
+
+        assertParserThrows("alter table my_table add column (a.b int)", 
SqlParseException.class);
+
+        assertParserThrows("alter table my_table drop column (a.b)", 
SqlParseException.class);
+    }
+
     /** */
     private void assertParserThrows(String sql, Class<? extends Exception> 
cls) {
         assertParserThrows(sql, cls, "");
@@ -361,10 +526,10 @@ public class SqlDdlParserTest extends 
GridCommonAbstractTest {
      * @param stmt Statement to parse.
      * @return An AST.
      */
-    private static SqlNode parse(String stmt) throws SqlParseException {
+    private static <T extends SqlNode> T parse(String stmt) throws 
SqlParseException {
         SqlParser parser = SqlParser.create(stmt, 
SqlParser.config().withParserFactory(IgniteSqlParserImpl.FACTORY));
 
-        return parser.parseStmt();
+        return (T)parser.parseStmt();
     }
 
     /**
@@ -426,6 +591,33 @@ public class SqlDdlParserTest extends 
GridCommonAbstractTest {
     }
 
     /**
+     * Matcher to verify identifier.
+     */
+    private static <T extends SqlIdentifier> Matcher<T> 
identifierWithName(String name) {
+        return new CustomMatcher<T>("identifier with name=" + name) {
+            @Override public boolean matches(Object item) {
+                return item instanceof SqlIdentifier
+                    && ((SqlIdentifier)item).names.get(0).equals(name);
+            }
+        };
+    }
+
+    /**
+     * Matcher to verify column declaration.
+     */
+    private static <T extends SqlColumnDeclaration> Matcher<T> 
columnDeclaration(String name, String type,
+        boolean nullable) {
+        return new CustomMatcher<T>("column declaration [name=" + name + ", 
type=" + type + ']') {
+            @Override public boolean matches(Object item) {
+                return item instanceof SqlColumnDeclaration
+                    && 
((SqlColumnDeclaration)item).name.names.get(0).equals(name)
+                    && 
((SqlColumnDeclaration)item).dataType.getTypeName().names.get(0).equals(type)
+                    && ((SqlColumnDeclaration)item).dataType.getNullable() == 
nullable;
+            }
+        };
+    }
+
+    /**
      * Matcher to verify that an object of the expected type and matches the 
given predicat.
      *
      * @param desc Description for this matcher.
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 a3d9fc0..20aa512 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
@@ -31,7 +31,6 @@ import java.util.SortedSet;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
-
 import javax.cache.configuration.Factory;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteLogger;
@@ -115,7 +114,6 @@ import static 
org.apache.ignite.internal.processors.odbc.jdbc.JdbcRequest.QRY_CL
 import static 
org.apache.ignite.internal.processors.odbc.jdbc.JdbcRequest.QRY_EXEC;
 import static 
org.apache.ignite.internal.processors.odbc.jdbc.JdbcRequest.QRY_FETCH;
 import static 
org.apache.ignite.internal.processors.odbc.jdbc.JdbcRequest.QRY_META;
-import static 
org.apache.ignite.internal.processors.query.GridQueryProcessor.executeWithExperimentalEngine;
 
 /**
  * JDBC request handler.
@@ -775,10 +773,8 @@ 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, 
cliCtx, cancel), qry.getSchema(),
-                    qry.getSql(), qry.getArgs());
-            }
+            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 eec8ccb..29ca5da 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
@@ -34,7 +34,6 @@ import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.regex.Pattern;
 import javax.cache.Cache;
 import javax.cache.CacheException;
 import org.apache.ignite.IgniteCheckedException;
@@ -142,7 +141,6 @@ import static java.util.Collections.newSetFromMap;
 import static java.util.Collections.singleton;
 import static java.util.Objects.isNull;
 import static java.util.Objects.nonNull;
-import static java.util.regex.Pattern.CASE_INSENSITIVE;
 import static java.util.stream.Collectors.toSet;
 import static 
org.apache.ignite.IgniteSystemProperties.IGNITE_EXPERIMENTAL_SQL_ENGINE;
 import static org.apache.ignite.IgniteSystemProperties.getBoolean;
@@ -173,10 +171,6 @@ public class GridQueryProcessor extends 
GridProcessorAdapter {
     /** */
     private static final ThreadLocal<AffinityTopologyVersion> requestTopVer = 
new ThreadLocal<>();
 
-    /** 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)", CASE_INSENSITIVE);
-
     /** For tests. */
     public static Class<? extends GridQueryIndexing> idxCls;
 
@@ -2871,10 +2865,8 @@ 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, 
cliCtx), qry.getSchema(), qry.getSql(),
-                    X.EMPTY_OBJECT_ARRAY);
-            }
+            return experimentalQueryEngine.query(QueryContext.of(qry, cliCtx), 
qry.getSchema(), qry.getSql(),
+                X.EMPTY_OBJECT_ARRAY);
         }
 
         return executeQuerySafe(cctx, () -> {
@@ -3662,14 +3654,6 @@ public class GridQueryProcessor extends 
GridProcessorAdapter {
     }
 
     /**
-     * @param sql Query to execute.
-     * @return {@code true} if the given query should be executed with 
Calcite-based engine.
-     */
-    public static boolean executeWithExperimentalEngine(String sql) {
-        return !H2_REDIRECTION_RULES.matcher(sql).find();
-    }
-
-    /**
      * @return Affinity topology version of the current request.
      */
     public static AffinityTopologyVersion getRequestAffinityTopologyVersion() {
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlAlterTableCommand.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlAlterTableCommand.java
index b6f762c..491bdc3 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlAlterTableCommand.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlAlterTableCommand.java
@@ -41,6 +41,24 @@ public class SqlAlterTableCommand implements SqlCommand {
     /** Logging flag. */
     private Boolean logging;
 
+    /**
+     * Default constructor.
+     */
+    public SqlAlterTableCommand() {
+    }
+
+    /**
+     * @param schemaName Schema name.
+     * @param tblName Table name.
+     * @param ifExists If exists clause.
+     * @param logging LOGGING/NOLOGGING.
+     */
+    public SqlAlterTableCommand(String schemaName, String tblName, boolean 
ifExists, boolean logging) {
+        this.schemaName = schemaName;
+        this.tblName = tblName;
+        this.logging = logging;
+    }
+
     /** {@inheritDoc} */
     @Override public String schemaName() {
         return schemaName;

Reply via email to