ignite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From sboi...@apache.org
Subject [16/27] ignite git commit: IGNITE-5572: SQL: ALTER TABLE ADD COLUMN support. This closes #2344.
Date Fri, 08 Sep 2017 07:55:58 GMT
IGNITE-5572: SQL: ALTER TABLE ADD COLUMN support. This closes #2344.


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/adec3e7e
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/adec3e7e
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/adec3e7e

Branch: refs/heads/ignite-3484
Commit: adec3e7ec20fe04aa37a34416253602bc0387f4f
Parents: ffaf108
Author: Alexander Paschenko <alexander.a.paschenko@gmail.com>
Authored: Wed Sep 6 18:18:23 2017 +0300
Committer: devozerov <ppozerov@gmail.com>
Committed: Wed Sep 6 18:18:23 2017 +0300

----------------------------------------------------------------------
 .../processors/query/GridQueryIndexing.java     |   15 +
 .../processors/query/GridQueryProcessor.java    |  132 ++-
 .../internal/processors/query/QueryField.java   |   64 ++
 .../internal/processors/query/QuerySchema.java  |   37 +-
 .../query/QueryTypeDescriptorImpl.java          |   17 +-
 .../query/schema/SchemaOperationWorker.java     |    2 +-
 .../SchemaAbstractAlterTableOperation.java      |   39 +
 .../SchemaAlterTableAddColumnOperation.java     |   96 ++
 ...IgniteClientCacheInitializationFailTest.java |    8 +
 .../processors/query/h2/H2RowDescriptor.java    |  105 +-
 .../processors/query/h2/H2TableEngine.java      |   13 +-
 .../processors/query/h2/IgniteH2Indexing.java   |   22 +-
 .../query/h2/ddl/DdlStatementsProcessor.java    |   50 +-
 .../processors/query/h2/opt/GridH2Table.java    |   55 +-
 .../h2/sql/GridSqlAlterTableAddColumn.java      |  113 ++
 .../query/h2/sql/GridSqlQueryParser.java        |  160 ++-
 ...ynamicColumnsAbstractConcurrentSelfTest.java | 1056 ++++++++++++++++++
 .../cache/index/DynamicColumnsAbstractTest.java |  311 ++++++
 ...umnsConcurrentAtomicPartitionedSelfTest.java |   33 +
 ...lumnsConcurrentAtomicReplicatedSelfTest.java |   33 +
 ...currentTransactionalPartitionedSelfTest.java |   33 +
 ...ncurrentTransactionalReplicatedSelfTest.java |   33 +
 .../H2DynamicColumnsAbstractBasicSelfTest.java  |  348 ++++++
 .../H2DynamicColumnsClientBasicSelfTest.java    |   28 +
 .../H2DynamicColumnsServerBasicSelfTest.java    |   28 +
 ...icColumnsServerCoordinatorBasicSelfTest.java |   28 +
 .../query/h2/sql/GridQueryParsingTest.java      |   92 +-
 .../IgniteCacheQuerySelfTestSuite.java          |   14 +
 28 files changed, 2841 insertions(+), 124 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryIndexing.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryIndexing.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryIndexing.java
index 8eecfc2..cecc5dd 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryIndexing.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryIndexing.java
@@ -165,6 +165,21 @@ public interface GridQueryIndexing {
     public void dynamicIndexDrop(String schemaName, String idxName, boolean ifExists) throws IgniteCheckedException;
 
     /**
+     * Add columns to dynamic table.
+     *
+     * @param schemaName Schema name.
+     * @param tblName Table name.
+     * @param cols Columns to add.
+     * @param ifTblExists Ignore operation if target table does not exist (instead of throwing an error).
+     * @param ifColNotExists Ignore operation if column already exists (instead of throwing an error) - is honored only
+     *     for single column case.
+     * @throws IgniteCheckedException If failed.
+     */
+    @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
+    public void dynamicAddColumn(String schemaName, String tblName, List<QueryField> cols, boolean ifTblExists,
+        boolean ifColNotExists) throws IgniteCheckedException;
+
+    /**
      * Registers cache.
      *
      * @param cacheName Cache name.

http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
----------------------------------------------------------------------
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 d5ac8fc..ea4c576 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
@@ -69,6 +69,7 @@ import org.apache.ignite.internal.processors.cache.query.CacheQueryFuture;
 import org.apache.ignite.internal.processors.cache.query.CacheQueryType;
 import org.apache.ignite.internal.processors.cache.query.GridCacheQueryType;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.processors.query.property.QueryBinaryProperty;
 import org.apache.ignite.internal.processors.query.schema.SchemaIndexCacheVisitor;
 import org.apache.ignite.internal.processors.query.schema.SchemaIndexCacheVisitorImpl;
 import org.apache.ignite.internal.processors.query.schema.SchemaIndexOperationCancellationToken;
@@ -81,6 +82,7 @@ import org.apache.ignite.internal.processors.query.schema.message.SchemaFinishDi
 import org.apache.ignite.internal.processors.query.schema.message.SchemaOperationStatusMessage;
 import org.apache.ignite.internal.processors.query.schema.message.SchemaProposeDiscoveryMessage;
 import org.apache.ignite.internal.processors.query.schema.operation.SchemaAbstractOperation;
+import org.apache.ignite.internal.processors.query.schema.operation.SchemaAlterTableAddColumnOperation;
 import org.apache.ignite.internal.processors.query.schema.operation.SchemaIndexCreateOperation;
 import org.apache.ignite.internal.processors.query.schema.operation.SchemaIndexDropOperation;
 import org.apache.ignite.internal.processors.timeout.GridTimeoutProcessor;
@@ -754,6 +756,16 @@ public class GridQueryProcessor extends GridProcessorAdapter {
 
                                         QueryUtils.processDynamicIndexChange(opDrop.indexName(), null, typeDesc);
                                     }
+                                    else if (op0 instanceof SchemaAlterTableAddColumnOperation) {
+                                        SchemaAlterTableAddColumnOperation opAddCol =
+                                            (SchemaAlterTableAddColumnOperation)op0;
+
+                                        QueryTypeDescriptorImpl typeDesc = tblTypMap.get(opAddCol.tableName());
+
+                                        assert typeDesc != null;
+
+                                        processDynamicAddColumn(typeDesc, opAddCol.columns());
+                                    }
                                     else
                                         assert false;
                                 }
@@ -966,6 +978,32 @@ public class GridQueryProcessor extends GridProcessorAdapter {
             else
                 type = oldIdx.typeDescriptor();
         }
+        else if (op instanceof SchemaAlterTableAddColumnOperation) {
+            SchemaAlterTableAddColumnOperation op0 = (SchemaAlterTableAddColumnOperation)op;
+
+            type = type(cacheName, op0.tableName());
+
+            if (type == null) {
+                if (op0.ifTableExists())
+                    nop = true;
+                else
+                    err = new SchemaOperationException(SchemaOperationException.CODE_TABLE_NOT_FOUND,
+                        op0.tableName());
+            }
+            else {
+                for (QueryField col : op0.columns()) {
+                    if (type.hasField(col.name())) {
+                        if (op0.ifNotExists()) {
+                            assert op0.columns().size() == 1;
+
+                            nop = true;
+                        }
+                        else
+                            err = new SchemaOperationException(SchemaOperationException.CODE_COLUMN_EXISTS, col.name());
+                    }
+                }
+            }
+        }
         else
             err = new SchemaOperationException("Unsupported operation: " + op);
 
@@ -1070,6 +1108,32 @@ public class GridQueryProcessor extends GridProcessorAdapter {
                     err = new SchemaOperationException(SchemaOperationException.CODE_INDEX_NOT_FOUND, idxName);
             }
         }
+        else if (op instanceof SchemaAlterTableAddColumnOperation) {
+            SchemaAlterTableAddColumnOperation op0 = (SchemaAlterTableAddColumnOperation)op;
+
+            QueryEntity e = tblMap.get(op0.tableName());
+
+            if (e == null) {
+                if (op0.ifTableExists())
+                    nop = true;
+                else
+                    err = new SchemaOperationException(SchemaOperationException.CODE_TABLE_NOT_FOUND,
+                        op0.tableName());
+            }
+            else {
+                for (QueryField fld : op0.columns()) {
+                    if (e.getFields().containsKey(fld.name())) {
+                        if (op0.ifNotExists()) {
+                            assert op0.columns().size() == 1;
+
+                            nop = true;
+                        }
+                        else
+                            err = new SchemaOperationException(SchemaOperationException.CODE_COLUMN_EXISTS, fld.name());
+                    }
+                }
+            }
+        }
         else
             err = new SchemaOperationException("Unsupported operation: " + op);
 
@@ -1180,9 +1244,7 @@ public class GridQueryProcessor extends GridProcessorAdapter {
 
                     idxs.put(idxKey, idxDesc);
                 }
-                else {
-                    assert op instanceof SchemaIndexDropOperation;
-
+                else if (op instanceof SchemaIndexDropOperation) {
                     SchemaIndexDropOperation op0 = (SchemaIndexDropOperation) op;
 
                     QueryUtils.processDynamicIndexChange(op0.indexName(), null, type);
@@ -1191,6 +1253,12 @@ public class GridQueryProcessor extends GridProcessorAdapter {
 
                     idxs.remove(idxKey);
                 }
+                else {
+                    assert op instanceof SchemaAlterTableAddColumnOperation;
+
+                    // No-op - all processing is done at "local" stage
+                    // as we must update both table and type descriptor atomically.
+                }
             }
             catch (IgniteCheckedException e) {
                 U.warn(log, "Failed to finish index operation [opId=" + op.id() + " op=" + op + ']', e);
@@ -1229,7 +1297,7 @@ public class GridQueryProcessor extends GridProcessorAdapter {
     }
 
     /**
-     * Process index operation.
+     * Process schema operation.
      *
      * @param op Operation.
      * @param type Type descriptor.
@@ -1237,7 +1305,8 @@ public class GridQueryProcessor extends GridProcessorAdapter {
      * @param cancelTok Cancel token.
      * @throws SchemaOperationException If failed.
      */
-    public void processIndexOperationLocal(SchemaAbstractOperation op, QueryTypeDescriptorImpl type, IgniteUuid depId,
+    @SuppressWarnings("StatementWithEmptyBody")
+    public void processSchemaOperationLocal(SchemaAbstractOperation op, QueryTypeDescriptorImpl type, IgniteUuid depId,
         SchemaIndexOperationCancellationToken cancelTok) throws SchemaOperationException {
         if (log.isDebugEnabled())
             log.debug("Started local index operation [opId=" + op.id() + ']');
@@ -1251,7 +1320,7 @@ public class GridQueryProcessor extends GridProcessorAdapter {
 
         try {
             if (op instanceof SchemaIndexCreateOperation) {
-                SchemaIndexCreateOperation op0 = (SchemaIndexCreateOperation) op;
+                SchemaIndexCreateOperation op0 = (SchemaIndexCreateOperation)op;
 
                 QueryIndexDescriptorImpl idxDesc = QueryUtils.createIndexDescriptor(type, op0.index());
 
@@ -1261,10 +1330,18 @@ public class GridQueryProcessor extends GridProcessorAdapter {
                 idx.dynamicIndexCreate(op0.schemaName(), op0.tableName(), idxDesc, op0.ifNotExists(), visitor);
             }
             else if (op instanceof SchemaIndexDropOperation) {
-                SchemaIndexDropOperation op0 = (SchemaIndexDropOperation) op;
+                SchemaIndexDropOperation op0 = (SchemaIndexDropOperation)op;
 
                 idx.dynamicIndexDrop(op0.schemaName(), op0.indexName(), op0.ifExists());
             }
+            else if (op instanceof SchemaAlterTableAddColumnOperation) {
+                SchemaAlterTableAddColumnOperation op0 = (SchemaAlterTableAddColumnOperation)op;
+
+                processDynamicAddColumn(type, op0.columns());
+
+                idx.dynamicAddColumn(op0.schemaName(), op0.tableName(), op0.columns(), op0.ifTableExists(),
+                    op0.ifNotExists());
+            }
             else
                 throw new SchemaOperationException("Unsupported operation: " + op);
         }
@@ -2063,6 +2140,23 @@ private IgniteInternalFuture<Object> rebuildIndexesFromHash(@Nullable final Stri
     }
 
     /**
+     * Entry point for add column procedure.
+     * @param schemaName Schema name.
+     * @param tblName Target table name.
+     * @param cols Columns to add.
+     * @param ifTblExists Ignore operation if target table doesn't exist.
+     * @param ifNotExists Ignore operation if column exists.
+     */
+    public IgniteInternalFuture<?> dynamicColumnAdd(String cacheName, String schemaName, String tblName,
+        List<QueryField> cols, boolean ifTblExists, boolean ifNotExists) {
+
+        SchemaAlterTableAddColumnOperation op = new SchemaAlterTableAddColumnOperation(UUID.randomUUID(), cacheName,
+            schemaName, tblName, cols, ifTblExists, ifNotExists);
+
+        return startIndexOperationDistributed(op);
+    }
+
+    /**
      * Start distributed index change operation.
      *
      * @param op Operation.
@@ -2130,6 +2224,30 @@ private IgniteInternalFuture<Object> rebuildIndexesFromHash(@Nullable final Stri
     }
 
     /**
+     * Update type descriptor with new fields metadata.
+     *
+     * @param d Type descriptor to update.
+     * @param cols Columns to add.
+     * @throws IgniteCheckedException If failed to update type descriptor.
+     */
+    private void processDynamicAddColumn(QueryTypeDescriptorImpl d, List<QueryField> cols) throws IgniteCheckedException {
+        List<GridQueryProperty> props = new ArrayList<>(cols.size());
+
+        for (QueryField col : cols) {
+            try {
+                props.add(new QueryBinaryProperty(ctx, col.name(), null, Class.forName(col.typeName()),
+                    false, null));
+            }
+            catch (ClassNotFoundException e) {
+                throw new SchemaOperationException("Class not found for new property: " + col.typeName());
+            }
+        }
+
+        for (GridQueryProperty p : props)
+            d.addProperty(p, true);
+    }
+
+    /**
      *
      * @param cacheName Cache name.
      * @param sql Query.

http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryField.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryField.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryField.java
new file mode 100644
index 0000000..968c287
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryField.java
@@ -0,0 +1,64 @@
+/*
+ * 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.util.typedef.internal.S;
+
+import java.io.Serializable;
+
+/**
+ * Query field metadata.
+ */
+public class QueryField implements Serializable {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Field name. */
+    private final String name;
+
+    /** Class name for this field's values. */
+    private final String typeName;
+
+    /**
+     * @param name Field name.
+     * @param typeName Class name for this field's values.
+     */
+    public QueryField(String name, String typeName) {
+        this.name = name;
+        this.typeName = typeName;
+    }
+
+    /**
+     * @return Field name.
+     */
+    public String name() {
+        return name;
+    }
+
+    /**
+     * @return Class name for this field's values.
+     */
+    public String typeName() {
+        return typeName;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(QueryField.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QuerySchema.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QuerySchema.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QuerySchema.java
index 34a4502..ccbba93 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QuerySchema.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QuerySchema.java
@@ -17,21 +17,21 @@
 
 package org.apache.ignite.internal.processors.query;
 
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
 import org.apache.ignite.cache.QueryEntity;
 import org.apache.ignite.cache.QueryIndex;
 import org.apache.ignite.internal.processors.query.schema.message.SchemaFinishDiscoveryMessage;
 import org.apache.ignite.internal.processors.query.schema.operation.SchemaAbstractOperation;
+import org.apache.ignite.internal.processors.query.schema.operation.SchemaAlterTableAddColumnOperation;
 import org.apache.ignite.internal.processors.query.schema.operation.SchemaIndexCreateOperation;
 import org.apache.ignite.internal.processors.query.schema.operation.SchemaIndexDropOperation;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.S;
 
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
-
 /**
  * Dynamic cache schema.
  */
@@ -118,9 +118,7 @@ public class QuerySchema implements Serializable {
                     }
                 }
             }
-            else {
-                assert op instanceof SchemaIndexDropOperation;
-
+            else if (op instanceof SchemaIndexDropOperation) {
                 SchemaIndexDropOperation op0 = (SchemaIndexDropOperation)op;
 
                 for (QueryEntity entity : entities) {
@@ -147,6 +145,27 @@ public class QuerySchema implements Serializable {
                     }
                 }
             }
+            else {
+                assert op instanceof SchemaAlterTableAddColumnOperation;
+
+                SchemaAlterTableAddColumnOperation op0 = (SchemaAlterTableAddColumnOperation)op;
+
+                QueryEntity target = null;
+
+                for (QueryEntity entity : entities()) {
+                    if (F.eq(entity.getTableName(), op0.tableName())) {
+                        target = entity;
+
+                        break;
+                    }
+                }
+
+                if (target == null)
+                    return;
+
+                for (QueryField field : op0.columns())
+                    target.getFields().put(field.name(), field.typeName());
+            }
         }
     }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryTypeDescriptorImpl.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryTypeDescriptorImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryTypeDescriptorImpl.java
index 79b90e5..0b77c4c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryTypeDescriptorImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryTypeDescriptorImpl.java
@@ -17,6 +17,12 @@
 
 package org.apache.ignite.internal.processors.query;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.cache.QueryIndexType;
 import org.apache.ignite.internal.util.tostring.GridToStringExclude;
@@ -25,13 +31,6 @@ import org.apache.ignite.internal.util.typedef.internal.A;
 import org.apache.ignite.internal.util.typedef.internal.S;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
 /**
  * Descriptor of type.
  */
@@ -50,7 +49,7 @@ public class QueryTypeDescriptorImpl implements GridQueryTypeDescriptor {
 
     /** Value field names and types with preserved order. */
     @GridToStringInclude
-    private final Map<String, Class<?>> fields = new LinkedHashMap<>();
+    private final LinkedHashMap<String, Class<?>> fields = new LinkedHashMap<>();
 
     /** */
     @GridToStringExclude
@@ -155,7 +154,7 @@ public class QueryTypeDescriptorImpl implements GridQueryTypeDescriptor {
     }
 
     /** {@inheritDoc} */
-    @Override public Map<String, Class<?>> fields() {
+    @Override public LinkedHashMap<String, Class<?>> fields() {
         return fields;
     }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/SchemaOperationWorker.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/SchemaOperationWorker.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/SchemaOperationWorker.java
index 06feecb..0bf4d22 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/SchemaOperationWorker.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/SchemaOperationWorker.java
@@ -105,7 +105,7 @@ public class SchemaOperationWorker extends GridWorker {
     @Override protected void body() throws InterruptedException, IgniteInterruptedCheckedException {
         try {
             // Execute.
-            qryProc.processIndexOperationLocal(op, type, depId, cancelToken);
+            qryProc.processSchemaOperationLocal(op, type, depId, cancelToken);
 
             fut.onDone();
         }

http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/operation/SchemaAbstractAlterTableOperation.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/operation/SchemaAbstractAlterTableOperation.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/operation/SchemaAbstractAlterTableOperation.java
new file mode 100644
index 0000000..a33bc08
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/operation/SchemaAbstractAlterTableOperation.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.schema.operation;
+
+import java.util.UUID;
+
+/**
+ * Parent class for ALTER TABLE command variants.
+ */
+public class SchemaAbstractAlterTableOperation extends SchemaAbstractOperation {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /**
+     * Constructor.
+     *
+     * @param opId Operation ID.
+     * @param cacheName Cache name.
+     * @param schemaName Schema name.
+     */
+    public SchemaAbstractAlterTableOperation(UUID opId, String cacheName, String schemaName) {
+        super(opId, cacheName, schemaName);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/operation/SchemaAlterTableAddColumnOperation.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/operation/SchemaAlterTableAddColumnOperation.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/operation/SchemaAlterTableAddColumnOperation.java
new file mode 100644
index 0000000..9bd109f
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/operation/SchemaAlterTableAddColumnOperation.java
@@ -0,0 +1,96 @@
+/*
+ * 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.schema.operation;
+
+import java.util.List;
+import java.util.UUID;
+import org.apache.ignite.internal.processors.query.QueryField;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+/**
+ * Schema index drop operation.
+ */
+public class SchemaAlterTableAddColumnOperation extends SchemaAbstractAlterTableOperation {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Target table name. */
+    private final String tblName;
+
+    /** Columns to add. */
+    private final List<QueryField> cols;
+
+    /** Ignore operation if target table doesn't exist. */
+    private final boolean ifTblExists;
+
+    /** Ignore operation if column exists. */
+    private final boolean ifNotExists;
+
+    /**
+     * Constructor.
+     *
+     * @param opId Operation id.
+     * @param schemaName Schema name.
+     * @param tblName Target table name.
+     * @param cols Columns to add.
+     * @param ifTblExists Ignore operation if target table doesn't exist.
+     * @param ifNotExists Ignore operation if column exists.
+     */
+    public SchemaAlterTableAddColumnOperation(UUID opId, String cacheName, String schemaName, String tblName,
+        List<QueryField> cols, boolean ifTblExists, boolean ifNotExists) {
+        super(opId, cacheName, schemaName);
+
+        this.tblName = tblName;
+        this.cols = cols;
+        this.ifTblExists = ifTblExists;
+        this.ifNotExists = ifNotExists;
+    }
+
+    /**
+     * @return Ignore operation if table doesn't exist.
+     */
+    public boolean ifTableExists() {
+        return ifTblExists;
+    }
+
+    /**
+     * @return Columns to add.
+     */
+    public List<QueryField> columns() {
+        return cols;
+    }
+
+    /**
+     * @return Quietly abort this command if column exists (honored only in single column case).
+     */
+    public boolean ifNotExists() {
+        return ifNotExists;
+    }
+
+    /**
+     * @return Target table name.
+     */
+    public String tableName() {
+        return tblName;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(SchemaAlterTableAddColumnOperation.class, this, "parent", super.toString());
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteClientCacheInitializationFailTest.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteClientCacheInitializationFailTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteClientCacheInitializationFailTest.java
index fa6dd70..c745d8a 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteClientCacheInitializationFailTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteClientCacheInitializationFailTest.java
@@ -49,6 +49,7 @@ import org.apache.ignite.internal.processors.query.GridQueryIndexing;
 import org.apache.ignite.internal.processors.query.GridQueryProcessor;
 import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
 import org.apache.ignite.internal.processors.query.GridRunningQueryInfo;
+import org.apache.ignite.internal.processors.query.QueryField;
 import org.apache.ignite.internal.processors.query.QueryIndexDescriptorImpl;
 import org.apache.ignite.internal.processors.query.schema.SchemaIndexCacheVisitor;
 import org.apache.ignite.internal.util.GridSpinBusyLock;
@@ -284,6 +285,13 @@ public class IgniteClientCacheInitializationFailTest extends GridCommonAbstractT
         }
 
         /** {@inheritDoc} */
+        @Override public void dynamicAddColumn(String schemaName, String tblName, List<QueryField> cols,
+                                               boolean ifTblExists, boolean ifColNotExists)
+            throws IgniteCheckedException {
+            // No-op.
+        }
+
+        /** {@inheritDoc} */
         @Override public void registerCache(String cacheName, String schemaName,
             GridCacheContext<?, ?> cctx) throws IgniteCheckedException {
             if (FAILED_CACHES.contains(cctx.name()) && cctx.kernalContext().clientNode())

http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2RowDescriptor.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2RowDescriptor.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2RowDescriptor.java
index 31f0e69..e59404e 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2RowDescriptor.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2RowDescriptor.java
@@ -17,6 +17,15 @@
 
 package org.apache.ignite.internal.processors.query.h2;
 
+import java.math.BigDecimal;
+import java.sql.Date;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.processors.cache.CacheObject;
 import org.apache.ignite.internal.processors.cache.GridCacheContext;
@@ -60,16 +69,6 @@ import org.h2.value.ValueUuid;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.math.BigDecimal;
-import java.sql.Date;
-import java.sql.Time;
-import java.sql.Timestamp;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
 import static org.apache.ignite.internal.processors.query.h2.opt.GridH2AbstractKeyValueRow.DEFAULT_COLUMNS_COUNT;
 import static org.apache.ignite.internal.processors.query.h2.opt.GridH2AbstractKeyValueRow.KEY_COL;
 import static org.apache.ignite.internal.processors.query.h2.opt.GridH2AbstractKeyValueRow.VAL_COL;
@@ -89,10 +88,10 @@ public class H2RowDescriptor implements GridH2RowDescriptor {
     private final GridQueryTypeDescriptor type;
 
     /** */
-    private final String[] fields;
+    private volatile String[] fields;
 
     /** */
-    private final int[] fieldTypes;
+    private volatile int[] fieldTypes;
 
     /** */
     private final int keyType;
@@ -107,13 +106,13 @@ public class H2RowDescriptor implements GridH2RowDescriptor {
     private final GridUnsafeGuard guard;
 
     /** */
-    private final GridQueryProperty[] props;
+    private volatile GridQueryProperty[] props;
 
     /** Id of user-defined key column */
-    private final int keyAliasColumnId;
+    private volatile int keyAliasColId;
 
     /** Id of user-defined value column */
-    private final int valueAliasColumnId;
+    private volatile int valAliasColId;
 
     /**
      * Constructor.
@@ -134,6 +133,17 @@ public class H2RowDescriptor implements GridH2RowDescriptor {
 
         guard = schema.offheap() == null ? null : new GridUnsafeGuard();
 
+        keyType = DataType.getTypeFromClass(type.keyClass());
+        valType = DataType.getTypeFromClass(type.valueClass());
+
+        refreshMetadataFromTypeDescriptor();
+    }
+
+    /**
+     * Update metadata of this row descriptor according to current state of type descriptor.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public final void refreshMetadataFromTypeDescriptor() {
         Map<String, Class<?>> allFields = new LinkedHashMap<>();
 
         allFields.putAll(type.fields());
@@ -147,9 +157,6 @@ public class H2RowDescriptor implements GridH2RowDescriptor {
         for (int i = 0; i < fieldTypes.length; i++)
             fieldTypes[i] = DataType.getTypeFromClass(classes[i]);
 
-        keyType = DataType.getTypeFromClass(type.keyClass());
-        valType = DataType.getTypeFromClass(type.valueClass());
-
         props = new GridQueryProperty[fields.length];
 
         for (int i = 0; i < fields.length; i++) {
@@ -160,12 +167,12 @@ public class H2RowDescriptor implements GridH2RowDescriptor {
             props[i] = p;
         }
 
-        final List<String> fieldsList = Arrays.asList(fields);
+        List<String> fieldsList = Arrays.asList(fields);
 
-        keyAliasColumnId =
+        keyAliasColId =
             (type.keyFieldName() != null) ? DEFAULT_COLUMNS_COUNT + fieldsList.indexOf(type.keyFieldAlias()) : -1;
 
-        valueAliasColumnId =
+        valAliasColId =
             (type.valueFieldName() != null) ? DEFAULT_COLUMNS_COUNT + fieldsList.indexOf(type.valueFieldAlias()) : -1;
     }
 
@@ -376,29 +383,29 @@ public class H2RowDescriptor implements GridH2RowDescriptor {
     }
 
     /** {@inheritDoc} */
-    @Override public boolean isKeyColumn(int columnId) {
-        assert columnId >= 0;
-        return columnId == KEY_COL || columnId == keyAliasColumnId;
+    @Override public boolean isKeyColumn(int colId) {
+        assert colId >= 0;
+        return colId == KEY_COL || colId == keyAliasColId;
     }
 
     /** {@inheritDoc} */
-    @Override public boolean isValueColumn(int columnId) {
-        assert columnId >= 0;
-        return columnId == VAL_COL || columnId == valueAliasColumnId;
+    @Override public boolean isValueColumn(int colId) {
+        assert colId >= 0;
+        return colId == VAL_COL || colId == valAliasColId;
     }
 
     /** {@inheritDoc} */
     @SuppressWarnings("RedundantIfStatement")
-    @Override public boolean isKeyValueOrVersionColumn(int columnId) {
-        assert columnId >= 0;
+    @Override public boolean isKeyValueOrVersionColumn(int colId) {
+        assert colId >= 0;
 
-        if (columnId < DEFAULT_COLUMNS_COUNT)
+        if (colId < DEFAULT_COLUMNS_COUNT)
             return true;
 
-        if (columnId == keyAliasColumnId)
+        if (colId == keyAliasColId)
             return true;
 
-        if (columnId == valueAliasColumnId)
+        if (colId == valAliasColId)
             return true;
 
         return false;
@@ -409,26 +416,26 @@ public class H2RowDescriptor implements GridH2RowDescriptor {
         assert masks != null;
         assert masks.length > 0;
 
-        if (keyAliasColumnId < 0)
+        if (keyAliasColId < 0)
             return (masks[KEY_COL] & mask) != 0;
         else
-            return (masks[KEY_COL] & mask) != 0 || (masks[keyAliasColumnId] & mask) != 0;
+            return (masks[KEY_COL] & mask) != 0 || (masks[keyAliasColId] & mask) != 0;
     }
 
     /** {@inheritDoc} */
-    @Override public void initValueCache(Value valCache[], Value key, Value value, Value version) {
+    @Override public void initValueCache(Value valCache[], Value key, Value val, Value ver) {
         assert valCache != null;
         assert valCache.length > 0;
 
         valCache[KEY_COL] = key;
-        valCache[VAL_COL] = value;
-        valCache[VER_COL] = version;
+        valCache[VAL_COL] = val;
+        valCache[VER_COL] = ver;
 
-        if (keyAliasColumnId > 0)
-            valCache[keyAliasColumnId] = key;
+        if (keyAliasColId > 0)
+            valCache[keyAliasColId] = key;
 
-        if (valueAliasColumnId > 0)
-            valCache[valueAliasColumnId] = value;
+        if (valAliasColId > 0)
+            valCache[valAliasColId] = val;
     }
 
     /** {@inheritDoc} */
@@ -440,8 +447,8 @@ public class H2RowDescriptor implements GridH2RowDescriptor {
         for (int idx = 0; idx < data.length; idx++)
             data[idx] = row.getValue(idx);
 
-        copyAliasColumnData(data, KEY_COL, keyAliasColumnId);
-        copyAliasColumnData(data, VAL_COL, valueAliasColumnId);
+        copyAliasColumnData(data, KEY_COL, keyAliasColId);
+        copyAliasColumnData(data, VAL_COL, valAliasColId);
 
         return new SimpleRow(data);
     }
@@ -466,16 +473,16 @@ public class H2RowDescriptor implements GridH2RowDescriptor {
 
     /** {@inheritDoc} */
     @Override public int getAlternativeColumnId(int colId) {
-        if (keyAliasColumnId > 0) {
+        if (keyAliasColId > 0) {
             if (colId == KEY_COL)
-                return keyAliasColumnId;
-            else if (colId == keyAliasColumnId)
+                return keyAliasColId;
+            else if (colId == keyAliasColId)
                 return KEY_COL;
         }
-        if (valueAliasColumnId > 0) {
+        if (valAliasColId > 0) {
             if (colId == VAL_COL)
-                return valueAliasColumnId;
-            else if (colId == valueAliasColumnId)
+                return valAliasColId;
+            else if (colId == valAliasColId)
                 return VAL_COL;
         }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TableEngine.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TableEngine.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TableEngine.java
index 6bdcc30..d3e9560 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TableEngine.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TableEngine.java
@@ -17,24 +17,21 @@
 
 package org.apache.ignite.internal.processors.query.h2;
 
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
 import org.apache.ignite.internal.processors.query.h2.database.H2RowFactory;
-import org.apache.ignite.internal.processors.query.h2.opt.GridH2RowDescriptor;
 import org.apache.ignite.internal.processors.query.h2.opt.GridH2Table;
 import org.h2.api.TableEngine;
 import org.h2.command.ddl.CreateTableData;
 import org.h2.table.TableBase;
-import org.jetbrains.annotations.Nullable;
-
-import java.sql.Connection;
-import java.sql.SQLException;
-import java.sql.Statement;
 
 /**
  * H2 Table engine.
  */
 public class H2TableEngine implements TableEngine {
     /** */
-    private static GridH2RowDescriptor rowDesc0;
+    private static H2RowDescriptor rowDesc0;
 
     /** */
     private static H2RowFactory rowFactory0;
@@ -56,7 +53,7 @@ public class H2TableEngine implements TableEngine {
      * @throws SQLException If failed.
      * @return Created table.
      */
-    public static synchronized GridH2Table createTable(Connection conn, String sql, GridH2RowDescriptor rowDesc,
+    public static synchronized GridH2Table createTable(Connection conn, String sql, H2RowDescriptor rowDesc,
         H2RowFactory rowFactory, H2TableDescriptor tblDesc)
         throws SQLException {
         rowDesc0 = rowDesc;

http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
----------------------------------------------------------------------
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 0f97a4b..dff82d7 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
@@ -90,6 +90,7 @@ import org.apache.ignite.internal.processors.query.GridQueryIndexing;
 import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
 import org.apache.ignite.internal.processors.query.GridRunningQueryInfo;
 import org.apache.ignite.internal.processors.query.IgniteSQLException;
+import org.apache.ignite.internal.processors.query.QueryField;
 import org.apache.ignite.internal.processors.query.QueryIndexDescriptorImpl;
 import org.apache.ignite.internal.processors.query.QueryUtils;
 import org.apache.ignite.internal.processors.query.h2.database.H2RowFactory;
@@ -708,6 +709,25 @@ public class IgniteH2Indexing implements GridQueryIndexing {
         executeSql(schemaName, sql);
     }
 
+    /** {@inheritDoc} */
+    @Override public void dynamicAddColumn(String schemaName, String tblName, List<QueryField> cols,
+        boolean ifTblExists, boolean ifColNotExists) throws IgniteCheckedException {
+        // Locate table.
+        H2Schema schema = schemas.get(schemaName);
+
+        H2TableDescriptor desc = (schema != null ? schema.tableByName(tblName) : null);
+
+        if (desc == null) {
+            if (!ifTblExists)
+                throw new IgniteCheckedException("Table not found in internal H2 database [schemaName=" + schemaName +
+                    ", tblName=" + tblName + ']');
+            else
+                return;
+        }
+
+        desc.table().addColumns(cols, ifColNotExists);
+    }
+
     /**
      * Execute DDL command.
      *
@@ -1628,7 +1648,7 @@ public class IgniteH2Indexing implements GridQueryIndexing {
         if (log.isDebugEnabled())
             log.debug("Creating DB table with SQL: " + sql);
 
-        GridH2RowDescriptor rowDesc = new H2RowDescriptor(this, tbl, tbl.type(), schema);
+        H2RowDescriptor rowDesc = new H2RowDescriptor(this, tbl, tbl.type(), schema);
 
         H2RowFactory rowFactory = tbl.rowFactory(rowDesc);
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/ddl/DdlStatementsProcessor.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/ddl/DdlStatementsProcessor.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/ddl/DdlStatementsProcessor.java
index 65e402d..f322010 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/ddl/DdlStatementsProcessor.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/ddl/DdlStatementsProcessor.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.processors.query.h2.ddl;
 
 import java.sql.PreparedStatement;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -34,9 +35,11 @@ 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;
 import org.apache.ignite.internal.processors.query.IgniteSQLException;
+import org.apache.ignite.internal.processors.query.QueryField;
 import org.apache.ignite.internal.processors.query.QueryUtils;
 import org.apache.ignite.internal.processors.query.h2.IgniteH2Indexing;
 import org.apache.ignite.internal.processors.query.h2.opt.GridH2Table;
+import org.apache.ignite.internal.processors.query.h2.sql.GridSqlAlterTableAddColumn;
 import org.apache.ignite.internal.processors.query.h2.sql.GridSqlColumn;
 import org.apache.ignite.internal.processors.query.h2.sql.GridSqlCreateIndex;
 import org.apache.ignite.internal.processors.query.h2.sql.GridSqlCreateTable;
@@ -48,6 +51,7 @@ import org.apache.ignite.internal.processors.query.schema.SchemaOperationExcepti
 import org.apache.ignite.internal.util.future.GridFinishedFuture;
 import org.apache.ignite.internal.util.typedef.F;
 import org.h2.command.Prepared;
+import org.h2.command.ddl.AlterTableAlterColumn;
 import org.h2.command.ddl.CreateIndex;
 import org.h2.command.ddl.CreateTable;
 import org.h2.command.ddl.DropIndex;
@@ -203,6 +207,50 @@ public class DdlStatementsProcessor {
                 else
                     ctx.query().dynamicTableDrop(tbl.cacheName(), cmd.tableName(), cmd.ifExists());
             }
+            else if (stmt0 instanceof GridSqlAlterTableAddColumn) {
+                GridSqlAlterTableAddColumn cmd = (GridSqlAlterTableAddColumn)stmt0;
+
+                GridH2Table tbl = idx.dataTable(cmd.schemaName(), cmd.tableName());
+
+                if (tbl == null && cmd.ifTableExists()) {
+                    ctx.cache().createMissingQueryCaches();
+
+                    tbl = idx.dataTable(cmd.schemaName(), cmd.tableName());
+                }
+
+                if (tbl == null) {
+                    if (!cmd.ifTableExists())
+                        throw new SchemaOperationException(SchemaOperationException.CODE_TABLE_NOT_FOUND,
+                            cmd.tableName());
+                }
+                else {
+                    List<QueryField> cols = new ArrayList<>(cmd.columns().length);
+
+                    for (GridSqlColumn col : cmd.columns()) {
+                        if (tbl.doesColumnExist(col.columnName())) {
+                            if ((!cmd.ifNotExists() || cmd.columns().length != 1)) {
+                                throw new SchemaOperationException(SchemaOperationException.CODE_COLUMN_EXISTS,
+                                    col.columnName());
+                            }
+                            else {
+                                cols = null;
+
+                                break;
+                            }
+                        }
+
+                        cols.add(new QueryField(col.columnName(),
+                            DataType.getTypeClassName(col.column().getType())));
+                    }
+
+                    if (cols != null) {
+                        assert tbl.rowDescriptor() != null;
+
+                        fut = ctx.query().dynamicColumnAdd(tbl.cacheName(), cmd.schemaName(),
+                            tbl.rowDescriptor().type().tableName(), cols, cmd.ifTableExists(), cmd.ifNotExists());
+                    }
+                }
+            }
             else
                 throw new IgniteSQLException("Unsupported DDL operation: " + sql,
                     IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
@@ -311,6 +359,6 @@ public class DdlStatementsProcessor {
      */
     public static boolean isDdlStatement(Prepared cmd) {
         return cmd instanceof CreateIndex || cmd instanceof DropIndex || cmd instanceof CreateTable ||
-            cmd instanceof DropTable;
+            cmd instanceof DropTable || cmd instanceof AlterTableAlterColumn;
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2Table.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2Table.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2Table.java
index 107e3bb..694346c 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2Table.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2Table.java
@@ -32,6 +32,9 @@ import org.apache.ignite.internal.processors.cache.GridCacheContext;
 import org.apache.ignite.internal.processors.cache.KeyCacheObject;
 import org.apache.ignite.internal.processors.cache.query.QueryTable;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.processors.query.IgniteSQLException;
+import org.apache.ignite.internal.processors.query.QueryField;
+import org.apache.ignite.internal.processors.query.h2.H2RowDescriptor;
 import org.apache.ignite.internal.processors.query.h2.database.H2RowFactory;
 import org.apache.ignite.internal.processors.query.h2.database.H2TreeIndex;
 import org.apache.ignite.internal.util.offheap.unsafe.GridUnsafeMemory;
@@ -49,9 +52,11 @@ import org.h2.result.Row;
 import org.h2.result.SearchRow;
 import org.h2.result.SortOrder;
 import org.h2.schema.SchemaObject;
+import org.h2.table.Column;
 import org.h2.table.IndexColumn;
 import org.h2.table.TableBase;
 import org.h2.table.TableType;
+import org.h2.value.DataType;
 import org.h2.value.Value;
 import org.jetbrains.annotations.Nullable;
 import org.jsr166.ConcurrentHashMap8;
@@ -68,7 +73,7 @@ public class GridH2Table extends TableBase {
     private final GridCacheContext cctx;
 
     /** */
-    private final GridH2RowDescriptor desc;
+    private final H2RowDescriptor desc;
 
     /** */
     private volatile ArrayList<Index> idxs;
@@ -121,7 +126,7 @@ public class GridH2Table extends TableBase {
      * @param idxsFactory Indexes factory.
      * @param cctx Cache context.
      */
-    public GridH2Table(CreateTableData createTblData, GridH2RowDescriptor desc, H2RowFactory rowFactory,
+    public GridH2Table(CreateTableData createTblData, H2RowDescriptor desc, H2RowFactory rowFactory,
         GridH2SystemIndexFactory idxsFactory, GridCacheContext cctx) {
         super(createTblData);
 
@@ -949,4 +954,50 @@ public class GridH2Table extends TableBase {
 
         return null;
     }
+
+    /**
+     * Add new columns to this table.
+     *
+     * @param cols Columns to add.
+     * @param ifNotExists Ignore this command if {@code cols} has size of 1 and column with given name already exists.
+     */
+    public void addColumns(List<QueryField> cols, boolean ifNotExists) {
+        assert !ifNotExists || cols.size() == 1;
+
+        lock(true);
+
+        try {
+            int pos = columns.length;
+
+            Column[] newCols = new Column[columns.length + cols.size()];
+
+            // First, let's copy existing columns to new array
+            System.arraycopy(columns, 0, newCols, 0, columns.length);
+
+            // And now, let's add new columns
+            for (QueryField col : cols) {
+                if (doesColumnExist(col.name())) {
+                    if (ifNotExists && cols.size() == 1)
+                        return;
+                    else
+                        throw new IgniteSQLException("Column already exists [tblName=" + getName() +
+                            ", colName=" + col.name() + ']');
+                }
+
+                try {
+                    newCols[pos++] = new Column(col.name(), DataType.getTypeFromClass(Class.forName(col.typeName())));
+                }
+                catch (ClassNotFoundException e) {
+                    throw new IgniteSQLException("H2 data type not found for class: " + col.typeName(), e);
+                }
+            }
+
+            setColumns(newCols);
+
+            desc.refreshMetadataFromTypeDescriptor();
+        }
+        finally {
+            unlock(true);
+        }
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlAlterTableAddColumn.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlAlterTableAddColumn.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlAlterTableAddColumn.java
new file mode 100644
index 0000000..40423c7
--- /dev/null
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlAlterTableAddColumn.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.h2.sql;
+
+/**
+ * ALTER TABLE ADD COLUMN command data holder.
+ */
+public class GridSqlAlterTableAddColumn extends GridSqlStatement {
+    /** Schema name. */
+    private String schemaName;
+
+    /** Target table name. */
+    private String tblName;
+
+    /** Columns to add. */
+    private GridSqlColumn[] cols;
+
+    /** Quietly abort this command if column exists (honored only in single column case). */
+    private boolean ifNotExists;
+
+    /** Quietly abort this command if target table does not exist. */
+    private boolean ifTblExists;
+
+    /**
+     * @return Columns to add.
+     */
+    public GridSqlColumn[] columns() {
+        return cols;
+    }
+
+    /**
+     * @param cols Columns to add.
+     */
+    public void columns(GridSqlColumn[] cols) {
+        this.cols = cols;
+    }
+
+    /**
+     * @return Quietly abort this command if column exists (honored only in single column case).
+     */
+    public boolean ifNotExists() {
+        return ifNotExists;
+    }
+
+    /**
+     * @param ifNotExists Quietly abort this command if column exists (honored only in single column case).
+     */
+    public void ifNotExists(boolean ifNotExists) {
+        this.ifNotExists = ifNotExists;
+    }
+
+    /**
+     * @return Quietly abort this command if target table does not exist.
+     */
+    public boolean ifTableExists() {
+        return ifTblExists;
+    }
+
+    /**
+     * @param ifTblExists Quietly abort this command if target table does not exist.
+     */
+    public void ifTableExists(boolean ifTblExists) {
+        this.ifTblExists = ifTblExists;
+    }
+
+    /**
+     * @return Target table name.
+     */
+    public String tableName() {
+        return tblName;
+    }
+
+    /**
+     * @param tblName Target table name.
+     */
+    public void tableName(String tblName) {
+        this.tblName = tblName;
+    }
+
+    /**
+     * @return Schema name.
+     */
+    public String schemaName() {
+        return schemaName;
+    }
+
+    /**
+     * @param schemaName Schema name.
+     */
+    public void schemaName(String schemaName) {
+        this.schemaName = schemaName;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getSQL() {
+        throw new UnsupportedOperationException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQueryParser.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQueryParser.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQueryParser.java
index 0d6a0b2..97e9d5d 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQueryParser.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQueryParser.java
@@ -38,8 +38,10 @@ import org.apache.ignite.internal.processors.query.QueryUtils;
 import org.apache.ignite.internal.util.typedef.F;
 import org.h2.command.Command;
 import org.h2.command.CommandContainer;
+import org.h2.command.CommandInterface;
 import org.h2.command.Prepared;
 import org.h2.command.ddl.AlterTableAddConstraint;
+import org.h2.command.ddl.AlterTableAlterColumn;
 import org.h2.command.ddl.CreateIndex;
 import org.h2.command.ddl.CreateTable;
 import org.h2.command.ddl.CreateTableData;
@@ -393,6 +395,30 @@ public class GridSqlQueryParser {
     private static final Getter<Column, Expression> COLUMN_CHECK_CONSTRAINT = getter(Column.class, "checkConstraint");
 
     /** */
+    private static final Getter<AlterTableAlterColumn, String> ALTER_COLUMN_TBL_NAME =
+        getter(AlterTableAlterColumn.class, "tableName");
+
+    /** */
+    private static final Getter<AlterTableAlterColumn, ArrayList<Column>> ALTER_COLUMN_NEW_COLS =
+        getter(AlterTableAlterColumn.class, "columnsToAdd");
+
+    /** */
+    private static final Getter<AlterTableAlterColumn, Boolean> ALTER_COLUMN_IF_NOT_EXISTS =
+        getter(AlterTableAlterColumn.class, "ifNotExists");
+
+    /** */
+    private static final Getter<AlterTableAlterColumn, Boolean> ALTER_COLUMN_IF_TBL_EXISTS =
+        getter(AlterTableAlterColumn.class, "ifTableExists");
+
+    /** */
+    private static final Getter<AlterTableAlterColumn, String> ALTER_COLUMN_BEFORE_COL =
+        getter(AlterTableAlterColumn.class, "addBefore");
+
+    /** */
+    private static final Getter<AlterTableAlterColumn, String> ALTER_COLUMN_AFTER_COL =
+        getter(AlterTableAlterColumn.class, "addAfter");
+
+    /** */
     private static final String PARAM_NAME_VALUE_SEPARATOR = "=";
 
     /** */
@@ -885,41 +911,8 @@ public class GridSqlQueryParser {
 
         LinkedHashMap<String, GridSqlColumn> cols = new LinkedHashMap<>(data.columns.size());
 
-        for (Column col : data.columns) {
-            if (col.isAutoIncrement())
-                throw new IgniteSQLException("AUTO_INCREMENT columns are not supported [colName=" + col.getName() + ']',
-                    IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
-
-            if (!col.isNullable())
-                throw new IgniteSQLException("Non nullable columns are forbidden [colName=" + col.getName() + ']',
-                    IgniteQueryErrorCode.PARSING);
-
-            if (COLUMN_IS_COMPUTED.get(col))
-                throw new IgniteSQLException("Computed columns are not supported [colName=" + col.getName() + ']',
-                    IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
-
-            if (col.getDefaultExpression() != null)
-                throw new IgniteSQLException("DEFAULT expressions are not supported [colName=" + col.getName() + ']',
-                    IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
-
-            if (col.getSequence() != null)
-                throw new IgniteSQLException("SEQUENCE columns are not supported [colName=" + col.getName() + ']',
-                    IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
-
-            if (col.getSelectivity() != Constants.SELECTIVITY_DEFAULT)
-                throw new IgniteSQLException("SELECTIVITY column attr is not supported [colName=" + col.getName() + ']',
-                    IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
-
-            if (COLUMN_CHECK_CONSTRAINT.get(col) != null)
-                throw new IgniteSQLException("Column CHECK constraints are not supported [colName=" + col.getName() +
-                    ']', IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
-
-            GridSqlColumn gridCol = new GridSqlColumn(col, null, col.getName());
-
-            gridCol.resultType(GridSqlType.fromColumn(col));
-
-            cols.put(col.getName(), gridCol);
-        }
+        for (Column col : data.columns)
+            cols.put(col.getName(), parseColumn(col));
 
         if (cols.containsKey(QueryUtils.KEY_FIELD_NAME.toUpperCase()) ||
             cols.containsKey(QueryUtils.VAL_FIELD_NAME.toUpperCase()))
@@ -1009,6 +1002,98 @@ public class GridSqlQueryParser {
     }
 
     /**
+     * Parse {@code ALTER TABLE} statement.
+     * @param stmt H2 statement.
+     */
+    private GridSqlStatement parseAlterColumn(AlterTableAlterColumn stmt) {
+        switch (stmt.getType()) {
+            case CommandInterface.ALTER_TABLE_ADD_COLUMN:
+                return parseAddColumn(stmt);
+
+            default:
+                throw new IgniteSQLException("Unsupported operation code: " + stmt.getType());
+        }
+    }
+
+    /**
+     * Turn H2 column to grid column and check requested features.
+     * @param col H2 column.
+     * @return Grid column.
+     */
+    private static GridSqlColumn parseColumn(Column col) {
+        if (col.isAutoIncrement())
+            throw new IgniteSQLException("AUTO_INCREMENT columns are not supported [colName=" + col.getName() + ']',
+                IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+
+        if (!col.isNullable())
+            throw new IgniteSQLException("Non nullable columns are not supported [colName=" + col.getName() + ']',
+                IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+
+        if (COLUMN_IS_COMPUTED.get(col))
+            throw new IgniteSQLException("Computed columns are not supported [colName=" + col.getName() + ']',
+                IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+
+        if (col.getDefaultExpression() != null)
+            throw new IgniteSQLException("DEFAULT expressions are not supported [colName=" + col.getName() + ']',
+                IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+
+        if (col.getSequence() != null)
+            throw new IgniteSQLException("SEQUENCE columns are not supported [colName=" + col.getName() + ']',
+                IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+
+        if (col.getSelectivity() != Constants.SELECTIVITY_DEFAULT)
+            throw new IgniteSQLException("SELECTIVITY column attribute is not supported [colName=" + col.getName() + ']',
+                IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+
+        if (COLUMN_CHECK_CONSTRAINT.get(col) != null)
+            throw new IgniteSQLException("Column CHECK constraints are not supported [colName=" + col.getName() +
+                ']', IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+
+        GridSqlColumn gridCol = new GridSqlColumn(col, null, col.getName());
+
+        gridCol.resultType(GridSqlType.fromColumn(col));
+
+        return gridCol;
+    }
+
+    /**
+     * Parse {@code ALTER TABLE ... ADD COLUMN} statement.
+     * @param addCol H2 statement.
+     * @see <a href="http://www.h2database.com/html/grammar.html#alter_table_add"></a>
+     */
+    private GridSqlStatement parseAddColumn(AlterTableAlterColumn addCol) {
+        assert addCol.getType() == CommandInterface.ALTER_TABLE_ADD_COLUMN;
+
+        if (ALTER_COLUMN_BEFORE_COL.get(addCol) != null || ALTER_COLUMN_AFTER_COL.get(addCol) != null)
+            throw new IgniteSQLException("ALTER TABLE ADD COLUMN BEFORE/AFTER is not supported",
+                IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+
+        GridSqlAlterTableAddColumn res = new GridSqlAlterTableAddColumn();
+
+        ArrayList<Column> h2NewCols = ALTER_COLUMN_NEW_COLS.get(addCol);
+
+        GridSqlColumn[] gridNewCols = new GridSqlColumn[h2NewCols.size()];
+
+        for (int i = 0; i < h2NewCols.size(); i++)
+            gridNewCols[i] = parseColumn(h2NewCols.get(i));
+
+        res.columns(gridNewCols);
+
+        if (gridNewCols.length == 1)
+            res.ifNotExists(ALTER_COLUMN_IF_NOT_EXISTS.get(addCol));
+
+        res.ifTableExists(ALTER_COLUMN_IF_TBL_EXISTS.get(addCol));
+
+        Schema schema = SCHEMA_COMMAND_SCHEMA.get(addCol);
+
+        res.schemaName(schema.getName());
+
+        res.tableName(ALTER_COLUMN_TBL_NAME.get(addCol));
+
+        return res;
+    }
+
+    /**
      * @param name Param name.
      * @param val Param value.
      * @param res Table params to update.
@@ -1206,7 +1291,10 @@ public class GridSqlQueryParser {
             return parseCreateTable((CreateTable)stmt);
 
         if (stmt instanceof DropTable)
-            return parseDropTable((DropTable) stmt);
+            return parseDropTable((DropTable)stmt);
+
+        if (stmt instanceof AlterTableAlterColumn)
+            return parseAlterColumn((AlterTableAlterColumn)stmt);
 
         throw new CacheException("Unsupported SQL statement: " + stmt);
     }


Mime
View raw message