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 f3564c5be8b1a6..e5f24bfb32f6d4 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 @@ -50,6 +50,7 @@ import org.apache.ignite.internal.processors.query.h2.H2Utils; import org.apache.ignite.internal.processors.query.h2.QueryTable; import org.apache.ignite.internal.processors.query.h2.database.H2TreeIndex; +import org.apache.ignite.internal.processors.query.schema.SchemaOperationException; import org.apache.ignite.internal.processors.query.stat.ObjectStatistics; import org.apache.ignite.internal.processors.query.stat.StatisticsKey; import org.apache.ignite.internal.util.tostring.GridToStringExclude; @@ -76,6 +77,7 @@ import static org.apache.ignite.cache.CacheMode.PARTITIONED; import static org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion.NONE; +import static org.apache.ignite.internal.processors.query.schema.SchemaOperationException.CODE_INDEX_EXISTS; /** * H2 Table implementation. @@ -751,23 +753,17 @@ public boolean rebuildFromHashInProgress() { } /** - * Checks index presence, return {@link Index} if index with same name or same fields and search direction already - * exist or {@code null} othervise. + * Checks that equivalent fields collection index already present. * * @param curIdx Index to check. - * @return Index if equal or subset index exist. - * @throws IgniteCheckedException If failed. */ - private @Nullable Index checkIndexPresence(Index curIdx) throws IgniteCheckedException { + private void checkEquivalentFieldsIndexIsPresent(Index curIdx) { IndexColumn[] curColumns = curIdx.getIndexColumns(); Index registredIdx = null; for (Index idx : idxs) { - if (F.eq(curIdx.getName(), idx.getName())) - throw new IgniteCheckedException("Index already exists: " + idx.getName()); - - if (!(curIdx instanceof H2TreeIndex) || !(idx instanceof H2TreeIndex)) + if (!(idx instanceof H2TreeIndex)) continue; IndexColumn[] idxColumns = idx.getIndexColumns(); @@ -789,11 +785,17 @@ public boolean rebuildFromHashInProgress() { } } - if (registredIdx != null) - return registredIdx; - } + if (registredIdx != null) { + String idxCols = Stream.of(registredIdx.getIndexColumns()) + .map(k -> k.columnName).collect(Collectors.joining(", ")); - return null; + U.warn(log, "Index with the given set or subset of columns already exists " + + "(consider dropping either new or existing index) [cacheName=" + cacheInfo.name() + ", " + + "schemaName=" + getSchema().getName() + ", tableName=" + getName() + + ", newIndexName=" + curIdx.getName() + ", existingIndexName=" + registredIdx.getName() + + ", existingIndexColumns=[" + idxCols + "]]"); + } + } } /** @@ -811,20 +813,14 @@ public void proposeUserIndex(Index idx) throws IgniteCheckedException { try { ensureNotDestroyed(); - Index idxExist = checkIndexPresence(idx); - - if (idxExist != null) { - String idxCols = Stream.of(idxExist.getIndexColumns()) - .map(k -> k.columnName).collect(Collectors.joining(", ")); - - U.warn(log, "Index with the given set or subset of columns already exists " + - "(consider dropping either new or existing index) [cacheName=" + cacheInfo.name() + ", " + - "schemaName=" + getSchema().getName() + ", tableName=" + getName() + - ", newIndexName=" + idx.getName() + ", existingIndexName=" + idxExist.getName() + - ", existingIndexColumns=[" + idxCols + "]]"); + for (Index idx0 : idxs) { + if (F.eq(idx.getName(), idx0.getName())) + throw new SchemaOperationException(CODE_INDEX_EXISTS, idx.getName()); } - Index oldTmpIdx = tmpIdxs.put(idx.getName(), (H2IndexCostedBase)idx); + checkEquivalentFieldsIndexIsPresent(idx); + + Index oldTmpIdx = tmpIdxs.put(idx.getName(), (GridH2IndexBase)idx); assert oldTmpIdx == null; } diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/DuplicateIndexCreationTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/DuplicateIndexCreationTest.java new file mode 100644 index 00000000000000..75d49d11b41d4b --- /dev/null +++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/DuplicateIndexCreationTest.java @@ -0,0 +1,107 @@ +/* + * 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.cache; + +import java.io.Serializable; +import javax.cache.CacheException; +import org.apache.ignite.IgniteCache; +import org.apache.ignite.cache.query.SqlFieldsQuery; +import org.apache.ignite.cache.query.annotations.QuerySqlField; +import org.apache.ignite.cluster.ClusterState; +import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.configuration.DataRegionConfiguration; +import org.apache.ignite.configuration.DataStorageConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.testframework.GridTestUtils; +import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; +import org.junit.Test; +import static java.lang.String.format; + +/** Duplicate index tests. */ +public class DuplicateIndexCreationTest extends GridCommonAbstractTest { + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); + cfg.setDataStorageConfiguration( + new DataStorageConfiguration() + .setDefaultDataRegionConfiguration(new DataRegionConfiguration() + .setPersistenceEnabled(true) + .setMaxSize(256 * 1024L * 1024L))); + cfg.setCacheConfiguration( + new CacheConfiguration<>() + .setName(DEFAULT_CACHE_NAME) + .setSqlSchema("PUBLIC") + .setIndexedTypes(Integer.class, Person.class)); + return cfg; + } + + /** {@inheritDoc} */ + @Override protected void beforeTest() throws Exception { + super.beforeTest(); + stopAllGrids(); + cleanPersistenceDir(); + } + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + super.afterTest(); + stopAllGrids(); + cleanPersistenceDir(); + } + + /** Repedeatly create index with the same name, rerun cluster. */ + @Test + public void testIndexCreation() throws Exception { + IgniteEx node = startGrid(0); + node.cluster().state(ClusterState.ACTIVE); + + IgniteCache cache = node.cache(DEFAULT_CACHE_NAME); + String sqlCreateIndexTemplate = "CREATE INDEX %s ON PUBLIC.PERSON (NAME)"; + + SqlFieldsQuery queryCreateIndex = new SqlFieldsQuery(format(sqlCreateIndexTemplate, "")); + SqlFieldsQuery queryCreateIndexIfNotExist = new SqlFieldsQuery(format(sqlCreateIndexTemplate, "IF NOT EXISTS")); + + cache.query(queryCreateIndex).getAll(); + + GridTestUtils.assertThrows(log, () -> cache.query(queryCreateIndex).getAll(), CacheException.class, null); + + stopGrid(0); + startGrid(0); + + stopGrid(0); + cleanPersistenceDir(); + + node = startGrid(0); + node.cluster().state(ClusterState.ACTIVE); + IgniteCache cache1 = node.cache(DEFAULT_CACHE_NAME); + cache1.query(queryCreateIndexIfNotExist).getAll(); + + stopGrid(0); + startGrid(0); + } + + /** + * Person class. + */ + private static class Person implements Serializable { + /** Indexed name. */ + @QuerySqlField(index = true) + public String name; + } +} diff --git a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteBinaryCacheQueryTestSuite.java b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteBinaryCacheQueryTestSuite.java index a7af46f773634e..ebbb4203e8319a 100644 --- a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteBinaryCacheQueryTestSuite.java +++ b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteBinaryCacheQueryTestSuite.java @@ -23,6 +23,7 @@ import org.apache.ignite.internal.processors.cache.CacheOffheapBatchIndexingMultiTypeTest; import org.apache.ignite.internal.processors.cache.CacheQueryBuildValueTest; import org.apache.ignite.internal.processors.cache.DdlTransactionSelfTest; +import org.apache.ignite.internal.processors.cache.DuplicateIndexCreationTest; import org.apache.ignite.internal.processors.cache.GridCacheCrossCacheQuerySelfTest; import org.apache.ignite.internal.processors.cache.GridCacheLazyQueryPartitionsReleaseTest; import org.apache.ignite.internal.processors.cache.GridCacheQueryIndexDisabledSelfTest; @@ -199,6 +200,7 @@ SqlResultSetMetaSelfTest.class, BasicIndexTest.class, + DuplicateIndexCreationTest.class, ErroneousQueryEntityConfigurationTest.class, ArrayIndexTest.class, BasicIndexMultinodeTest.class,