You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by vo...@apache.org on 2015/11/26 08:50:12 UTC

[08/11] ignite git commit: Queries optimizations: - cache prepared statements and two-step queries - skip reduce query if possible - do not use index snapshots by default

Queries optimizations:
- cache prepared statements and two-step queries
- skip reduce query if possible
 - do not use index snapshots by default


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

Branch: refs/heads/ignite-1956
Commit: 4af461a7f403fc0e4df13d23d0789eee49200b26
Parents: 3c42da8
Author: sboikov <sb...@gridgain.com>
Authored: Thu Nov 26 09:30:10 2015 +0300
Committer: sboikov <sb...@gridgain.com>
Committed: Thu Nov 26 09:30:10 2015 +0300

----------------------------------------------------------------------
 .../configuration/CacheConfiguration.java       |  30 +++
 .../cache/query/GridCacheSqlQuery.java          |  42 ++++
 .../cache/query/GridCacheTwoStepQuery.java      |  35 ++-
 .../processors/query/GridQueryProcessor.java    |  40 +---
 .../processors/query/GridQueryProperty.java     |  45 ++++
 .../query/GridQueryTypeDescriptor.java          |   6 +
 .../query/h2/GridH2ResultSetIterator.java       |  19 +-
 .../processors/query/h2/IgniteH2Indexing.java   | 227 ++++++++++++++++---
 .../query/h2/opt/GridH2RowDescriptor.java       |   5 +
 .../processors/query/h2/opt/GridH2Table.java    |  69 +++++-
 .../query/h2/opt/GridH2TreeIndex.java           |  98 +++++---
 .../query/h2/sql/GridSqlQuerySplitter.java      |  73 ++++--
 .../processors/query/h2/sql/GridSqlSelect.java  |  27 +++
 .../query/h2/twostep/GridMapQueryExecutor.java  |  17 +-
 .../query/h2/twostep/GridMergeIndex.java        |   7 +
 .../h2/twostep/GridMergeIndexUnsorted.java      |  14 ++
 .../h2/twostep/GridReduceQueryExecutor.java     | 117 +++++++---
 .../cache/IgniteCacheAbstractQuerySelfTest.java |  69 +++---
 ...PartitionedSnapshotEnabledQuerySelfTest.java |  28 +++
 .../h2/GridIndexingSpiAbstractSelfTest.java     |  19 ++
 .../IgniteCacheQuerySelfTestSuite.java          |   2 +
 21 files changed, 776 insertions(+), 213 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/4af461a7/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java b/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java
index e87346f..e2bf912 100644
--- a/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java
+++ b/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java
@@ -373,6 +373,9 @@ public class CacheConfiguration<K, V> extends MutableConfiguration<K, V> {
     /** */
     private int sqlOnheapRowCacheSize = DFLT_SQL_ONHEAP_ROW_CACHE_SIZE;
 
+    /** */
+    private boolean snapshotableIdx;
+
     /** Copy on read flag. */
     private boolean cpOnRead = DFLT_COPY_ON_READ;
 
@@ -463,6 +466,7 @@ public class CacheConfiguration<K, V> extends MutableConfiguration<K, V> {
         rebalancePoolSize = cc.getRebalanceThreadPoolSize();
         rebalanceTimeout = cc.getRebalanceTimeout();
         rebalanceThrottle = cc.getRebalanceThrottle();
+        snapshotableIdx = cc.isSnapshotableIndex();
         sqlEscapeAll = cc.isSqlEscapeAll();
         sqlFuncCls = cc.getSqlFunctionClasses();
         sqlOnheapRowCacheSize = cc.getSqlOnheapRowCacheSize();
@@ -1900,6 +1904,32 @@ public class CacheConfiguration<K, V> extends MutableConfiguration<K, V> {
     }
 
     /**
+     * Gets flag indicating whether SQL indexes should support snapshots.
+     *
+     * @return {@code True} if SQL indexes should support snapshots.
+     */
+    public boolean isSnapshotableIndex() {
+        return snapshotableIdx;
+    }
+
+    /**
+     * Sets flag indicating whether SQL indexes should support snapshots.
+     * <p>
+     * Default value is {@code false}.
+     * <p>
+     * <b>Note</b> that this flag is ignored if indexes are stored in offheap memory,
+     * for offheap indexes snapshots are always enabled.
+     *
+     * @param snapshotableIdx {@code True} if SQL indexes should support snapshots.
+     * @return {@code this} for chaining.
+     */
+    public CacheConfiguration<K, V> setSnapshotableIndex(boolean snapshotableIdx) {
+        this.snapshotableIdx = snapshotableIdx;
+
+        return this;
+    }
+
+    /**
      * Gets array of cache plugin configurations.
      *
      * @return Cache plugin configurations.

http://git-wip-us.apache.org/repos/asf/ignite/blob/4af461a7/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheSqlQuery.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheSqlQuery.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheSqlQuery.java
index be7bbe9..e56e445 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheSqlQuery.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheSqlQuery.java
@@ -55,6 +55,16 @@ public class GridCacheSqlQuery implements Message {
     /** */
     @GridToStringInclude
     @GridDirectTransient
+    private int[] paramIdxs;
+
+    /** */
+    @GridToStringInclude
+    @GridDirectTransient
+    private int paramsSize;
+
+    /** */
+    @GridToStringInclude
+    @GridDirectTransient
     private LinkedHashMap<String, ?> cols;
 
     /** Field kept for backward compatibility. */
@@ -77,6 +87,14 @@ public class GridCacheSqlQuery implements Message {
         this.qry = qry;
 
         this.params = F.isEmpty(params) ? EMPTY_PARAMS : params;
+        paramsSize = this.params.length;
+    }
+
+    /**
+     * @param paramIdxs Parameter indexes.
+     */
+    public void parameterIndexes(int[] paramIdxs) {
+        this.paramIdxs = paramIdxs;
     }
 
     /**
@@ -222,4 +240,28 @@ public class GridCacheSqlQuery implements Message {
     @Override public byte fieldsCount() {
         return 3;
     }
+
+    /**
+     * @param args Arguments.
+     * @return Copy.
+     */
+    public GridCacheSqlQuery copy(Object[] args) {
+        GridCacheSqlQuery cp = new GridCacheSqlQuery();
+
+        cp.qry = qry;
+        cp.cols = cols;
+        cp.paramIdxs = paramIdxs;
+        cp.paramsSize = paramsSize;
+
+        if (F.isEmpty(args))
+            cp.params = EMPTY_PARAMS;
+        else {
+            cp.params = new Object[paramsSize];
+
+            for (int paramIdx : paramIdxs)
+                cp.params[paramIdx] = args[paramIdx];
+        }
+
+        return cp;
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/4af461a7/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheTwoStepQuery.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheTwoStepQuery.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheTwoStepQuery.java
index 4b5fe22..da59c18 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheTwoStepQuery.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheTwoStepQuery.java
@@ -47,16 +47,27 @@ public class GridCacheTwoStepQuery {
     /** */
     private Set<String> spaces;
 
+    /** */
+    private final boolean skipMergeTbl;
+
     /**
      * @param spaces All spaces accessed in query.
      * @param rdc Reduce query.
+     * @param skipMergeTbl {@code True} if reduce query can skip merge table creation and
+     *      get data directly from merge index.
      */
-    public GridCacheTwoStepQuery(Set<String> spaces, GridCacheSqlQuery rdc) {
+    public GridCacheTwoStepQuery(Set<String> spaces, GridCacheSqlQuery rdc, boolean skipMergeTbl) {
         assert rdc != null;
 
         this.spaces = spaces;
-
         this.rdc = rdc;
+        this.skipMergeTbl = skipMergeTbl;
+    }
+    /**
+     * @return {@code True} if reduce query can skip merge table creation and get data directly from merge index.
+     */
+    public boolean skipMergeTable() {
+        return skipMergeTbl;
     }
 
     /**
@@ -89,9 +100,12 @@ public class GridCacheTwoStepQuery {
 
     /**
      * @param qry SQL Query.
+     * @return {@code this}.
      */
-    public void addMapQuery(GridCacheSqlQuery qry) {
+    public GridCacheTwoStepQuery addMapQuery(GridCacheSqlQuery qry) {
         mapQrys.add(qry);
+
+        return this;
     }
 
     /**
@@ -122,6 +136,21 @@ public class GridCacheTwoStepQuery {
         this.spaces = spaces;
     }
 
+    /**
+     * @param args New arguments to copy with.
+     * @return Copy.
+     */
+    public GridCacheTwoStepQuery copy(Object[] args) {
+        assert !explain;
+
+        GridCacheTwoStepQuery cp = new GridCacheTwoStepQuery(spaces, rdc.copy(args), skipMergeTbl);
+        cp.pageSize = pageSize;
+        for (int i = 0; i < mapQrys.size(); i++)
+            cp.mapQrys.add(mapQrys.get(i).copy(args));
+
+        return cp;
+    }
+
     /** {@inheritDoc} */
     @Override public String toString() {
         return S.toString(GridCacheTwoStepQuery.class, this);

http://git-wip-us.apache.org/repos/asf/ignite/blob/4af461a7/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 d0eeeb1..814229c 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
@@ -1672,34 +1672,9 @@ public class GridQueryProcessor extends GridProcessorAdapter {
     }
 
     /**
-     *
-     */
-    private abstract static class Property {
-        /**
-         * Gets this property value from the given object.
-         *
-         * @param key Key.
-         * @param val Value.
-         * @return Property value.
-         * @throws IgniteCheckedException If failed.
-         */
-        public abstract Object value(Object key, Object val) throws IgniteCheckedException;
-
-        /**
-         * @return Property name.
-         */
-        public abstract String name();
-
-        /**
-         * @return Class member type.
-         */
-        public abstract Class<?> type();
-    }
-
-    /**
      * Description of type property.
      */
-    private static class ClassProperty extends Property {
+    private static class ClassProperty extends GridQueryProperty {
         /** */
         private final Member member;
 
@@ -1794,7 +1769,7 @@ public class GridQueryProcessor extends GridProcessorAdapter {
     /**
      *
      */
-    private class PortableProperty extends Property {
+    private class PortableProperty extends GridQueryProperty {
         /** Property name. */
         private String propName;
 
@@ -1938,7 +1913,7 @@ public class GridQueryProcessor extends GridProcessorAdapter {
 
         /** */
         @GridToStringExclude
-        private final Map<String, Property> props = new HashMap<>();
+        private final Map<String, GridQueryProperty> props = new HashMap<>();
 
         /** */
         @GridToStringInclude
@@ -1993,11 +1968,16 @@ public class GridQueryProcessor extends GridProcessorAdapter {
         }
 
         /** {@inheritDoc} */
+        @Override public GridQueryProperty property(String name) {
+            return props.get(name);
+        }
+
+        /** {@inheritDoc} */
         @SuppressWarnings("unchecked")
         @Override public <T> T value(String field, Object key, Object val) throws IgniteCheckedException {
             assert field != null;
 
-            Property prop = props.get(field);
+            GridQueryProperty prop = props.get(field);
 
             if (prop == null)
                 throw new IgniteCheckedException("Failed to find field '" + field + "' in type '" + name + "'.");
@@ -2096,7 +2076,7 @@ public class GridQueryProcessor extends GridProcessorAdapter {
          * @param failOnDuplicate Fail on duplicate flag.
          * @throws IgniteCheckedException In case of error.
          */
-        public void addProperty(Property prop, boolean failOnDuplicate) throws IgniteCheckedException {
+        public void addProperty(GridQueryProperty prop, boolean failOnDuplicate) throws IgniteCheckedException {
             String name = prop.name();
 
             if (props.put(name, prop) != null && failOnDuplicate)

http://git-wip-us.apache.org/repos/asf/ignite/blob/4af461a7/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProperty.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProperty.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProperty.java
new file mode 100644
index 0000000..d623d25
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProperty.java
@@ -0,0 +1,45 @@
+/*
+ * 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.IgniteCheckedException;
+
+/**
+ * Description and access method for query entity field.
+ */
+public abstract class GridQueryProperty {
+    /**
+     * Gets this property value from the given object.
+     *
+     * @param key Key.
+     * @param val Value.
+     * @return Property value.
+     * @throws IgniteCheckedException If failed.
+     */
+    public abstract Object value(Object key, Object val) throws IgniteCheckedException;
+
+    /**
+     * @return Property name.
+     */
+    public abstract String name();
+
+    /**
+     * @return Class member type.
+     */
+    public abstract Class<?> type();
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/4af461a7/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryTypeDescriptor.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryTypeDescriptor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryTypeDescriptor.java
index b05e1d8..45919ef 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryTypeDescriptor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryTypeDescriptor.java
@@ -50,6 +50,12 @@ public interface GridQueryTypeDescriptor {
     public <T> T value(String field, Object key, Object val) throws IgniteCheckedException;
 
     /**
+     * @param name Property name.
+     * @return Property.
+     */
+    public GridQueryProperty property(String name);
+
+    /**
      * Gets indexes for this type.
      *
      * @return Indexes for this type.

http://git-wip-us.apache.org/repos/asf/ignite/blob/4af461a7/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/GridH2ResultSetIterator.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/GridH2ResultSetIterator.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/GridH2ResultSetIterator.java
index 2c67638..3603bb5 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/GridH2ResultSetIterator.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/GridH2ResultSetIterator.java
@@ -41,14 +41,19 @@ public abstract class GridH2ResultSetIterator<T> extends GridCloseableIteratorAd
     protected final Object[] row;
 
     /** */
+    private final boolean closeStmt;
+
+    /** */
     private boolean hasRow;
 
     /**
      * @param data Data array.
+     * @param closeStmt If {@code true} closes result set statement when iterator is closed.
      * @throws IgniteCheckedException If failed.
      */
-    protected GridH2ResultSetIterator(ResultSet data) throws IgniteCheckedException {
+    protected GridH2ResultSetIterator(ResultSet data, boolean closeStmt) throws IgniteCheckedException {
         this.data = data;
+        this.closeStmt = closeStmt;
 
         if (data != null) {
             try {
@@ -115,11 +120,13 @@ public abstract class GridH2ResultSetIterator<T> extends GridCloseableIteratorAd
             // Nothing to close.
             return;
 
-        try {
-            U.closeQuiet(data.getStatement());
-        }
-        catch (SQLException e) {
-            throw new IgniteCheckedException(e);
+        if (closeStmt) {
+            try {
+                U.closeQuiet(data.getStatement());
+            }
+            catch (SQLException e) {
+                throw new IgniteCheckedException(e);
+            }
         }
 
         U.closeQuiet(data);

http://git-wip-us.apache.org/repos/asf/ignite/blob/4af461a7/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 d5efebf..cc452c1 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
@@ -79,6 +79,7 @@ import org.apache.ignite.internal.processors.query.GridQueryFieldsResult;
 import org.apache.ignite.internal.processors.query.GridQueryFieldsResultAdapter;
 import org.apache.ignite.internal.processors.query.GridQueryIndexDescriptor;
 import org.apache.ignite.internal.processors.query.GridQueryIndexing;
+import org.apache.ignite.internal.processors.query.GridQueryProperty;
 import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
 import org.apache.ignite.internal.processors.query.h2.opt.GridH2IndexBase;
 import org.apache.ignite.internal.processors.query.h2.opt.GridH2KeyValueRowOffheap;
@@ -93,12 +94,14 @@ import org.apache.ignite.internal.processors.query.h2.opt.GridLuceneIndex;
 import org.apache.ignite.internal.processors.query.h2.sql.GridSqlQuerySplitter;
 import org.apache.ignite.internal.processors.query.h2.twostep.GridMapQueryExecutor;
 import org.apache.ignite.internal.processors.query.h2.twostep.GridReduceQueryExecutor;
+import org.apache.ignite.internal.util.GridBoundedConcurrentLinkedHashMap;
 import org.apache.ignite.internal.util.GridEmptyCloseableIterator;
 import org.apache.ignite.internal.util.GridSpinBusyLock;
 import org.apache.ignite.internal.util.lang.GridCloseableIterator;
 import org.apache.ignite.internal.util.offheap.unsafe.GridUnsafeGuard;
 import org.apache.ignite.internal.util.offheap.unsafe.GridUnsafeMemory;
 import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.T3;
 import org.apache.ignite.internal.util.typedef.internal.LT;
 import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.internal.util.typedef.internal.SB;
@@ -176,6 +179,12 @@ public class IgniteH2Indexing implements GridQueryIndexing {
         ";DEFAULT_LOCK_TIMEOUT=10000;FUNCTIONS_IN_SCHEMA=true;OPTIMIZE_REUSE_RESULTS=0;QUERY_CACHE_SIZE=0;" +
         "RECOMPILE_ALWAYS=1;MAX_OPERATION_MEMORY=0";
 
+    /** */
+    private static final int PREPARED_STMT_CACHE_SIZE = 256;
+
+    /** */
+    private static final int TWO_STEP_QRY_CACHE_SIZE = 1024;
+
     /** Field name for key. */
     public static final String KEY_FIELD_NAME = "_key";
 
@@ -190,6 +199,8 @@ public class IgniteH2Indexing implements GridQueryIndexing {
      */
     static {
         try {
+            System.setProperty("h2.objectCache", "false");
+
             COMMAND_FIELD = JdbcPreparedStatement.class.getDeclaredField("command");
 
             COMMAND_FIELD.setAccessible(true);
@@ -242,6 +253,9 @@ public class IgniteH2Indexing implements GridQueryIndexing {
                 c = initialValue();
 
                 set(c);
+
+                // Reset statement cache when new connection is created.
+                stmtCache.get().clear();
             }
 
             return c;
@@ -266,6 +280,17 @@ public class IgniteH2Indexing implements GridQueryIndexing {
     /** */
     private volatile GridKernalContext ctx;
 
+    /** */
+    private final ThreadLocal<StatementCache> stmtCache = new ThreadLocal<StatementCache>() {
+        @Override protected StatementCache initialValue() {
+            return new StatementCache(PREPARED_STMT_CACHE_SIZE);
+        }
+    };
+
+    /** */
+    private final GridBoundedConcurrentLinkedHashMap<T3<String, String, Boolean>, TwoStepCachedQuery> twoStepCache =
+        new GridBoundedConcurrentLinkedHashMap<>(TWO_STEP_QRY_CACHE_SIZE);
+
     /**
      * @param space Space.
      * @return Connection.
@@ -280,6 +305,35 @@ public class IgniteH2Indexing implements GridQueryIndexing {
     }
 
     /**
+     * @param c Connection.
+     * @param sql SQL.
+     * @param useStmtCache If {@code true} uses statement cache.
+     * @return Prepared statement.
+     * @throws SQLException If failed.
+     */
+    private PreparedStatement prepareStatement(Connection c, String sql, boolean useStmtCache) throws SQLException {
+        if (useStmtCache) {
+            StatementCache cache = stmtCache.get();
+
+            PreparedStatement stmt = cache.get(sql);
+
+            if (stmt != null && !stmt.isClosed()) {
+                assert stmt.getConnection() == c;
+
+                return stmt;
+            }
+
+            stmt = c.prepareStatement(sql);
+
+            cache.put(sql, stmt);
+
+            return stmt;
+        }
+        else
+            return c.prepareStatement(sql);
+    }
+
+    /**
      * Gets DB connection.
      *
      * @param schema Whether to set schema for connection or not.
@@ -648,7 +702,7 @@ public class IgniteH2Indexing implements GridQueryIndexing {
         try {
             Connection conn = connectionForThread(schema(spaceName));
 
-            ResultSet rs = executeSqlQueryWithTimer(spaceName, conn, qry, params);
+            ResultSet rs = executeSqlQueryWithTimer(spaceName, conn, qry, params, true);
 
             List<GridQueryFieldMetadata> meta = null;
 
@@ -710,15 +764,16 @@ public class IgniteH2Indexing implements GridQueryIndexing {
      * @param conn Connection,.
      * @param sql Sql query.
      * @param params Parameters.
+     * @param useStmtCache If {@code true} uses statement cache.
      * @return Result.
      * @throws IgniteCheckedException If failed.
      */
-    private ResultSet executeSqlQuery(Connection conn, String sql, Collection<Object> params)
+    private ResultSet executeSqlQuery(Connection conn, String sql, Collection<Object> params, boolean useStmtCache)
         throws IgniteCheckedException {
         PreparedStatement stmt;
 
         try {
-            stmt = conn.prepareStatement(sql);
+            stmt = prepareStatement(conn, sql, useStmtCache);
         }
         catch (SQLException e) {
             throw new IgniteCheckedException("Failed to parse SQL query: " + sql, e);
@@ -747,18 +802,23 @@ public class IgniteH2Indexing implements GridQueryIndexing {
     /**
      * Executes sql query and prints warning if query is too slow..
      *
+     * @param space Space name.
      * @param conn Connection,.
      * @param sql Sql query.
      * @param params Parameters.
+     * @param useStmtCache If {@code true} uses statement cache.
      * @return Result.
      * @throws IgniteCheckedException If failed.
      */
-    public ResultSet executeSqlQueryWithTimer(String space, Connection conn, String sql,
-        @Nullable Collection<Object> params) throws IgniteCheckedException {
+    public ResultSet executeSqlQueryWithTimer(String space,
+        Connection conn,
+        String sql,
+        @Nullable Collection<Object> params,
+        boolean useStmtCache) throws IgniteCheckedException {
         long start = U.currentTimeMillis();
 
         try {
-            ResultSet rs = executeSqlQuery(conn, sql, params);
+            ResultSet rs = executeSqlQuery(conn, sql, params, useStmtCache);
 
             long time = U.currentTimeMillis() - start;
 
@@ -767,7 +827,7 @@ public class IgniteH2Indexing implements GridQueryIndexing {
             if (time > longQryExecTimeout) {
                 String msg = "Query execution is too long (" + time + " ms): " + sql;
 
-                ResultSet plan = executeSqlQuery(conn, "EXPLAIN " + sql, params);
+                ResultSet plan = executeSqlQuery(conn, "EXPLAIN " + sql, params, false);
 
                 plan.next();
 
@@ -803,7 +863,7 @@ public class IgniteH2Indexing implements GridQueryIndexing {
 
         String sql = generateQuery(qry, tbl);
 
-        return executeSqlQueryWithTimer(space, conn, sql, params);
+        return executeSqlQueryWithTimer(space, conn, sql, params, true);
     }
 
     /**
@@ -924,41 +984,52 @@ public class IgniteH2Indexing implements GridQueryIndexing {
 
     /** {@inheritDoc} */
     @Override public QueryCursor<List<?>> queryTwoStep(GridCacheContext<?,?> cctx, SqlFieldsQuery qry) {
-        String space = cctx.name();
-        String sqlQry = qry.getSql();
+        final String space = cctx.name();
+        final String sqlQry = qry.getSql();
 
         Connection c = connectionForSpace(space);
 
-        PreparedStatement stmt;
+        GridCacheTwoStepQuery twoStepQry;
+        List<GridQueryFieldMetadata> meta;
 
-        try {
-            stmt = c.prepareStatement(sqlQry);
-        }
-        catch (SQLException e) {
-            throw new CacheException("Failed to parse query: " + sqlQry, e);
-        }
+        final T3<String, String, Boolean> cachedQryKey = new T3<>(space, sqlQry, qry.isCollocated());
+        TwoStepCachedQuery cachedQry = twoStepCache.get(cachedQryKey);
 
-        try {
-            bindParameters(stmt, F.asList(qry.getArgs()));
-        }
-        catch (IgniteCheckedException e) {
-            throw new CacheException("Failed to bind parameters: [qry=" + sqlQry + ", params=" +
-                Arrays.deepToString(qry.getArgs()) + "]", e);
+        if (cachedQry != null) {
+            twoStepQry = cachedQry.twoStepQry.copy(qry.getArgs());
+            meta = cachedQry.meta;
         }
+        else {
+            PreparedStatement stmt;
 
-        GridCacheTwoStepQuery twoStepQry;
-        List<GridQueryFieldMetadata> meta;
+            try {
+                // Do not cache this statement because the whole two step query object will be cached later on.
+                stmt = prepareStatement(c, sqlQry, false);
+            }
+            catch (SQLException e) {
+                throw new CacheException("Failed to parse query: " + sqlQry, e);
+            }
+            try {
+                try {
+                    bindParameters(stmt, F.asList(qry.getArgs()));
+                }
+                catch (IgniteCheckedException e) {
+                    throw new CacheException("Failed to bind parameters: [qry=" + sqlQry + ", params=" +
+                        Arrays.deepToString(qry.getArgs()) + "]", e);
+                }
 
-        try {
-            twoStepQry = GridSqlQuerySplitter.split((JdbcPreparedStatement)stmt, qry.getArgs(), qry.isCollocated());
+                try {
+                    twoStepQry = GridSqlQuerySplitter.split((JdbcPreparedStatement)stmt, qry.getArgs(), qry.isCollocated());
 
-            meta = meta(stmt.getMetaData());
-        }
-        catch (SQLException e) {
-            throw new CacheException(e);
-        }
-        finally {
-            U.close(stmt, log);
+                    meta = meta(stmt.getMetaData());
+                }
+                catch (SQLException e) {
+                    throw new CacheException(e);
+                }
+            }
+            finally {
+                U.close(stmt, log);
+            }
         }
 
         if (log.isDebugEnabled())
@@ -970,6 +1041,11 @@ public class IgniteH2Indexing implements GridQueryIndexing {
 
         cursor.fieldsMeta(meta);
 
+        if (cachedQry == null && !twoStepQry.explain()) {
+            cachedQry = new TwoStepCachedQuery(meta, twoStepQry.copy(null));
+            twoStepCache.putIfAbsent(cachedQryKey, cachedQry);
+        }
+
         return cursor;
     }
 
@@ -1542,6 +1618,31 @@ public class IgniteH2Indexing implements GridQueryIndexing {
     }
 
     /**
+     * Cached two-step query.
+     */
+    private static final class TwoStepCachedQuery {
+        /** */
+        final List<GridQueryFieldMetadata> meta;
+
+        /** */
+        final GridCacheTwoStepQuery twoStepQry;
+
+        /**
+         * @param meta Fields metadata.
+         * @param twoStepQry Query.
+         */
+        public TwoStepCachedQuery(List<GridQueryFieldMetadata> meta, GridCacheTwoStepQuery twoStepQry) {
+            this.meta = meta;
+            this.twoStepQry = twoStepQry;
+        }
+
+        /** {@inheritDoc} */
+        @Override public String toString() {
+            return S.toString(TwoStepCachedQuery.class, this);
+        }
+    }
+
+    /**
      * Wrapper to store connection and flag is schema set or not.
      */
     private static class ConnectionWrapper {
@@ -1889,7 +1990,7 @@ public class IgniteH2Indexing implements GridQueryIndexing {
          * @throws IgniteCheckedException If failed.
          */
         protected FieldsIterator(ResultSet data) throws IgniteCheckedException {
-            super(data);
+            super(data, false);
         }
 
         /** {@inheritDoc} */
@@ -1914,7 +2015,7 @@ public class IgniteH2Indexing implements GridQueryIndexing {
          * @throws IgniteCheckedException If failed.
          */
         protected KeyValIterator(ResultSet data) throws IgniteCheckedException {
-            super(data);
+            super(data, false);
         }
 
         /** {@inheritDoc} */
@@ -2091,6 +2192,12 @@ public class IgniteH2Indexing implements GridQueryIndexing {
         /** */
         private final boolean preferSwapVal;
 
+        /** */
+        private final boolean snapshotableIdx;
+
+        /** */
+        private final GridQueryProperty[] props;
+
         /**
          * @param type Type descriptor.
          * @param schema Schema.
@@ -2120,7 +2227,18 @@ public class IgniteH2Indexing implements GridQueryIndexing {
             keyType = DataType.getTypeFromClass(type.keyClass());
             valType = DataType.getTypeFromClass(type.valueClass());
 
+            props = new GridQueryProperty[fields.length];
+
+            for (int i = 0; i < fields.length; i++) {
+                GridQueryProperty p = type.property(fields[i]);
+
+                assert p != null : fields[i];
+
+                props[i] = p;
+            }
+
             preferSwapVal = schema.ccfg.getMemoryMode() == CacheMemoryMode.OFFHEAP_TIERED;
+            snapshotableIdx = schema.ccfg.isSnapshotableIndex() || schema.offheap != null;
         }
 
         /** {@inheritDoc} */
@@ -2274,7 +2392,7 @@ public class IgniteH2Indexing implements GridQueryIndexing {
         /** {@inheritDoc} */
         @Override public Object columnValue(Object key, Object val, int col) {
             try {
-                return type.value(fields[col], key, val);
+                return props[col].value(key, val);
             }
             catch (IgniteCheckedException e) {
                 throw DbException.convert(e);
@@ -2298,5 +2416,40 @@ public class IgniteH2Indexing implements GridQueryIndexing {
         @Override public boolean preferSwapValue() {
             return preferSwapVal;
         }
+
+        /** {@inheritDoc} */
+        @Override public boolean snapshotableIndex() {
+            return snapshotableIdx;
+        }
+    }
+
+    /**
+     * Statement cache.
+     */
+    private static class StatementCache extends LinkedHashMap<String, PreparedStatement> {
+        /** */
+        private int size;
+
+        /**
+         * @param size Size.
+         */
+        private StatementCache(int size) {
+            super(size, (float)0.75, true);
+
+            this.size = size;
+        }
+
+        /** {@inheritDoc} */
+        @Override protected boolean removeEldestEntry(Map.Entry<String, PreparedStatement> eldest) {
+            boolean rmv = size() > size;
+
+            if (rmv) {
+                PreparedStatement stmt = eldest.getValue();
+
+                U.closeQuiet(stmt);
+            }
+
+            return rmv;
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/4af461a7/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2RowDescriptor.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2RowDescriptor.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2RowDescriptor.java
index ed3ff7a..80dcfcb 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2RowDescriptor.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2RowDescriptor.java
@@ -116,4 +116,9 @@ public interface GridH2RowDescriptor extends GridOffHeapSmartPointerFactory<Grid
      * @return {@code True} if should check swap value before offheap.
      */
     public boolean preferSwapValue();
+
+    /**
+     * @return {@code True} if index should support snapshots.
+     */
+    public boolean snapshotableIndex();
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/4af461a7/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 bf318b2..f1e5b16 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
@@ -54,6 +54,7 @@ import org.h2.table.TableFilter;
 import org.h2.value.Value;
 import org.jetbrains.annotations.Nullable;
 import org.jsr166.ConcurrentHashMap8;
+import org.jsr166.LongAdder8;
 
 import static org.apache.ignite.internal.processors.query.h2.opt.GridH2AbstractKeyValueRow.VAL_COL;
 
@@ -79,6 +80,12 @@ public class GridH2Table extends TableBase {
     /** */
     private volatile Object[] actualSnapshot;
 
+    /** */
+    private final LongAdder8 size = new LongAdder8();
+
+    /** */
+    private final boolean snapshotEnabled;
+
     /**
      * Creates table.
      *
@@ -101,10 +108,12 @@ public class GridH2Table extends TableBase {
         assert idxs != null;
         assert idxs.size() >= 1;
 
-        lock =  new ReentrantReadWriteLock();
+        lock = new ReentrantReadWriteLock();
 
         // Add scan index at 0 which is required by H2.
         idxs.add(0, new ScanIndex(index(0)));
+
+        snapshotEnabled = desc == null || desc.snapshotableIndex();
     }
 
     /** {@inheritDoc} */
@@ -164,7 +173,7 @@ public class GridH2Table extends TableBase {
 
         GridUnsafeMemory mem = desc.memory();
 
-        lock.readLock().lock();
+        readLock();
 
         if (mem != null)
             desc.guard().begin();
@@ -183,7 +192,7 @@ public class GridH2Table extends TableBase {
             return true;
         }
         finally {
-            lock.readLock().unlock();
+            readUnlock();
 
             if (mem != null)
                 desc.guard().end();
@@ -209,6 +218,9 @@ public class GridH2Table extends TableBase {
             }
         }
 
+        if (!snapshotEnabled)
+            return;
+
         Object[] snapshot;
 
         for (long waitTime = 100;; waitTime *= 2) { // Increase wait time to avoid starvation.
@@ -297,16 +309,14 @@ public class GridH2Table extends TableBase {
      * Closes table and releases resources.
      */
     public void close() {
-        Lock l = lock.writeLock();
-
-        l.lock();
+        writeLock();
 
         try {
             for (int i = 1, len = idxs.size(); i < len; i++)
                 index(i).close(null);
         }
         finally {
-            l.unlock();
+            writeUnlock();
         }
     }
 
@@ -363,7 +373,7 @@ public class GridH2Table extends TableBase {
         // getting updated from different threads with different rows with the same key is impossible.
         GridUnsafeMemory mem = desc == null ? null : desc.memory();
 
-        lock.readLock().lock();
+        readLock();
 
         if (mem != null)
             desc.guard().begin();
@@ -379,6 +389,8 @@ public class GridH2Table extends TableBase {
 
                     kvOld.onUnswap(kvOld.getValue(VAL_COL), true);
                 }
+                else if (old == null)
+                    size.increment();
 
                 int len = idxs.size();
 
@@ -414,6 +426,8 @@ public class GridH2Table extends TableBase {
                 }
 
                 if (old != null) {
+                    size.decrement();
+
                     // Remove row from all indexes.
                     // Start from 2 because 0 - Scan (don't need to update), 1 - PK (already updated).
                     for (int i = 2, len = idxs.size(); i < len; i++) {
@@ -432,7 +446,7 @@ public class GridH2Table extends TableBase {
             return true;
         }
         finally {
-            lock.readLock().unlock();
+            readUnlock();
 
             if (mem != null)
                 desc.guard().end();
@@ -469,6 +483,9 @@ public class GridH2Table extends TableBase {
      * Rebuilds all indexes of this table.
      */
     public void rebuildIndexes() {
+        if (!snapshotEnabled)
+            return;
+
         GridUnsafeMemory memory = desc == null ? null : desc.memory();
 
         lock.writeLock().lock();
@@ -579,7 +596,7 @@ public class GridH2Table extends TableBase {
 
     /** {@inheritDoc} */
     @Override public long getRowCountApproximation() {
-        return getUniqueIndex().getRowCountApproximation();
+        return size.longValue();
     }
 
     /** {@inheritDoc} */
@@ -605,6 +622,38 @@ public class GridH2Table extends TableBase {
     }
 
     /**
+     *
+     */
+    private void readLock() {
+        if (snapshotEnabled)
+            lock.readLock().lock();
+    }
+
+    /**
+     *
+     */
+    private void readUnlock() {
+        if (snapshotEnabled)
+            lock.readLock().unlock();
+    }
+
+    /**
+     *
+     */
+    private void writeLock() {
+        if (snapshotEnabled)
+            lock.writeLock().lock();
+    }
+
+    /**
+     *
+     */
+    private void writeUnlock() {
+        if (snapshotEnabled)
+            lock.writeLock().unlock();
+    }
+
+    /**
      * H2 Table engine.
      */
     @SuppressWarnings({"PublicInnerClass", "FieldAccessedSynchronizedAndUnsynchronized"})

http://git-wip-us.apache.org/repos/asf/ignite/blob/4af461a7/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2TreeIndex.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2TreeIndex.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2TreeIndex.java
index 387c58b..28adeee 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2TreeIndex.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2TreeIndex.java
@@ -23,6 +23,7 @@ import java.util.Comparator;
 import java.util.Iterator;
 import java.util.NavigableMap;
 import java.util.concurrent.ConcurrentNavigableMap;
+import java.util.concurrent.ConcurrentSkipListMap;
 import org.apache.ignite.internal.util.GridEmptyIterator;
 import org.apache.ignite.internal.util.offheap.unsafe.GridOffHeapSnapTreeMap;
 import org.apache.ignite.internal.util.offheap.unsafe.GridUnsafeGuard;
@@ -51,8 +52,10 @@ public class GridH2TreeIndex extends GridH2IndexBase implements Comparator<GridS
     protected final ConcurrentNavigableMap<GridSearchRowPointer, GridH2Row> tree;
 
     /** */
-    private final ThreadLocal<ConcurrentNavigableMap<GridSearchRowPointer, GridH2Row>> snapshot =
-        new ThreadLocal<>();
+    private final ThreadLocal<ConcurrentNavigableMap<GridSearchRowPointer, GridH2Row>> snapshot = new ThreadLocal<>();
+
+    /** */
+    private final boolean snapshotEnabled;
 
     /**
      * Constructor with index initialization.
@@ -81,40 +84,68 @@ public class GridH2TreeIndex extends GridH2IndexBase implements Comparator<GridS
 
         final GridH2RowDescriptor desc = tbl.rowDescriptor();
 
-        tree = desc == null || desc.memory() == null ? new SnapTreeMap<GridSearchRowPointer, GridH2Row>(this) {
-            @Override protected void afterNodeUpdate_nl(Node<GridSearchRowPointer, GridH2Row> node, Object val) {
-                if (val != null)
-                    node.key = (GridSearchRowPointer)val;
-            }
+        if (desc == null || desc.memory() == null) {
+            snapshotEnabled = desc == null || desc.snapshotableIndex();
 
-            @Override protected Comparable<? super GridSearchRowPointer> comparable(Object key) {
-                if (key instanceof ComparableRow)
-                    return (Comparable<? super SearchRow>)key;
+            if (snapshotEnabled) {
+                tree = new SnapTreeMap<GridSearchRowPointer, GridH2Row>(this) {
+                    @Override protected void afterNodeUpdate_nl(Node<GridSearchRowPointer, GridH2Row> node, Object val) {
+                        if (val != null)
+                            node.key = (GridSearchRowPointer)val;
+                    }
 
-                return super.comparable(key);
+                    @Override protected Comparable<? super GridSearchRowPointer> comparable(Object key) {
+                        if (key instanceof ComparableRow)
+                            return (Comparable<? super SearchRow>)key;
+
+                        return super.comparable(key);
+                    }
+                };
             }
-        } : new GridOffHeapSnapTreeMap<GridSearchRowPointer, GridH2Row>(desc, desc, desc.memory(), desc.guard(), this) {
-            @Override protected void afterNodeUpdate_nl(long node, GridH2Row val) {
-                final long oldKey = keyPtr(node);
+            else {
+                tree = new ConcurrentSkipListMap<>(
+                        new Comparator<GridSearchRowPointer>() {
+                            @Override public int compare(GridSearchRowPointer o1, GridSearchRowPointer o2) {
+                                if (o1 instanceof ComparableRow)
+                                    return ((ComparableRow)o1).compareTo(o2);
 
-                if (val != null) {
-                    key(node, val);
+                                if (o2 instanceof ComparableRow)
+                                    return -((ComparableRow)o2).compareTo(o1);
 
-                    guard.finalizeLater(new Runnable() {
-                        @Override public void run() {
-                            desc.createPointer(oldKey).decrementRefCount();
+                                return compareRows(o1, o2);
+                            }
                         }
-                    });
-                }
+                );
             }
+        }
+        else {
+            assert desc.snapshotableIndex() : desc;
 
-            @Override protected Comparable<? super GridSearchRowPointer> comparable(Object key) {
-                if (key instanceof ComparableRow)
-                    return (Comparable<? super SearchRow>)key;
+            snapshotEnabled = true;
 
-                return super.comparable(key);
-            }
-        };
+            tree = new GridOffHeapSnapTreeMap<GridSearchRowPointer, GridH2Row>(desc, desc, desc.memory(), desc.guard(), this) {
+                @Override protected void afterNodeUpdate_nl(long node, GridH2Row val) {
+                    final long oldKey = keyPtr(node);
+
+                    if (val != null) {
+                        key(node, val);
+
+                        guard.finalizeLater(new Runnable() {
+                            @Override public void run() {
+                                desc.createPointer(oldKey).decrementRefCount();
+                            }
+                        });
+                    }
+                }
+
+                @Override protected Comparable<? super GridSearchRowPointer> comparable(Object key) {
+                    if (key instanceof ComparableRow)
+                        return (Comparable<? super SearchRow>)key;
+
+                    return super.comparable(key);
+                }
+            };
+        }
     }
 
     /**
@@ -133,6 +164,9 @@ public class GridH2TreeIndex extends GridH2IndexBase implements Comparator<GridS
      */
     @SuppressWarnings("unchecked")
     @Override public Object takeSnapshot(@Nullable Object s) {
+        if (!snapshotEnabled)
+            return null;
+
         assert snapshot.get() == null;
 
         if (s == null)
@@ -148,6 +182,9 @@ public class GridH2TreeIndex extends GridH2IndexBase implements Comparator<GridS
      * Releases snapshot for current thread.
      */
     @Override public void releaseSnapshot() {
+        if (!snapshotEnabled)
+            return;
+
         ConcurrentNavigableMap<GridSearchRowPointer, GridH2Row> s = snapshot.get();
 
         snapshot.remove();
@@ -160,6 +197,9 @@ public class GridH2TreeIndex extends GridH2IndexBase implements Comparator<GridS
      * @return Snapshot for current thread if there is one.
      */
     private ConcurrentNavigableMap<GridSearchRowPointer, GridH2Row> treeForRead() {
+        if (!snapshotEnabled)
+            return tree;
+
         ConcurrentNavigableMap<GridSearchRowPointer, GridH2Row> res = snapshot.get();
 
         if (res == null)
@@ -199,7 +239,7 @@ public class GridH2TreeIndex extends GridH2IndexBase implements Comparator<GridS
 
     /** {@inheritDoc} */
     @Override public long getRowCountApproximation() {
-        return tree.size();
+        return table.getRowCountApproximation();
     }
 
     /** {@inheritDoc} */
@@ -372,7 +412,7 @@ public class GridH2TreeIndex extends GridH2IndexBase implements Comparator<GridS
      * Comparable row with bias. Will be used for queries to have correct bounds (in case of multicolumn index
      * and query on few first columns we will multiple equal entries in tree).
      */
-    private class ComparableRow implements GridSearchRowPointer, Comparable<SearchRow> {
+    private final class ComparableRow implements GridSearchRowPointer, Comparable<SearchRow> {
         /** */
         private final SearchRow row;
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/4af461a7/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQuerySplitter.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQuerySplitter.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQuerySplitter.java
index 7b89824..0c9c8fe 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQuerySplitter.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQuerySplitter.java
@@ -28,6 +28,7 @@ import org.apache.ignite.internal.processors.cache.query.GridCacheTwoStepQuery;
 import org.apache.ignite.internal.processors.query.h2.IgniteH2Indexing;
 import org.apache.ignite.internal.util.typedef.F;
 import org.h2.jdbc.JdbcPreparedStatement;
+import org.h2.util.IntArray;
 import org.jetbrains.annotations.Nullable;
 
 import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlFunctionType.AVG;
@@ -224,13 +225,23 @@ public class GridSqlQuerySplitter {
             rdcQry.distinct(true);
         }
 
-        // Build resulting two step query.
-        GridCacheTwoStepQuery res = new GridCacheTwoStepQuery(spaces, new GridCacheSqlQuery(rdcQry.getSQL(),
-            findParams(rdcQry, params, new ArrayList<>()).toArray()));
+        IntArray paramIdxs = new IntArray(params.length);
+
+        GridCacheSqlQuery rdc = new GridCacheSqlQuery(rdcQry.getSQL(),
+            findParams(rdcQry, params, new ArrayList<>(), paramIdxs).toArray());
+
+        rdc.parameterIndexes(toIntArray(paramIdxs));
+
+        paramIdxs = new IntArray(params.length);
 
-        res.addMapQuery(new GridCacheSqlQuery(mapQry.getSQL(),
-            findParams(mapQry, params, new ArrayList<>(params.length)).toArray())
-            .columns(collectColumns(mapExps)));
+        GridCacheSqlQuery map = new GridCacheSqlQuery(mapQry.getSQL(),
+            findParams(mapQry, params, new ArrayList<>(params.length), paramIdxs).toArray())
+            .columns(collectColumns(mapExps));
+
+        map.parameterIndexes(toIntArray(paramIdxs));
+
+        // Build resulting two step query.
+        GridCacheTwoStepQuery res = new GridCacheTwoStepQuery(spaces, rdc, rdcQry.simpleQuery()).addMapQuery(map);
 
         res.explain(explain);
 
@@ -238,6 +249,16 @@ public class GridSqlQuerySplitter {
     }
 
     /**
+     * @param arr Integer array.
+     * @return Primitive int array.
+     */
+    private static int[] toIntArray(IntArray arr) {
+        int[] res = new int[arr.size()];
+        arr.toArray(res);
+        return res;
+    }
+
+    /**
      * @param cols Columns from SELECT clause.
      * @return Map of columns with types.
      */
@@ -341,19 +362,21 @@ public class GridSqlQuerySplitter {
      * @param qry Select.
      * @param params Parameters.
      * @param target Extracted parameters.
+     * @param paramIdxs Parameter indexes.
      * @return Extracted parameters list.
      */
-    private static List<Object> findParams(GridSqlQuery qry, Object[] params, ArrayList<Object> target) {
+    private static List<Object> findParams(GridSqlQuery qry, Object[] params, ArrayList<Object> target,
+        IntArray paramIdxs) {
         if (qry instanceof GridSqlSelect)
-            return findParams((GridSqlSelect)qry, params, target);
+            return findParams((GridSqlSelect)qry, params, target, paramIdxs);
 
         GridSqlUnion union = (GridSqlUnion)qry;
 
-        findParams(union.left(), params, target);
-        findParams(union.right(), params, target);
+        findParams(union.left(), params, target, paramIdxs);
+        findParams(union.right(), params, target, paramIdxs);
 
-        findParams(qry.limit(), params, target);
-        findParams(qry.offset(), params, target);
+        findParams(qry.limit(), params, target, paramIdxs);
+        findParams(qry.offset(), params, target, paramIdxs);
 
         return target;
     }
@@ -362,22 +385,24 @@ public class GridSqlQuerySplitter {
      * @param qry Select.
      * @param params Parameters.
      * @param target Extracted parameters.
+     * @param paramIdxs Parameter indexes.
      * @return Extracted parameters list.
      */
-    private static List<Object> findParams(GridSqlSelect qry, Object[] params, ArrayList<Object> target) {
+    private static List<Object> findParams(GridSqlSelect qry, Object[] params, ArrayList<Object> target,
+        IntArray paramIdxs) {
         if (params.length == 0)
             return target;
 
         for (GridSqlElement el : qry.columns(false))
-            findParams(el, params, target);
+            findParams(el, params, target, paramIdxs);
 
-        findParams(qry.from(), params, target);
-        findParams(qry.where(), params, target);
+        findParams(qry.from(), params, target, paramIdxs);
+        findParams(qry.where(), params, target, paramIdxs);
 
         // Don't search in GROUP BY and HAVING since they expected to be in select list.
 
-        findParams(qry.limit(), params, target);
-        findParams(qry.offset(), params, target);
+        findParams(qry.limit(), params, target, paramIdxs);
+        findParams(qry.offset(), params, target, paramIdxs);
 
         return target;
     }
@@ -386,15 +411,17 @@ public class GridSqlQuerySplitter {
      * @param el Element.
      * @param params Parameters.
      * @param target Extracted parameters.
+     * @param paramIdxs Parameter indexes.
      */
-    private static void findParams(@Nullable GridSqlElement el, Object[] params, ArrayList<Object> target) {
+    private static void findParams(@Nullable GridSqlElement el, Object[] params, ArrayList<Object> target,
+        IntArray paramIdxs) {
         if (el == null)
             return;
 
         if (el instanceof GridSqlParameter) {
             // H2 Supports queries like "select ?5" but first 4 non-existing parameters are need to be set to any value.
             // Here we will set them to NULL.
-            int idx = ((GridSqlParameter)el).index();
+            final int idx = ((GridSqlParameter)el).index();
 
             while (target.size() < idx)
                 target.add(null);
@@ -409,12 +436,14 @@ public class GridSqlQuerySplitter {
                 target.add(param);
             else
                 target.set(idx, param);
+
+            paramIdxs.add(idx);
         }
         else if (el instanceof GridSqlSubquery)
-            findParams(((GridSqlSubquery)el).select(), params, target);
+            findParams(((GridSqlSubquery)el).select(), params, target, paramIdxs);
         else
             for (GridSqlElement child : el)
-                findParams(child, params, target);
+                findParams(child, params, target, paramIdxs);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/ignite/blob/4af461a7/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlSelect.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlSelect.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlSelect.java
index e537ace..e190c87 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlSelect.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlSelect.java
@@ -103,6 +103,33 @@ public class GridSqlSelect extends GridSqlQuery {
     }
 
     /**
+     * @return {@code True} if this simple SQL query like 'SELECT A, B, C from SOME_TABLE' without any conditions
+     *      and expressions.
+     */
+    public boolean simpleQuery() {
+        boolean simple = !distinct &&
+            from instanceof GridSqlTable &&
+            where == null &&
+            grpCols == null &&
+            havingCol < 0 &&
+            sort.isEmpty() &&
+            limit == null &&
+            offset == null;
+
+        if (simple) {
+            for (GridSqlElement expression : columns(true)) {
+                if (expression instanceof GridSqlAlias)
+                    expression = expression.child();
+
+                if (!(expression instanceof GridSqlColumn))
+                    return false;
+            }
+        }
+
+        return simple;
+    }
+
+    /**
      * @param buff Statement builder.
      * @param expression Alias expression.
      */

http://git-wip-us.apache.org/repos/asf/ignite/blob/4af461a7/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMapQueryExecutor.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMapQueryExecutor.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMapQueryExecutor.java
index b4e1932..21541ec 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMapQueryExecutor.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMapQueryExecutor.java
@@ -451,8 +451,11 @@ public class GridMapQueryExecutor {
             int i = 0;
 
             for (GridCacheSqlQuery qry : qrys) {
-                ResultSet rs = h2.executeSqlQueryWithTimer(req.space(), h2.connectionForSpace(req.space()), qry.query(),
-                    F.asList(qry.parameters()));
+                ResultSet rs = h2.executeSqlQueryWithTimer(req.space(),
+                    h2.connectionForSpace(req.space()),
+                    qry.query(),
+                    F.asList(qry.parameters()),
+                    true);
 
                 if (ctx.event().isRecordable(EVT_CACHE_QUERY_EXECUTED)) {
                     ctx.event().record(new CacheQueryExecutedEvent<>(
@@ -820,17 +823,7 @@ public class GridMapQueryExecutor {
 
             closed = true;
 
-            Statement stmt;
-
-            try {
-                stmt = rs.getStatement();
-            }
-            catch (SQLException e) {
-                throw new IllegalStateException(e); // Must not happen.
-            }
-
             U.close(rs, log);
-            U.close(stmt, log);
         }
     }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/4af461a7/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMergeIndex.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMergeIndex.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMergeIndex.java
index 7f8caed..12c2240 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMergeIndex.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMergeIndex.java
@@ -80,6 +80,13 @@ public abstract class GridMergeIndex extends BaseIndex {
     }
 
     /**
+     *
+     */
+    protected GridMergeIndex() {
+        // No-op.
+    }
+
+    /**
      * @return Return source nodes for this merge index.
      */
     public Set<UUID> sources() {

http://git-wip-us.apache.org/repos/asf/ignite/blob/4af461a7/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMergeIndexUnsorted.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMergeIndexUnsorted.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMergeIndexUnsorted.java
index 5f5eb25..501480a 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMergeIndexUnsorted.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMergeIndexUnsorted.java
@@ -47,6 +47,20 @@ public class GridMergeIndexUnsorted extends GridMergeIndex {
         super(tbl, name, IndexType.createScan(false), IndexColumn.wrap(tbl.getColumns()));
     }
 
+    /**
+     * @return Dummy index instance.
+     */
+    public static GridMergeIndexUnsorted createDummy() {
+        return new GridMergeIndexUnsorted();
+    }
+
+    /**
+     *
+     */
+    private GridMergeIndexUnsorted() {
+        // No-op.
+    }
+
     /** {@inheritDoc} */
     @Override protected void addPage0(GridResultPage page) {
         assert page.rowsInPage() > 0 || page.isLast() || page.isFail();

http://git-wip-us.apache.org/repos/asf/ignite/blob/4af461a7/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridReduceQueryExecutor.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridReduceQueryExecutor.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridReduceQueryExecutor.java
index f515a78..1d4fa30 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridReduceQueryExecutor.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridReduceQueryExecutor.java
@@ -79,10 +79,12 @@ import org.apache.ignite.marshaller.Marshaller;
 import org.apache.ignite.plugin.extensions.communication.Message;
 import org.h2.command.ddl.CreateTableData;
 import org.h2.engine.Session;
+import org.h2.index.Cursor;
 import org.h2.jdbc.JdbcConnection;
 import org.h2.jdbc.JdbcResultSet;
 import org.h2.jdbc.JdbcStatement;
 import org.h2.result.ResultInterface;
+import org.h2.result.Row;
 import org.h2.table.Column;
 import org.h2.util.IntArray;
 import org.h2.value.Value;
@@ -184,8 +186,8 @@ public class GridReduceQueryExecutor {
                 UUID nodeId = ((DiscoveryEvent)evt).eventNode().id();
 
                 for (QueryRun r : runs.values()) {
-                    for (GridMergeTable tbl : r.tbls) {
-                        if (tbl.getScanIndex(null).hasSource(nodeId)) {
+                    for (GridMergeIndex idx : r.idxs) {
+                        if (idx.hasSource(nodeId)) {
                             handleNodeLeft(r, nodeId);
 
                             break;
@@ -270,7 +272,7 @@ public class GridReduceQueryExecutor {
 
         final int pageSize = r.pageSize;
 
-        GridMergeIndex idx = r.tbls.get(msg.query()).getScanIndex(null);
+        GridMergeIndex idx = r.idxs.get(msg.query());
 
         GridResultPage page;
 
@@ -468,7 +470,7 @@ public class GridReduceQueryExecutor {
 
             r.pageSize = qry.pageSize() <= 0 ? GridCacheTwoStepQuery.DFLT_PAGE_SIZE : qry.pageSize();
 
-            r.tbls = new ArrayList<>(qry.mapQueries().size());
+            r.idxs = new ArrayList<>(qry.mapQueries().size());
 
             String space = cctx.name();
 
@@ -501,7 +503,7 @@ public class GridReduceQueryExecutor {
             assert !nodes.isEmpty();
 
             if (cctx.isReplicated() || qry.explain()) {
-                assert qry.explain() || !nodes.contains(ctx.cluster().get().localNode()) :
+                assert qry.explain() || !nodes.contains(ctx.discovery().localNode()) :
                     "We must be on a client node.";
 
                 // Select random data node to run query on a replicated data or get EXPLAIN PLAN from a single node.
@@ -510,27 +512,35 @@ public class GridReduceQueryExecutor {
 
             int tblIdx = 0;
 
+            final boolean skipMergeTbl = !qry.explain() && qry.skipMergeTable();
+
             for (GridCacheSqlQuery mapQry : qry.mapQueries()) {
-                GridMergeTable tbl;
+                GridMergeIndex idx;
 
-                try {
-                    tbl = createMergeTable(r.conn, mapQry, qry.explain());
-                }
-                catch (IgniteCheckedException e) {
-                    throw new IgniteException(e);
-                }
+                if (!skipMergeTbl) {
+                    GridMergeTable tbl;
+
+                    try {
+                        tbl = createMergeTable(r.conn, mapQry, qry.explain());
+                    }
+                    catch (IgniteCheckedException e) {
+                        throw new IgniteException(e);
+                    }
 
-                GridMergeIndex idx = tbl.getScanIndex(null);
+                    idx = tbl.getScanIndex(null);
+
+                    fakeTable(r.conn, tblIdx++).setInnerTable(tbl);
+                }
+                else
+                    idx = GridMergeIndexUnsorted.createDummy();
 
                 for (ClusterNode node : nodes)
                     idx.addSource(node.id());
 
-                r.tbls.add(tbl);
-
-                fakeTable(r.conn, tblIdx++).setInnerTable(tbl);
+                r.idxs.add(idx);
             }
 
-            r.latch = new CountDownLatch(r.tbls.size() * nodes.size());
+            r.latch = new CountDownLatch(r.idxs.size() * nodes.size());
 
             runs.put(qryReqId, r);
 
@@ -586,19 +596,52 @@ public class GridReduceQueryExecutor {
                 else // Send failed.
                     retry = true;
 
-                ResultSet res = null;
+                Iterator<List<?>> resIter = null;
 
                 if (!retry) {
                     if (qry.explain())
                         return explainPlan(r.conn, space, qry);
 
-                    GridCacheSqlQuery rdc = qry.reduceQuery();
+                    if (skipMergeTbl) {
+                        List<List<?>> res = new ArrayList<>();
+
+                        assert r.idxs.size() == 1 : r.idxs;
 
-                    res = h2.executeSqlQueryWithTimer(space, r.conn, rdc.query(), F.asList(rdc.parameters()));
+                        GridMergeIndex idx = r.idxs.get(0);
+
+                        Cursor cur = idx.findInStream(null, null);
+
+                        while (cur.next()) {
+                            Row row = cur.get();
+
+                            int cols = row.getColumnCount();
+
+                            List<Object> resRow  = new ArrayList<>(cols);
+
+                            for (int c = 0; c < cols; c++)
+                                resRow.add(row.getValue(c).getObject());
+
+                            res.add(resRow);
+                        }
+
+                        resIter = res.iterator();
+                    }
+                    else {
+                        GridCacheSqlQuery rdc = qry.reduceQuery();
+
+                        // Statement caching is prohibited here because we can't guarantee correct merge index reuse.
+                        ResultSet res = h2.executeSqlQueryWithTimer(space,
+                            r.conn,
+                            rdc.query(),
+                            F.asList(rdc.parameters()),
+                            false);
+
+                        resIter = new Iter(res);
+                    }
                 }
 
-                for (GridMergeTable tbl : r.tbls) {
-                    if (!tbl.getScanIndex(null).fetchedAll()) // We have to explicitly cancel queries on remote nodes.
+                for (GridMergeIndex idx : r.idxs) {
+                    if (!idx.fetchedAll()) // We have to explicitly cancel queries on remote nodes.
                         send(nodes, new GridQueryCancelRequest(qryReqId), null);
                 }
 
@@ -609,7 +652,7 @@ public class GridReduceQueryExecutor {
                     continue;
                 }
 
-                return new GridQueryCacheObjectsIterator(new Iter(res), cctx, keepPortable);
+                return new GridQueryCacheObjectsIterator(resIter, cctx, keepPortable);
             }
             catch (IgniteCheckedException | RuntimeException e) {
                 U.closeQuiet(r.conn);
@@ -633,8 +676,10 @@ public class GridReduceQueryExecutor {
                 if (!runs.remove(qryReqId, r))
                     U.warn(log, "Query run was already removed: " + qryReqId);
 
-                for (int i = 0, mapQrys = qry.mapQueries().size(); i < mapQrys; i++)
-                    fakeTable(null, i).setInnerTable(null); // Drop all merge tables.
+                if (!skipMergeTbl) {
+                    for (int i = 0, mapQrys = qry.mapQueries().size(); i < mapQrys; i++)
+                        fakeTable(null, i).setInnerTable(null); // Drop all merge tables.
+                }
             }
         }
     }
@@ -941,7 +986,7 @@ public class GridReduceQueryExecutor {
         List<List<?>> lists = new ArrayList<>();
 
         for (int i = 0, mapQrys = qry.mapQueries().size(); i < mapQrys; i++) {
-            ResultSet rs = h2.executeSqlQueryWithTimer(space, c, "SELECT PLAN FROM " + table(i), null);
+            ResultSet rs = h2.executeSqlQueryWithTimer(space, c, "SELECT PLAN FROM " + table(i), null, false);
 
             lists.add(F.asList(getPlan(rs)));
         }
@@ -956,7 +1001,11 @@ public class GridReduceQueryExecutor {
 
         GridCacheSqlQuery rdc = qry.reduceQuery();
 
-        ResultSet rs = h2.executeSqlQueryWithTimer(space, c, "EXPLAIN " + rdc.query(), F.asList(rdc.parameters()));
+        ResultSet rs = h2.executeSqlQueryWithTimer(space,
+            c,
+            "EXPLAIN " + rdc.query(),
+            F.asList(rdc.parameters()),
+            false);
 
         lists.add(F.asList(getPlan(rs)));
 
@@ -1013,7 +1062,7 @@ public class GridReduceQueryExecutor {
         }
 
         if (locNodeFound) // Local node goes the last to allow parallel execution.
-            h2.mapQueryExecutor().onMessage(ctx.localNodeId(), copy(msg, ctx.cluster().get().localNode(), partsMap));
+            h2.mapQueryExecutor().onMessage(ctx.localNodeId(), copy(msg, ctx.discovery().localNode(), partsMap));
 
         return ok;
     }
@@ -1120,7 +1169,7 @@ public class GridReduceQueryExecutor {
      */
     private static class QueryRun {
         /** */
-        private List<GridMergeTable> tbls;
+        private List<GridMergeIndex> idxs;
 
         /** */
         private CountDownLatch latch;
@@ -1148,8 +1197,8 @@ public class GridReduceQueryExecutor {
             while (latch.getCount() != 0) // We don't need to wait for all nodes to reply.
                 latch.countDown();
 
-            for (GridMergeTable tbl : tbls) // Fail all merge indexes.
-                tbl.getScanIndex(null).fail(nodeId);
+            for (GridMergeIndex idx : idxs) // Fail all merge indexes.
+                idx.fail(nodeId);
         }
 
         /**
@@ -1162,8 +1211,8 @@ public class GridReduceQueryExecutor {
             while (latch.getCount() != 0) // We don't need to wait for all nodes to reply.
                 latch.countDown();
 
-            for (GridMergeTable tbl : tbls) // Fail all merge indexes.
-                tbl.getScanIndex(null).fail(e);
+            for (GridMergeIndex idx : idxs) // Fail all merge indexes.
+                idx.fail(e);
         }
     }
 
@@ -1179,7 +1228,7 @@ public class GridReduceQueryExecutor {
          * @throws IgniteCheckedException If failed.
          */
         protected Iter(ResultSet data) throws IgniteCheckedException {
-            super(data);
+            super(data, true);
         }
 
         /** {@inheritDoc} */

http://git-wip-us.apache.org/repos/asf/ignite/blob/4af461a7/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheAbstractQuerySelfTest.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheAbstractQuerySelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheAbstractQuerySelfTest.java
index f3fbf15..c510600 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheAbstractQuerySelfTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheAbstractQuerySelfTest.java
@@ -156,36 +156,38 @@ public abstract class IgniteCacheAbstractQuerySelfTest extends GridCommonAbstrac
                 if (i > 0)
                     cc.setName("c" + i);
 
-            cc.setCacheMode(cacheMode());
-            cc.setAtomicityMode(atomicityMode());
-            cc.setNearConfiguration(nearCacheConfiguration());
-            cc.setWriteSynchronizationMode(FULL_SYNC);
-            cc.setCacheStoreFactory(new StoreFactory());
-            cc.setReadThrough(true);
-            cc.setWriteThrough(true);
-            cc.setLoadPreviousValue(true);
-            cc.setRebalanceMode(SYNC);
-            cc.setSwapEnabled(true);
-            cc.setSqlFunctionClasses(SqlFunctions.class);
-            cc.setIndexedTypes(
-                BadHashKeyObject.class, Byte.class,
-                ObjectValue.class, Long.class,
-                Integer.class, Integer.class,
-                Integer.class, String.class,
-                Integer.class, ObjectValue.class,
-                String.class, ObjectValueOther.class,
-                Integer.class, ArrayObject.class,
-                Key.class, GridCacheQueryTestValue.class,
-                UUID.class, Person.class,
-                IgniteCacheReplicatedQuerySelfTest.CacheKey.class, IgniteCacheReplicatedQuerySelfTest.CacheValue.class
-            );
-
-            if (cacheMode() != CacheMode.LOCAL)
-                cc.setAffinity(new RendezvousAffinityFunction());
-
-            // Explicitly set number of backups equal to number of grids.
-            if (cacheMode() == CacheMode.PARTITIONED)
-                cc.setBackups(gridCount());
+                cc.setCacheMode(cacheMode());
+                cc.setAtomicityMode(atomicityMode());
+                cc.setNearConfiguration(nearCacheConfiguration());
+                cc.setWriteSynchronizationMode(FULL_SYNC);
+                cc.setCacheStoreFactory(new StoreFactory());
+                cc.setReadThrough(true);
+                cc.setWriteThrough(true);
+                cc.setLoadPreviousValue(true);
+                cc.setRebalanceMode(SYNC);
+                cc.setSwapEnabled(true);
+                cc.setSqlFunctionClasses(SqlFunctions.class);
+                cc.setIndexedTypes(
+                    BadHashKeyObject.class, Byte.class,
+                    ObjectValue.class, Long.class,
+                    Integer.class, Integer.class,
+                    Integer.class, String.class,
+                    Integer.class, ObjectValue.class,
+                    String.class, ObjectValueOther.class,
+                    Integer.class, ArrayObject.class,
+                    Key.class, GridCacheQueryTestValue.class,
+                    UUID.class, Person.class,
+                    IgniteCacheReplicatedQuerySelfTest.CacheKey.class, IgniteCacheReplicatedQuerySelfTest.CacheValue.class
+                );
+
+                if (cacheMode() != CacheMode.LOCAL)
+                    cc.setAffinity(new RendezvousAffinityFunction());
+
+                // Explicitly set number of backups equal to number of grids.
+                if (cacheMode() == CacheMode.PARTITIONED)
+                    cc.setBackups(gridCount());
+
+                cc.setSnapshotableIndex(snapshotableIndex());
 
                 ccs[i] = cc;
             }
@@ -198,6 +200,13 @@ public abstract class IgniteCacheAbstractQuerySelfTest extends GridCommonAbstrac
         return c;
     }
 
+    /**
+     * @return {@code True} if index snapshot is enabled.
+     */
+    protected boolean snapshotableIndex() {
+        return false;
+    }
+
     /** {@inheritDoc} */
     @Override protected void beforeTest() throws Exception {
         ignite = startGridsMultiThreaded(gridCount());

http://git-wip-us.apache.org/repos/asf/ignite/blob/4af461a7/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/distributed/near/IgniteCachePartitionedSnapshotEnabledQuerySelfTest.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/distributed/near/IgniteCachePartitionedSnapshotEnabledQuerySelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/distributed/near/IgniteCachePartitionedSnapshotEnabledQuerySelfTest.java
new file mode 100644
index 0000000..9a68b16
--- /dev/null
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/distributed/near/IgniteCachePartitionedSnapshotEnabledQuerySelfTest.java
@@ -0,0 +1,28 @@
+/*
+ * 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.distributed.near;
+
+/**
+ *
+ */
+public class IgniteCachePartitionedSnapshotEnabledQuerySelfTest extends IgniteCachePartitionedQuerySelfTest {
+    /** {@inheritDoc} */
+    @Override protected boolean snapshotableIndex() {
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/4af461a7/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/GridIndexingSpiAbstractSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/GridIndexingSpiAbstractSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/GridIndexingSpiAbstractSelfTest.java
index 194fb82..c027b26 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/GridIndexingSpiAbstractSelfTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/GridIndexingSpiAbstractSelfTest.java
@@ -34,6 +34,7 @@ import org.apache.ignite.internal.processors.cache.CacheObjectContext;
 import org.apache.ignite.internal.processors.query.GridQueryFieldsResult;
 import org.apache.ignite.internal.processors.query.GridQueryIndexDescriptor;
 import org.apache.ignite.internal.processors.query.GridQueryIndexType;
+import org.apache.ignite.internal.processors.query.GridQueryProperty;
 import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.U;
@@ -505,6 +506,24 @@ public abstract class GridIndexingSpiAbstractSelfTest extends GridCommonAbstract
         }
 
         /** {@inheritDoc} */
+        @Override public GridQueryProperty property(final String name) {
+            return new GridQueryProperty() {
+                @Override public Object value(Object key, Object val) throws IgniteCheckedException {
+                    return TypeDesc.this.value(name, key, val);
+                }
+
+                @Override public String name() {
+                    return name;
+                }
+
+                @Override
+                public Class<?> type() {
+                    return Object.class;
+                }
+            };
+        }
+
+        /** {@inheritDoc} */
         @SuppressWarnings("unchecked")
         @Override public <T> T value(String field, Object key, Object val) throws IgniteSpiException {
             assert !F.isEmpty(field);

http://git-wip-us.apache.org/repos/asf/ignite/blob/4af461a7/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheQuerySelfTestSuite.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheQuerySelfTestSuite.java b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheQuerySelfTestSuite.java
index 6cc2599..0745cd7 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheQuerySelfTestSuite.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheQuerySelfTestSuite.java
@@ -55,6 +55,7 @@ import org.apache.ignite.internal.processors.cache.distributed.near.IgniteCacheP
 import org.apache.ignite.internal.processors.cache.distributed.near.IgniteCachePartitionedFieldsQuerySelfTest;
 import org.apache.ignite.internal.processors.cache.distributed.near.IgniteCachePartitionedQueryP2PDisabledSelfTest;
 import org.apache.ignite.internal.processors.cache.distributed.near.IgniteCachePartitionedQuerySelfTest;
+import org.apache.ignite.internal.processors.cache.distributed.near.IgniteCachePartitionedSnapshotEnabledQuerySelfTest;
 import org.apache.ignite.internal.processors.cache.distributed.near.IgniteCacheQueryNodeRestartSelfTest;
 import org.apache.ignite.internal.processors.cache.distributed.near.IgniteCacheQueryNodeRestartSelfTest2;
 import org.apache.ignite.internal.processors.cache.distributed.replicated.IgniteCacheReplicatedFieldsQueryP2PEnabledSelfTest;
@@ -118,6 +119,7 @@ public class IgniteCacheQuerySelfTestSuite extends TestSuite {
         suite.addTestSuite(IgniteCacheReplicatedQuerySelfTest.class);
         suite.addTestSuite(IgniteCacheReplicatedQueryP2PDisabledSelfTest.class);
         suite.addTestSuite(IgniteCachePartitionedQuerySelfTest.class);
+        suite.addTestSuite(IgniteCachePartitionedSnapshotEnabledQuerySelfTest.class);
         suite.addTestSuite(IgniteCacheAtomicQuerySelfTest.class);
         suite.addTestSuite(IgniteCacheAtomicNearEnabledQuerySelfTest.class);
         suite.addTestSuite(IgniteCachePartitionedQueryP2PDisabledSelfTest.class);