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-3478 Commit: adec3e7ec20fe04aa37a34416253602bc0387f4f Parents: ffaf108 Author: Alexander Paschenko <[email protected]> Authored: Wed Sep 6 18:18:23 2017 +0300 Committer: devozerov <[email protected]> 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); }
