You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ag...@apache.org on 2017/04/27 07:17:05 UTC

[03/37] ignite git commit: IGNITE-3487: hidden _key and _val columns - Fixes #1865.

IGNITE-3487: hidden _key and _val columns - Fixes #1865.

Signed-off-by: Sergi Vladykin <se...@gmail.com>


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

Branch: refs/heads/ignite-5090
Commit: efb7abce8323e0dc3449d7ce1bcd1d9558e6f2bc
Parents: 760cf95
Author: devozerov <vo...@gridgain.com>
Authored: Wed Apr 26 17:17:12 2017 +0300
Committer: Sergi Vladykin <se...@gmail.com>
Committed: Wed Apr 26 17:17:12 2017 +0300

----------------------------------------------------------------------
 .../jdbc2/JdbcAbstractDmlStatementSelfTest.java |   6 +
 .../internal/jdbc2/JdbcMetadataSelfTest.java    |  28 +-
 .../ignite/jdbc/JdbcMetadataSelfTest.java       |   8 +-
 .../org/apache/ignite/cache/QueryEntity.java    |  81 ++++
 .../configuration/CacheConfiguration.java       |  12 +-
 .../cache/query/GridCacheQueryManager.java      |  14 +-
 .../utils/PlatformConfigurationUtils.java       |   6 +
 .../query/GridQueryTypeDescriptor.java          |  12 +
 .../query/QueryTypeDescriptorImpl.java          |  34 +-
 .../internal/processors/query/QueryUtils.java   | 134 +++++--
 .../query/h2/DmlStatementsProcessor.java        |  17 +-
 .../processors/query/h2/IgniteH2Indexing.java   | 191 ++++++++-
 .../query/h2/dml/UpdatePlanBuilder.java         |  32 +-
 .../query/h2/opt/GridH2AbstractKeyValueRow.java |  78 ++--
 .../query/h2/opt/GridH2CollocationModel.java    |   4 +-
 .../query/h2/opt/GridH2IndexBase.java           |  15 +-
 .../query/h2/opt/GridH2KeyValueRowOffheap.java  |   6 +-
 .../query/h2/opt/GridH2KeyValueRowOnheap.java   |   6 +-
 .../query/h2/opt/GridH2ProxyIndex.java          | 204 ++++++++++
 .../query/h2/opt/GridH2ProxySpatialIndex.java   |  70 ++++
 .../query/h2/opt/GridH2RowDescriptor.java       |  67 ++++
 .../processors/query/h2/opt/GridH2Table.java    | 114 +++++-
 .../query/h2/opt/GridLuceneIndex.java           |   4 +-
 .../processors/query/h2/sql/DmlAstUtils.java    |  36 +-
 .../IgniteBinaryObjectQueryArgumentsTest.java   |   4 +-
 .../IgniteCacheAbstractFieldsQuerySelfTest.java |  15 +-
 .../IgniteCacheDeleteSqlQuerySelfTest.java      |   6 +-
 .../IgniteCacheUpdateSqlQuerySelfTest.java      |   8 +-
 .../cache/index/AbstractSchemaSelfTest.java     |   3 +
 .../index/DynamicIndexAbstractSelfTest.java     |   3 +
 .../query/IgniteSqlKeyValueFieldsTest.java      | 392 +++++++++++++++++++
 .../query/IgniteSqlSplitterSelfTest.java        |   8 +-
 .../h2/GridIndexingSpiAbstractSelfTest.java     |  10 +
 .../query/h2/sql/GridQueryParsingTest.java      |   2 +-
 .../IgniteCacheQuerySelfTestSuite.java          |   2 +
 .../core-test/config/cache-query-default.xml    |   6 +
 .../cpp/core-test/src/cache_query_test.cpp      |  82 ++++
 .../cpp/odbc-test/config/queries-default.xml    |   5 +
 .../cpp/odbc-test/include/complex_type.h        |  25 ++
 .../cpp/odbc-test/src/queries_test.cpp          | 148 +++++++
 .../Cache/Query/CacheLinqTest.cs                |   7 +
 .../Cache/Query/CacheQueriesTest.cs             |  52 +++
 .../IgniteConfigurationSerializerTest.cs        |   4 +-
 .../Cache/Configuration/QueryEntity.cs          |  20 +
 .../IgniteConfigurationSection.xsd              |  14 +
 .../Impl/CacheQueryExpressionVisitor.cs         |  14 +-
 .../Impl/CacheQueryModelVisitor.cs              |  12 +-
 47 files changed, 1822 insertions(+), 199 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/efb7abce/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcAbstractDmlStatementSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcAbstractDmlStatementSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcAbstractDmlStatementSelfTest.java
index f23dde7..90f52bd 100644
--- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcAbstractDmlStatementSelfTest.java
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcAbstractDmlStatementSelfTest.java
@@ -44,6 +44,9 @@ public abstract class JdbcAbstractDmlStatementSelfTest extends GridCommonAbstrac
     /** SQL SELECT query for verification. */
     static final String SQL_SELECT = "select _key, id, firstName, lastName, age from Person";
 
+    /** Alias for _key */
+    private static final String KEY_ALIAS = "key";
+
     /** Connection. */
     protected Connection conn;
 
@@ -97,6 +100,9 @@ public abstract class JdbcAbstractDmlStatementSelfTest extends GridCommonAbstrac
         e.setKeyType(String.class.getName());
         e.setValueType("Person");
 
+        e.setKeyFieldName(KEY_ALIAS);
+
+        e.addQueryField(KEY_ALIAS, e.getKeyType(), null);
         e.addQueryField("id", Integer.class.getName(), null);
         e.addQueryField("age", Integer.class.getName(), null);
         e.addQueryField("firstName", String.class.getName(), null);

http://git-wip-us.apache.org/repos/asf/ignite/blob/efb7abce/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java
index 750af74..ebf8874 100755
--- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java
@@ -203,8 +203,6 @@ public class JdbcMetadataSelfTest extends GridCommonAbstractTest {
             names.add("NAME");
             names.add("AGE");
             names.add("ORGID");
-            names.add("_KEY");
-            names.add("_VAL");
 
             int cnt = 0;
 
@@ -222,22 +220,12 @@ public class JdbcMetadataSelfTest extends GridCommonAbstractTest {
                     assertEquals("INTEGER", rs.getString("TYPE_NAME"));
                     assertEquals(0, rs.getInt("NULLABLE"));
                 }
-                if ("_KEY".equals(name)) {
-                    assertEquals(OTHER, rs.getInt("DATA_TYPE"));
-                    assertEquals("OTHER", rs.getString("TYPE_NAME"));
-                    assertEquals(0, rs.getInt("NULLABLE"));
-                }
-                if ("_VAL".equals(name)) {
-                    assertEquals(OTHER, rs.getInt("DATA_TYPE"));
-                    assertEquals("OTHER", rs.getString("TYPE_NAME"));
-                    assertEquals(0, rs.getInt("NULLABLE"));
-                }
 
                 cnt++;
             }
 
             assertTrue(names.isEmpty());
-            assertEquals(5, cnt);
+            assertEquals(3, cnt);
 
             rs = meta.getColumns("", "org", "Organization", "%");
 
@@ -245,8 +233,6 @@ public class JdbcMetadataSelfTest extends GridCommonAbstractTest {
 
             names.add("ID");
             names.add("NAME");
-            names.add("_KEY");
-            names.add("_VAL");
 
             cnt = 0;
 
@@ -264,22 +250,12 @@ public class JdbcMetadataSelfTest extends GridCommonAbstractTest {
                     assertEquals("VARCHAR", rs.getString("TYPE_NAME"));
                     assertEquals(1, rs.getInt("NULLABLE"));
                 }
-                if ("_KEY".equals(name)) {
-                    assertEquals(VARCHAR, rs.getInt("DATA_TYPE"));
-                    assertEquals("VARCHAR", rs.getString("TYPE_NAME"));
-                    assertEquals(0, rs.getInt("NULLABLE"));
-                }
-                if ("_VAL".equals(name)) {
-                    assertEquals(OTHER, rs.getInt("DATA_TYPE"));
-                    assertEquals("OTHER", rs.getString("TYPE_NAME"));
-                    assertEquals(0, rs.getInt("NULLABLE"));
-                }
 
                 cnt++;
             }
 
             assertTrue(names.isEmpty());
-            assertEquals(4, cnt);
+            assertEquals(2, cnt);
         }
     }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/efb7abce/modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcMetadataSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcMetadataSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcMetadataSelfTest.java
index 3ba5ed2..a02f489 100644
--- a/modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcMetadataSelfTest.java
+++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcMetadataSelfTest.java
@@ -195,8 +195,6 @@ public class JdbcMetadataSelfTest extends GridCommonAbstractTest {
             names.add("NAME");
             names.add("AGE");
             names.add("ORGID");
-            names.add("_KEY");
-            names.add("_VAL");
 
             int cnt = 0;
 
@@ -229,7 +227,7 @@ public class JdbcMetadataSelfTest extends GridCommonAbstractTest {
             }
 
             assert names.isEmpty();
-            assert cnt == 5;
+            assert cnt == 3;
 
             rs = meta.getColumns("", "org", "Organization", "%");
 
@@ -237,8 +235,6 @@ public class JdbcMetadataSelfTest extends GridCommonAbstractTest {
 
             names.add("ID");
             names.add("NAME");
-            names.add("_KEY");
-            names.add("_VAL");
 
             cnt = 0;
 
@@ -271,7 +267,7 @@ public class JdbcMetadataSelfTest extends GridCommonAbstractTest {
             }
 
             assert names.isEmpty();
-            assert cnt == 4;
+            assert cnt == 2;
         }
     }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/efb7abce/modules/core/src/main/java/org/apache/ignite/cache/QueryEntity.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/cache/QueryEntity.java b/modules/core/src/main/java/org/apache/ignite/cache/QueryEntity.java
index 806cd7d..31d89a3 100644
--- a/modules/core/src/main/java/org/apache/ignite/cache/QueryEntity.java
+++ b/modules/core/src/main/java/org/apache/ignite/cache/QueryEntity.java
@@ -45,6 +45,12 @@ public class QueryEntity implements Serializable {
     /** Value type. */
     private String valType;
 
+    /** Key name. Can be used in field list to denote the key as a whole. */
+    private String keyFieldName;
+
+    /** Value name. Can be used in field list to denote the entire value. */
+    private String valueFieldName;
+
     /** Fields available for query. A map from field name to type name. */
     @GridToStringInclude
     private LinkedHashMap<String, String> fields = new LinkedHashMap<>();
@@ -80,6 +86,9 @@ public class QueryEntity implements Serializable {
         keyType = other.keyType;
         valType = other.valType;
 
+        keyFieldName = other.keyFieldName;
+        valueFieldName = other.valueFieldName;
+
         fields = new LinkedHashMap<>(other.fields);
         keyFields = other.keyFields != null ? new HashSet<>(other.keyFields) : null;
 
@@ -110,6 +119,21 @@ public class QueryEntity implements Serializable {
     }
 
     /**
+     * Attempts to get key type from fields in case it was not set directly.
+     *
+     * @return Key type.
+     */
+    public String findKeyType() {
+        if (keyType != null)
+            return keyType;
+
+        if (fields != null && keyFieldName != null)
+            return fields.get(keyFieldName);
+
+        return null;
+    }
+
+    /**
      * Sets key type for this query pair.
      *
      * @param keyType Key type.
@@ -131,6 +155,21 @@ public class QueryEntity implements Serializable {
     }
 
     /**
+     * Attempts to get value type from fields in case it was not set directly.
+     *
+     * @return Value type.
+     */
+    public String findValueType() {
+        if (valType != null)
+            return valType;
+
+        if (fields != null && valueFieldName != null)
+            return fields.get(valueFieldName);
+
+        return null;
+    }
+
+    /**
      * Sets value type for this query pair.
      *
      * @param valType Value type.
@@ -191,6 +230,48 @@ public class QueryEntity implements Serializable {
     }
 
     /**
+     * Gets key field name.
+     *
+     * @return Key name.
+     */
+    public String getKeyFieldName() {
+        return keyFieldName;
+    }
+
+    /**
+     * Sets key field name.
+     *
+     * @param keyFieldName Key name.
+     * @return {@code this} for chaining.
+     */
+    public QueryEntity setKeyFieldName(String keyFieldName) {
+        this.keyFieldName = keyFieldName;
+
+        return this;
+    }
+
+    /**
+     * Get value field name.
+     *
+     * @return Value name.
+     */
+    public String getValueFieldName() {
+        return valueFieldName;
+    }
+
+    /**
+     * Sets value field name.
+     *
+     * @param valueFieldName value name.
+     * @return {@code this} for chaining.
+     */
+    public QueryEntity setValueFieldName(String valueFieldName) {
+        this.valueFieldName = valueFieldName;
+
+        return this;
+    }
+
+    /**
      * Gets a collection of index entities.
      *
      * @return Collection of index entities.

http://git-wip-us.apache.org/repos/asf/ignite/blob/efb7abce/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 8a3874f..275a21e 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
@@ -1749,7 +1749,7 @@ public class CacheConfiguration<K, V> extends MutableConfiguration<K, V> {
             boolean dup = false;
 
             for (QueryEntity entity : qryEntities) {
-                if (F.eq(entity.getValueType(), converted.getValueType())) {
+                if (F.eq(entity.findValueType(), converted.findValueType())) {
                     dup = true;
 
                     break;
@@ -1832,7 +1832,7 @@ public class CacheConfiguration<K, V> extends MutableConfiguration<K, V> {
             boolean found = false;
 
             for (QueryEntity existing : this.qryEntities) {
-                if (F.eq(entity.getValueType(), existing.getValueType())) {
+                if (F.eq(entity.findValueType(), existing.findValueType())) {
                     found = true;
 
                     break;
@@ -2032,10 +2032,10 @@ public class CacheConfiguration<K, V> extends MutableConfiguration<K, V> {
 
                 txtIdx.setIndexType(QueryIndexType.FULLTEXT);
 
-                txtIdx.setFieldNames(Arrays.asList(QueryUtils._VAL), true);
+                txtIdx.setFieldNames(Arrays.asList(QueryUtils.VAL_FIELD_NAME), true);
             }
             else
-                txtIdx.getFields().put(QueryUtils._VAL, true);
+                txtIdx.getFields().put(QueryUtils.VAL_FIELD_NAME, true);
         }
 
         if (txtIdx != null)
@@ -2089,12 +2089,12 @@ public class CacheConfiguration<K, V> extends MutableConfiguration<K, V> {
         @Nullable ClassProperty parent) {
         if (U.isJdk(cls) || QueryUtils.isGeometryClass(cls)) {
             if (parent == null && !key && QueryUtils.isSqlType(cls)) { // We have to index primitive _val.
-                String idxName = cls.getSimpleName() + "_" + QueryUtils._VAL + "_idx";
+                String idxName = cls.getSimpleName() + "_" + QueryUtils.VAL_FIELD_NAME + "_idx";
 
                 type.addIndex(idxName, QueryUtils.isGeometryClass(cls) ?
                     QueryIndexType.GEOSPATIAL : QueryIndexType.SORTED);
 
-                type.addFieldToIndex(idxName, QueryUtils._VAL, 0, false);
+                type.addFieldToIndex(idxName, QueryUtils.VAL_FIELD_NAME, 0, false);
             }
 
             return;

http://git-wip-us.apache.org/repos/asf/ignite/blob/efb7abce/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java
index 900e96f..0f7b0df 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java
@@ -2031,6 +2031,12 @@ public abstract class GridCacheQueryManager<K, V> extends GridCacheManagerAdapte
         /** */
         private static final long serialVersionUID = 0L;
 
+        /**
+         * Number of fields to report when no fields defined.
+         * Includes _key and _val columns.
+         */
+        private static final int NO_FIELDS_COLUMNS_COUNT = 2;
+
         /** Grid */
         @IgniteInstanceResource
         private Ignite ignite;
@@ -2073,13 +2079,15 @@ public abstract class GridCacheQueryManager<K, V> extends GridCacheManagerAdapte
                         keyClasses.put(type.name(), type.keyClass().getName());
                         valClasses.put(type.name(), type.valueClass().getName());
 
-                        int size = 2 + type.fields().size();
+                        int size = type.fields().isEmpty() ? NO_FIELDS_COLUMNS_COUNT : type.fields().size();
 
                         Map<String, String> fieldsMap = U.newLinkedHashMap(size);
 
                         // _KEY and _VAL are not included in GridIndexingTypeDescriptor.valueFields
-                        fieldsMap.put("_KEY", type.keyClass().getName());
-                        fieldsMap.put("_VAL", type.valueClass().getName());
+                        if (type.fields().isEmpty()) {
+                            fieldsMap.put("_KEY", type.keyClass().getName());
+                            fieldsMap.put("_VAL", type.valueClass().getName());
+                        }
 
                         for (Map.Entry<String, Class<?>> e : type.fields().entrySet())
                             fieldsMap.put(e.getKey().toUpperCase(), e.getValue().getName());

http://git-wip-us.apache.org/repos/asf/ignite/blob/efb7abce/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/utils/PlatformConfigurationUtils.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/utils/PlatformConfigurationUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/utils/PlatformConfigurationUtils.java
index eb3e716..9bbf5d6 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/utils/PlatformConfigurationUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/utils/PlatformConfigurationUtils.java
@@ -493,6 +493,9 @@ public class PlatformConfigurationUtils {
             res.setIndexes(indexes);
         }
 
+        res.setKeyFieldName(in.readString());
+        res.setValueFieldName(in.readString());
+
         return res;
     }
 
@@ -909,6 +912,9 @@ public class PlatformConfigurationUtils {
         }
         else
             writer.writeInt(0);
+
+        writer.writeString(queryEntity.getKeyFieldName());
+        writer.writeString(queryEntity.getValueFieldName());
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/ignite/blob/efb7abce/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 b7434d3..3c75ac4 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
@@ -133,4 +133,16 @@ public interface GridQueryTypeDescriptor {
      * @return BinaryObject's type ID if indexed value is BinaryObject, otherwise value class' hash code.
      */
     public int typeId();
+
+    /**
+     * Gets key field name.
+     * @return Key field name.
+     */
+    public String keyFieldName();
+
+    /**
+     * Gets value field name.
+     * @return value field name.
+     */
+    public String valueFieldName();
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/efb7abce/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryTypeDescriptorImpl.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryTypeDescriptorImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryTypeDescriptorImpl.java
index 119a389..56c6aa5 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryTypeDescriptorImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryTypeDescriptorImpl.java
@@ -90,6 +90,12 @@ public class QueryTypeDescriptorImpl implements GridQueryTypeDescriptor {
     /** */
     private String affKey;
 
+    /** */
+    private String keyFieldName;
+
+    /** */
+    private String valFieldName;
+
     /** Obsolete. */
     private volatile boolean obsolete;
 
@@ -265,7 +271,7 @@ public class QueryTypeDescriptorImpl implements GridQueryTypeDescriptor {
      * @return {@code True} if exists.
      */
     public boolean hasField(String field) {
-        return props.containsKey(field) || QueryUtils._VAL.equalsIgnoreCase(field);
+        return props.containsKey(field) || QueryUtils.VAL_FIELD_NAME.equalsIgnoreCase(field);
     }
 
     /**
@@ -415,4 +421,30 @@ public class QueryTypeDescriptorImpl implements GridQueryTypeDescriptor {
     @Override public String toString() {
         return S.toString(QueryTypeDescriptorImpl.class, this);
     }
+
+    /**
+     * Sets key field name.
+     * @param keyFieldName Key field name.
+     */
+    public void keyFieldName(String keyFieldName) {
+        this.keyFieldName = keyFieldName;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String keyFieldName() {
+        return keyFieldName;
+    }
+
+    /**
+     * Sets value field name.
+     * @param valueFieldName value field name.
+     */
+    public void valueFieldName(String valueFieldName) {
+        this.valFieldName = valueFieldName;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String valueFieldName() {
+        return valFieldName;
+    }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/efb7abce/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryUtils.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryUtils.java
index e56f39f..1a80a37 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryUtils.java
@@ -45,8 +45,10 @@ import java.lang.reflect.Method;
 import java.math.BigDecimal;
 import java.sql.Time;
 import java.sql.Timestamp;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -60,8 +62,15 @@ import static org.apache.ignite.IgniteSystemProperties.getInteger;
  * Utility methods for queries.
  */
 public class QueryUtils {
-    /** */
-    public static final String _VAL = "_val";
+
+    /** Field name for key. */
+    public static final String KEY_FIELD_NAME = "_key";
+
+    /** Field name for value. */
+    public static final String VAL_FIELD_NAME = "_val";
+
+    /** Version field name. */
+    public static final String VER_FIELD_NAME = "_ver";
 
     /** Discovery history size. */
     private static final int DISCO_HIST_SIZE = getInteger(IGNITE_INDEXING_DISCOVERY_HISTORY_SIZE, 1000);
@@ -98,7 +107,7 @@ public class QueryUtils {
         String res = entity.getTableName();
 
         if (res == null)
-            res = typeName(entity.getValueType());
+            res = typeName(entity.findValueType());
 
         return res;
     }
@@ -176,8 +185,8 @@ public class QueryUtils {
 
         // Key and value classes still can be available if they are primitive or JDK part.
         // We need that to set correct types for _key and _val columns.
-        Class<?> keyCls = U.classForName(qryEntity.getKeyType(), null);
-        Class<?> valCls = U.classForName(qryEntity.getValueType(), null);
+        Class<?> keyCls = U.classForName(qryEntity.findKeyType(), null);
+        Class<?> valCls = U.classForName(qryEntity.findValueType(), null);
 
         // If local node has the classes and they are externalizable, we must use reflection properties.
         boolean keyMustDeserialize = mustDeserializeBinary(ctx, keyCls);
@@ -188,7 +197,7 @@ public class QueryUtils {
         if (keyCls == null)
             keyCls = Object.class;
 
-        String simpleValType = ((valCls == null) ? typeName(qryEntity.getValueType()) : typeName(valCls));
+        String simpleValType = ((valCls == null) ? typeName(qryEntity.findValueType()) : typeName(valCls));
 
         desc.name(simpleValType);
 
@@ -209,14 +218,17 @@ public class QueryUtils {
         else {
             if (valCls == null)
                 throw new IgniteCheckedException("Failed to find value class in the node classpath " +
-                    "(use default marshaller to enable binary objects) : " + qryEntity.getValueType());
+                    "(use default marshaller to enable binary objects) : " + qryEntity.findValueType());
 
             desc.valueClass(valCls);
             desc.keyClass(keyCls);
         }
 
-        desc.keyTypeName(qryEntity.getKeyType());
-        desc.valueTypeName(qryEntity.getValueType());
+        desc.keyTypeName(qryEntity.findKeyType());
+        desc.valueTypeName(qryEntity.findValueType());
+
+        desc.keyFieldName(qryEntity.getKeyFieldName());
+        desc.valueFieldName(qryEntity.getValueFieldName());
 
         if (binaryEnabled && keyOrValMustDeserialize) {
             if (keyMustDeserialize)
@@ -232,14 +244,14 @@ public class QueryUtils {
         if (valCls == null || (binaryEnabled && !keyOrValMustDeserialize)) {
             processBinaryMeta(ctx, qryEntity, desc);
 
-            typeId = new QueryTypeIdKey(space, ctx.cacheObjects().typeId(qryEntity.getValueType()));
+            typeId = new QueryTypeIdKey(space, ctx.cacheObjects().typeId(qryEntity.findValueType()));
 
             if (valCls != null)
                 altTypeId = new QueryTypeIdKey(space, valCls);
 
-            if (!cctx.customAffinityMapper() && qryEntity.getKeyType() != null) {
+            if (!cctx.customAffinityMapper() && qryEntity.findKeyType() != null) {
                 // Need to setup affinity key for distributed joins.
-                String affField = ctx.cacheObjects().affinityField(qryEntity.getKeyType());
+                String affField = ctx.cacheObjects().affinityField(qryEntity.findKeyType());
 
                 if (affField != null)
                     desc.affinityKey(affField);
@@ -259,7 +271,7 @@ public class QueryUtils {
             }
 
             typeId = new QueryTypeIdKey(space, valCls);
-            altTypeId = new QueryTypeIdKey(space, ctx.cacheObjects().typeId(qryEntity.getValueType()));
+            altTypeId = new QueryTypeIdKey(space, ctx.cacheObjects().typeId(qryEntity.findValueType()));
         }
 
         return new QueryTypeCandidate(typeId, altTypeId, desc);
@@ -310,7 +322,7 @@ public class QueryUtils {
 
         processIndexes(qryEntity, d);
     }
-    
+
     /**
      * Processes declarative metadata for binary object.
      *
@@ -321,9 +333,11 @@ public class QueryUtils {
     public static void processClassMeta(QueryEntity qryEntity, QueryTypeDescriptorImpl d, CacheObjectContext coCtx)
         throws IgniteCheckedException {
         for (Map.Entry<String, String> entry : qryEntity.getFields().entrySet()) {
-            QueryClassProperty prop = buildClassProperty(
+            GridQueryProperty prop = buildProperty(
                 d.keyClass(),
                 d.valueClass(),
+                d.keyFieldName(),
+                d.valueFieldName(),
                 entry.getKey(),
                 U.classForName(entry.getValue(), Object.class),
                 d.aliases(),
@@ -429,7 +443,7 @@ public class QueryUtils {
         else
             throw new IllegalArgumentException("Index type is not set: " + idx.getName());
     }
-    
+
     /**
      * Builds binary object property.
      *
@@ -443,7 +457,7 @@ public class QueryUtils {
      * @return Binary property.
      */
     public static QueryBinaryProperty buildBinaryProperty(GridKernalContext ctx, String pathStr, Class<?> resType,
-        Map<String, String> aliases, @Nullable Boolean isKeyField) throws IgniteCheckedException {
+                                     Map<String, String> aliases, @Nullable Boolean isKeyField) throws IgniteCheckedException {
         String[] path = pathStr.split("\\.");
 
         QueryBinaryProperty res = null;
@@ -464,7 +478,7 @@ public class QueryUtils {
 
         return res;
     }
-    
+
     /**
      * @param keyCls Key class.
      * @param valCls Value class.
@@ -494,6 +508,33 @@ public class QueryUtils {
     }
 
     /**
+     * @param keyCls Key class.
+     * @param valCls Value class.
+     * @param keyFieldName Key Field.
+     * @param valueFieldName Value Field.
+     * @param pathStr Path string.
+     * @param resType Result type.
+     * @param aliases Aliases.
+     * @return Class property.
+     * @throws IgniteCheckedException If failed.
+     */
+    public static GridQueryProperty buildProperty(Class<?> keyCls, Class<?> valCls, String keyFieldName, String valueFieldName, String pathStr,
+                                                  Class<?> resType, Map<String,String> aliases, CacheObjectContext coCtx) throws IgniteCheckedException {
+        if (pathStr.equals(keyFieldName))
+            return new KeyOrValProperty(true, pathStr, keyCls);
+
+        if (pathStr.equals(valueFieldName))
+            return new KeyOrValProperty(false, pathStr, valCls);
+
+        return buildClassProperty(keyCls,
+                valCls,
+                pathStr,
+                resType,
+                aliases,
+                coCtx);
+    }
+
+    /**
      * Exception message to compare in tests.
      *
      * @param keyCls key class
@@ -508,7 +549,7 @@ public class QueryUtils {
             resType.getName() + "' for key class '" + keyCls + "' and value class '" + valCls + "'. " +
             "Make sure that one of these classes contains respective getter method or field.";
     }
-    
+
     /**
      * @param key If this is a key property.
      * @param cls Source type class.
@@ -553,7 +594,7 @@ public class QueryUtils {
 
         return res;
     }
-    
+
     /**
      * Find a member (either a getter method or a field) with given name of given class.
      * @param prop Property name.
@@ -807,7 +848,7 @@ public class QueryUtils {
 
         if (!F.isEmpty(entities)) {
             for (QueryEntity entity : entities) {
-                if (F.isEmpty(entity.getValueType()))
+                if (F.isEmpty(entity.findValueType()))
                     continue;
 
                 Collection<QueryIndex> idxs = entity.getIndexes();
@@ -839,7 +880,7 @@ public class QueryUtils {
 
         if (!F.isEmpty(entities)) {
             for (QueryEntity entity : entities) {
-                if (F.isEmpty(entity.getValueType()))
+                if (F.isEmpty(entity.findValueType()))
                     throw new IgniteCheckedException("Value type cannot be null or empty [cacheName=" +
                         ccfg.getName() + ", queryEntity=" + entity + ']');
 
@@ -875,4 +916,53 @@ public class QueryUtils {
     private QueryUtils() {
         // No-op.
     }
+
+    /** Property used for keyFieldName or valueFieldName */
+    public static class KeyOrValProperty implements GridQueryProperty {
+        /** */
+        boolean isKey;
+
+        /** */
+        String name;
+
+        /** */
+        Class<?> cls;
+
+        /** */
+        public KeyOrValProperty(boolean key, String name, Class<?> cls) {
+            this.isKey = key;
+            this.name = name;
+            this.cls = cls;
+        }
+
+        /** {@inheritDoc} */
+        @Override public Object value(Object key, Object val) throws IgniteCheckedException {
+            return isKey ? key : val;
+        }
+
+        /** {@inheritDoc} */
+        @Override public void setValue(Object key, Object val, Object propVal) throws IgniteCheckedException {
+            //No-op
+        }
+
+        /** {@inheritDoc} */
+        @Override public String name() {
+            return name;
+        }
+
+        /** {@inheritDoc} */
+        @Override public Class<?> type() {
+            return cls;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean key() {
+            return isKey;
+        }
+
+        /** {@inheritDoc} */
+        @Override public GridQueryProperty parent() {
+            return null;
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/efb7abce/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 e8dc73b..4194bc7 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
@@ -86,6 +86,7 @@ import org.jetbrains.annotations.Nullable;
 
 import static org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode.createJdbcSqlException;
 import static org.apache.ignite.internal.processors.query.h2.IgniteH2Indexing.UPDATE_RESULT_META;
+import static org.apache.ignite.internal.processors.query.h2.opt.GridH2AbstractKeyValueRow.DEFAULT_COLUMNS_COUNT;
 
 /**
  *
@@ -587,9 +588,12 @@ public class DmlStatementsProcessor {
             if (newVal == null)
                 throw new IgniteSQLException("New value for UPDATE must not be null", IgniteQueryErrorCode.NULL_VALUE);
 
-            // Skip key and value - that's why we start off with 2nd column
-            for (int i = 0; i < plan.tbl.getColumns().length - 2; i++) {
-                Column c = plan.tbl.getColumn(i + 2);
+            // Skip key and value - that's why we start off with 3rd column
+            for (int i = 0; i < plan.tbl.getColumns().length - DEFAULT_COLUMNS_COUNT; i++) {
+                Column c = plan.tbl.getColumn(i + DEFAULT_COLUMNS_COUNT);
+
+                if (desc.isKeyValueOrVersionColumn(c.getColumnId()))
+                    continue;
 
                 GridQueryProperty prop = desc.type().property(c.getName());
 
@@ -965,8 +969,11 @@ public class DmlStatementsProcessor {
         // column order preserves their precedence for correct update of nested properties.
         Column[] cols = plan.tbl.getColumns();
 
-        // First 2 columns are _key and _val, skip 'em.
-        for (int i = 2; i < cols.length; i++) {
+        // First 3 columns are _key, _val and _ver. Skip 'em.
+        for (int i = DEFAULT_COLUMNS_COUNT; i < cols.length; i++) {
+            if (plan.tbl.rowDescriptor().isKeyValueOrVersionColumn(i))
+                continue;
+
             String colName = cols[i].getName();
 
             if (!newColVals.containsKey(colName))

http://git-wip-us.apache.org/repos/asf/ignite/blob/efb7abce/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 361b55b..83fb1f2 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
@@ -115,6 +115,7 @@ import org.apache.ignite.internal.processors.query.h2.opt.GridH2IndexBase;
 import org.apache.ignite.internal.processors.query.h2.opt.GridH2SystemIndexFactory;
 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.GridH2ProxyIndex;
 import org.apache.ignite.internal.processors.query.h2.opt.GridH2QueryContext;
 import org.apache.ignite.internal.processors.query.h2.opt.GridH2Row;
 import org.apache.ignite.internal.processors.query.h2.opt.GridH2RowDescriptor;
@@ -165,11 +166,14 @@ import org.h2.engine.Session;
 import org.h2.engine.SysProperties;
 import org.h2.index.Cursor;
 import org.h2.index.Index;
+import org.h2.index.SpatialIndex;
 import org.h2.jdbc.JdbcConnection;
 import org.h2.jdbc.JdbcPreparedStatement;
 import org.h2.jdbc.JdbcStatement;
 import org.h2.message.DbException;
 import org.h2.mvstore.cache.CacheLongKeyLIRS;
+import org.h2.result.SearchRow;
+import org.h2.result.SimpleRow;
 import org.h2.result.SortOrder;
 import org.h2.server.web.WebServer;
 import org.h2.table.Column;
@@ -209,9 +213,15 @@ import static org.apache.ignite.IgniteSystemProperties.getString;
 import static org.apache.ignite.internal.processors.cache.query.GridCacheQueryType.SQL;
 import static org.apache.ignite.internal.processors.cache.query.GridCacheQueryType.SQL_FIELDS;
 import static org.apache.ignite.internal.processors.cache.query.GridCacheQueryType.TEXT;
+import static org.apache.ignite.internal.processors.query.QueryUtils.KEY_FIELD_NAME;
+import static org.apache.ignite.internal.processors.query.QueryUtils.VAL_FIELD_NAME;
+import static org.apache.ignite.internal.processors.query.QueryUtils.VER_FIELD_NAME;
 import static org.apache.ignite.internal.processors.query.h2.opt.DistributedJoinMode.OFF;
 import static org.apache.ignite.internal.processors.query.h2.opt.DistributedJoinMode.distributedJoinMode;
+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;
 import static org.apache.ignite.internal.processors.query.h2.opt.GridH2QueryType.LOCAL;
 import static org.apache.ignite.internal.processors.query.h2.opt.GridH2QueryType.PREPARE;
 
@@ -262,12 +272,6 @@ public class IgniteH2Indexing implements GridQueryIndexing {
     /** */
     private static final int TWO_STEP_QRY_CACHE_SIZE = 1024;
 
-    /** Field name for key. */
-    public static final String KEY_FIELD_NAME = "_KEY";
-
-    /** Field name for value. */
-    public static final String VAL_FIELD_NAME = "_VAL";
-
     /** */
     private static final Field COMMAND_FIELD;
 
@@ -1886,9 +1890,23 @@ public class IgniteH2Indexing implements GridQueryIndexing {
         String ptrn = "Name ''{0}'' is reserved and cannot be used as a field name [type=" + type.name() + "]";
 
         for (String name : names) {
-            if (name.equalsIgnoreCase(KEY_FIELD_NAME) || name.equalsIgnoreCase(VAL_FIELD_NAME))
+            if (name.equalsIgnoreCase(KEY_FIELD_NAME) ||
+                name.equalsIgnoreCase(VAL_FIELD_NAME) ||
+                name.equalsIgnoreCase(VER_FIELD_NAME))
                 throw new IgniteCheckedException(MessageFormat.format(ptrn, name));
         }
+
+        if (type.keyFieldName() != null && !type.fields().containsKey(type.keyFieldName())) {
+            throw new IgniteCheckedException(
+                    MessageFormat.format("Name ''{0}'' must be amongst fields since it is configured as ''keyFieldName'' [type=" +
+                            type.name() + "]", type.keyFieldName()));
+        }
+
+        if (type.valueFieldName() != null && !type.fields().containsKey(type.valueFieldName())) {
+            throw new IgniteCheckedException(
+                    MessageFormat.format("Name ''{0}'' must be amongst fields since it is configured as ''valueFieldName'' [type=" +
+                            type.name() + "]", type.valueFieldName()));
+        }
     }
 
     /**
@@ -1965,10 +1983,13 @@ public class IgniteH2Indexing implements GridQueryIndexing {
 
         SB sql = new SB();
 
+        String keyValVisibility = tbl.type().fields().isEmpty() ? " VISIBLE" : " INVISIBLE";
+
         sql.a("CREATE TABLE ").a(tbl.fullTableName()).a(" (")
-            .a(KEY_FIELD_NAME).a(' ').a(keyType).a(" NOT NULL");
+            .a(KEY_FIELD_NAME).a(' ').a(keyType).a(keyValVisibility).a(" NOT NULL");
 
-        sql.a(',').a(VAL_FIELD_NAME).a(' ').a(valTypeStr);
+        sql.a(',').a(VAL_FIELD_NAME).a(' ').a(valTypeStr).a(keyValVisibility);
+        sql.a(',').a(VER_FIELD_NAME).a(" OTHER INVISIBLE");
 
         for (Map.Entry<String, Class<?>> e : tbl.type().fields().entrySet())
             sql.a(',').a(escapeName(e.getKey(), escapeAll)).a(' ').a(dbTypeFromClass(e.getValue()));
@@ -2758,15 +2779,32 @@ public class IgniteH2Indexing implements GridQueryIndexing {
     }
 
     /**
+     * Check whether columns list contains key or key alias column.
+     *
+     * @param desc Row descriptor.
+     * @param cols Columns list.
+     * @return Result.
+     */
+    private static boolean containsKeyColumn(GridH2RowDescriptor desc, List<IndexColumn> cols) {
+        for (int i = cols.size() - 1; i >= 0; i--) {
+            if (desc.isKeyColumn(cols.get(i).column.getColumnId()))
+                return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * @param desc Row descriptor.
      * @param cols Columns list.
      * @param keyCol Primary key column.
      * @param affCol Affinity key column.
      * @return The same list back.
      */
-    private static List<IndexColumn> treeIndexColumns(List<IndexColumn> cols, IndexColumn keyCol, IndexColumn affCol) {
+    private static List<IndexColumn> treeIndexColumns(GridH2RowDescriptor desc, List<IndexColumn> cols, IndexColumn keyCol, IndexColumn affCol) {
         assert keyCol != null;
 
-        if (!containsColumn(cols, keyCol))
+        if (!containsKeyColumn(desc, cols))
             cols.add(keyCol);
 
         if (affCol != null && !containsColumn(cols, affCol))
@@ -3077,11 +3115,13 @@ public class IgniteH2Indexing implements GridQueryIndexing {
             if (affCol != null && equal(affCol, keyCol))
                 affCol = null;
 
+            GridH2RowDescriptor desc = tbl.rowDescriptor();
+
             Index hashIdx = createHashIndex(
                 schema,
                 tbl,
                 "_key_PK_hash",
-                treeIndexColumns(new ArrayList<IndexColumn>(2), keyCol, affCol)
+                treeIndexColumns(desc, new ArrayList<IndexColumn>(2), keyCol, affCol)
             );
 
             if (hashIdx != null)
@@ -3093,7 +3133,7 @@ public class IgniteH2Indexing implements GridQueryIndexing {
                 "_key_PK",
                 tbl,
                 true,
-                treeIndexColumns(new ArrayList<IndexColumn>(2), keyCol, affCol),
+                treeIndexColumns(desc, new ArrayList<IndexColumn>(2), keyCol, affCol),
                 -1
             );
 
@@ -3144,7 +3184,7 @@ public class IgniteH2Indexing implements GridQueryIndexing {
             // Add explicit affinity key index if nothing alike was found.
             if (affCol != null && !affIdxFound) {
                 idxs.add(createSortedIndex(schema, "AFFINITY_KEY", tbl, false,
-                    treeIndexColumns(new ArrayList<IndexColumn>(2), affCol, keyCol), -1));
+                    treeIndexColumns(desc, new ArrayList<IndexColumn>(2), affCol, keyCol), -1));
             }
 
             return idxs;
@@ -3194,13 +3234,14 @@ public class IgniteH2Indexing implements GridQueryIndexing {
                     idxDesc.descending(field) ? SortOrder.DESCENDING : SortOrder.ASCENDING));
             }
 
+            GridH2RowDescriptor desc = tbl.rowDescriptor();
             if (idxDesc.type() == QueryIndexType.SORTED) {
-                cols = treeIndexColumns(cols, keyCol, affCol);
-
+                cols = treeIndexColumns(desc, cols, keyCol, affCol);
                 return createSortedIndex(schema, name, tbl, false, cols, idxDesc.inlineSize());
             }
-            else if (idxDesc.type() == QueryIndexType.GEOSPATIAL)
+            else if (idxDesc.type() == QueryIndexType.GEOSPATIAL) {
                 return createSpatialIndex(tbl, name, cols.toArray(new IndexColumn[cols.size()]));
+            }
 
             throw new IllegalStateException("Index type: " + idxDesc.type());
         }
@@ -3483,6 +3524,12 @@ public class IgniteH2Indexing implements GridQueryIndexing {
         /** */
         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.
@@ -3522,6 +3569,10 @@ public class IgniteH2Indexing implements GridQueryIndexing {
                 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;
         }
@@ -3649,8 +3700,8 @@ public class IgniteH2Indexing implements GridQueryIndexing {
                     row = GridH2RowFactory.create(wrap(key, keyType));
                 else
                     row = schema.offheap == null ?
-                        new GridH2KeyValueRowOnheap(this, key, keyType, val, valType, expirationTime) :
-                        new GridH2KeyValueRowOffheap(this, key, keyType, val, valType, expirationTime);
+                        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. " +
@@ -3733,6 +3784,108 @@ public class IgniteH2Indexing implements GridQueryIndexing {
         @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} */
+        @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/efb7abce/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdatePlanBuilder.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdatePlanBuilder.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdatePlanBuilder.java
index 77946dd..22c3e33 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdatePlanBuilder.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdatePlanBuilder.java
@@ -31,7 +31,6 @@ import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
 import org.apache.ignite.internal.processors.query.IgniteSQLException;
 import org.apache.ignite.internal.processors.query.QueryUtils;
 import org.apache.ignite.internal.processors.query.h2.DmlStatementsProcessor;
-import org.apache.ignite.internal.processors.query.h2.IgniteH2Indexing;
 import org.apache.ignite.internal.processors.query.h2.opt.GridH2RowDescriptor;
 import org.apache.ignite.internal.processors.query.h2.opt.GridH2Table;
 import org.apache.ignite.internal.processors.query.h2.sql.DmlAstUtils;
@@ -54,8 +53,7 @@ import org.h2.command.Prepared;
 import org.h2.table.Column;
 import org.jetbrains.annotations.Nullable;
 
-import static org.apache.ignite.internal.processors.query.h2.IgniteH2Indexing.KEY_FIELD_NAME;
-import static org.apache.ignite.internal.processors.query.h2.IgniteH2Indexing.VAL_FIELD_NAME;
+import static org.apache.ignite.internal.processors.query.h2.opt.GridH2AbstractKeyValueRow.DEFAULT_COLUMNS_COUNT;
 
 /**
  * Logic for building update plans performed by {@link DmlStatementsProcessor}.
@@ -133,7 +131,7 @@ public final class UpdatePlanBuilder {
             // not for updates, and hence will allow putting new pairs only.
             // We don't quote _key and _val column names on CREATE TABLE, so they are always uppercase here.
             GridSqlColumn[] keys = merge.keys();
-            if (keys.length != 1 || !IgniteH2Indexing.KEY_FIELD_NAME.equals(keys[0].columnName()))
+            if (keys.length != 1 || !desc.isKeyColumn(tbl.dataTable().getColumn(keys[0].columnName()).getColumnId()))
                 throw new CacheException("SQL MERGE does not support arbitrary keys");
 
             cols = merge.columns();
@@ -173,12 +171,13 @@ public final class UpdatePlanBuilder {
 
             colTypes[i] = col.resultType().type();
 
-            if (KEY_FIELD_NAME.equals(colName)) {
+            int colId = col.column().getColumnId();
+            if (desc.isKeyColumn(colId)) {
                 keyColIdx = i;
                 continue;
             }
 
-            if (VAL_FIELD_NAME.equals(colName)) {
+            if (desc.isValueColumn(colId)) {
                 valColIdx = i;
                 continue;
             }
@@ -267,7 +266,8 @@ public final class UpdatePlanBuilder {
 
                     colTypes[i] = updatedCols.get(i).resultType().type();
 
-                    if (VAL_FIELD_NAME.equals(colNames[i]))
+                    Column column = updatedCols.get(i).column();
+                    if (desc.isValueColumn(column.getColumnId()))
                         valColIdx = i;
                 }
 
@@ -485,17 +485,19 @@ public final class UpdatePlanBuilder {
     private static boolean updateAffectsKeyColumns(GridH2Table gridTbl, Set<String> affectedColNames) {
         GridH2RowDescriptor desc = gridTbl.rowDescriptor();
 
-        Column[] cols = gridTbl.getColumns();
+        for (String colName : affectedColNames) {
+            int colId = gridTbl.getColumn(colName).getColumnId();
 
-        // Check "_key" column itself - always has index of 0.
-        if (affectedColNames.contains(cols[0].getName()))
-            return true;
-
-        // Start off from i = 2 to skip indices of 0 an 1 corresponding to key and value respectively.
-        for (int i = 2; i < cols.length; i++)
-            if (affectedColNames.contains(cols[i].getName()) && desc.isColumnKeyProperty(i - 2))
+            // Check "_key" column and alias key column
+            if (desc.isKeyColumn(colId))
                 return true;
 
+            // column ids 0..2 are _key, _val, _ver
+            if (colId >= DEFAULT_COLUMNS_COUNT) {
+                if (desc.isColumnKeyProperty(colId - DEFAULT_COLUMNS_COUNT))
+                    return true;
+            }
+        }
         return false;
     }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/efb7abce/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2AbstractKeyValueRow.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2AbstractKeyValueRow.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2AbstractKeyValueRow.java
index 28dd891..499d258 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2AbstractKeyValueRow.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2AbstractKeyValueRow.java
@@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteException;
 import org.apache.ignite.IgniteInterruptedException;
+import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
 import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
 import org.apache.ignite.internal.util.typedef.internal.SB;
 import org.apache.ignite.internal.util.typedef.internal.U;
@@ -40,7 +41,7 @@ import org.jetbrains.annotations.Nullable;
  */
 public abstract class GridH2AbstractKeyValueRow extends GridH2Row {
     /** */
-    public static final int DEFAULT_COLUMNS_COUNT = 2;
+    public static final int DEFAULT_COLUMNS_COUNT = 3;
 
     /** Key column. */
     public static final int KEY_COL = 0;
@@ -48,6 +49,9 @@ public abstract class GridH2AbstractKeyValueRow extends GridH2Row {
     /** Value column. */
     public static final int VAL_COL = 1;
 
+    /** Version column. */
+    public static final int VER_COL = 2;
+
     /** */
     protected final GridH2RowDescriptor desc;
 
@@ -64,6 +68,9 @@ public abstract class GridH2AbstractKeyValueRow extends GridH2Row {
     /** */
     private Value[] valCache;
 
+    /** */
+    private Value version;
+
     /**
      * Constructor.
      *
@@ -76,14 +83,17 @@ public abstract class GridH2AbstractKeyValueRow extends GridH2Row {
      * @throws IgniteCheckedException If failed.
      */
     protected GridH2AbstractKeyValueRow(GridH2RowDescriptor desc, Object key, int keyType, @Nullable Object val,
-        int valType, long expirationTime) throws IgniteCheckedException {
+                                        int valType, GridCacheVersion ver, long expirationTime) throws IgniteCheckedException {
+        this.desc = desc;
+        this.expirationTime = expirationTime;
+
         setValue(KEY_COL, desc.wrap(key, keyType));
 
         if (val != null) // We remove by key only, so value can be null here.
             setValue(VAL_COL, desc.wrap(val, valType));
 
-        this.desc = desc;
-        this.expirationTime = expirationTime;
+        if (ver != null)
+            setValue(VER_COL, desc.wrap(ver, Value.JAVA_OBJECT));
     }
 
     /** {@inheritDoc} */
@@ -165,7 +175,13 @@ public abstract class GridH2AbstractKeyValueRow extends GridH2Row {
      * @return Value if exists.
      */
     protected final Value peekValue(int col) {
-        return col == KEY_COL ? key : val;
+        if (col == KEY_COL)
+            return key;
+        if (col == VAL_COL)
+            return val;
+
+        assert col == VER_COL;
+        return version;
     }
 
     /** {@inheritDoc} */
@@ -179,32 +195,33 @@ public abstract class GridH2AbstractKeyValueRow extends GridH2Row {
                 return v;
         }
 
-        if (col < DEFAULT_COLUMNS_COUNT) {
-            Value v;
+        Value v;
 
-            if (col == VAL_COL)
-                v = peekValue(VAL_COL);
-            else {
-                assert col == KEY_COL : col;
+        if (desc.isValueColumn(col)) {
+            v = peekValue(VAL_COL);
 
-                v = peekValue(KEY_COL);
+            assert !(v instanceof WeakValue) : v;
+            return v;
+        }
+        else if (desc.isKeyColumn(col)) {
+            v = peekValue(KEY_COL);
 
-                if (v == null) {
-                    v = getOffheapValue(KEY_COL);
+            if (v == null) {
+                v = getOffheapValue(KEY_COL);
 
-                    assert v != null;
+                assert v != null;
 
-                    setValue(KEY_COL, v);
+                setValue(KEY_COL, v);
 
-                    if (peekValue(VAL_COL) == null)
-                        cache();
-                }
+                if (peekValue(VAL_COL) == null)
+                    cache();
             }
 
             assert !(v instanceof WeakValue) : v;
-
             return v;
         }
+        else if (col == VER_COL)
+            return version;
 
         col -= DEFAULT_COLUMNS_COUNT;
 
@@ -218,8 +235,6 @@ public abstract class GridH2AbstractKeyValueRow extends GridH2Row {
 
         Object res = desc.columnValue(key.getObject(), val.getObject(), col);
 
-        Value v;
-
         if (res == null)
             v = ValueNull.INSTANCE;
         else {
@@ -242,8 +257,7 @@ public abstract class GridH2AbstractKeyValueRow extends GridH2Row {
      */
     public void valuesCache(Value[] valCache) {
         if (valCache != null) {
-            valCache[KEY_COL] = key;
-            valCache[VAL_COL] = val;
+            desc.initValueCache(valCache, key, val, version);
         }
 
         this.valCache = valCache;
@@ -281,16 +295,20 @@ public abstract class GridH2AbstractKeyValueRow extends GridH2Row {
         v = WeakValue.unwrap(peekValue(VAL_COL));
         sb.a(", val: ").a(v == null ? "nil" : v.getString());
 
+        v = peekValue(VER_COL);
+        sb.a(", ver: ").a(v == null ? "nil" : v.getString());
+
         sb.a(" ][ ");
 
         if (v != null) {
-            for (int i = 2, cnt = getColumnCount(); i < cnt; i++) {
+            for (int i = DEFAULT_COLUMNS_COUNT, cnt = getColumnCount(); i < cnt; i++) {
                 v = getValue(i);
 
-                if (i != 2)
+                if (i != DEFAULT_COLUMNS_COUNT)
                     sb.a(", ");
 
-                sb.a(v == null ? "nil" : v.getString());
+                if (!desc.isKeyValueOrVersionColumn(i))
+                    sb.a(v == null ? "nil" : v.getString());
             }
         }
 
@@ -418,10 +436,12 @@ public abstract class GridH2AbstractKeyValueRow extends GridH2Row {
 
     /** {@inheritDoc} */
     @Override public void setValue(int idx, Value v) {
-        if (idx == VAL_COL)
+        if (desc.isValueColumn(idx))
             val = v;
+        else if (idx == VER_COL)
+            version = v;
         else {
-            assert idx == KEY_COL : idx + " " + v;
+            assert desc.isKeyColumn(idx) : idx + " " + v;
 
             key = v;
         }

http://git-wip-us.apache.org/repos/asf/ignite/blob/efb7abce/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2CollocationModel.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2CollocationModel.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2CollocationModel.java
index 4df355e..037607b 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2CollocationModel.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2CollocationModel.java
@@ -39,8 +39,6 @@ import org.h2.table.Table;
 import org.h2.table.TableFilter;
 import org.h2.table.TableView;
 
-import static org.apache.ignite.internal.processors.query.h2.opt.GridH2AbstractKeyValueRow.KEY_COL;
-
 /**
  * Collocation model for a query.
  */
@@ -413,7 +411,7 @@ public final class GridH2CollocationModel {
                 int cmpType = c.getCompareType();
 
                 if ((cmpType == Comparison.EQUAL || cmpType == Comparison.EQUAL_NULL_SAFE) &&
-                    (colId == affColId || colId == KEY_COL) && c.isEvaluatable()) {
+                    (colId == affColId || tbl.rowDescriptor().isKeyColumn(colId)) && c.isEvaluatable()) {
                     affKeyCondFound = true;
 
                     Expression exp = c.getExpression();

http://git-wip-us.apache.org/repos/asf/ignite/blob/efb7abce/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2IndexBase.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2IndexBase.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2IndexBase.java
index 81a9620..623da09 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2IndexBase.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2IndexBase.java
@@ -397,6 +397,7 @@ public abstract class GridH2IndexBase extends BaseIndex {
             return null;
 
         IndexColumn affCol = getTable().getAffinityKeyColumn();
+        GridH2RowDescriptor desc = getTable().rowDescriptor();
 
         int affColId = -1;
         boolean ucast = false;
@@ -407,7 +408,7 @@ public abstract class GridH2IndexBase extends BaseIndex {
 
             if (masks != null) {
                 ucast = (masks[affColId] & IndexCondition.EQUALITY) != 0 ||
-                    (masks[KEY_COL] & IndexCondition.EQUALITY) != 0;
+                        desc.checkKeyIndexCondition(masks, IndexCondition.EQUALITY);
             }
         }
 
@@ -416,6 +417,16 @@ public abstract class GridH2IndexBase extends BaseIndex {
         return new DistributedLookupBatch(cctx, ucast, affColId);
     }
 
+    /** {@inheritDoc} */
+    @Override public void removeChildrenAndResources(Session session) {
+        // The sole purpose of this override is to pass session to table.removeIndex
+        assert table instanceof GridH2Table;
+
+        ((GridH2Table)table).removeIndex(session, this);
+        remove(session);
+        database.removeMeta(session, getId());
+    }
+
     /**
      * @param nodes Nodes.
      * @param msg Message.
@@ -1130,7 +1141,7 @@ public abstract class GridH2IndexBase extends BaseIndex {
             if (affKeyFirst != null && equal(affKeyFirst, affKeyLast))
                 return affKeyFirst == ValueNull.INSTANCE ? EXPLICIT_NULL : affKeyFirst.getObject();
 
-            if (affColId == KEY_COL)
+            if (getTable().rowDescriptor().isKeyColumn(affColId))
                 return null;
 
             // Try to extract affinity key from primary key.

http://git-wip-us.apache.org/repos/asf/ignite/blob/efb7abce/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2KeyValueRowOffheap.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2KeyValueRowOffheap.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2KeyValueRowOffheap.java
index fd310ce..3d92777 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2KeyValueRowOffheap.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2KeyValueRowOffheap.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.processors.query.h2.opt;
 
 import java.util.concurrent.locks.Lock;
 import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
 import org.apache.ignite.internal.util.GridStripedLock;
 import org.apache.ignite.internal.util.offheap.unsafe.GridUnsafeMemory;
 import org.apache.ignite.internal.util.typedef.internal.SB;
@@ -84,12 +85,13 @@ public class GridH2KeyValueRowOffheap extends GridH2AbstractKeyValueRow {
      * @param keyType Key type.
      * @param val Value.
      * @param valType Value type.
+     * @param ver Version.
      * @param expirationTime Expiration time.
      * @throws IgniteCheckedException If failed.
      */
     public GridH2KeyValueRowOffheap(GridH2RowDescriptor desc, Object key, int keyType, @Nullable Object val, int valType,
-        long expirationTime) throws IgniteCheckedException {
-        super(desc, key, keyType, val, valType, expirationTime);
+                                    GridCacheVersion ver, long expirationTime) throws IgniteCheckedException {
+        super(desc, key, keyType, val, valType, ver, expirationTime);
     }
 
     /** {@inheritDoc} */

http://git-wip-us.apache.org/repos/asf/ignite/blob/efb7abce/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2KeyValueRowOnheap.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2KeyValueRowOnheap.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2KeyValueRowOnheap.java
index 772302f..4975092 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2KeyValueRowOnheap.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2KeyValueRowOnheap.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.processors.query.h2.opt;
 
 
 import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
 import org.h2.value.Value;
 import org.jetbrains.annotations.Nullable;
 
@@ -34,12 +35,13 @@ public class GridH2KeyValueRowOnheap extends GridH2AbstractKeyValueRow {
      * @param keyType Key type.
      * @param val Value.
      * @param valType Value type.
+     * @param ver Version.
      * @param expirationTime Expiration time.
      * @throws IgniteCheckedException If failed.
      */
     public GridH2KeyValueRowOnheap(GridH2RowDescriptor desc, Object key, int keyType, @Nullable Object val, int valType,
-        long expirationTime) throws IgniteCheckedException {
-        super(desc, key, keyType, val, valType, expirationTime);
+                                   GridCacheVersion ver, long expirationTime) throws IgniteCheckedException {
+        super(desc, key, keyType, val, valType, ver, expirationTime);
     }
 
     /** {@inheritDoc} */

http://git-wip-us.apache.org/repos/asf/ignite/blob/efb7abce/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2ProxyIndex.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2ProxyIndex.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2ProxyIndex.java
new file mode 100644
index 0000000..7c70ded
--- /dev/null
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2ProxyIndex.java
@@ -0,0 +1,204 @@
+/*
+ * 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.opt;
+
+import org.h2.engine.Session;
+import org.h2.index.BaseIndex;
+import org.h2.index.Cursor;
+import org.h2.index.Index;
+import org.h2.index.IndexLookupBatch;
+import org.h2.index.IndexType;
+import org.h2.index.SpatialIndex;
+import org.h2.message.DbException;
+import org.h2.result.Row;
+import org.h2.result.SearchRow;
+import org.h2.result.SortOrder;
+import org.h2.table.Column;
+import org.h2.table.IndexColumn;
+import org.h2.table.TableFilter;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.Future;
+
+/**
+ * Allows to have 'free' index for alias columns
+ * Delegates the calls to underlying normal index
+ */
+public class GridH2ProxyIndex extends BaseIndex {
+
+    /** Underlying normal index */
+    protected Index idx;
+
+    /**
+     *
+     * @param tbl Table.
+     * @param name Name of the proxy index.
+     * @param colsList Column list for the proxy index.
+     * @param idx Target index.
+     */
+    public GridH2ProxyIndex(GridH2Table tbl,
+                            String name,
+                            List<IndexColumn> colsList,
+                            Index idx) {
+
+        IndexColumn[] cols = colsList.toArray(new IndexColumn[colsList.size()]);
+
+        IndexColumn.mapColumns(cols, tbl);
+
+        initBaseIndex(tbl, 0, name, cols, IndexType.createNonUnique(false, false, idx instanceof SpatialIndex));
+
+        this.idx = idx;
+    }
+
+    /**
+     * @return Underlying index.
+     */
+    public Index underlyingIndex() {
+        return idx;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void checkRename() {
+        throw DbException.getUnsupportedException("rename");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void close(Session session) {
+        // No-op.
+    }
+
+    /** {@inheritDoc} */
+    @Override public void add(Session session, Row row) {
+        throw DbException.getUnsupportedException("add");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void remove(Session session, Row row) {
+        throw DbException.getUnsupportedException("remove row");
+    }
+
+    /** {@inheritDoc} */
+    @Override public Cursor find(Session session, SearchRow first, SearchRow last) {
+        GridH2RowDescriptor desc = ((GridH2Table)idx.getTable()).rowDescriptor();
+        return idx.find(session, desc.prepareProxyIndexRow(first), desc.prepareProxyIndexRow(last));
+    }
+
+    /** {@inheritDoc} */
+    @Override public double getCost(Session session, int[] masks, TableFilter[] filters, int filter, SortOrder sortOrder, HashSet<Column> allColumnsSet) {
+        long rowCnt = getRowCountApproximation();
+
+        double baseCost = getCostRangeIndex(masks, rowCnt, filters, filter, sortOrder, false, allColumnsSet);
+
+        int mul = ((GridH2IndexBase)idx).getDistributedMultiplier(session, filters, filter);
+
+        return mul * baseCost;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void remove(Session session) {
+        throw DbException.getUnsupportedException("remove index");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void truncate(Session session) {
+        throw DbException.getUnsupportedException("truncate");
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean canGetFirstOrLast() {
+        return idx.canGetFirstOrLast();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Cursor findFirstOrLast(Session session, boolean first) {
+        return idx.findFirstOrLast(session, first);
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean needRebuild() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public long getRowCount(Session session) {
+        return idx.getRowCount(session);
+    }
+
+    /** {@inheritDoc} */
+    @Override public long getRowCountApproximation() {
+        return idx.getRowCountApproximation();
+    }
+
+    /** {@inheritDoc} */
+    @Override public long getDiskSpaceUsed() {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public IndexLookupBatch createLookupBatch(TableFilter[] filters, int filter) {
+        return new ProxyIndexLookupBatch(idx.createLookupBatch(filters, filter));
+    }
+
+    /** {@inheritDoc} */
+    @Override public void removeChildrenAndResources(Session session) {
+        // No-op. Will be removed when underlying index is removed
+    }
+
+    /** Proxy lookup batch */
+    private class ProxyIndexLookupBatch implements IndexLookupBatch {
+
+        /** Underlying normal lookup batch */
+        private final IndexLookupBatch target;
+
+        /**
+         * Creates proxy lookup batch.
+         *
+         * @param target Underlying index lookup batch.
+         */
+        private ProxyIndexLookupBatch(IndexLookupBatch target) {
+            this.target = target;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean addSearchRows(SearchRow first, SearchRow last) {
+            GridH2RowDescriptor desc = ((GridH2Table)idx.getTable()).rowDescriptor();
+            return target.addSearchRows(desc.prepareProxyIndexRow(first), desc.prepareProxyIndexRow(last));
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean isBatchFull() {
+            return target.isBatchFull();
+        }
+
+        /** {@inheritDoc} */
+        @Override public List<Future<Cursor>> find() {
+            return target.find();
+        }
+
+        /** {@inheritDoc} */
+        @Override public String getPlanSQL() {
+            return target.getPlanSQL();
+        }
+
+        /** {@inheritDoc} */
+        @Override public void reset(boolean beforeQuery) {
+            target.reset(beforeQuery);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/efb7abce/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2ProxySpatialIndex.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2ProxySpatialIndex.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2ProxySpatialIndex.java
new file mode 100644
index 0000000..8af5099
--- /dev/null
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2ProxySpatialIndex.java
@@ -0,0 +1,70 @@
+/*
+ * 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.opt;
+
+
+import org.h2.engine.Session;
+import org.h2.index.Cursor;
+import org.h2.index.Index;
+import org.h2.index.SpatialIndex;
+import org.h2.index.SpatialTreeIndex;
+import org.h2.result.SearchRow;
+import org.h2.result.SortOrder;
+import org.h2.table.Column;
+import org.h2.table.IndexColumn;
+import org.h2.table.TableFilter;
+
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Allows to have 'free' spatial index for alias columns
+ * Delegates the calls to underlying normal index
+ */
+public class GridH2ProxySpatialIndex extends GridH2ProxyIndex implements SpatialIndex {
+    /**
+     *
+     * @param tbl Table.
+     * @param name Name of the proxy index.
+     * @param colsList Column list for the proxy index.
+     * @param idx Target index.
+     */
+    public GridH2ProxySpatialIndex(GridH2Table tbl,
+                                   String name,
+                                   List<IndexColumn> colsList,
+                                   Index idx) {
+        super(tbl, name, colsList, idx);
+    }
+
+    /** {@inheritDoc} */
+    @Override public double getCost(Session ses, int[] masks, TableFilter[] filters, int filter,
+                                    SortOrder sortOrder, HashSet<Column> cols) {
+        return SpatialTreeIndex.getCostRangeIndex(masks,
+                table.getRowCountApproximation(), columns) / 10;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Cursor findByGeometry(TableFilter filter, SearchRow first, SearchRow last, SearchRow intersection) {
+        GridH2RowDescriptor desc = ((GridH2Table)idx.getTable()).rowDescriptor();
+
+        return ((SpatialIndex)idx).findByGeometry(filter,
+                desc.prepareProxyIndexRow(first),
+                desc.prepareProxyIndexRow(last),
+                desc.prepareProxyIndexRow(intersection));
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/efb7abce/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 61de362..778ebfb 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
@@ -28,6 +28,7 @@ import org.apache.ignite.internal.processors.query.h2.IgniteH2Indexing;
 import org.apache.ignite.internal.util.offheap.unsafe.GridOffHeapSmartPointerFactory;
 import org.apache.ignite.internal.util.offheap.unsafe.GridUnsafeGuard;
 import org.apache.ignite.internal.util.offheap.unsafe.GridUnsafeMemory;
+import org.h2.result.SearchRow;
 import org.h2.value.Value;
 import org.jetbrains.annotations.Nullable;
 
@@ -161,4 +162,70 @@ public interface GridH2RowDescriptor extends GridOffHeapSmartPointerFactory<Grid
      * @return {@code True} if index should support snapshots.
      */
     public boolean snapshotableIndex();
+
+    /**
+     * Checks if provided column id matches key column or key alias.
+     *
+     * @param colId Column id.
+     * @return Result.
+     */
+    public boolean isKeyColumn(int colId);
+
+    /**
+     * Checks if provided column id matches value column or alias.
+     *
+     * @param colId Column id.
+     * @return Result.
+     */
+    public boolean isValueColumn(int colId);
+
+    /**
+     * Checks if provided column id matches key, key alias,
+     * value, value alias or version column.
+     *
+     * @param colId Column id.
+     * @return Result.
+     */
+    public boolean isKeyValueOrVersionColumn(int colId);
+
+    /**
+     * Checks if provided index condition is allowed for key column or key alias column.
+     *
+     * @param masks Array containing Index Condition masks for each column.
+     * @param mask Index Condition to check.
+     * @return Result.
+     */
+    public boolean checkKeyIndexCondition(int masks[], int mask);
+
+    /**
+     * Initializes value cache with key, val and version.
+     *
+     * @param valCache Value cache.
+     * @param key Key.
+     * @param value Value.
+     * @param version Version.
+     */
+    public void initValueCache(Value valCache[], Value key, Value value, Value version);
+
+    /**
+     * Clones provided row and copies values of alias key and val columns
+     * into respective key and val positions.
+     *
+     * @param row Source row.
+     * @return Result.
+     */
+    public SearchRow prepareProxyIndexRow(SearchRow row);
+
+    /**
+     * Gets alternative column id that may substitute the given column id.
+     *
+     * For alias column returns original one.
+     * For original column returns its alias.
+     *
+     * Otherwise, returns the given column id.
+     *
+     * @param colId Column id.
+     * @return Result.
+     */
+    public int getAlternativeColumnId(int colId);
 }
\ No newline at end of file