You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ag...@apache.org on 2019/08/01 13:15:20 UTC

[ignite] branch master updated: IGNITE-10654 Print warn message in case of index creating with already existing fields.

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

agura pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new c41466f  IGNITE-10654 Print warn message in case of index creating with already existing fields.
c41466f is described below

commit c41466f480aca524f7a9da2891d1988c0e82bc43
Author: zstan <st...@gmail.com>
AuthorDate: Thu Aug 1 16:14:50 2019 +0300

    IGNITE-10654 Print warn message in case of index creating with already existing fields.
    
    Signed-off-by: Andrey Gura <ag...@apache.org>
---
 .../processors/query/QueryIndexDescriptorImpl.java |   2 +-
 .../processors/query/h2/opt/GridH2Table.java       |  76 +++-
 .../cache/IgniteDynamicSqlRestoreTest.java         |   2 +-
 .../processors/cache/index/BasicIndexTest.java     | 487 ++++++++++++++++++++-
 4 files changed, 560 insertions(+), 7 deletions(-)

diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryIndexDescriptorImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryIndexDescriptorImpl.java
index 1b85af5..d8b972c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryIndexDescriptorImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryIndexDescriptorImpl.java
@@ -127,7 +127,7 @@ public class QueryIndexDescriptorImpl implements GridQueryIndexDescriptor {
 
         if (descending) {
             if (descendings == null)
-                descendings  = new HashSet<>();
+                descendings = new HashSet<>();
 
             descendings.add(field);
         }
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 250fe2d..a4dfa16 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
@@ -29,10 +29,15 @@ import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.LongAdder;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteInterruptedException;
 import org.apache.ignite.cache.query.QueryRetryException;
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.internal.GridKernalContext;
 import org.apache.ignite.internal.processors.cache.GridCacheContext;
 import org.apache.ignite.internal.processors.cache.GridCacheContextInfo;
 import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
@@ -48,6 +53,7 @@ import org.apache.ignite.internal.processors.query.h2.database.H2IndexType;
 import org.apache.ignite.internal.processors.query.h2.database.H2TreeIndex;
 import org.apache.ignite.internal.processors.query.h2.database.H2TreeIndexBase;
 import org.apache.ignite.internal.processors.query.h2.database.IndexInformation;
+import org.apache.ignite.internal.util.tostring.GridToStringExclude;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.spi.indexing.IndexingQueryCacheFilter;
@@ -163,6 +169,10 @@ public class GridH2Table extends TableBase {
     /** Table statistics. */
     private volatile TableStatistics tblStats;
 
+    /** Logger. */
+    @GridToStringExclude
+    private IgniteLogger log;
+
     /**
      * Creates table.
      *
@@ -231,6 +241,12 @@ public class GridH2Table extends TableBase {
         // Init stats with the dummy values. This prevents us from scanning index with backup filter when
         // topology may not be initialized yet.
         tblStats = new TableStatistics(0, 0);
+
+        if (desc != null && desc.context() != null) {
+            GridKernalContext ctx = desc.context().kernalContext();
+
+            log = ctx.log(getClass());
+        }
     }
 
     /**
@@ -858,6 +874,52 @@ public class GridH2Table extends TableBase {
     }
 
     /**
+     * Checks index presence, return {@link Index} if index with same name or same fields and search direction already
+     * exist or {@code null} othervise.
+     *
+     * @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 {
+        IndexColumn[] curColumns = curIdx.getIndexColumns();
+
+        Index registredIdx = null;
+
+        for (Index idx : idxs) {
+            if (!(idx instanceof H2TreeIndex))
+                continue;
+
+            if (F.eq(curIdx.getName(), idx.getName()))
+                throw new IgniteCheckedException("Index already exists: " + idx.getName());
+
+            IndexColumn[] idxColumns = idx.getIndexColumns();
+
+            for (int i = 0; i < Math.min(idxColumns.length, curColumns.length); ++i) {
+                IndexColumn idxCol = idxColumns[i];
+                IndexColumn curCol = curColumns[i];
+
+                // pk attach at the end of listed fields.
+                if (curCol.column.getColumnId() == 0 && registredIdx != null)
+                    continue;
+
+                if (H2Utils.equals(idxCol, curCol) && idxCol.sortType == curCol.sortType)
+                    registredIdx = idx;
+                else {
+                    registredIdx = null;
+
+                    break;
+                }
+            }
+
+            if (registredIdx != null)
+                return registredIdx;
+        }
+
+        return null;
+    }
+
+    /**
      * Add index that is in an intermediate state and is still being built, thus is not used in queries until it is
      * promoted.
      *
@@ -872,9 +934,17 @@ public class GridH2Table extends TableBase {
         try {
             ensureNotDestroyed();
 
-            for (Index oldIdx : idxs) {
-                if (F.eq(oldIdx.getName(), idx.getName()))
-                    throw new IgniteCheckedException("Index already exists: " + idx.getName());
+            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 + "]]");
             }
 
             Index oldTmpIdx = tmpIdxs.put(idx.getName(), (GridH2IndexBase)idx);
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteDynamicSqlRestoreTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteDynamicSqlRestoreTest.java
index 8163a76..6ee3e53 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteDynamicSqlRestoreTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteDynamicSqlRestoreTest.java
@@ -199,7 +199,7 @@ public class IgniteDynamicSqlRestoreTest extends GridCommonAbstractTest implemen
                 " ADDRESS VARCHAR,\n" +
                 " LANG VARCHAR,\n" +
                 " BIRTH_DATE TIMESTAMP,\n" +
-                " CONSTRAINT PK_PESON PRIMARY KEY (FIRST_NAME,LAST_NAME,ADDRESS,LANG)\n" +
+                " CONSTRAINT PK_PERSON PRIMARY KEY (FIRST_NAME,LAST_NAME,ADDRESS,LANG)\n" +
                 " ) WITH \"key_type=PersonKeyType, CACHE_NAME=PersonCache, value_type=PersonValueType, AFFINITY_KEY=FIRST_NAME,template=PARTITIONED,backups=1\"");
 
             try (PreparedStatement stmt = conn.prepareStatement(
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/BasicIndexTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/BasicIndexTest.java
index dae6dcd..553d10a 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/BasicIndexTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/BasicIndexTest.java
@@ -26,10 +26,13 @@ import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Objects;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import org.apache.ignite.IgniteCache;
+import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.cache.QueryEntity;
 import org.apache.ignite.cache.QueryIndex;
+import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction;
 import org.apache.ignite.cache.query.SqlFieldsQuery;
 import org.apache.ignite.configuration.CacheConfiguration;
 import org.apache.ignite.configuration.DataRegionConfiguration;
@@ -38,15 +41,34 @@ import org.apache.ignite.configuration.IgniteConfiguration;
 import org.apache.ignite.internal.IgniteEx;
 import org.apache.ignite.internal.processors.cache.IgniteInternalCache;
 import org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager;
+import org.apache.ignite.internal.processors.query.GridQueryProcessor;
+import org.apache.ignite.internal.processors.query.QueryUtils;
 import org.apache.ignite.internal.util.typedef.G;
 import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.testframework.ListeningTestLogger;
+import org.apache.ignite.testframework.LogListener;
+import org.jetbrains.annotations.Nullable;
 import org.junit.Test;
 
+import static org.apache.ignite.internal.processors.query.h2.opt.H2TableScanIndex.SCAN_INDEX_NAME_SUFFIX;
+
 /**
  * A set of basic tests for caches with indexes.
  */
 public class BasicIndexTest extends AbstractIndexingCommonTest {
+    /** Default client name. */
+    private static final String CLIENT_NAME = "client";
+
+    /** {@code True} If index need to be created throught static config. */
+    private static boolean createIdx = true;
+
+    /** {@code True} If cache nedd to be created throught static config. */
+    private static boolean createStaticCache = true;
+
+    /** Default table name. */
+    private static final String TEST_TBL_NAME = "PUBLIC.TEST_TABLE";
+
     /** */
     private Collection<QueryIndex> indexes = Collections.emptyList();
 
@@ -59,6 +81,12 @@ public class BasicIndexTest extends AbstractIndexingCommonTest {
     /** */
     private int gridCount = 1;
 
+    /** Server listening logger. */
+    private ListeningTestLogger srvLog;
+
+    /** Client listening logger. */
+    private ListeningTestLogger clientLog;
+
     /** {@inheritDoc} */
     @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
         assertNotNull(inlineSize);
@@ -70,6 +98,17 @@ public class BasicIndexTest extends AbstractIndexingCommonTest {
 
         igniteCfg.setConsistentId(igniteInstanceName);
 
+        if (igniteInstanceName.startsWith(CLIENT_NAME)) {
+            igniteCfg.setClientMode(true);
+
+            if (clientLog != null)
+                igniteCfg.setGridLogger(clientLog);
+        }
+        else {
+            if (srvLog != null)
+                igniteCfg.setGridLogger(srvLog);
+        }
+
         LinkedHashMap<String, String> fields = new LinkedHashMap<>();
         fields.put("keyStr", String.class.getName());
         fields.put("keyLong", Long.class.getName());
@@ -78,7 +117,11 @@ public class BasicIndexTest extends AbstractIndexingCommonTest {
         fields.put("valLong", Long.class.getName());
         fields.put("valPojo", Pojo.class.getName());
 
+        if (!createIdx)
+            indexes = Collections.emptyList();
+
         CacheConfiguration<Key, Val> ccfg = new CacheConfiguration<Key, Val>(DEFAULT_CACHE_NAME)
+            .setAffinity(new RendezvousAffinityFunction(false, 32))
             .setQueryEntities(Collections.singleton(
                 new QueryEntity()
                     .setKeyType(Key.class.getName())
@@ -86,15 +129,17 @@ public class BasicIndexTest extends AbstractIndexingCommonTest {
                     .setFields(fields)
                     .setKeyFields(new HashSet<>(Arrays.asList("keyStr", "keyLong", "keyPojo")))
                     .setIndexes(indexes)
+                    .setAliases(Collections.singletonMap(QueryUtils.KEY_FIELD_NAME, "pk_id"))
             ))
             .setSqlIndexMaxInlineSize(inlineSize);
 
-        igniteCfg.setCacheConfiguration(ccfg);
+        if (createStaticCache)
+            igniteCfg.setCacheConfiguration(ccfg);
 
         if (isPersistenceEnabled) {
             igniteCfg.setDataStorageConfiguration(new DataStorageConfiguration()
                 .setDefaultDataRegionConfiguration(
-                    new DataRegionConfiguration().setPersistenceEnabled(true)
+                    new DataRegionConfiguration().setPersistenceEnabled(true).setMaxSize(10 * 1024 * 1024)
                 )
             );
         }
@@ -121,6 +166,8 @@ public class BasicIndexTest extends AbstractIndexingCommonTest {
 
         cleanPersistenceDir();
 
+        srvLog = clientLog = null;
+
         super.afterTest();
     }
 
@@ -209,6 +256,442 @@ public class BasicIndexTest extends AbstractIndexingCommonTest {
         }
     }
 
+    /**
+     * Tests mixed dynamic and static caches with indexes creation.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testDynamicIdxOnStaticCacheWithIdxWithoutPersistence() throws Exception {
+        runDynamicIdxOnStaticCacheWithIdx(false);
+    }
+
+    /**
+     * Tests mixed dynamic and static caches with indexes creation.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testDynamicIdxOnStaticCacheWithIdxWithPersistence() throws Exception {
+        runDynamicIdxOnStaticCacheWithIdx(true);
+    }
+
+    /** */
+    private void runDynamicIdxOnStaticCacheWithIdx(boolean persistEnabled) throws Exception {
+        isPersistenceEnabled = persistEnabled;
+
+        inlineSize = 10;
+
+        createIdx = false;
+
+        indexes = Collections.singletonList(new QueryIndex("valStr"));
+
+        IgniteEx ig0 = startGrid(0);
+
+        createIdx = true;
+
+        startGrid(1);
+
+        if (persistEnabled)
+            ig0.cluster().active(true);
+
+        IgniteCache<Key, Val> cache = grid(0).cache(DEFAULT_CACHE_NAME);
+
+        populateCache();
+
+        String plan = cache.query(new SqlFieldsQuery("explain select * from Val where valStr between 0 and ?")
+            .setArgs(100)).getAll().get(0).get(0).toString();
+
+        assertTrue(plan, plan.contains(SCAN_INDEX_NAME_SUFFIX));
+
+        stopAllGrids();
+
+        if (persistEnabled)
+            cleanPersistenceDir();
+
+        createStaticCache = false;
+
+        ig0 = startGrid(0);
+
+        if (persistEnabled)
+            ig0.cluster().active(true);
+
+        ig0.getOrCreateCache(DEFAULT_CACHE_NAME);
+
+        populateCache();
+
+        createStaticCache = true;
+
+        try {
+            startGrid(1);
+
+            fail("Exception wasn't thrown");
+        }
+        catch (IgniteCheckedException e) {
+            // no op.
+        }
+    }
+
+    /**
+     * Tests dynamic indexes creation with equal fields.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testEqualFieldsDynamicIndexesWithoutPersistence() throws Exception {
+        runEqualFieldsDynamicIndexes(false);
+    }
+
+    /**
+     * Tests dynamic indexes creation with equal fields.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testEqualFieldsDynamicIndexesWithPersistence() throws Exception {
+        runEqualFieldsDynamicIndexes(true);
+    }
+
+    /** */
+    private void runEqualFieldsDynamicIndexes(boolean persistEnabled) throws Exception {
+        isPersistenceEnabled = persistEnabled;
+
+        indexes = Collections.singletonList(new QueryIndex("valStr"));
+
+        inlineSize = 10;
+
+        srvLog = new ListeningTestLogger(false, log);
+
+        clientLog = new ListeningTestLogger(false, log);
+
+        String msg1 = "Index with the given set or subset of columns already exists";
+
+        LogListener lsnr = LogListener.matches(msg1).andMatches(Pattern.compile(".*newIndexName=idx[0-9]")).build();
+
+        LogListener staticCachesLsnr = LogListener.matches(msg1).build();
+
+        srvLog.registerListener(staticCachesLsnr);
+
+        IgniteEx ig0 = startGrid(0);
+
+        if (persistEnabled)
+            ig0.cluster().active(true);
+
+        IgniteCache<Key, Val> cache = grid(0).cache(DEFAULT_CACHE_NAME);
+
+        populateCache();
+
+        cache.query(new SqlFieldsQuery("create index \"idx0\" on Val(valStr)"));
+
+        assertTrue(staticCachesLsnr.check());
+
+        srvLog.unregisterListener(staticCachesLsnr);
+
+        srvLog.registerListener(lsnr);
+
+        cache.query(new SqlFieldsQuery("create index \"idx1\" on Val(valStr, valLong)"));
+
+        cache.query(new SqlFieldsQuery("create index \"idx2\" on Val(valStr desc, valLong)"));
+
+        assertFalse(lsnr.check());
+
+        cache.query(new SqlFieldsQuery("create index \"idx3\" on Val(valStr, valLong)"));
+
+        cache.query(new SqlFieldsQuery("create index \"idx4\" on Val(valLong)"));
+
+        assertTrue(lsnr.check());
+
+        srvLog.unregisterListener(lsnr);
+
+        IgniteEx client = startGrid(CLIENT_NAME);
+
+        cache = client.cache(DEFAULT_CACHE_NAME);
+
+        LogListener lsnrIdx5 = LogListener.matches(msg1).andMatches("idx5").build();
+
+        srvLog.registerListener(lsnrIdx5);
+
+        cache.query(new SqlFieldsQuery("create index \"idx5\" on Val(valStr desc, valLong)"));
+
+        assertTrue(lsnrIdx5.check());
+
+        LogListener lsnrIdx7 = LogListener.matches(msg1).andMatches("idx7").build();
+
+        srvLog.registerListener(lsnrIdx7);
+
+        cache.query(new SqlFieldsQuery("create index \"idx6\" on Val(valLong)"));
+
+        cache.query(new SqlFieldsQuery("create index \"idx7\" on Val(keyStr, keyLong, keyPojo, valLong)"));
+
+        assertFalse(lsnrIdx7.check());
+    }
+
+    /** */
+    private boolean checkIdxUsed(GridQueryProcessor qryProc, @Nullable String idxName, String tblName, String... reqFlds) {
+        String sql = "explain select * from " + tblName + " where ";
+
+        for (int i = 0; i < reqFlds.length; ++i)
+            sql += reqFlds[i] + " > 0 " + ((i < reqFlds.length - 1) ? " and " : "");
+
+        String plan = qryProc.querySqlFields(new SqlFieldsQuery(sql), true)
+            .getAll().get(0).get(0).toString().toUpperCase();
+
+        return idxName != null ? (!plan.contains(SCAN_INDEX_NAME_SUFFIX) && plan.contains(idxName.toUpperCase())) : !plan.contains(SCAN_INDEX_NAME_SUFFIX);
+    }
+
+    /** */
+    private boolean checkIdxAlreadyExistLog(GridQueryProcessor qryProc, String idxName, String tblName, String... reqFlds) {
+        String msg0 = "Index with the given set or subset of columns already exists";
+
+        String sql = "create index \"" + idxName + "\" on " + tblName + "(";
+
+        for (int i = 0; i < reqFlds.length; ++i)
+            sql += reqFlds[i] + ((i < reqFlds.length - 1) ? ", " : ")");
+
+        LogListener lsnrIdx = LogListener.matches(msg0).andMatches(idxName).build();
+
+        srvLog.registerListener(lsnrIdx);
+
+        qryProc.querySqlFields(new SqlFieldsQuery(sql), true).getAll();
+
+        return lsnrIdx.check();
+    }
+
+    /** */
+    private void populateTable(GridQueryProcessor qryProc, String tblName, int consPkFldsNum, String... reqFlds) {
+        assert consPkFldsNum <= reqFlds.length;
+
+        String sql = "CREATE TABLE " + tblName + " (";
+
+        String sqlIns = "INSERT INTO " + tblName + " (";
+
+        for (int i = 0; i < reqFlds.length; ++i) {
+            sql += reqFlds[i] + " VARCHAR, ";
+
+            sqlIns += reqFlds[i] + ((i < reqFlds.length - 1) ? ", " : ") values (");
+        }
+
+        if (consPkFldsNum > 0) {
+            sql += " CONSTRAINT PK_PERSON PRIMARY KEY (";
+
+            for (int i = 0; i < consPkFldsNum; ++i)
+                sql += reqFlds[i] + ((i < consPkFldsNum - 1) ? ", " : "))");
+        }
+        else
+            sql += ")";
+
+        qryProc.querySqlFields(new SqlFieldsQuery(sql), true);
+
+        for (int i = 0; i < 10; ++i) {
+            String s0 = sqlIns;
+
+            for (int f = 0; f < reqFlds.length; ++f)
+                s0 += i + ((f < reqFlds.length - 1) ? ", " : ")");
+
+            qryProc.querySqlFields(new SqlFieldsQuery(s0), true).getAll();
+        }
+    }
+
+    /**
+     *  Checks index usage for full coverage.
+     */
+    @Test
+    public void testAllTableFieldsCoveredByIdx() throws Exception {
+        inlineSize = 10;
+
+        srvLog = new ListeningTestLogger(false, log);
+
+        IgniteEx ig0 = startGrid(0);
+
+        GridQueryProcessor qryProc = ig0.context().query();
+
+        populateTable(qryProc, TEST_TBL_NAME, 2, "FIRST_NAME", "LAST_NAME",
+            "ADDRESS", "LANG");
+
+        assertFalse(checkIdxUsed(qryProc, null, TEST_TBL_NAME, "LANG"));
+
+        assertFalse(checkIdxUsed(qryProc, null, TEST_TBL_NAME, "LAST_NAME"));
+
+        assertTrue(checkIdxUsed(qryProc, null, TEST_TBL_NAME, "FIRST_NAME"));
+
+        assertTrue(checkIdxUsed(qryProc, "_key_PK", TEST_TBL_NAME, "FIRST_NAME",
+            "LAST_NAME", "LANG", "ADDRESS"));
+
+        assertTrue(checkIdxAlreadyExistLog(
+            qryProc, "idx1", TEST_TBL_NAME, "FIRST_NAME", "LAST_NAME"));
+
+        String sqlIdx2 = String.format("create index \"idx2\" on %s(LANG, ADDRESS)", TEST_TBL_NAME);
+
+        qryProc.querySqlFields(new SqlFieldsQuery(sqlIdx2), true).getAll();
+
+        assertTrue(checkIdxUsed(qryProc, "idx2", TEST_TBL_NAME, "FIRST_NAME",
+            "LAST_NAME", "LANG", "ADDRESS"));
+
+        assertTrue(checkIdxUsed(qryProc, null, TEST_TBL_NAME, "FIRST_NAME",
+            "LAST_NAME", "ADDRESS", "LANG"));
+        // first idx fields not belongs to request fields.
+        assertTrue(checkIdxUsed(qryProc, "idx2", TEST_TBL_NAME, "ADDRESS",
+            "LAST_NAME"));
+    }
+
+    /**
+     *  Checks index usage for partial coverage.
+     *  Last field not participate in any index.
+     */
+    @Test
+    public void testPartialTableFieldsCoveredByIdx() throws Exception {
+        inlineSize = 10;
+
+        String msg0 = "Index with the given set or subset of columns already exists";
+
+        srvLog = new ListeningTestLogger(false, log);
+
+        IgniteEx ig0 = startGrid(0);
+
+        GridQueryProcessor qryProc = ig0.context().query();
+
+        String cacheName = QueryUtils.createTableCacheName("PUBLIC", "TEST_TABLE");
+
+        populateTable(qryProc, TEST_TBL_NAME, 2, "FIRST_NAME", "LAST_NAME",
+            "ADDRESS", "LANG", "GENDER");
+
+        IgniteCache<Object, Object> jcache = ig0.cache(cacheName);
+
+        assertFalse(checkIdxUsed(qryProc, null, TEST_TBL_NAME, "LANG"));
+
+        assertFalse(checkIdxUsed(qryProc, null, TEST_TBL_NAME, "LAST_NAME"));
+
+        assertTrue(checkIdxUsed(qryProc, null, TEST_TBL_NAME, "FIRST_NAME"));
+
+        assertTrue(checkIdxUsed(qryProc, "_key_PK", TEST_TBL_NAME, "FIRST_NAME",
+            "LAST_NAME", "LANG", "ADDRESS"));
+
+        assertTrue(checkIdxAlreadyExistLog(
+            qryProc, "idx1", TEST_TBL_NAME, "FIRST_NAME", "LAST_NAME"));
+
+        String sqlIdx2 = String.format("create index \"idx2\" on %s(LANG, ADDRESS)", TEST_TBL_NAME);
+
+        qryProc.querySqlFields(new SqlFieldsQuery(sqlIdx2), true).getAll();
+        // _key_PK used.
+        assertFalse(checkIdxUsed(qryProc, "idx2", TEST_TBL_NAME, "FIRST_NAME",
+            "LAST_NAME", "LANG", "ADDRESS"));
+        // _key_PK used.
+        assertTrue(checkIdxUsed(qryProc, null, TEST_TBL_NAME, "FIRST_NAME",
+            "LAST_NAME", "LANG", "ADDRESS"));
+
+        assertTrue(checkIdxUsed(qryProc, null, TEST_TBL_NAME, "FIRST_NAME",
+            "LAST_NAME", "ADDRESS", "LANG"));
+        // first idx fields not belongs to request fields.
+        assertFalse(checkIdxUsed(qryProc, "idx2", TEST_TBL_NAME, "ADDRESS",
+            "LAST_NAME"));
+
+        assertFalse(checkIdxAlreadyExistLog(
+            qryProc, "idx3", TEST_TBL_NAME, "ADDRESS", "LANG"));
+
+        assertTrue(checkIdxAlreadyExistLog(
+            qryProc, "idx4", TEST_TBL_NAME, "FIRST_NAME", "LAST_NAME", "ADDRESS", "LANG"));
+
+        LogListener lsnrIdx4 = LogListener.matches(msg0).andMatches("_key_PK").build();
+
+        srvLog.registerListener(lsnrIdx4);
+
+        String sqlIdx5 = String.format("create index \"idx5\" on %s(FIRST_NAME, LAST_NAME, LANG, ADDRESS)", TEST_TBL_NAME);
+
+        jcache.query(new SqlFieldsQuery(sqlIdx5)).getAll();
+
+        assertTrue(lsnrIdx4.check());
+    }
+
+    /**
+     * Tests different fields sequence in indexes.
+     */
+    @Test
+    public void testCreateIdxWithDifferentIdxFldsSeq() throws Exception {
+        inlineSize = 10;
+
+        srvLog = new ListeningTestLogger(false, log);
+
+        IgniteEx ig0 = startGrid(0);
+
+        IgniteEx client = startGrid(CLIENT_NAME);
+
+        GridQueryProcessor qryProc = ig0.context().query();
+
+        populateTable(qryProc, TEST_TBL_NAME, 1, "c1", "c2", "c3", "c4", "c5");
+
+        assertFalse(checkIdxAlreadyExistLog(
+            qryProc, "idx1", TEST_TBL_NAME, "c1", "c2", "c3", "c4", "c5"));
+
+        assertFalse(checkIdxAlreadyExistLog(
+            qryProc, "idx2", TEST_TBL_NAME, "c1", "c3", "c4", "c5"));
+
+        assertTrue(checkIdxAlreadyExistLog(
+            qryProc, "idx3", TEST_TBL_NAME, "c1", "c2"));
+
+        assertTrue(checkIdxAlreadyExistLog(
+            qryProc, "idx4", TEST_TBL_NAME, "c1", "c3"));
+
+        assertFalse(checkIdxAlreadyExistLog(
+            qryProc, "idx5", TEST_TBL_NAME, "c1", "c4", "c5"));
+
+        GridQueryProcessor qryProcCl = client.context().query();
+
+        assertTrue(checkIdxAlreadyExistLog(
+            qryProcCl, "idx6", TEST_TBL_NAME, "c1", "c2"));
+    }
+
+    /**
+     * Tests different fields sequence in indexes.
+     * Last field not participate in any index.
+     */
+    @Test
+    public void testIndexWithDifferentFldsReqPartialFldsInIdx() throws Exception {
+        inlineSize = 10;
+
+        IgniteEx ig0 = startGrid(0);
+
+        GridQueryProcessor qryProc = ig0.context().query();
+
+        populateTable(qryProc, TEST_TBL_NAME, 2, "FIRST_NAME", "LAST_NAME",
+            "ADDRESS", "LANG", "GENDER");
+
+        String sqlIdx1 = String.format("create index \"idx1\" on %s(LANG, LAST_NAME, ADDRESS, FIRST_NAME)", TEST_TBL_NAME);
+
+        qryProc.querySqlFields(new SqlFieldsQuery(sqlIdx1), true).getAll();
+
+        assertTrue(checkIdxUsed(qryProc, null, TEST_TBL_NAME, "FIRST_NAME", "LAST_NAME", "LANG"));
+
+        assertTrue(checkIdxUsed(qryProc, null, TEST_TBL_NAME, "FIRST_NAME", "LAST_NAME", "ADDRESS"));
+
+        assertFalse(checkIdxUsed(qryProc, null, TEST_TBL_NAME,  "LAST_NAME", "ADDRESS"));
+    }
+
+    /**
+     * Tests different fields sequence in indexes.
+     * All fields covered by indexes.
+     */
+    @Test
+    public void testIndexWithDifferentFldsReqAllFldsInIdx() throws Exception {
+        inlineSize = 10;
+
+        IgniteEx ig0 = startGrid(0);
+
+        GridQueryProcessor qryProc = ig0.context().query();
+
+        populateTable(qryProc, TEST_TBL_NAME, 2, "FIRST_NAME", "LAST_NAME",
+            "ADDRESS", "LANG");
+
+        String sqlIdx1 = String.format("create index \"idx1\" on %s(LANG, LAST_NAME, ADDRESS, FIRST_NAME)", TEST_TBL_NAME);
+
+        qryProc.querySqlFields(new SqlFieldsQuery(sqlIdx1), true).getAll();
+
+        assertTrue(checkIdxUsed(qryProc, null, TEST_TBL_NAME, "FIRST_NAME", "LAST_NAME", "LANG"));
+
+        assertTrue(checkIdxUsed(qryProc, null, TEST_TBL_NAME, "FIRST_NAME", "LAST_NAME", "ADDRESS"));
+
+        assertTrue(checkIdxUsed(qryProc, null, TEST_TBL_NAME,  "LAST_NAME", "ADDRESS"));
+    }
+
     /** */
     @Test
     public void testNoIndexesWithPersistence() throws Exception {