You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sb...@apache.org on 2017/05/26 14:06:18 UTC

[18/39] ignite git commit: IGNITE-5284: Splitted IgniteH2Indexing into several classes. This closes #1999.

IGNITE-5284: Splitted IgniteH2Indexing into several classes. This closes #1999.


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

Branch: refs/heads/ignite-5075-cc-debug
Commit: 8c75e4de89496e1b80d854fbaa64d3bffa8193bd
Parents: d8eeea8
Author: devozerov <vo...@gridgain.com>
Authored: Wed May 24 15:38:14 2017 +0300
Committer: devozerov <vo...@gridgain.com>
Committed: Wed May 24 15:38:14 2017 +0300

----------------------------------------------------------------------
 .../query/h2/DmlStatementsProcessor.java        |   39 +-
 .../query/h2/GridH2ResultSetIterator.java       |  191 --
 .../query/h2/H2ConnectionWrapper.java           |   67 +
 .../processors/query/h2/H2DatabaseType.java     |  161 ++
 .../processors/query/h2/H2FieldsIterator.java   |   50 +
 .../processors/query/h2/H2KeyValueIterator.java |   48 +
 .../query/h2/H2ResultSetIterator.java           |  191 ++
 .../processors/query/h2/H2RowDescriptor.java    |  479 +++++
 .../internal/processors/query/h2/H2Schema.java  |  135 ++
 .../processors/query/h2/H2SqlFieldMetadata.java |  111 +
 .../processors/query/h2/H2StatementCache.java   |   73 +
 .../processors/query/h2/H2TableDescriptor.java  |  345 ++++
 .../processors/query/h2/H2TableEngine.java      |   89 +
 .../query/h2/H2TwoStepCachedQuery.java          |   49 +
 .../query/h2/H2TwoStepCachedQueryKey.java       |  107 +
 .../internal/processors/query/h2/H2Utils.java   |  299 +++
 .../processors/query/h2/IgniteH2Indexing.java   | 1941 ++----------------
 .../query/h2/sql/GridSqlQuerySplitter.java      |    4 +-
 .../query/h2/twostep/GridMapQueryExecutor.java  |    6 +-
 .../h2/twostep/GridReduceQueryExecutor.java     |   21 +-
 20 files changed, 2413 insertions(+), 1993 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/8c75e4de/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/DmlStatementsProcessor.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/DmlStatementsProcessor.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/DmlStatementsProcessor.java
index e40c328..47b5ef4 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/DmlStatementsProcessor.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/DmlStatementsProcessor.java
@@ -125,13 +125,13 @@ public class DmlStatementsProcessor {
      * @param cacheName Cache name.
      */
     public void onCacheStop(String cacheName) {
-        planCache.remove(cacheName);
+        planCache.remove(idx.schema(cacheName));
     }
 
     /**
      * Execute DML statement, possibly with few re-attempts in case of concurrent data modifications.
      *
-     * @param cacheName Cache name.
+     * @param schema Schema.
      * @param stmt JDBC statement.
      * @param fieldsQry Original query.
      * @param loc Query locality flag.
@@ -140,14 +140,13 @@ public class DmlStatementsProcessor {
      * @return Update result (modified items count and failed keys).
      * @throws IgniteCheckedException if failed.
      */
-    private UpdateResult updateSqlFields(String cacheName, PreparedStatement stmt, SqlFieldsQuery fieldsQry,
-        boolean loc, IndexingQueryFilter filters, GridQueryCancel cancel)
-        throws IgniteCheckedException {
+    private UpdateResult updateSqlFields(String schema, PreparedStatement stmt, SqlFieldsQuery fieldsQry,
+        boolean loc, IndexingQueryFilter filters, GridQueryCancel cancel) throws IgniteCheckedException {
         Object[] errKeys = null;
 
         long items = 0;
 
-        UpdatePlan plan = getPlanForStatement(cacheName, stmt, null);
+        UpdatePlan plan = getPlanForStatement(schema, stmt, null);
 
         GridCacheContext<?, ?> cctx = plan.tbl.rowDescriptor().context();
 
@@ -195,7 +194,7 @@ public class DmlStatementsProcessor {
     }
 
     /**
-     * @param cacheName Cache name.
+     * @param schema Schema.
      * @param stmt Prepared statement.
      * @param fieldsQry Initial query
      * @param cancel Query cancel.
@@ -203,9 +202,9 @@ public class DmlStatementsProcessor {
      * @throws IgniteCheckedException if failed.
      */
     @SuppressWarnings("unchecked")
-    QueryCursorImpl<List<?>> updateSqlFieldsDistributed(String cacheName, PreparedStatement stmt,
+    QueryCursorImpl<List<?>> updateSqlFieldsDistributed(String schema, PreparedStatement stmt,
         SqlFieldsQuery fieldsQry, GridQueryCancel cancel) throws IgniteCheckedException {
-        UpdateResult res = updateSqlFields(cacheName, stmt, fieldsQry, false, null, cancel);
+        UpdateResult res = updateSqlFields(schema, stmt, fieldsQry, false, null, cancel);
 
         QueryCursorImpl<List<?>> resCur = (QueryCursorImpl<List<?>>)new QueryCursorImpl(Collections.singletonList
             (Collections.singletonList(res.cnt)), null, false);
@@ -217,7 +216,8 @@ public class DmlStatementsProcessor {
 
     /**
      * Execute DML statement on local cache.
-     * @param cacheName Cache name.
+     *
+     * @param schema Schema.
      * @param stmt Prepared statement.
      * @param fieldsQry Fields query.
      * @param filters Cache name and key filter.
@@ -226,10 +226,10 @@ public class DmlStatementsProcessor {
      * @throws IgniteCheckedException if failed.
      */
     @SuppressWarnings("unchecked")
-    GridQueryFieldsResult updateSqlFieldsLocal(String cacheName, PreparedStatement stmt,
+    GridQueryFieldsResult updateSqlFieldsLocal(String schema, PreparedStatement stmt,
         SqlFieldsQuery fieldsQry, IndexingQueryFilter filters, GridQueryCancel cancel)
         throws IgniteCheckedException {
-        UpdateResult res = updateSqlFields(cacheName, stmt, fieldsQry, true, filters, cancel);
+        UpdateResult res = updateSqlFields(schema, stmt, fieldsQry, true, filters, cancel);
 
         return new GridQueryFieldsResultAdapter(UPDATE_RESULT_META,
             new IgniteSingletonIterator(Collections.singletonList(res.cnt)));
@@ -333,7 +333,7 @@ public class DmlStatementsProcessor {
         Object[] failedKeys) throws IgniteCheckedException {
         Integer errKeysPos = null;
 
-        UpdatePlan plan = getPlanForStatement(cctx.name(), prepStmt, errKeysPos);
+        UpdatePlan plan = getPlanForStatement(idx.schema(cctx.name()), prepStmt, errKeysPos);
 
         if (plan.fastUpdateArgs != null) {
             assert F.isEmpty(failedKeys) && errKeysPos == null;
@@ -398,23 +398,22 @@ public class DmlStatementsProcessor {
     /**
      * Generate SELECT statements to retrieve data for modifications from and find fast UPDATE or DELETE args,
      * if available.
-     * @param cacheName Cache name.
+     *
+     * @param schema Schema.
      * @param prepStmt JDBC statement.
      * @return Update plan.
      */
     @SuppressWarnings({"unchecked", "ConstantConditions"})
-    private UpdatePlan getPlanForStatement(String cacheName, PreparedStatement prepStmt,
-        @Nullable Integer errKeysPos) throws IgniteCheckedException {
+    private UpdatePlan getPlanForStatement(String schema, PreparedStatement prepStmt, @Nullable Integer errKeysPos)
+        throws IgniteCheckedException {
         Prepared p = GridSqlQueryParser.prepared(prepStmt);
 
-        cacheName = F.isEmpty(cacheName) ? "default" : cacheName;
-
-        ConcurrentMap<String, UpdatePlan> cachePlans = planCache.get(cacheName);
+        ConcurrentMap<String, UpdatePlan> cachePlans = planCache.get(schema);
 
         if (cachePlans == null) {
             cachePlans = new GridBoundedConcurrentLinkedHashMap<>(PLAN_CACHE_SIZE);
 
-            cachePlans = U.firstNotNull(planCache.putIfAbsent(cacheName, cachePlans), cachePlans);
+            cachePlans = U.firstNotNull(planCache.putIfAbsent(schema, cachePlans), cachePlans);
         }
 
         // getSQL returns field value, so it's fast

http://git-wip-us.apache.org/repos/asf/ignite/blob/8c75e4de/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
deleted file mode 100644
index fed292a..0000000
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/GridH2ResultSetIterator.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.h2;
-
-import java.lang.reflect.Field;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.NoSuchElementException;
-import org.apache.ignite.IgniteCheckedException;
-import org.apache.ignite.internal.processors.cache.GridCacheContext;
-import org.apache.ignite.internal.processors.query.IgniteSQLException;
-import org.apache.ignite.internal.processors.query.h2.opt.GridH2ValueCacheObject;
-import org.apache.ignite.internal.util.GridCloseableIteratorAdapter;
-import org.apache.ignite.internal.util.typedef.internal.S;
-import org.apache.ignite.internal.util.typedef.internal.U;
-import org.h2.jdbc.JdbcResultSet;
-import org.h2.result.ResultInterface;
-import org.h2.value.Value;
-
-/**
- * Iterator over result set.
- */
-public abstract class GridH2ResultSetIterator<T> extends GridCloseableIteratorAdapter<T> {
-    /** */
-    private static final Field RESULT_FIELD;
-
-    /**
-     * Initialize.
-     */
-    static {
-        try {
-            RESULT_FIELD = JdbcResultSet.class.getDeclaredField("result");
-
-            RESULT_FIELD.setAccessible(true);
-        }
-        catch (NoSuchFieldException e) {
-            throw new IllegalStateException("Check H2 version in classpath.", e);
-        }
-    }
-
-    /** */
-    private static final long serialVersionUID = 0L;
-
-    /** */
-    private final ResultInterface res;
-
-    /** */
-    private final ResultSet data;
-
-    /** */
-    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.
-     * @param needCpy {@code True} if need copy cache object's value.
-     * @throws IgniteCheckedException If failed.
-     */
-    protected GridH2ResultSetIterator(ResultSet data, boolean closeStmt, boolean needCpy) throws IgniteCheckedException {
-        this.data = data;
-        this.closeStmt = closeStmt;
-
-        try {
-            res = needCpy ? (ResultInterface)RESULT_FIELD.get(data) : null;
-        }
-        catch (IllegalAccessException e) {
-            throw new IllegalStateException(e); // Must not happen.
-        }
-
-        if (data != null) {
-            try {
-                row = new Object[data.getMetaData().getColumnCount()];
-            }
-            catch (SQLException e) {
-                throw new IgniteCheckedException(e);
-            }
-        }
-        else
-            row = null;
-    }
-
-    /**
-     * @return {@code true} If next row was fetched successfully.
-     */
-    private boolean fetchNext() {
-        if (data == null)
-            return false;
-
-        try {
-            if (!data.next())
-                return false;
-
-            if (res != null) {
-                Value[] values = res.currentRow();
-
-                for (int c = 0; c < row.length; c++) {
-                    Value val = values[c];
-
-                    if (val instanceof GridH2ValueCacheObject) {
-                        GridH2ValueCacheObject valCacheObj = (GridH2ValueCacheObject)values[c];
-
-                        GridCacheContext cctx = valCacheObj.getCacheContext();
-
-                        row[c] = valCacheObj.getObject(cctx != null && cctx.needValueCopy());
-                    }
-                    else
-                        row[c] = val.getObject();
-                }
-            }
-            else {
-                for (int c = 0; c < row.length; c++)
-                    row[c] = data.getObject(c + 1);
-            }
-
-            return true;
-        }
-        catch (SQLException e) {
-            throw new IgniteSQLException(e);
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override public boolean onHasNext() {
-        return hasRow || (hasRow = fetchNext());
-    }
-
-    /** {@inheritDoc} */
-    @SuppressWarnings("IteratorNextCanNotThrowNoSuchElementException")
-    @Override public T onNext() {
-        if (!hasNext())
-            throw new NoSuchElementException();
-
-        hasRow = false;
-
-        return createRow();
-    }
-
-    /**
-     * @return Row.
-     */
-    protected abstract T createRow();
-
-    /** {@inheritDoc} */
-    @Override public void onRemove() {
-        throw new UnsupportedOperationException();
-    }
-
-    /** {@inheritDoc} */
-    @Override public void onClose() throws IgniteCheckedException {
-        if (data == null)
-            // Nothing to close.
-            return;
-
-        if (closeStmt) {
-            try {
-                U.closeQuiet(data.getStatement());
-            }
-            catch (SQLException e) {
-                throw new IgniteCheckedException(e);
-            }
-        }
-
-        U.closeQuiet(data);
-    }
-
-    /** {@inheritDoc} */
-    @Override public String toString() {
-        return S.toString(GridH2ResultSetIterator.class, this);
-    }
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/8c75e4de/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2ConnectionWrapper.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2ConnectionWrapper.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2ConnectionWrapper.java
new file mode 100644
index 0000000..e180c9c
--- /dev/null
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2ConnectionWrapper.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.h2;
+
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.jetbrains.annotations.Nullable;
+
+import java.sql.Connection;
+
+/**
+ * Wrapper to store connection and flag is schema set or not.
+ */
+public class H2ConnectionWrapper {
+    /** */
+    private Connection conn;
+
+    /** */
+    private volatile String schema;
+
+    /**
+     * @param conn Connection to use.
+     */
+    H2ConnectionWrapper(Connection conn) {
+        this.conn = conn;
+    }
+
+    /**
+     * @return Schema name if schema is set, null otherwise.
+     */
+    public String schema() {
+        return schema;
+    }
+
+    /**
+     * @param schema Schema name set on this connection.
+     */
+    public void schema(@Nullable String schema) {
+        this.schema = schema;
+    }
+
+    /**
+     * @return Connection.
+     */
+    public Connection connection() {
+        return conn;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(H2ConnectionWrapper.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/8c75e4de/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2DatabaseType.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2DatabaseType.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2DatabaseType.java
new file mode 100644
index 0000000..47c7eb9
--- /dev/null
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2DatabaseType.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.h2;
+
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.h2.value.DataType;
+
+import java.math.BigDecimal;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Enum that helps to map java types to database types.
+ */
+public enum H2DatabaseType {
+    /** */
+    INT("INT"),
+
+    /** */
+    BOOL("BOOL"),
+
+    /** */
+    TINYINT("TINYINT"),
+
+    /** */
+    SMALLINT("SMALLINT"),
+
+    /** */
+    BIGINT("BIGINT"),
+
+    /** */
+    DECIMAL("DECIMAL"),
+
+    /** */
+    DOUBLE("DOUBLE"),
+
+    /** */
+    REAL("REAL"),
+
+    /** */
+    TIME("TIME"),
+
+    /** */
+    TIMESTAMP("TIMESTAMP"),
+
+    /** */
+    DATE("DATE"),
+
+    /** */
+    VARCHAR("VARCHAR"),
+
+    /** */
+    CHAR("CHAR"),
+
+    /** */
+    BINARY("BINARY"),
+
+    /** */
+    UUID("UUID"),
+
+    /** */
+    ARRAY("ARRAY"),
+
+    /** */
+    GEOMETRY("GEOMETRY"),
+
+    /** */
+    OTHER("OTHER");
+
+    /** Map of Class to enum. */
+    private static final Map<Class<?>, H2DatabaseType> map = new HashMap<>();
+
+    /**
+     * Initialize map of DB types.
+     */
+    static {
+        map.put(int.class, INT);
+        map.put(Integer.class, INT);
+        map.put(boolean.class, BOOL);
+        map.put(Boolean.class, BOOL);
+        map.put(byte.class, TINYINT);
+        map.put(Byte.class, TINYINT);
+        map.put(short.class, SMALLINT);
+        map.put(Short.class, SMALLINT);
+        map.put(long.class, BIGINT);
+        map.put(Long.class, BIGINT);
+        map.put(BigDecimal.class, DECIMAL);
+        map.put(double.class, DOUBLE);
+        map.put(Double.class, DOUBLE);
+        map.put(float.class, REAL);
+        map.put(Float.class, REAL);
+        map.put(Time.class, TIME);
+        map.put(Timestamp.class, TIMESTAMP);
+        map.put(java.util.Date.class, TIMESTAMP);
+        map.put(java.sql.Date.class, DATE);
+        map.put(String.class, VARCHAR);
+        map.put(java.util.UUID.class, UUID);
+        map.put(byte[].class, BINARY);
+    }
+
+    /** */
+    private final String dbType;
+
+    /**
+     * Constructs new instance.
+     *
+     * @param dbType DB type name.
+     */
+    H2DatabaseType(String dbType) {
+        this.dbType = dbType;
+    }
+
+    /**
+     * Resolves enum by class.
+     *
+     * @param cls Class.
+     * @return Enum value.
+     */
+    public static H2DatabaseType fromClass(Class<?> cls) {
+        H2DatabaseType res = map.get(cls);
+
+        if (res != null)
+            return res;
+
+        if (DataType.isGeometryClass(cls))
+            return GEOMETRY;
+
+        return cls.isArray() && !cls.getComponentType().isPrimitive() ? ARRAY : OTHER;
+    }
+
+    /**
+     * Gets DB type name.
+     *
+     * @return DB type name.
+     */
+    public String dBTypeAsString() {
+        return dbType;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(H2DatabaseType.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/8c75e4de/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2FieldsIterator.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2FieldsIterator.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2FieldsIterator.java
new file mode 100644
index 0000000..f300c3f
--- /dev/null
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2FieldsIterator.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.h2;
+
+import org.apache.ignite.IgniteCheckedException;
+
+import java.sql.ResultSet;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Special field set iterator based on database result set.
+ */
+public class H2FieldsIterator extends H2ResultSetIterator<List<?>> {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /**
+     * @param data Data.
+     * @throws IgniteCheckedException If failed.
+     */
+    public H2FieldsIterator(ResultSet data) throws IgniteCheckedException {
+        super(data, false, true);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected List<?> createRow() {
+        ArrayList<Object> res = new ArrayList<>(row.length);
+
+        Collections.addAll(res, row);
+
+        return res;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/8c75e4de/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2KeyValueIterator.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2KeyValueIterator.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2KeyValueIterator.java
new file mode 100644
index 0000000..2088e44
--- /dev/null
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2KeyValueIterator.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.h2;
+
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.lang.IgniteBiTuple;
+
+import java.sql.ResultSet;
+
+/**
+ * Special key/value iterator based on database result set.
+ */
+public class H2KeyValueIterator<K, V> extends H2ResultSetIterator<IgniteBiTuple<K, V>> {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /**
+     * @param data Data array.
+     * @throws IgniteCheckedException If failed.
+     */
+    protected H2KeyValueIterator(ResultSet data) throws IgniteCheckedException {
+        super(data, false, true);
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    @Override protected IgniteBiTuple<K, V> createRow() {
+        K key = (K)row[0];
+        V val = (V)row[1];
+
+        return new IgniteBiTuple<>(key, val);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/8c75e4de/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2ResultSetIterator.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2ResultSetIterator.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2ResultSetIterator.java
new file mode 100644
index 0000000..494f069
--- /dev/null
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2ResultSetIterator.java
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.h2;
+
+import java.lang.reflect.Field;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.NoSuchElementException;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.query.IgniteSQLException;
+import org.apache.ignite.internal.processors.query.h2.opt.GridH2ValueCacheObject;
+import org.apache.ignite.internal.util.GridCloseableIteratorAdapter;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.h2.jdbc.JdbcResultSet;
+import org.h2.result.ResultInterface;
+import org.h2.value.Value;
+
+/**
+ * Iterator over result set.
+ */
+public abstract class H2ResultSetIterator<T> extends GridCloseableIteratorAdapter<T> {
+    /** */
+    private static final Field RESULT_FIELD;
+
+    /**
+     * Initialize.
+     */
+    static {
+        try {
+            RESULT_FIELD = JdbcResultSet.class.getDeclaredField("result");
+
+            RESULT_FIELD.setAccessible(true);
+        }
+        catch (NoSuchFieldException e) {
+            throw new IllegalStateException("Check H2 version in classpath.", e);
+        }
+    }
+
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** */
+    private final ResultInterface res;
+
+    /** */
+    private final ResultSet data;
+
+    /** */
+    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.
+     * @param needCpy {@code True} if need copy cache object's value.
+     * @throws IgniteCheckedException If failed.
+     */
+    protected H2ResultSetIterator(ResultSet data, boolean closeStmt, boolean needCpy) throws IgniteCheckedException {
+        this.data = data;
+        this.closeStmt = closeStmt;
+
+        try {
+            res = needCpy ? (ResultInterface)RESULT_FIELD.get(data) : null;
+        }
+        catch (IllegalAccessException e) {
+            throw new IllegalStateException(e); // Must not happen.
+        }
+
+        if (data != null) {
+            try {
+                row = new Object[data.getMetaData().getColumnCount()];
+            }
+            catch (SQLException e) {
+                throw new IgniteCheckedException(e);
+            }
+        }
+        else
+            row = null;
+    }
+
+    /**
+     * @return {@code true} If next row was fetched successfully.
+     */
+    private boolean fetchNext() {
+        if (data == null)
+            return false;
+
+        try {
+            if (!data.next())
+                return false;
+
+            if (res != null) {
+                Value[] values = res.currentRow();
+
+                for (int c = 0; c < row.length; c++) {
+                    Value val = values[c];
+
+                    if (val instanceof GridH2ValueCacheObject) {
+                        GridH2ValueCacheObject valCacheObj = (GridH2ValueCacheObject)values[c];
+
+                        GridCacheContext cctx = valCacheObj.getCacheContext();
+
+                        row[c] = valCacheObj.getObject(cctx != null && cctx.needValueCopy());
+                    }
+                    else
+                        row[c] = val.getObject();
+                }
+            }
+            else {
+                for (int c = 0; c < row.length; c++)
+                    row[c] = data.getObject(c + 1);
+            }
+
+            return true;
+        }
+        catch (SQLException e) {
+            throw new IgniteSQLException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean onHasNext() {
+        return hasRow || (hasRow = fetchNext());
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("IteratorNextCanNotThrowNoSuchElementException")
+    @Override public T onNext() {
+        if (!hasNext())
+            throw new NoSuchElementException();
+
+        hasRow = false;
+
+        return createRow();
+    }
+
+    /**
+     * @return Row.
+     */
+    protected abstract T createRow();
+
+    /** {@inheritDoc} */
+    @Override public void onRemove() {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onClose() throws IgniteCheckedException {
+        if (data == null)
+            // Nothing to close.
+            return;
+
+        if (closeStmt) {
+            try {
+                U.closeQuiet(data.getStatement());
+            }
+            catch (SQLException e) {
+                throw new IgniteCheckedException(e);
+            }
+        }
+
+        U.closeQuiet(data);
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(H2ResultSetIterator.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/8c75e4de/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2RowDescriptor.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2RowDescriptor.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2RowDescriptor.java
new file mode 100644
index 0000000..6f5ce3e
--- /dev/null
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2RowDescriptor.java
@@ -0,0 +1,479 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.h2;
+
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.internal.processors.cache.CacheObject;
+import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.cache.KeyCacheObject;
+import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+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.GridH2KeyValueRowOffheap;
+import org.apache.ignite.internal.processors.query.h2.opt.GridH2KeyValueRowOnheap;
+import org.apache.ignite.internal.processors.query.h2.opt.GridH2Row;
+import org.apache.ignite.internal.processors.query.h2.opt.GridH2RowDescriptor;
+import org.apache.ignite.internal.processors.query.h2.opt.GridH2RowFactory;
+import org.apache.ignite.internal.processors.query.h2.opt.GridH2ValueCacheObject;
+import org.apache.ignite.internal.util.offheap.unsafe.GridUnsafeGuard;
+import org.apache.ignite.internal.util.offheap.unsafe.GridUnsafeMemory;
+import org.h2.message.DbException;
+import org.h2.result.SearchRow;
+import org.h2.result.SimpleRow;
+import org.h2.value.DataType;
+import org.h2.value.Value;
+import org.h2.value.ValueArray;
+import org.h2.value.ValueBoolean;
+import org.h2.value.ValueByte;
+import org.h2.value.ValueBytes;
+import org.h2.value.ValueDate;
+import org.h2.value.ValueDecimal;
+import org.h2.value.ValueDouble;
+import org.h2.value.ValueFloat;
+import org.h2.value.ValueGeometry;
+import org.h2.value.ValueInt;
+import org.h2.value.ValueJavaObject;
+import org.h2.value.ValueLong;
+import org.h2.value.ValueNull;
+import org.h2.value.ValueShort;
+import org.h2.value.ValueString;
+import org.h2.value.ValueTime;
+import org.h2.value.ValueTimestamp;
+import org.h2.value.ValueUuid;
+import org.jetbrains.annotations.Nullable;
+
+import java.math.BigDecimal;
+import java.sql.Date;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import static org.apache.ignite.internal.processors.query.h2.opt.GridH2AbstractKeyValueRow.DEFAULT_COLUMNS_COUNT;
+import static org.apache.ignite.internal.processors.query.h2.opt.GridH2AbstractKeyValueRow.KEY_COL;
+import static org.apache.ignite.internal.processors.query.h2.opt.GridH2AbstractKeyValueRow.VAL_COL;
+import static org.apache.ignite.internal.processors.query.h2.opt.GridH2AbstractKeyValueRow.VER_COL;
+
+/**
+ * Row descriptor.
+ */
+public class H2RowDescriptor implements GridH2RowDescriptor {
+    /** Indexing SPI. */
+    private final IgniteH2Indexing idx;
+
+    /** */
+    private final GridQueryTypeDescriptor type;
+
+    /** */
+    private final String[] fields;
+
+    /** */
+    private final int[] fieldTypes;
+
+    /** */
+    private final int keyType;
+
+    /** */
+    private final int valType;
+
+    /** */
+    private final H2Schema schema;
+
+    /** */
+    private final GridUnsafeGuard guard;
+
+    /** */
+    private final boolean snapshotableIdx;
+
+    /** */
+    private final GridQueryProperty[] props;
+
+    /** Id of user-defined key column */
+    private final int keyAliasColumnId;
+
+    /** Id of user-defined value column */
+    private final int valueAliasColumnId;
+
+    /**
+     * @param type Type descriptor.
+     * @param schema Schema.
+     */
+    H2RowDescriptor(IgniteH2Indexing idx, GridQueryTypeDescriptor type, H2Schema schema) {
+        assert type != null;
+        assert schema != null;
+
+        this.idx = idx;
+        this.type = type;
+        this.schema = schema;
+
+        guard = schema.offheap() == null ? null : new GridUnsafeGuard();
+
+        Map<String, Class<?>> allFields = new LinkedHashMap<>();
+
+        allFields.putAll(type.fields());
+
+        fields = allFields.keySet().toArray(new String[allFields.size()]);
+
+        fieldTypes = new int[fields.length];
+
+        Class[] classes = allFields.values().toArray(new Class[fields.length]);
+
+        for (int i = 0; i < fieldTypes.length; i++)
+            fieldTypes[i] = DataType.getTypeFromClass(classes[i]);
+
+        keyType = DataType.getTypeFromClass(type.keyClass());
+        valType = DataType.getTypeFromClass(type.valueClass());
+
+        props = new GridQueryProperty[fields.length];
+
+        for (int i = 0; i < fields.length; i++) {
+            GridQueryProperty p = type.property(fields[i]);
+
+            assert p != null : fields[i];
+
+            props[i] = p;
+        }
+
+        final List<String> fieldsList = Arrays.asList(fields);
+        keyAliasColumnId = (type.keyFieldName() != null) ? DEFAULT_COLUMNS_COUNT + fieldsList.indexOf(type.keyFieldName()) : -1;
+        valueAliasColumnId = (type.valueFieldName() != null) ? DEFAULT_COLUMNS_COUNT + fieldsList.indexOf(type.valueFieldName()) : -1;
+
+        // Index is not snapshotable in db-x.
+        snapshotableIdx = false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public IgniteH2Indexing indexing() {
+        return idx;
+    }
+
+    /** {@inheritDoc} */
+    @Override public GridQueryTypeDescriptor type() {
+        return type;
+    }
+
+    /** {@inheritDoc} */
+    @Override public GridCacheContext<?, ?> context() {
+        return schema.cacheContext();
+    }
+
+    /** {@inheritDoc} */
+    @Override public CacheConfiguration configuration() {
+        return schema.cacheContext().config();
+    }
+
+    /** {@inheritDoc} */
+    @Override public GridUnsafeGuard guard() {
+        return guard;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void cache(GridH2Row row) {
+        long ptr = row.pointer();
+
+        assert ptr > 0 : ptr;
+
+        schema.rowCache().put(ptr, row);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void uncache(long ptr) {
+        schema.rowCache().remove(ptr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public GridUnsafeMemory memory() {
+        return schema.offheap();
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("ConstantConditions")
+    @Override public Value wrap(Object obj, int type) throws IgniteCheckedException {
+        assert obj != null;
+
+        if (obj instanceof CacheObject) { // Handle cache object.
+            CacheObject co = (CacheObject)obj;
+
+            if (type == Value.JAVA_OBJECT)
+                return new GridH2ValueCacheObject(idx.cacheContext(schema.cacheName()), co);
+
+            obj = co.value(idx.objectContext(schema.cacheName()), false);
+        }
+
+        switch (type) {
+            case Value.BOOLEAN:
+                return ValueBoolean.get((Boolean)obj);
+            case Value.BYTE:
+                return ValueByte.get((Byte)obj);
+            case Value.SHORT:
+                return ValueShort.get((Short)obj);
+            case Value.INT:
+                return ValueInt.get((Integer)obj);
+            case Value.FLOAT:
+                return ValueFloat.get((Float)obj);
+            case Value.LONG:
+                return ValueLong.get((Long)obj);
+            case Value.DOUBLE:
+                return ValueDouble.get((Double)obj);
+            case Value.UUID:
+                UUID uuid = (UUID)obj;
+                return ValueUuid.get(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits());
+            case Value.DATE:
+                return ValueDate.get((Date)obj);
+            case Value.TIME:
+                return ValueTime.get((Time)obj);
+            case Value.TIMESTAMP:
+                if (obj instanceof java.util.Date && !(obj instanceof Timestamp))
+                    obj = new Timestamp(((java.util.Date)obj).getTime());
+
+                return ValueTimestamp.get((Timestamp)obj);
+            case Value.DECIMAL:
+                return ValueDecimal.get((BigDecimal)obj);
+            case Value.STRING:
+                return ValueString.get(obj.toString());
+            case Value.BYTES:
+                return ValueBytes.get((byte[])obj);
+            case Value.JAVA_OBJECT:
+                return ValueJavaObject.getNoCopy(obj, null, null);
+            case Value.ARRAY:
+                Object[] arr = (Object[])obj;
+
+                Value[] valArr = new Value[arr.length];
+
+                for (int i = 0; i < arr.length; i++) {
+                    Object o = arr[i];
+
+                    valArr[i] = o == null ? ValueNull.INSTANCE : wrap(o, DataType.getTypeFromClass(o.getClass()));
+                }
+
+                return ValueArray.get(valArr);
+
+            case Value.GEOMETRY:
+                return ValueGeometry.getFromGeometry(obj);
+        }
+
+        throw new IgniteCheckedException("Failed to wrap value[type=" + type + ", value=" + obj + "]");
+    }
+
+    /** {@inheritDoc} */
+    @Override public GridH2Row createRow(KeyCacheObject key, int partId, @Nullable CacheObject val,
+        GridCacheVersion ver,
+        long expirationTime) throws IgniteCheckedException {
+        GridH2Row row;
+
+        try {
+            if (val == null) // Only can happen for remove operation, can create simple search row.
+                row = GridH2RowFactory.create(wrap(key, keyType));
+            else
+                row = schema.offheap() == null ?
+                    new GridH2KeyValueRowOnheap(this, key, keyType, val, valType, ver, expirationTime) :
+                    new GridH2KeyValueRowOffheap(this, key, keyType, val, valType, ver, expirationTime);
+        }
+        catch (ClassCastException e) {
+            throw new IgniteCheckedException("Failed to convert key to SQL type. " +
+                "Please make sure that you always store each value type with the same key type " +
+                "or configure key type as common super class for all actual keys for this value type.", e);
+        }
+
+        GridCacheContext cctx = idx.cacheContext(schema.cacheName());
+
+        if (cctx.offheapIndex()) {
+            row.ver = ver;
+
+            row.key = key;
+            row.val = val;
+            row.partId = partId;
+        }
+
+        return row;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int valueType() {
+        return valType;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int fieldsCount() {
+        return fields.length;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int fieldType(int col) {
+        return fieldTypes[col];
+    }
+
+    /** {@inheritDoc} */
+    @Override public Object columnValue(Object key, Object val, int col) {
+        try {
+            return props[col].value(key, val);
+        }
+        catch (IgniteCheckedException e) {
+            throw DbException.convert(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setColumnValue(Object key, Object val, Object colVal, int col) {
+        try {
+            props[col].setValue(key, val, colVal);
+        }
+        catch (IgniteCheckedException e) {
+            throw DbException.convert(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isColumnKeyProperty(int col) {
+        return props[col].key();
+    }
+
+    /** {@inheritDoc} */
+    @Override public GridH2KeyValueRowOffheap createPointer(long ptr) {
+        GridH2KeyValueRowOffheap row = (GridH2KeyValueRowOffheap)schema.rowCache().get(ptr);
+
+        if (row != null) {
+            assert row.pointer() == ptr : ptr + " " + row.pointer();
+
+            return row;
+        }
+
+        return new GridH2KeyValueRowOffheap(this, ptr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public GridH2Row cachedRow(long link) {
+        return schema.rowCache().get(link);
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean snapshotableIndex() {
+        return snapshotableIdx;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isKeyColumn(int columnId) {
+        assert columnId >= 0;
+        return columnId == KEY_COL || columnId == keyAliasColumnId;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isValueColumn(int columnId) {
+        assert columnId >= 0;
+        return columnId == VAL_COL || columnId == valueAliasColumnId;
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("RedundantIfStatement")
+    @Override public boolean isKeyValueOrVersionColumn(int columnId) {
+        assert columnId >= 0;
+
+        if (columnId < DEFAULT_COLUMNS_COUNT)
+            return true;
+
+        if (columnId == keyAliasColumnId)
+            return true;
+
+        if (columnId == valueAliasColumnId)
+            return true;
+
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean checkKeyIndexCondition(int masks[], int mask) {
+        assert masks != null;
+        assert masks.length > 0;
+
+        if (keyAliasColumnId < 0)
+            return (masks[KEY_COL] & mask) != 0;
+        else
+            return (masks[KEY_COL] & mask) != 0 || (masks[keyAliasColumnId] & mask) != 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void initValueCache(Value valCache[], Value key, Value value, Value version) {
+        assert valCache != null;
+        assert valCache.length > 0;
+
+        valCache[KEY_COL] = key;
+        valCache[VAL_COL] = value;
+        valCache[VER_COL] = version;
+
+        if (keyAliasColumnId > 0)
+            valCache[keyAliasColumnId] = key;
+
+        if (valueAliasColumnId > 0)
+            valCache[valueAliasColumnId] = value;
+    }
+
+    /** {@inheritDoc} */
+    @Override public SearchRow prepareProxyIndexRow(SearchRow row) {
+        if (row == null)
+            return null;
+
+        Value[] data = new Value[row.getColumnCount()];
+        for (int idx = 0; idx < data.length; idx++)
+            data[idx] = row.getValue(idx);
+
+        copyAliasColumnData(data, KEY_COL, keyAliasColumnId);
+        copyAliasColumnData(data, VAL_COL, valueAliasColumnId);
+
+        return new SimpleRow(data);
+    }
+
+    /**
+     * Copies data between original and alias columns
+     *
+     * @param data Array of values.
+     * @param colId Original column id.
+     * @param aliasColId Alias column id.
+     */
+    private void copyAliasColumnData(Value[] data, int colId, int aliasColId) {
+        if (aliasColId <= 0)
+            return;
+
+        if (data[aliasColId] == null && data[colId] != null)
+            data[aliasColId] = data[colId];
+
+        if (data[colId] == null && data[aliasColId] != null)
+            data[colId] = data[aliasColId];
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getAlternativeColumnId(int colId) {
+        if (keyAliasColumnId > 0) {
+            if (colId == KEY_COL)
+                return keyAliasColumnId;
+            else if (colId == keyAliasColumnId)
+                return KEY_COL;
+        }
+        if (valueAliasColumnId > 0) {
+            if (colId == VAL_COL)
+                return valueAliasColumnId;
+            else if (colId == valueAliasColumnId)
+                return VAL_COL;
+        }
+
+        return colId;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/8c75e4de/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2Schema.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2Schema.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2Schema.java
new file mode 100644
index 0000000..603a0c1
--- /dev/null
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2Schema.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.h2;
+
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.query.h2.opt.GridH2Row;
+import org.apache.ignite.internal.util.offheap.unsafe.GridUnsafeMemory;
+import org.h2.mvstore.cache.CacheLongKeyLIRS;
+import org.jsr166.ConcurrentHashMap8;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Database schema object.
+ */
+public class H2Schema {
+    /** */
+    private final String cacheName;
+
+    /** */
+    private final String schemaName;
+
+    /** */
+    private final GridUnsafeMemory offheap = null;
+
+    /** */
+    private final ConcurrentMap<String, H2TableDescriptor> tbls = new ConcurrentHashMap8<>();
+
+    /** Cache for deserialized offheap rows. */
+    private final CacheLongKeyLIRS<GridH2Row> rowCache;
+
+    /** */
+    private final GridCacheContext<?, ?> cctx;
+
+    /** */
+    private final CacheConfiguration<?, ?> ccfg;
+
+    /**
+     * @param cacheName Cache name.
+     * @param schemaName Schema name.
+     * @param cctx Cache context.
+     * @param ccfg Cache configuration.
+     */
+    H2Schema(String cacheName, String schemaName, GridCacheContext<?, ?> cctx,
+        CacheConfiguration<?, ?> ccfg) {
+        this.cacheName = cacheName;
+        this.cctx = cctx;
+        this.schemaName = schemaName;
+        this.ccfg = ccfg;
+
+        rowCache = null;
+    }
+
+    /**
+     * @return Cache context.
+     */
+    public GridCacheContext cacheContext() {
+        return cctx;
+    }
+
+    /**
+     * @return Cache name.
+     */
+    public String cacheName() {
+        return cacheName;
+    }
+
+    /**
+     * @return Schema name.
+     */
+    public String schemaName() {
+        return schemaName;
+    }
+
+    /**
+     * @return Unsafe memory.
+     */
+    public GridUnsafeMemory offheap() {
+        return offheap;
+    }
+
+    /**
+     * @return Row cache.
+     */
+    public CacheLongKeyLIRS<GridH2Row> rowCache() {
+        return rowCache;
+    }
+
+    /**
+     * @return Tables.
+     */
+    public Map<String, H2TableDescriptor> tables() {
+        return tbls;
+    }
+
+    /**
+     * @param tbl Table descriptor.
+     */
+    public void add(H2TableDescriptor tbl) {
+        if (tbls.putIfAbsent(tbl.typeName(), tbl) != null)
+            throw new IllegalStateException("Table already registered: " + tbl.fullTableName());
+    }
+
+    /**
+     * @return Escape all.
+     */
+    public boolean escapeAll() {
+        return ccfg.isSqlEscapeAll();
+    }
+
+    /**
+     * Called after the schema was dropped.
+     */
+    public void onDrop() {
+        for (H2TableDescriptor tblDesc : tbls.values())
+            tblDesc.onDrop();
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/8c75e4de/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2SqlFieldMetadata.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2SqlFieldMetadata.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2SqlFieldMetadata.java
new file mode 100644
index 0000000..46aa1fc
--- /dev/null
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2SqlFieldMetadata.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.h2;
+
+import org.apache.ignite.internal.processors.query.GridQueryFieldMetadata;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+/**
+ * Field descriptor.
+ */
+public class H2SqlFieldMetadata implements GridQueryFieldMetadata {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Schema name. */
+    private String schemaName;
+
+    /** Type name. */
+    private String typeName;
+
+    /** Name. */
+    private String name;
+
+    /** Type. */
+    private String type;
+
+    /**
+     * Required by {@link Externalizable}.
+     */
+    public H2SqlFieldMetadata() {
+        // No-op
+    }
+
+    /**
+     * @param schemaName Schema name.
+     * @param typeName Type name.
+     * @param name Name.
+     * @param type Type.
+     */
+    H2SqlFieldMetadata(@Nullable String schemaName, @Nullable String typeName, String name, String type) {
+        assert name != null && type != null : schemaName + " | " + typeName + " | " + name + " | " + type;
+
+        this.schemaName = schemaName;
+        this.typeName = typeName;
+        this.name = name;
+        this.type = type;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String schemaName() {
+        return schemaName;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String typeName() {
+        return typeName;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String fieldName() {
+        return name;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String fieldTypeName() {
+        return type;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void writeExternal(ObjectOutput out) throws IOException {
+        U.writeString(out, schemaName);
+        U.writeString(out, typeName);
+        U.writeString(out, name);
+        U.writeString(out, type);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+        schemaName = U.readString(in);
+        typeName = U.readString(in);
+        name = U.readString(in);
+        type = U.readString(in);
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(H2SqlFieldMetadata.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/8c75e4de/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2StatementCache.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2StatementCache.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2StatementCache.java
new file mode 100644
index 0000000..d395112
--- /dev/null
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2StatementCache.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.h2;
+
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+import java.sql.PreparedStatement;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Statement cache.
+ */
+public class H2StatementCache extends LinkedHashMap<String, PreparedStatement> {
+    /** */
+    private int size;
+
+    /** Last usage. */
+    private volatile long lastUsage;
+
+    /**
+     * @param size Size.
+     */
+    H2StatementCache(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;
+    }
+
+    /**
+     * The timestamp of the last usage of the cache.
+     *
+     * @return last usage timestamp
+     */
+    public long lastUsage() {
+        return lastUsage;
+    }
+
+    /**
+     * Updates the {@link #lastUsage} timestamp by current time.
+     */
+    public void updateLastUsage() {
+        lastUsage = U.currentTimeMillis();
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/8c75e4de/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TableDescriptor.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TableDescriptor.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TableDescriptor.java
new file mode 100644
index 0000000..a9548aa
--- /dev/null
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TableDescriptor.java
@@ -0,0 +1,345 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.h2;
+
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.cache.QueryIndexType;
+import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.query.GridQueryIndexDescriptor;
+import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
+import org.apache.ignite.internal.processors.query.h2.database.H2PkHashIndex;
+import org.apache.ignite.internal.processors.query.h2.database.H2RowFactory;
+import org.apache.ignite.internal.processors.query.h2.opt.GridH2IndexBase;
+import org.apache.ignite.internal.processors.query.h2.opt.GridH2RowDescriptor;
+import org.apache.ignite.internal.processors.query.h2.opt.GridH2SystemIndexFactory;
+import org.apache.ignite.internal.processors.query.h2.opt.GridH2Table;
+import org.apache.ignite.internal.processors.query.h2.opt.GridLuceneIndex;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.h2.index.Index;
+import org.h2.result.SortOrder;
+import org.h2.table.Column;
+import org.h2.table.IndexColumn;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import static org.apache.ignite.internal.processors.query.h2.opt.GridH2AbstractKeyValueRow.KEY_COL;
+
+/**
+ * Information about table in database.
+ */
+public class H2TableDescriptor implements GridH2SystemIndexFactory {
+    /** Indexing. */
+    private final IgniteH2Indexing idx;
+
+    /** */
+    private final String fullTblName;
+
+    /** */
+    private final GridQueryTypeDescriptor type;
+
+    /** */
+    private final H2Schema schema;
+
+    /** */
+    private GridH2Table tbl;
+
+    /** */
+    private GridLuceneIndex luceneIdx;
+
+    /** */
+    private H2PkHashIndex pkHashIdx;
+
+    /**
+     * Constructor.
+     *
+     * @param idx Indexing.
+     * @param schema Schema.
+     * @param type Type descriptor.
+     */
+    H2TableDescriptor(IgniteH2Indexing idx, H2Schema schema, GridQueryTypeDescriptor type) {
+        this.idx = idx;
+        this.type = type;
+        this.schema = schema;
+
+        String tblName = H2Utils.escapeName(type.tableName(), schema.escapeAll());
+
+        fullTblName = schema.schemaName() + "." + tblName;
+    }
+
+    /**
+     * @return Primary key hash index.
+     */
+    H2PkHashIndex primaryKeyHashIndex() {
+        return pkHashIdx;
+    }
+
+    /**
+     * @return Table.
+     */
+    public GridH2Table table() {
+        return tbl;
+    }
+
+    /**
+     * @param tbl Table.
+     */
+    public void table(GridH2Table tbl) {
+        this.tbl = tbl;
+    }
+
+    /**
+     * @return Schema.
+     */
+    public H2Schema schema() {
+        return schema;
+    }
+
+    /**
+     * @return Schema name.
+     */
+    public String schemaName() {
+        return schema.schemaName();
+    }
+
+    /**
+     * @return Database full table name.
+     */
+    String fullTableName() {
+        return fullTblName;
+    }
+
+    /**
+     * @return type name.
+     */
+    String typeName() {
+        return type.name();
+    }
+
+    /**
+     * @return Type.
+     */
+    GridQueryTypeDescriptor type() {
+        return type;
+    }
+
+    /**
+     * @return Lucene index.
+     */
+    GridLuceneIndex luceneIndex() {
+        return luceneIdx;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(H2TableDescriptor.class, this);
+    }
+
+    /**
+     * Create H2 row factory.
+     *
+     * @param rowDesc Row descriptor.
+     * @return H2 row factory.
+     */
+    H2RowFactory rowFactory(GridH2RowDescriptor rowDesc) {
+        GridCacheContext cctx = schema.cacheContext();
+
+        if (cctx.affinityNode() && cctx.offheapIndex())
+            return new H2RowFactory(rowDesc, cctx);
+
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public ArrayList<Index> createSystemIndexes(GridH2Table tbl) {
+        ArrayList<Index> idxs = new ArrayList<>();
+
+        IndexColumn keyCol = tbl.indexColumn(KEY_COL, SortOrder.ASCENDING);
+        IndexColumn affCol = tbl.getAffinityKeyColumn();
+
+        if (affCol != null && H2Utils.equals(affCol, keyCol))
+            affCol = null;
+
+        GridH2RowDescriptor desc = tbl.rowDescriptor();
+
+        Index hashIdx = createHashIndex(
+            schema,
+            tbl,
+            "_key_PK_hash",
+            H2Utils.treeIndexColumns(desc, new ArrayList<IndexColumn>(2), keyCol, affCol)
+        );
+
+        if (hashIdx != null)
+            idxs.add(hashIdx);
+
+        // Add primary key index.
+        Index pkIdx = idx.createSortedIndex(
+            schema,
+            "_key_PK",
+            tbl,
+            true,
+            H2Utils.treeIndexColumns(desc, new ArrayList<IndexColumn>(2), keyCol, affCol),
+            -1
+        );
+
+        idxs.add(pkIdx);
+
+        if (type().valueClass() == String.class) {
+            try {
+                luceneIdx = new GridLuceneIndex(idx.kernalContext(), schema.offheap(), schema.cacheName(), type);
+            }
+            catch (IgniteCheckedException e1) {
+                throw new IgniteException(e1);
+            }
+        }
+
+        boolean affIdxFound = false;
+
+        GridQueryIndexDescriptor textIdx = type.textIndex();
+
+        if (textIdx != null) {
+            try {
+                luceneIdx = new GridLuceneIndex(idx.kernalContext(), schema.offheap(), schema.cacheName(), type);
+            }
+            catch (IgniteCheckedException e1) {
+                throw new IgniteException(e1);
+            }
+        }
+
+        // Locate index where affinity column is first (if any).
+        if (affCol != null) {
+            for (GridQueryIndexDescriptor idxDesc : type.indexes().values()) {
+                if (idxDesc.type() != QueryIndexType.SORTED)
+                    continue;
+
+                String firstField = idxDesc.fields().iterator().next();
+
+                String firstFieldName =
+                    schema.escapeAll() ? firstField : H2Utils.escapeName(firstField, false).toUpperCase();
+
+                Column col = tbl.getColumn(firstFieldName);
+
+                IndexColumn idxCol = tbl.indexColumn(col.getColumnId(),
+                    idxDesc.descending(firstField) ? SortOrder.DESCENDING : SortOrder.ASCENDING);
+
+                affIdxFound |= H2Utils.equals(idxCol, affCol);
+            }
+        }
+
+        // Add explicit affinity key index if nothing alike was found.
+        if (affCol != null && !affIdxFound) {
+            idxs.add(idx.createSortedIndex(schema, "AFFINITY_KEY", tbl, false,
+                H2Utils.treeIndexColumns(desc, new ArrayList<IndexColumn>(2), affCol, keyCol), -1));
+        }
+
+        return idxs;
+    }
+
+    /**
+     * Get collection of user indexes.
+     *
+     * @return User indexes.
+     */
+    public Collection<GridH2IndexBase> createUserIndexes() {
+        assert tbl != null;
+
+        ArrayList<GridH2IndexBase> res = new ArrayList<>();
+
+        for (GridQueryIndexDescriptor idxDesc : type.indexes().values()) {
+            GridH2IndexBase idx = createUserIndex(idxDesc);
+
+            res.add(idx);
+        }
+
+        return res;
+    }
+
+    /**
+     * Create user index.
+     *
+     * @param idxDesc Index descriptor.
+     * @return Index.
+     */
+    public GridH2IndexBase createUserIndex(GridQueryIndexDescriptor idxDesc) {
+        String name = schema.escapeAll() ? idxDesc.name() : H2Utils.escapeName(idxDesc.name(), false).toUpperCase();
+
+        IndexColumn keyCol = tbl.indexColumn(KEY_COL, SortOrder.ASCENDING);
+        IndexColumn affCol = tbl.getAffinityKeyColumn();
+
+        List<IndexColumn> cols = new ArrayList<>(idxDesc.fields().size() + 2);
+
+        boolean escapeAll = schema.escapeAll();
+
+        for (String field : idxDesc.fields()) {
+            String fieldName = escapeAll ? field : H2Utils.escapeName(field, false).toUpperCase();
+
+            Column col = tbl.getColumn(fieldName);
+
+            cols.add(tbl.indexColumn(col.getColumnId(),
+                idxDesc.descending(field) ? SortOrder.DESCENDING : SortOrder.ASCENDING));
+        }
+
+        GridH2RowDescriptor desc = tbl.rowDescriptor();
+        if (idxDesc.type() == QueryIndexType.SORTED) {
+            cols = H2Utils.treeIndexColumns(desc, cols, keyCol, affCol);
+            return idx.createSortedIndex(schema, name, tbl, false, cols, idxDesc.inlineSize());
+        }
+        else if (idxDesc.type() == QueryIndexType.GEOSPATIAL) {
+            return H2Utils.createSpatialIndex(tbl, name, cols.toArray(new IndexColumn[cols.size()]));
+        }
+
+        throw new IllegalStateException("Index type: " + idxDesc.type());
+    }
+
+    /**
+     * Create hash index.
+     *
+     * @param schema Schema.
+     * @param tbl Table.
+     * @param idxName Index name.
+     * @param cols Columns.
+     * @return Index.
+     */
+    private Index createHashIndex(H2Schema schema, GridH2Table tbl, String idxName, List<IndexColumn> cols) {
+        GridCacheContext cctx = schema.cacheContext();
+
+        if (cctx.affinityNode() && cctx.offheapIndex()) {
+            assert pkHashIdx == null : pkHashIdx;
+
+            pkHashIdx = new H2PkHashIndex(cctx, tbl, idxName, cols);
+
+            return pkHashIdx;
+        }
+
+        return null;
+    }
+
+    /**
+     * Handle drop.
+     */
+    void onDrop() {
+        idx.removeDataTable(tbl);
+
+        tbl.destroy();
+
+        U.closeQuiet(luceneIdx);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/8c75e4de/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TableEngine.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TableEngine.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TableEngine.java
new file mode 100644
index 0000000..4cf5166
--- /dev/null
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TableEngine.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.h2;
+
+import org.apache.ignite.internal.processors.query.h2.database.H2RowFactory;
+import org.apache.ignite.internal.processors.query.h2.opt.GridH2RowDescriptor;
+import org.apache.ignite.internal.processors.query.h2.opt.GridH2Table;
+import org.h2.api.TableEngine;
+import org.h2.command.ddl.CreateTableData;
+import org.h2.table.TableBase;
+import org.jetbrains.annotations.Nullable;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+/**
+ * H2 Table engine.
+ */
+public class H2TableEngine implements TableEngine {
+    /** */
+    private static GridH2RowDescriptor rowDesc0;
+
+    /** */
+    private static H2RowFactory rowFactory0;
+
+    /** */
+    private static H2TableDescriptor tblDesc0;
+
+    /** */
+    private static GridH2Table resTbl0;
+
+    /**
+     * Creates table using given connection, DDL clause for given type descriptor and list of indexes.
+     *
+     * @param conn Connection.
+     * @param sql DDL clause.
+     * @param rowDesc Row descriptor.
+     * @param rowFactory Row factory.
+     * @param tblDesc Table descriptor.
+     * @throws SQLException If failed.
+     * @return Created table.
+     */
+    public static synchronized GridH2Table createTable(Connection conn, String sql,
+        @Nullable GridH2RowDescriptor rowDesc, H2RowFactory rowFactory, H2TableDescriptor tblDesc)
+        throws SQLException {
+        rowDesc0 = rowDesc;
+        rowFactory0 = rowFactory;
+        tblDesc0 = tblDesc;
+
+        try {
+            try (Statement s = conn.createStatement()) {
+                s.execute(sql + " engine \"" + H2TableEngine.class.getName() + "\"");
+            }
+
+            tblDesc.table(resTbl0);
+
+            return resTbl0;
+        }
+        finally {
+            resTbl0 = null;
+            tblDesc0 = null;
+            rowFactory0 = null;
+            rowDesc0 = null;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public TableBase createTable(CreateTableData createTblData) {
+        resTbl0 = new GridH2Table(createTblData, rowDesc0, rowFactory0, tblDesc0, tblDesc0.schema().cacheName());
+
+        return resTbl0;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/8c75e4de/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TwoStepCachedQuery.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TwoStepCachedQuery.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TwoStepCachedQuery.java
new file mode 100644
index 0000000..dd1b44c
--- /dev/null
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TwoStepCachedQuery.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.h2;
+
+import org.apache.ignite.internal.processors.cache.query.GridCacheTwoStepQuery;
+import org.apache.ignite.internal.processors.query.GridQueryFieldMetadata;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+import java.util.List;
+
+/**
+ * Cached two-step query.
+ */
+public class H2TwoStepCachedQuery {
+    /** */
+    final List<GridQueryFieldMetadata> meta;
+
+    /** */
+    final GridCacheTwoStepQuery twoStepQry;
+
+    /**
+     * @param meta Fields metadata.
+     * @param twoStepQry Query.
+     */
+    public H2TwoStepCachedQuery(List<GridQueryFieldMetadata> meta, GridCacheTwoStepQuery twoStepQry) {
+        this.meta = meta;
+        this.twoStepQry = twoStepQry;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(H2TwoStepCachedQuery.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/8c75e4de/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TwoStepCachedQueryKey.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TwoStepCachedQueryKey.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TwoStepCachedQueryKey.java
new file mode 100644
index 0000000..1452a83
--- /dev/null
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TwoStepCachedQueryKey.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.h2;
+
+/**
+ * Key for cached two-step query.
+ */
+public class H2TwoStepCachedQueryKey {
+    /** */
+    private final String cacheName;
+
+    /** */
+    private final String sql;
+
+    /** */
+    private final boolean grpByCollocated;
+
+    /** */
+    private final boolean distributedJoins;
+
+    /** */
+    private final boolean enforceJoinOrder;
+
+    /** */
+    private final boolean isLocal;
+
+    /**
+     * @param cacheName Cache name.
+     * @param sql Sql.
+     * @param grpByCollocated Collocated GROUP BY.
+     * @param distributedJoins Distributed joins enabled.
+     * @param enforceJoinOrder Enforce join order of tables.
+     * @param isLocal Query is local flag.
+     */
+    H2TwoStepCachedQueryKey(String cacheName,
+        String sql,
+        boolean grpByCollocated,
+        boolean distributedJoins,
+        boolean enforceJoinOrder,
+        boolean isLocal) {
+        this.cacheName = cacheName;
+        this.sql = sql;
+        this.grpByCollocated = grpByCollocated;
+        this.distributedJoins = distributedJoins;
+        this.enforceJoinOrder = enforceJoinOrder;
+        this.isLocal = isLocal;
+    }
+
+    /**
+     * @return Cache name.
+     */
+    public String cacheName() {
+        return cacheName;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+
+        if (o == null || getClass() != o.getClass())
+            return false;
+
+        H2TwoStepCachedQueryKey that = (H2TwoStepCachedQueryKey)o;
+
+        if (grpByCollocated != that.grpByCollocated)
+            return false;
+
+        if (distributedJoins != that.distributedJoins)
+            return false;
+
+        if (enforceJoinOrder != that.enforceJoinOrder)
+            return false;
+
+        if (cacheName != null ? !cacheName.equals(that.cacheName) : that.cacheName != null)
+            return false;
+
+        return isLocal == that.isLocal && sql.equals(that.sql);
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        int res = cacheName != null ? cacheName.hashCode() : 0;
+        res = 31 * res + sql.hashCode();
+        res = 31 * res + (grpByCollocated ? 1 : 0);
+        res = res + (distributedJoins ? 2 : 0);
+        res = res + (enforceJoinOrder ? 4 : 0);
+        res = res + (isLocal ? 8 : 0);
+
+        return res;
+    }
+}