You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by vo...@apache.org on 2016/11/23 09:58:46 UTC

[8/8] ignite git commit: IGNITE-2294: Implemented DML.

IGNITE-2294: Implemented DML.


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

Branch: refs/heads/ignite-1.8
Commit: 86d143bb8bdbdccba9209762b1fd802d5dc40268
Parents: 93c3ccd
Author: Alexander Paschenko <al...@gmail.com>
Authored: Wed Nov 23 12:58:26 2016 +0300
Committer: devozerov <vo...@gridgain.com>
Committed: Wed Nov 23 12:58:27 2016 +0300

----------------------------------------------------------------------
 .../clients/src/test/config/jdbc-bin-config.xml |   54 +
 .../jdbc2/JdbcAbstractDmlStatementSelfTest.java |  263 +++++
 .../JdbcAbstractUpdateStatementSelfTest.java    |   37 +
 ...BinaryMarshallerInsertStatementSelfTest.java |   37 +
 ...cBinaryMarshallerMergeStatementSelfTest.java |   37 +
 .../jdbc2/JdbcDeleteStatementSelfTest.java      |   49 +
 .../jdbc2/JdbcInsertStatementSelfTest.java      |  122 +++
 .../jdbc2/JdbcMergeStatementSelfTest.java       |   91 ++
 .../jdbc/suite/IgniteJdbcDriverTestSuite.java   |    5 +
 .../java/org/apache/ignite/IgniteCache.java     |    2 +
 .../binary/BinaryAbstractIdentityResolver.java  |   53 +
 .../binary/BinaryArrayIdentityResolver.java     |  224 ++++
 .../binary/BinaryFieldIdentityResolver.java     |  307 ++++++
 .../ignite/binary/BinaryIdentityResolver.java   |   42 +
 .../ignite/binary/BinaryTypeConfiguration.java  |   27 +-
 .../org/apache/ignite/cache/QueryEntity.java    |   27 +
 .../ignite/cache/query/SqlFieldsQuery.java      |    2 +-
 .../configuration/CacheConfiguration.java       |   19 +-
 .../internal/binary/BinaryClassDescriptor.java  |   15 +
 .../ignite/internal/binary/BinaryContext.java   |   76 +-
 .../ignite/internal/binary/BinaryFieldImpl.java |   10 +-
 .../internal/binary/BinaryObjectExImpl.java     |   90 +-
 .../internal/binary/BinaryObjectImpl.java       |   48 +-
 .../binary/BinaryObjectOffheapImpl.java         |   44 +-
 .../internal/binary/BinaryPrimitives.java       |   24 +
 .../binary/BinarySerializedFieldComparator.java |  343 ++++++
 .../ignite/internal/binary/BinaryUtils.java     |    2 +-
 .../internal/binary/BinaryWriterExImpl.java     |   47 +-
 .../binary/builder/BinaryObjectBuilderImpl.java |    6 +
 .../streams/BinaryAbstractInputStream.java      |    5 +
 .../streams/BinaryAbstractOutputStream.java     |    5 +
 .../binary/streams/BinaryHeapInputStream.java   |    5 +
 .../binary/streams/BinaryHeapOutputStream.java  |    5 +
 .../streams/BinaryOffheapInputStream.java       |   10 +
 .../streams/BinaryOffheapOutputStream.java      |    4 +-
 .../internal/binary/streams/BinaryStream.java   |   12 +-
 .../ignite/internal/jdbc2/JdbcConnection.java   |   17 +
 .../internal/jdbc2/JdbcPreparedStatement.java   |   71 +-
 .../ignite/internal/jdbc2/JdbcQueryTask.java    |    5 +-
 .../ignite/internal/jdbc2/JdbcQueryTaskV2.java  |  406 +++++++
 .../ignite/internal/jdbc2/JdbcResultSet.java    |   27 +
 .../internal/jdbc2/JdbcSqlFieldsQuery.java      |   49 +
 .../ignite/internal/jdbc2/JdbcStatement.java    |  213 +++-
 .../apache/ignite/internal/jdbc2/JdbcUtils.java |   25 +-
 .../processors/cache/QueryCursorImpl.java       |   31 +-
 .../cache/query/GridCacheTwoStepQuery.java      |  253 -----
 .../cache/query/IgniteQueryErrorCode.java       |   91 ++
 .../memory/PlatformInputStreamImpl.java         |   10 +
 .../memory/PlatformOutputStreamImpl.java        |   12 +
 .../processors/query/GridQueryIndexing.java     |   23 +-
 .../processors/query/GridQueryProcessor.java    |  578 ++++++++--
 .../processors/query/GridQueryProperty.java     |   20 +
 .../query/GridQueryTypeDescriptor.java          |   25 +
 .../processors/query/IgniteSQLException.java    |   89 ++
 .../util/lang/IgniteSingletonIterator.java      |   56 +
 .../BinaryArrayIdentityResolverSelfTest.java    |  300 +++++
 .../BinaryFieldIdentityResolverSelfTest.java    |  333 ++++++
 ...ryIdentityResolverConfigurationSelfTest.java |  138 +++
 .../BinarySerialiedFieldComparatorSelfTest.java |  568 ++++++++++
 .../GridCacheBinaryObjectsAbstractSelfTest.java |  260 ++++-
 .../IgniteBinaryObjectsTestSuite.java           |   10 +
 .../cache/query/GridCacheTwoStepQuery.java      |  253 +++++
 .../query/h2/DmlStatementsProcessor.java        | 1027 ++++++++++++++++++
 .../query/h2/GridH2ResultSetIterator.java       |    3 +-
 .../processors/query/h2/IgniteH2Indexing.java   |  124 ++-
 .../query/h2/dml/FastUpdateArgument.java        |   27 +
 .../query/h2/dml/FastUpdateArguments.java       |   53 +
 .../query/h2/dml/KeyValueSupplier.java          |   30 +
 .../processors/query/h2/dml/UpdateMode.java     |   36 +
 .../processors/query/h2/dml/UpdatePlan.java     |  121 +++
 .../query/h2/dml/UpdatePlanBuilder.java         |  502 +++++++++
 .../processors/query/h2/dml/package-info.java   |   22 +
 .../query/h2/opt/GridH2RowDescriptor.java       |   23 +
 .../query/h2/opt/GridH2TreeIndex.java           |    2 +-
 .../processors/query/h2/sql/DmlAstUtils.java    |  599 ++++++++++
 .../processors/query/h2/sql/GridSqlArray.java   |    8 +
 .../processors/query/h2/sql/GridSqlConst.java   |    6 +
 .../processors/query/h2/sql/GridSqlDelete.java  |   68 ++
 .../query/h2/sql/GridSqlFunction.java           |    5 +-
 .../processors/query/h2/sql/GridSqlInsert.java  |  149 +++
 .../processors/query/h2/sql/GridSqlKeyword.java |   46 +
 .../processors/query/h2/sql/GridSqlMerge.java   |  143 +++
 .../processors/query/h2/sql/GridSqlQuery.java   |   44 +-
 .../query/h2/sql/GridSqlQueryParser.java        |  323 +++++-
 .../query/h2/sql/GridSqlQuerySplitter.java      |    6 +-
 .../processors/query/h2/sql/GridSqlSelect.java  |    3 +-
 .../query/h2/sql/GridSqlStatement.java          |   64 ++
 .../processors/query/h2/sql/GridSqlUpdate.java  |  105 ++
 .../h2/twostep/GridReduceQueryExecutor.java     |   28 +-
 ...niteCacheAbstractInsertSqlQuerySelfTest.java |  567 ++++++++++
 .../IgniteCacheAbstractSqlDmlQuerySelfTest.java |  219 ++++
 .../IgniteCacheDeleteSqlQuerySelfTest.java      |   81 ++
 .../IgniteCacheInsertSqlQuerySelfTest.java      |  203 ++++
 .../cache/IgniteCacheMergeSqlQuerySelfTest.java |  153 +++
 .../IgniteCacheUpdateSqlQuerySelfTest.java      |  150 +++
 .../IgniteCacheAtomicFieldsQuerySelfTest.java   |   21 -
 .../h2/GridIndexingSpiAbstractSelfTest.java     |   40 +-
 .../query/h2/sql/GridQueryParsingTest.java      |  109 +-
 .../IgniteCacheQuerySelfTestSuite.java          |    9 +
 .../config/benchmark-bin-identity.properties    |   94 ++
 .../config/benchmark-multicast.properties       |   15 +
 .../config/benchmark-sql-dml.properties         |   72 ++
 modules/yardstick/config/ignite-base-config.xml |   73 +-
 .../config/ignite-bin-multicast-config.xml      |   86 ++
 .../cache/IgniteBinaryIdentityBenchmark.java    |  108 ++
 .../cache/IgniteBinaryIdentityGetBenchmark.java |   34 +
 .../cache/IgniteBinaryIdentityPutBenchmark.java |   35 +
 .../IgniteFieldsBinaryIdentityGetBenchmark.java |   30 +
 .../IgniteFieldsBinaryIdentityPutBenchmark.java |   30 +
 .../IgniteLegacyBinaryIdentityGetBenchmark.java |   30 +
 .../IgniteLegacyBinaryIdentityPutBenchmark.java |   30 +
 ...IgnitePutIfAbsentIndexedValue1Benchmark.java |   45 +
 .../IgniteReplaceIndexedValue1Benchmark.java    |   79 ++
 .../cache/dml/IgniteSqlDeleteBenchmark.java     |   83 ++
 .../dml/IgniteSqlDeleteFilteredBenchmark.java   |   88 ++
 .../IgniteSqlInsertIndexedValue1Benchmark.java  |   48 +
 .../IgniteSqlInsertIndexedValue2Benchmark.java  |   48 +
 .../IgniteSqlInsertIndexedValue8Benchmark.java  |   48 +
 .../cache/dml/IgniteSqlMergeAllBenchmark.java   |   82 ++
 .../cache/dml/IgniteSqlMergeBenchmark.java      |   42 +
 .../IgniteSqlMergeIndexedValue1Benchmark.java   |   43 +
 .../IgniteSqlMergeIndexedValue2Benchmark.java   |   43 +
 .../IgniteSqlMergeIndexedValue8Benchmark.java   |   43 +
 .../cache/dml/IgniteSqlMergeQueryBenchmark.java |  116 ++
 .../cache/dml/IgniteSqlUpdateBenchmark.java     |   82 ++
 .../dml/IgniteSqlUpdateFilteredBenchmark.java   |   88 ++
 .../yardstick/cache/model/SampleValue.java      |    2 +
 127 files changed, 12020 insertions(+), 728 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/86d143bb/modules/clients/src/test/config/jdbc-bin-config.xml
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/config/jdbc-bin-config.xml b/modules/clients/src/test/config/jdbc-bin-config.xml
new file mode 100644
index 0000000..69f85a1
--- /dev/null
+++ b/modules/clients/src/test/config/jdbc-bin-config.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  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.
+-->
+
+<!--
+    Ignite Spring configuration file.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="
+        http://www.springframework.org/schema/beans
+        http://www.springframework.org/schema/beans/spring-beans.xsd">
+    <bean id="grid.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
+        <!-- JDBC driver should force true value -->
+        <property name="clientMode" value="false"/>
+
+        <property name="localHost" value="127.0.0.1"/>
+
+        <property name="marshaller">
+            <bean class="org.apache.ignite.internal.binary.BinaryMarshaller" />
+        </property>
+
+        <property name="discoverySpi">
+            <bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
+                <property name="ipFinder">
+                    <bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder">
+                        <property name="addresses">
+                            <list>
+                                <value>127.0.0.1:47500..47549</value>
+                            </list>
+                        </property>
+                    </bean>
+                </property>
+            </bean>
+        </property>
+
+        <property name="peerClassLoadingEnabled" value="true"/>
+    </bean>
+</beans>

http://git-wip-us.apache.org/repos/asf/ignite/blob/86d143bb/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
new file mode 100644
index 0000000..4a97aef
--- /dev/null
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcAbstractDmlStatementSelfTest.java
@@ -0,0 +1,263 @@
+/*
+ * 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.jdbc2;
+
+import java.io.Serializable;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.util.Collections;
+import org.apache.ignite.cache.CachePeekMode;
+import org.apache.ignite.cache.QueryEntity;
+import org.apache.ignite.cache.query.annotations.QuerySqlField;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.ConnectorConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.binary.BinaryMarshaller;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+
+import static org.apache.ignite.IgniteJdbcDriver.CFG_URL_PREFIX;
+import static org.apache.ignite.cache.CacheMode.PARTITIONED;
+import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_SYNC;
+
+/**
+ * Statement test.
+ */
+public abstract class JdbcAbstractDmlStatementSelfTest extends GridCommonAbstractTest {
+    /** IP finder. */
+    private static final TcpDiscoveryIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true);
+
+    /** JDBC URL. */
+    private static final String BASE_URL = CFG_URL_PREFIX + "modules/clients/src/test/config/jdbc-config.xml";
+
+    /** JDBC URL for tests involving binary objects manipulation. */
+    static final String BASE_URL_BIN = CFG_URL_PREFIX + "modules/clients/src/test/config/jdbc-bin-config.xml";
+
+    /** SQL SELECT query for verification. */
+    private static final String SQL_SELECT = "select _key, id, firstName, lastName, age from Person";
+
+    /** Connection. */
+    protected Connection conn;
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String gridName) throws Exception {
+        return getConfiguration0(gridName);
+    }
+
+    /**
+     * @param gridName Grid name.
+     * @return Grid configuration used for starting the grid.
+     * @throws Exception If failed.
+     */
+    private IgniteConfiguration getConfiguration0(String gridName) throws Exception {
+        IgniteConfiguration cfg = super.getConfiguration(gridName);
+
+        CacheConfiguration<?,?> cache = defaultCacheConfiguration();
+
+        cache.setCacheMode(PARTITIONED);
+        cache.setBackups(1);
+        cache.setWriteSynchronizationMode(FULL_SYNC);
+        cache.setIndexedTypes(
+            String.class, Person.class
+        );
+
+        cfg.setCacheConfiguration(cache);
+
+        TcpDiscoverySpi disco = new TcpDiscoverySpi();
+
+        disco.setIpFinder(IP_FINDER);
+
+        cfg.setDiscoverySpi(disco);
+
+        cfg.setConnectorConfiguration(new ConnectorConfiguration());
+
+        return cfg;
+    }
+
+    /**
+     * @param gridName Grid name.
+     * @return Grid configuration used for starting the grid ready for manipulating binary objects.
+     * @throws Exception If failed.
+     */
+    IgniteConfiguration getBinaryConfiguration(String gridName) throws Exception {
+        IgniteConfiguration cfg = getConfiguration0(gridName);
+
+        cfg.setMarshaller(new BinaryMarshaller());
+
+        CacheConfiguration ccfg = cfg.getCacheConfiguration()[0];
+
+        ccfg.getQueryEntities().clear();
+
+        QueryEntity e = new QueryEntity();
+
+        e.setKeyType(String.class.getName());
+        e.setValueType("Person");
+
+        e.addQueryField("id", Integer.class.getName(), null);
+        e.addQueryField("age", Integer.class.getName(), null);
+        e.addQueryField("firstName", String.class.getName(), null);
+        e.addQueryField("lastName", String.class.getName(), null);
+
+        ccfg.setQueryEntities(Collections.singletonList(e));
+
+        return cfg;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTestsStarted() throws Exception {
+        startGridsMultiThreaded(3);
+
+        Class.forName("org.apache.ignite.IgniteJdbcDriver");
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTestsStopped() throws Exception {
+        stopAllGrids();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        conn = DriverManager.getConnection(getCfgUrl());
+    }
+
+    /**
+     * @return URL of XML configuration file.
+     */
+    protected String getCfgUrl() {
+        return BASE_URL;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        try (Statement selStmt = conn.createStatement()) {
+            assert selStmt.execute(SQL_SELECT);
+
+            ResultSet rs = selStmt.getResultSet();
+
+            assert rs != null;
+
+            while (rs.next()) {
+                int id = rs.getInt("id");
+
+                switch (id) {
+                    case 1:
+                        assertEquals("p1", rs.getString("_key"));
+                        assertEquals("John", rs.getString("firstName"));
+                        assertEquals("White", rs.getString("lastName"));
+                        assertEquals(25, rs.getInt("age"));
+                        break;
+
+                    case 2:
+                        assertEquals("p2", rs.getString("_key"));
+                        assertEquals("Joe", rs.getString("firstName"));
+                        assertEquals("Black", rs.getString("lastName"));
+                        assertEquals(35, rs.getInt("age"));
+                        break;
+
+                    case 3:
+                        assertEquals("p3", rs.getString("_key"));
+                        assertEquals("Mike", rs.getString("firstName"));
+                        assertEquals("Green", rs.getString("lastName"));
+                        assertEquals(40, rs.getInt("age"));
+                        break;
+
+                    case 4:
+                        assertEquals("p4", rs.getString("_key"));
+                        assertEquals("Leah", rs.getString("firstName"));
+                        assertEquals("Grey", rs.getString("lastName"));
+                        assertEquals(22, rs.getInt("age"));
+                        break;
+
+                    default:
+                        assert false : "Invalid ID: " + id;
+                }
+            }
+        }
+
+        grid(0).cache(null).clear();
+
+        assertEquals(0, grid(0).cache(null).size(CachePeekMode.ALL));
+    }
+
+    /**
+     * Person.
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    static class Person implements Serializable {
+        /** ID. */
+        @QuerySqlField
+        private final int id;
+
+        /** First name. */
+        @QuerySqlField
+        private final String firstName;
+
+        /** Last name. */
+        @QuerySqlField
+        private final String lastName;
+
+        /** Age. */
+        @QuerySqlField
+        private final int age;
+
+        /**
+         * @param id ID.
+         * @param firstName First name.
+         * @param lastName Last name.
+         * @param age Age.
+         */
+        Person(int id, String firstName, String lastName, int age) {
+            assert !F.isEmpty(firstName);
+            assert !F.isEmpty(lastName);
+            assert age > 0;
+
+            this.id = id;
+            this.firstName = firstName;
+            this.lastName = lastName;
+            this.age = age;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            Person person = (Person) o;
+
+            if (id != person.id) return false;
+            if (age != person.age) return false;
+            if (firstName != null ? !firstName.equals(person.firstName) : person.firstName != null) return false;
+            return lastName != null ? lastName.equals(person.lastName) : person.lastName == null;
+
+        }
+
+        /** {@inheritDoc} */
+        @Override public int hashCode() {
+            int result = id;
+            result = 31 * result + (firstName != null ? firstName.hashCode() : 0);
+            result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
+            result = 31 * result + age;
+            return result;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/86d143bb/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcAbstractUpdateStatementSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcAbstractUpdateStatementSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcAbstractUpdateStatementSelfTest.java
new file mode 100644
index 0000000..a20b815
--- /dev/null
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcAbstractUpdateStatementSelfTest.java
@@ -0,0 +1,37 @@
+/*
+ * 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.jdbc2;
+
+import java.sql.Statement;
+
+public abstract class JdbcAbstractUpdateStatementSelfTest extends JdbcAbstractDmlStatementSelfTest {
+    /** SQL query to populate cache. */
+    private static final String ITEMS_SQL = "insert into Person(_key, id, firstName, lastName, age) values " +
+        "('p1', 1, 'John', 'White', 25), " +
+        "('p2', 2, 'Joe', 'Black', 35), " +
+        "('p3', 3, 'Mike', 'Green', 40)";
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        super.beforeTest();
+        jcache(0).clear();
+        try (Statement s = conn.createStatement()) {
+            s.executeUpdate(ITEMS_SQL);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/86d143bb/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcBinaryMarshallerInsertStatementSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcBinaryMarshallerInsertStatementSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcBinaryMarshallerInsertStatementSelfTest.java
new file mode 100644
index 0000000..667e9f0
--- /dev/null
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcBinaryMarshallerInsertStatementSelfTest.java
@@ -0,0 +1,37 @@
+/*
+ * 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.jdbc2;
+
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.binary.BinaryMarshaller;
+import org.apache.ignite.testframework.config.GridTestProperties;
+
+/**
+ * JDBC test of INSERT statement w/binary marshaller - no nodes know about classes.
+ */
+public class JdbcBinaryMarshallerInsertStatementSelfTest extends JdbcInsertStatementSelfTest {
+    /** {@inheritDoc} */
+    @Override protected String getCfgUrl() {
+        return BASE_URL_BIN;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String gridName) throws Exception {
+        return getBinaryConfiguration(gridName);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/86d143bb/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcBinaryMarshallerMergeStatementSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcBinaryMarshallerMergeStatementSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcBinaryMarshallerMergeStatementSelfTest.java
new file mode 100644
index 0000000..93451e7
--- /dev/null
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcBinaryMarshallerMergeStatementSelfTest.java
@@ -0,0 +1,37 @@
+/*
+ * 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.jdbc2;
+
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.binary.BinaryMarshaller;
+import org.apache.ignite.testframework.config.GridTestProperties;
+
+/**
+ * JDBC test of MERGE statement w/binary marshaller - no nodes know about classes.
+ */
+public class JdbcBinaryMarshallerMergeStatementSelfTest extends JdbcMergeStatementSelfTest {
+    /** {@inheritDoc} */
+    @Override protected String getCfgUrl() {
+        return BASE_URL_BIN;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String gridName) throws Exception {
+        return getBinaryConfiguration(gridName);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/86d143bb/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcDeleteStatementSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcDeleteStatementSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcDeleteStatementSelfTest.java
new file mode 100644
index 0000000..d55c979
--- /dev/null
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcDeleteStatementSelfTest.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.jdbc2;
+
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.HashSet;
+
+/**
+ *
+ */
+public class JdbcDeleteStatementSelfTest extends JdbcAbstractUpdateStatementSelfTest {
+    /**
+     *
+     */
+    public void testExecute() throws SQLException {
+        conn.createStatement().execute("delete from Person where cast(substring(_key, 2, 1) as int) % 2 = 0");
+
+        assertFalse(jcache(0).containsKey("p2"));
+        assertTrue(jcache(0).containsKeys(new HashSet<Object>(Arrays.asList("p1", "p3"))));
+    }
+
+    /**
+     *
+     */
+    public void testExecuteUpdate() throws SQLException {
+        int res =
+            conn.createStatement().executeUpdate("delete from Person where cast(substring(_key, 2, 1) as int) % 2 = 0");
+
+        assertEquals(1, res);
+        assertFalse(jcache(0).containsKey("p2"));
+        assertTrue(jcache(0).containsKeys(new HashSet<Object>(Arrays.asList("p1", "p3"))));
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/86d143bb/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcInsertStatementSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcInsertStatementSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcInsertStatementSelfTest.java
new file mode 100644
index 0000000..7fc92de
--- /dev/null
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcInsertStatementSelfTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.jdbc2;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.concurrent.Callable;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.testframework.GridTestUtils;
+
+/**
+ * Statement test.
+ */
+public class JdbcInsertStatementSelfTest extends JdbcAbstractDmlStatementSelfTest {
+    /** SQL query. */
+    private static final String SQL = "insert into Person(_key, id, firstName, lastName, age) values " +
+        "('p1', 1, 'John', 'White', 25), " +
+        "('p2', 2, 'Joe', 'Black', 35), " +
+        "('p3', 3, 'Mike', 'Green', 40)";
+
+    /** SQL query. */
+    private static final String SQL_PREPARED = "insert into Person(_key, id, firstName, lastName, age) values " +
+        "(?, ?, ?, ?, ?), (?, ?, ?, ?, ?)";
+
+    /** Statement. */
+    private Statement stmt;
+
+    /** Prepared statement. */
+    private PreparedStatement prepStmt;
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        super.beforeTest();
+        stmt = conn.createStatement();
+        prepStmt = conn.prepareStatement(SQL_PREPARED);
+
+        assertNotNull(stmt);
+        assertFalse(stmt.isClosed());
+
+        assertNotNull(prepStmt);
+        assertFalse(prepStmt.isClosed());
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        super.afterTest();
+
+        if (stmt != null && !stmt.isClosed())
+            stmt.close();
+
+        if (prepStmt != null && !prepStmt.isClosed())
+            prepStmt.close();
+
+        conn.close();
+
+        assertTrue(prepStmt.isClosed());
+        assertTrue(stmt.isClosed());
+        assertTrue(conn.isClosed());
+    }
+
+    /**
+     * @throws SQLException If failed.
+     */
+    public void testExecuteUpdate() throws SQLException {
+        int res = stmt.executeUpdate(SQL);
+
+        assertEquals(3, res);
+    }
+
+    /**
+     * @throws SQLException If failed.
+     */
+    public void testExecute() throws SQLException {
+        boolean res = stmt.execute(SQL);
+
+        assertEquals(false, res);
+    }
+
+    /**
+     *
+     */
+    public void testDuplicateKeys() {
+        jcache(0).put("p2", new Person(2, "Joe", "Black", 35));
+
+        Throwable reason = GridTestUtils.assertThrows(log, new Callable<Object>() {
+            /** {@inheritDoc} */
+            @Override public Object call() throws Exception {
+                return stmt.execute(SQL);
+            }
+        }, SQLException.class, null);
+
+        assertNotNull(reason.getCause());
+
+        reason = reason.getCause().getCause();
+
+        assertNotNull(reason);
+
+        assertEquals(IgniteException.class, reason.getClass());
+
+        assertEquals("Failed to INSERT some keys because they are already in cache [keys=[p2]]", reason.getMessage());
+
+        assertEquals(3, jcache(0).withKeepBinary().getAll(new HashSet<>(Arrays.asList("p1", "p2", "p3"))).size());
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/86d143bb/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMergeStatementSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMergeStatementSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMergeStatementSelfTest.java
new file mode 100644
index 0000000..ecf6032
--- /dev/null
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMergeStatementSelfTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.jdbc2;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+/**
+ * MERGE statement test.
+ */
+public class JdbcMergeStatementSelfTest extends JdbcAbstractDmlStatementSelfTest {
+    /** SQL query. */
+    private static final String SQL = "merge into Person(_key, id, firstName, lastName, age) values " +
+        "('p1', 1, 'John', 'White', 25), " +
+        "('p2', 2, 'Joe', 'Black', 35), " +
+        "('p3', 3, 'Mike', 'Green', 40)";
+
+    /** SQL query. */
+    protected static final String SQL_PREPARED = "merge into Person(_key, id, firstName, lastName, age) values " +
+        "(?, ?, ?, ?, ?), (?, ?, ?, ?, ?)";
+
+    /** Statement. */
+    protected Statement stmt;
+
+    /** Prepared statement. */
+    protected PreparedStatement prepStmt;
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        super.beforeTest();
+        stmt = conn.createStatement();
+        prepStmt = conn.prepareStatement(SQL_PREPARED);
+
+        assertNotNull(stmt);
+        assertFalse(stmt.isClosed());
+
+        assertNotNull(prepStmt);
+        assertFalse(prepStmt.isClosed());
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        super.afterTest();
+
+        if (stmt != null && !stmt.isClosed())
+            stmt.close();
+
+        if (prepStmt != null && !prepStmt.isClosed())
+            prepStmt.close();
+
+        conn.close();
+
+        assertTrue(prepStmt.isClosed());
+        assertTrue(stmt.isClosed());
+        assertTrue(conn.isClosed());
+    }
+
+    /**
+     * @throws SQLException If failed.
+     */
+    public void testExecuteUpdate() throws SQLException {
+        int res = stmt.executeUpdate(SQL);
+
+        assertEquals(3, res);
+    }
+
+    /**
+     * @throws SQLException If failed.
+     */
+    public void testExecute() throws SQLException {
+        boolean res = stmt.execute(SQL);
+
+        assertEquals(false, res);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/86d143bb/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java
index b1053b0..048643b 100644
--- a/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java
+++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java
@@ -62,6 +62,11 @@ public class IgniteJdbcDriverTestSuite extends TestSuite {
         suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcEmptyCacheSelfTest.class));
         suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcLocalCachesSelfTest.class));
         suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcNoDefaultCacheTest.class));
+        suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcMergeStatementSelfTest.class));
+        suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcBinaryMarshallerMergeStatementSelfTest.class));
+        suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcInsertStatementSelfTest.class));
+        suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcBinaryMarshallerInsertStatementSelfTest.class));
+        suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcDeleteStatementSelfTest.class));
 
         return suite;
     }

http://git-wip-us.apache.org/repos/asf/ignite/blob/86d143bb/modules/core/src/main/java/org/apache/ignite/IgniteCache.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/IgniteCache.java b/modules/core/src/main/java/org/apache/ignite/IgniteCache.java
index 7eb6e91..d7bccf5 100644
--- a/modules/core/src/main/java/org/apache/ignite/IgniteCache.java
+++ b/modules/core/src/main/java/org/apache/ignite/IgniteCache.java
@@ -47,6 +47,7 @@ import org.apache.ignite.cache.query.QueryDetailMetrics;
 import org.apache.ignite.cache.query.QueryMetrics;
 import org.apache.ignite.cache.query.ScanQuery;
 import org.apache.ignite.cache.query.SpiQuery;
+import org.apache.ignite.cache.query.SqlFieldsQuery;
 import org.apache.ignite.cache.query.SqlQuery;
 import org.apache.ignite.cache.query.TextQuery;
 import org.apache.ignite.cache.store.CacheStore;
@@ -291,6 +292,7 @@ public interface IgniteCache<K, V> extends javax.cache.Cache<K, V>, IgniteAsyncS
      * @return Cursor.
      * @see ScanQuery
      * @see SqlQuery
+     * @see SqlFieldsQuery
      * @see TextQuery
      * @see SpiQuery
      */

http://git-wip-us.apache.org/repos/asf/ignite/blob/86d143bb/modules/core/src/main/java/org/apache/ignite/binary/BinaryAbstractIdentityResolver.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/binary/BinaryAbstractIdentityResolver.java b/modules/core/src/main/java/org/apache/ignite/binary/BinaryAbstractIdentityResolver.java
new file mode 100644
index 0000000..b3036e2
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/binary/BinaryAbstractIdentityResolver.java
@@ -0,0 +1,53 @@
+/*
+ * 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.binary;
+
+/**
+ * Abstract identity resolver with common routines.
+ */
+public abstract class BinaryAbstractIdentityResolver implements BinaryIdentityResolver {
+    /** {@inheritDoc} */
+    @Override public int hashCode(BinaryObject obj) {
+        if (obj == null)
+            throw new BinaryObjectException("Cannot calculate hash code because binary object is null.");
+
+        return hashCode0(obj);
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(BinaryObject o1, BinaryObject o2) {
+        return o1 == o2 || (o1 != null && o2 != null && equals0(o1, o2));
+    }
+
+    /**
+     * Internal hash code routine.
+     *
+     * @param obj Object.
+     * @return Result.
+     */
+    protected abstract int hashCode0(BinaryObject obj);
+
+    /**
+     * Internal equals routine.
+     *
+     * @param o1 First object.
+     * @param o2 Second object.
+     * @return Result.
+     */
+    protected abstract boolean equals0(BinaryObject o1, BinaryObject o2);
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/86d143bb/modules/core/src/main/java/org/apache/ignite/binary/BinaryArrayIdentityResolver.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/binary/BinaryArrayIdentityResolver.java b/modules/core/src/main/java/org/apache/ignite/binary/BinaryArrayIdentityResolver.java
new file mode 100644
index 0000000..2f04c02
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/binary/BinaryArrayIdentityResolver.java
@@ -0,0 +1,224 @@
+/*
+ * 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.binary;
+
+import java.util.Arrays;
+
+import org.apache.ignite.internal.binary.BinaryEnumObjectImpl;
+import org.apache.ignite.internal.binary.BinaryObjectEx;
+import org.apache.ignite.internal.binary.BinaryObjectExImpl;
+import org.apache.ignite.internal.binary.BinaryPrimitives;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+/**
+ * Identity resolver implementation which compares raw array content of the binary object.
+ * <p>
+ * Hash code is calculated in the same way as {@link Arrays#hashCode(byte[])} does.
+ */
+public class BinaryArrayIdentityResolver extends BinaryAbstractIdentityResolver {
+    /** Singleton instance */
+    private static final BinaryArrayIdentityResolver INSTANCE = new BinaryArrayIdentityResolver();
+
+    /**
+     * Get singleton instance.
+     *
+     * @return Singleton instance.
+     */
+    public static BinaryArrayIdentityResolver instance() {
+        return INSTANCE;
+    }
+
+    /**
+     * Default constructor.
+     */
+    public BinaryArrayIdentityResolver() {
+        // No-op.
+    }
+
+    /** {@inheritDoc} */
+    @Override protected int hashCode0(BinaryObject obj) {
+        int hash = 1;
+
+        if (obj instanceof BinaryObjectExImpl) {
+            BinaryObjectExImpl ex = (BinaryObjectExImpl)obj;
+
+            int start = ex.dataStartOffset();
+            int end = ex.footerStartOffset();
+
+            if (ex.hasArray()) {
+                // Handle heap object.
+                byte[] data = ex.array();
+
+                for (int i = start; i < end; i++)
+                    hash = 31 * hash + data[i];
+            }
+            else {
+                // Handle offheap object.
+                long ptr = ex.offheapAddress();
+
+                for (int i = start; i < end; i++)
+                    hash = 31 * hash + BinaryPrimitives.readByte(ptr, i);
+            }
+        }
+        else if (obj instanceof BinaryEnumObjectImpl) {
+            int ord = obj.enumOrdinal();
+
+            // Construct hash as if it was an int serialized in little-endian form.
+            hash = 31 * hash + (ord & 0x000000FF);
+            hash = 31 * hash + (ord & 0x0000FF00);
+            hash = 31 * hash + (ord & 0x00FF0000);
+            hash = 31 * hash + (ord & 0xFF000000);
+        }
+        else
+            throw new BinaryObjectException("Array identity resolver cannot be used with provided BinaryObject " +
+                "implementation: " + obj.getClass().getName());
+
+        return hash;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected boolean equals0(BinaryObject o1, BinaryObject o2) {
+        if (o1 instanceof BinaryObjectEx && o2 instanceof BinaryObjectEx) {
+            BinaryObjectEx ex1 = (BinaryObjectEx)o1;
+            BinaryObjectEx ex2 = (BinaryObjectEx)o2;
+
+            if (ex1.typeId() != ex2.typeId())
+                return false;
+
+            if (ex1 instanceof BinaryObjectExImpl) {
+                // Handle regular object.
+                assert ex2 instanceof BinaryObjectExImpl;
+
+                BinaryObjectExImpl exx1 = (BinaryObjectExImpl)ex1;
+                BinaryObjectExImpl exx2 = (BinaryObjectExImpl)ex2;
+
+                if (exx1.hasArray())
+                    return exx2.hasArray() ? equalsHeap(exx1, exx2) : equalsHeapOffheap(exx1, exx2);
+                else
+                    return exx2.hasArray() ? equalsHeapOffheap(exx2, exx1) : equalsOffheap(exx1, exx2);
+            }
+            else {
+                // Handle enums.
+                assert ex1 instanceof BinaryEnumObjectImpl;
+                assert ex2 instanceof BinaryEnumObjectImpl;
+
+                return ex1.enumOrdinal() == ex2.enumOrdinal();
+            }
+        }
+
+        BinaryObject o = o1 instanceof BinaryObjectEx ? o2 : o1;
+
+        throw new BinaryObjectException("Array identity resolver cannot be used with provided BinaryObject " +
+            "implementation: " + o.getClass().getName());
+    }
+
+    /**
+     * Compare two heap objects.
+     *
+     * @param o1 Object 1.
+     * @param o2 Object 2.
+     * @return Result.
+     */
+    private static boolean equalsHeap(BinaryObjectExImpl o1, BinaryObjectExImpl o2) {
+        byte[] arr1 = o1.array();
+        byte[] arr2 = o2.array();
+
+        assert arr1 != null && arr2 != null;
+
+        int i = o1.dataStartOffset();
+        int j = o2.dataStartOffset();
+
+        int end = o1.footerStartOffset();
+
+        // Check length.
+        if (end - i != o2.footerStartOffset() - j)
+            return false;
+
+        for (; i < end; i++, j++) {
+            if (arr1[i] != arr2[j])
+                return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Compare heap and offheap objects.
+     *
+     * @param o1 Object 1 (heap).
+     * @param o2 Object 2 (offheap).
+     * @return Result.
+     */
+    private static boolean equalsHeapOffheap(BinaryObjectExImpl o1, BinaryObjectExImpl o2) {
+        byte[] arr1 = o1.array();
+        long ptr2 = o2.offheapAddress();
+
+        assert arr1 != null && ptr2 != 0;
+
+        int i = o1.dataStartOffset();
+        int j = o2.dataStartOffset();
+
+        int end = o1.footerStartOffset();
+
+        // Check length.
+        if (end - i != o2.footerStartOffset() - j)
+            return false;
+
+        for (; i < end; i++, j++) {
+            if (arr1[i] != BinaryPrimitives.readByte(ptr2, j))
+                return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Compare two offheap objects.
+     *
+     * @param o1 Object 1.
+     * @param o2 Object 2.
+     * @return Result.
+     */
+    private static boolean equalsOffheap(BinaryObjectExImpl o1, BinaryObjectExImpl o2) {
+        long ptr1 = o1.offheapAddress();
+        long ptr2 = o2.offheapAddress();
+
+        assert ptr1 != 0 && ptr2 != 0;
+
+        int i = o1.dataStartOffset();
+        int j = o2.dataStartOffset();
+
+        int end = o1.footerStartOffset();
+
+        // Check length.
+        if (end - i != o2.footerStartOffset() - j)
+            return false;
+
+        for (; i < end; i++, j++) {
+            if (BinaryPrimitives.readByte(ptr1, i) != BinaryPrimitives.readByte(ptr2, j))
+                return false;
+        }
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(BinaryArrayIdentityResolver.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/86d143bb/modules/core/src/main/java/org/apache/ignite/binary/BinaryFieldIdentityResolver.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/binary/BinaryFieldIdentityResolver.java b/modules/core/src/main/java/org/apache/ignite/binary/BinaryFieldIdentityResolver.java
new file mode 100644
index 0000000..c4fc869
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/binary/BinaryFieldIdentityResolver.java
@@ -0,0 +1,307 @@
+/*
+ * 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.binary;
+
+import org.apache.ignite.internal.binary.BinaryEnumObjectImpl;
+import org.apache.ignite.internal.binary.BinaryFieldImpl;
+import org.apache.ignite.internal.binary.BinaryObjectExImpl;
+import org.apache.ignite.internal.binary.BinarySerializedFieldComparator;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+import java.util.HashMap;
+
+/**
+ * Identity resolver implementation which use the list of provided fields to calculate the hash code and to perform
+ * equality checks.
+ * <p>
+ * Standard polynomial function with multiplier {@code 31} is used to calculate hash code. For example, for three
+ * fields {@code [a, b, c]}it would be {@code hash = 31 * (31 * a + b) + c}. Order of fields is important.
+ */
+public class BinaryFieldIdentityResolver extends BinaryAbstractIdentityResolver {
+    /** Mutex for synchronization. */
+    private final Object mux = new Object();
+
+    /** Cached single accessor. */
+    private volatile FieldAccessor accessor;
+
+    /** Cached accessors used when multiple (typeId, schemaId) pairs are met. */
+    private volatile HashMap<Long, FieldAccessor> accessors;
+
+    /** Field names. */
+    private String[] fieldNames;
+
+    /**
+     * Default constructor.
+     */
+    public BinaryFieldIdentityResolver() {
+        // No-op.
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param other Other instance.
+     */
+    public BinaryFieldIdentityResolver(BinaryFieldIdentityResolver other) {
+        fieldNames = other.fieldNames;
+    }
+
+    /**
+     * @return Fields list to hash/compare objects based upon.
+     */
+    public String[] getFieldNames() {
+        return fieldNames;
+    }
+
+    /**
+     * Set field names.
+     *
+     * @param fieldNames Field names.
+     * @return {@code this} for chaining.
+     */
+    public BinaryFieldIdentityResolver setFieldNames(String... fieldNames) {
+        this.fieldNames = fieldNames;
+
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode0(BinaryObject obj) {
+        if (obj instanceof BinaryObjectExImpl) {
+            BinaryObjectExImpl obj0 = (BinaryObjectExImpl)obj;
+
+            if (obj0.hasSchema()) {
+                // Handle optimized case.
+                FieldAccessor accessor = accessor(obj0, obj0.typeId(), obj0.schemaId());
+
+                assert accessor != null;
+
+                return accessor.hashCode(obj0);
+            }
+        }
+        else if (obj instanceof BinaryEnumObjectImpl)
+            throw new BinaryObjectException("Field identity resolver cannot be used with enums: " + obj);
+
+        // Handle regular case.
+        int hash = 0;
+
+        for (String fieldName : fieldNames) {
+            Object val = obj.field(fieldName);
+
+            hash = 31 * hash + (val != null ? val.hashCode() : 0);
+        }
+
+        return hash;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals0(BinaryObject o1, BinaryObject o2) {
+        if (o1 instanceof BinaryObjectExImpl && o2 instanceof BinaryObjectExImpl) {
+            BinaryObjectExImpl ex1 = (BinaryObjectExImpl) o1;
+            BinaryObjectExImpl ex2 = (BinaryObjectExImpl) o2;
+
+            int typeId = ex1.typeId();
+
+            if (typeId != ex2.typeId())
+                return false;
+
+            if (ex1.hasSchema() && ex2.hasSchema()) {
+                // Optimistic case: both objects have schemas.
+                int schemaId1 = ex1.schemaId();
+                int schemaId2 = ex2.schemaId();
+
+                FieldAccessor accessor1 = accessor(ex1, typeId, schemaId1);
+
+                FieldAccessor accessor2;
+
+                if (schemaId1 == schemaId2)
+                    accessor2 = accessor1;
+                else
+                    accessor2 = accessor(ex2, typeId, schemaId2);
+
+                // Even better case: compare fields without deserialization.
+                BinarySerializedFieldComparator comp1 = ex1.createFieldComparator();
+                BinarySerializedFieldComparator comp2 = ex2.createFieldComparator();
+
+                for (int i = 0; i < fieldNames.length; i++) {
+                    comp1.findField(accessor1.orders[i]);
+                    comp2.findField(accessor2.orders[i]);
+
+                    if (!BinarySerializedFieldComparator.equals(comp1, comp2))
+                        return false;
+                }
+
+                return true;
+            }
+            else
+                // Pessimistic case: object of unknown types, or without schemas. Have to read fields in usual way.
+                return equalsSlow(ex1, ex2);
+        }
+
+        if (o1 instanceof BinaryEnumObjectImpl)
+            throw new BinaryObjectException("Field identity resolver cannot be used with enums: " + o1);
+
+        if (o2 instanceof BinaryEnumObjectImpl)
+            throw new BinaryObjectException("Field identity resolver cannot be used with enums: " + o2);
+
+        return o1.type().typeId() == o2.type().typeId() && equalsSlow(o1, o2);
+    }
+
+    /**
+     * Slow-path equals routine: regular fields comparison.
+     *
+     * @param o1 Object 1.
+     * @param o2 Object 2.
+     * @return Result.
+     */
+    private boolean equalsSlow(BinaryObject o1, BinaryObject o2) {
+        for (String fieldName : fieldNames) {
+            Object val1 = o1.field(fieldName);
+            Object val2 = o2.field(fieldName);
+
+            if (!F.eq(val1, val2))
+                return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Get fields accessor for the given object.
+     *
+     * @param obj Object.
+     * @param typId Type ID.
+     * @param schemaId Schema ID.
+     * @return Accessor.
+     */
+    private FieldAccessor accessor(BinaryObjectExImpl obj, int typId, int schemaId) {
+        // Try getting single accessor.
+        FieldAccessor res = accessor;
+
+        if (res != null && res.applicableTo(typId, schemaId))
+            return res;
+
+        // Try reading from map.
+        long key = ((long)typId << 32) + schemaId;
+
+        HashMap<Long, FieldAccessor> accessors0 = accessors;
+
+        if (accessors0 != null) {
+            res = accessors0.get(key);
+
+            if (res != null)
+                return res;
+        }
+
+        // Failed to get from cache, go to locking.
+        synchronized (mux) {
+            // Create accessor.
+            int[] orders = new int[fieldNames.length];
+
+            BinaryType type = obj.type();
+
+            for (int i = 0; i < fieldNames.length; i++) {
+                BinaryFieldImpl field = (BinaryFieldImpl)type.field(fieldNames[i]);
+
+                orders[i] = field.fieldOrder(obj);
+            }
+
+            res = new FieldAccessor(typId, schemaId, orders);
+
+            // Set accessor.
+            if (accessor == null)
+                accessor = res;
+            else {
+                if (accessors == null) {
+                    accessor = null;
+
+                    accessors0 = new HashMap<>();
+                }
+                else
+                    accessors0 = new HashMap<>(accessors);
+
+                accessors0.put(key, res);
+
+                accessors = accessors0;
+            }
+        }
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(BinaryFieldIdentityResolver.class, this);
+    }
+
+    /**
+     * Optimized fields accessor.
+     */
+    private static class FieldAccessor {
+        /** Type ID. */
+        private final int typeId;
+
+        /** Schema ID. */
+        private final int schemaId;
+
+        /** Field orders. */
+        private final int[] orders;
+
+        /**
+         * Constructor.
+         *
+         * @param typeId Type ID.
+         * @param schemaId Schema ID.
+         * @param orders Field orders.
+         */
+        private FieldAccessor(int typeId, int schemaId, int[] orders) {
+            this.typeId = typeId;
+            this.schemaId = schemaId;
+            this.orders = orders;
+        }
+
+        /**
+         * Check whether object is applicable to that hash code accessor.
+         * @param expTypeId Expected schema ID.
+         * @param expSchemaId Expected schema ID.
+         * @return {@code True} if matches.
+         */
+        private boolean applicableTo(int expTypeId, int expSchemaId) {
+            return typeId == expTypeId && schemaId == expSchemaId;
+        }
+
+        /**
+         * Calculate object hash code.
+         *
+         * @param obj Object.
+         * @return Hash code.
+         */
+        private int hashCode(BinaryObjectExImpl obj) {
+            int hash = 0;
+
+            for (int order : orders) {
+                Object val = obj.fieldByOrder(order);
+
+                hash = 31 * hash + (val != null ? val.hashCode() : 0);
+            }
+
+            return hash;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/86d143bb/modules/core/src/main/java/org/apache/ignite/binary/BinaryIdentityResolver.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/binary/BinaryIdentityResolver.java b/modules/core/src/main/java/org/apache/ignite/binary/BinaryIdentityResolver.java
new file mode 100644
index 0000000..9796eca
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/binary/BinaryIdentityResolver.java
@@ -0,0 +1,42 @@
+/*
+ * 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.binary;
+
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Interface to compute hash codes for new binary objects and compare them for equality.
+ */
+public interface BinaryIdentityResolver {
+    /**
+     * Compute hash code for binary object.
+     *
+     * @param obj Binary object.
+     * @return Hash code value.
+     */
+    public int hashCode(BinaryObject obj);
+
+    /**
+     * Compare two binary objects for equality.
+     *
+     * @param o1 First object.
+     * @param o2 Second object.
+     * @return {@code True} if both objects are equal.
+     */
+    public boolean equals(@Nullable BinaryObject o1, @Nullable BinaryObject o2);
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/86d143bb/modules/core/src/main/java/org/apache/ignite/binary/BinaryTypeConfiguration.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/binary/BinaryTypeConfiguration.java b/modules/core/src/main/java/org/apache/ignite/binary/BinaryTypeConfiguration.java
index fea0af7..d95e0ae 100644
--- a/modules/core/src/main/java/org/apache/ignite/binary/BinaryTypeConfiguration.java
+++ b/modules/core/src/main/java/org/apache/ignite/binary/BinaryTypeConfiguration.java
@@ -21,6 +21,7 @@ import org.apache.ignite.configuration.BinaryConfiguration;
 import org.apache.ignite.configuration.IgniteConfiguration;
 import org.apache.ignite.internal.util.typedef.internal.A;
 import org.apache.ignite.internal.util.typedef.internal.S;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Defines configuration properties for a specific binary type. Providing per-type
@@ -42,6 +43,9 @@ public class BinaryTypeConfiguration {
     /** Serializer. */
     private BinarySerializer serializer;
 
+    /** Identity. */
+    private BinaryIdentityResolver identityRslvr;
+
     /** Enum flag. */
     private boolean isEnum;
 
@@ -60,10 +64,11 @@ public class BinaryTypeConfiguration {
     public BinaryTypeConfiguration(BinaryTypeConfiguration other) {
         A.notNull(other, "other");
 
-        typeName = other.typeName;
+        identityRslvr = other.identityRslvr;
         idMapper = other.idMapper;
-        serializer = other.serializer;
         isEnum = other.isEnum;
+        serializer = other.serializer;
+        typeName = other.typeName;
     }
 
     /**
@@ -146,6 +151,24 @@ public class BinaryTypeConfiguration {
     }
 
     /**
+     * Gets identity resolver.
+     *
+     * @return Identity resolver.
+     */
+    @Nullable public BinaryIdentityResolver getIdentityResolver() {
+        return identityRslvr;
+    }
+
+    /**
+     * Sets identity resolver.
+     *
+     * @param identityRslvr Identity resolver.
+     */
+    public void setIdentityResolver(@Nullable BinaryIdentityResolver identityRslvr) {
+        this.identityRslvr = identityRslvr;
+    }
+
+    /**
      * Gets whether this is enum type.
      *
      * @return {@code True} if enum.

http://git-wip-us.apache.org/repos/asf/ignite/blob/86d143bb/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 9758cfc..fc3b921 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
@@ -20,8 +20,10 @@ package org.apache.ignite.cache;
 import java.io.Serializable;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.Set;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.A;
 
@@ -42,6 +44,9 @@ public class QueryEntity implements Serializable {
     /** Fields available for query. A map from field name to type name. */
     private LinkedHashMap<String, String> fields = new LinkedHashMap<>();
 
+    /** Set of field names that belong to the key. */
+    private Set<String> keyFields = new HashSet<>();
+
     /** Aliases. */
     private Map<String, String> aliases = new HashMap<>();
 
@@ -123,6 +128,28 @@ public class QueryEntity implements Serializable {
     }
 
     /**
+     * Gets query fields for this query pair that belongs to the key. We need this for the cases when no key-value classes
+     * are present on cluster nodes, and we need to build/modify keys and values during SQL DML operations.
+     * Thus, setting this parameter in XML is not mandatory and should be based on particular use case.
+     *
+     * @return Set of names of key fields.
+     */
+    public Set<String> getKeyFields() {
+        return keyFields;
+    }
+
+    /**
+     * Gets query fields for this query pair that belongs to the key. We need this for the cases when no key-value classes
+     * are present on cluster nodes, and we need to build/modify keys and values during SQL DML operations.
+     * Thus, setting this parameter in XML is not mandatory and should be based on particular use case.
+     *
+     * @param keyFields Set of names of key fields.
+     */
+    public void setKeyFields(Set<String> keyFields) {
+        this.keyFields = keyFields;
+    }
+
+    /**
      * Gets a collection of index entities.
      *
      * @return Collection of index entities.

http://git-wip-us.apache.org/repos/asf/ignite/blob/86d143bb/modules/core/src/main/java/org/apache/ignite/cache/query/SqlFieldsQuery.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/cache/query/SqlFieldsQuery.java b/modules/core/src/main/java/org/apache/ignite/cache/query/SqlFieldsQuery.java
index d3f85af..9b17e78 100644
--- a/modules/core/src/main/java/org/apache/ignite/cache/query/SqlFieldsQuery.java
+++ b/modules/core/src/main/java/org/apache/ignite/cache/query/SqlFieldsQuery.java
@@ -44,7 +44,7 @@ import org.apache.ignite.internal.util.typedef.internal.S;
  *
  * @see IgniteCache#query(Query)
  */
-public final class SqlFieldsQuery extends Query<List<?>> {
+public class SqlFieldsQuery extends Query<List<?>> {
     /** */
     private static final long serialVersionUID = 0L;
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/86d143bb/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 f9c114b..56fc5b4 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
@@ -31,6 +31,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.Set;
 import java.util.TreeSet;
 import javax.cache.Cache;
 import javax.cache.CacheException;
@@ -2198,7 +2199,7 @@ public class CacheConfiguration<K, V> extends MutableConfiguration<K, V> {
      * @param desc Type descriptor.
      * @return Type metadata.
      */
-    static QueryEntity convert(TypeDescriptor desc) {
+    private static QueryEntity convert(TypeDescriptor desc) {
         QueryEntity entity = new QueryEntity();
 
         // Key and val types.
@@ -2208,6 +2209,8 @@ public class CacheConfiguration<K, V> extends MutableConfiguration<K, V> {
         for (ClassProperty prop : desc.props.values())
             entity.addQueryField(prop.fullName(), U.box(prop.type()).getName(), prop.alias());
 
+        entity.setKeyFields(desc.keyProperties);
+
         QueryIndex txtIdx = null;
 
         Collection<QueryIndex> idxs = new ArrayList<>();
@@ -2355,7 +2358,7 @@ public class CacheConfiguration<K, V> extends MutableConfiguration<K, V> {
 
                     processAnnotation(key, sqlAnn, txtAnn, field.getType(), prop, type);
 
-                    type.addProperty(prop, true);
+                    type.addProperty(prop, key, true);
                 }
             }
 
@@ -2377,7 +2380,7 @@ public class CacheConfiguration<K, V> extends MutableConfiguration<K, V> {
 
                     processAnnotation(key, sqlAnn, txtAnn, mtd.getReturnType(), prop, type);
 
-                    type.addProperty(prop, true);
+                    type.addProperty(prop, key, true);
                 }
             }
         }
@@ -2461,6 +2464,10 @@ public class CacheConfiguration<K, V> extends MutableConfiguration<K, V> {
 
         /** */
         @GridToStringInclude
+        private final Set<String> keyProperties = new HashSet<>();
+
+        /** */
+        @GridToStringInclude
         private final Map<String, IndexDescriptor> indexes = new HashMap<>();
 
         /** */
@@ -2567,15 +2574,19 @@ public class CacheConfiguration<K, V> extends MutableConfiguration<K, V> {
          * Adds property to the type descriptor.
          *
          * @param prop Property.
+         * @param key Property ownership flag (key or not).
          * @param failOnDuplicate Fail on duplicate flag.
          */
-        public void addProperty(ClassProperty prop, boolean failOnDuplicate) {
+        void addProperty(ClassProperty prop, boolean key, boolean failOnDuplicate) {
             String name = prop.fullName();
 
             if (props.put(name, prop) != null && failOnDuplicate)
                 throw new CacheException("Property with name '" + name + "' already exists.");
 
             fields.put(name, prop.type());
+
+            if (key)
+                keyProperties.add(name);
         }
 
         /**

http://git-wip-us.apache.org/repos/asf/ignite/blob/86d143bb/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryClassDescriptor.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryClassDescriptor.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryClassDescriptor.java
index b121337..afe7b37 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryClassDescriptor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryClassDescriptor.java
@@ -751,6 +751,8 @@ public class BinaryClassDescriptor {
                                 schemaReg.addSchema(newSchema.schemaId(), newSchema);
                             }
                         }
+
+                        postWriteHashCode(writer, obj);
                     }
                     finally {
                         writer.popSchema();
@@ -780,6 +782,7 @@ public class BinaryClassDescriptor {
                         writer.schemaId(stableSchema.schemaId());
 
                         postWrite(writer, obj);
+                        postWriteHashCode(writer, obj);
                     }
                     finally {
                         writer.popSchema();
@@ -888,6 +891,18 @@ public class BinaryClassDescriptor {
     }
 
     /**
+     * Post-write routine for hash code.
+     *
+     * @param writer Writer.
+     * @param obj Object.
+     */
+    private void postWriteHashCode(BinaryWriterExImpl writer, Object obj) {
+        // No need to call "postWriteHashCode" here because we do not care about hash code.
+        if (!(obj instanceof CacheObjectImpl))
+            writer.postWriteHashCode(registered ? null : cls.getName());
+    }
+
+    /**
      * @return Instance.
      * @throws BinaryObjectException In case of error.
      */

http://git-wip-us.apache.org/repos/asf/ignite/blob/86d143bb/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
index cc18318..f1f205d 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
@@ -26,6 +26,7 @@ import org.apache.ignite.binary.BinaryIdMapper;
 import org.apache.ignite.binary.BinaryInvalidTypeException;
 import org.apache.ignite.binary.BinaryNameMapper;
 import org.apache.ignite.binary.BinaryObjectException;
+import org.apache.ignite.binary.BinaryIdentityResolver;
 import org.apache.ignite.binary.BinaryReflectiveSerializer;
 import org.apache.ignite.binary.BinarySerializer;
 import org.apache.ignite.binary.BinaryType;
@@ -135,7 +136,7 @@ public class BinaryContext {
     /** Set of system classes that should be marshalled with BinaryMarshaller. */
     private static final Set<String> BINARYLIZABLE_SYS_CLSS;
 
-    /** Binarylizable system classes set initialization. */
+    /* Binarylizable system classes set initialization. */
     static {
         Set<String> sysClss = new HashSet<>();
 
@@ -222,6 +223,9 @@ public class BinaryContext {
     /** Maps className to mapper */
     private final ConcurrentMap<String, BinaryInternalMapper> cls2Mappers = new ConcurrentHashMap8<>(0);
 
+    /** Affinity key field names. */
+    private final ConcurrentMap<Integer, BinaryIdentityResolver> identities = new ConcurrentHashMap8<>(0);
+
     /** */
     private BinaryMetadataHandler metaHnd;
 
@@ -433,39 +437,29 @@ public class BinaryContext {
                     throw new BinaryObjectException("Class name is required for binary type configuration.");
 
                 // Resolve mapper.
-                BinaryIdMapper idMapper = globalIdMapper;
-
-                if (typeCfg.getIdMapper() != null)
-                    idMapper = typeCfg.getIdMapper();
-
-                BinaryNameMapper nameMapper = globalNameMapper;
-
-                if (typeCfg.getNameMapper() != null)
-                    nameMapper = typeCfg.getNameMapper();
+                BinaryIdMapper idMapper = U.firstNotNull(typeCfg.getIdMapper(), globalIdMapper);
+                BinaryNameMapper nameMapper = U.firstNotNull(typeCfg.getNameMapper(), globalNameMapper);
+                BinarySerializer serializer = U.firstNotNull(typeCfg.getSerializer(), globalSerializer);
+                BinaryIdentityResolver identity = typeCfg.getIdentityResolver();
 
                 BinaryInternalMapper mapper = resolveMapper(nameMapper, idMapper);
 
-                // Resolve serializer.
-                BinarySerializer serializer = globalSerializer;
-
-                if (typeCfg.getSerializer() != null)
-                    serializer = typeCfg.getSerializer();
-
                 if (clsName.endsWith(".*")) {
                     String pkgName = clsName.substring(0, clsName.length() - 2);
 
                     for (String clsName0 : classesInPackage(pkgName))
-                        descs.add(clsName0, mapper, serializer, affFields.get(clsName0),
+                        descs.add(clsName0, mapper, serializer, identity, affFields.get(clsName0),
                             typeCfg.isEnum(), true);
                 }
                 else
-                    descs.add(clsName, mapper, serializer, affFields.get(clsName),
+                    descs.add(clsName, mapper, serializer, identity, affFields.get(clsName),
                         typeCfg.isEnum(), false);
             }
         }
 
         for (TypeDescriptor desc : descs.descriptors())
-            registerUserType(desc.clsName, desc.mapper, desc.serializer, desc.affKeyFieldName, desc.isEnum);
+            registerUserType(desc.clsName, desc.mapper, desc.serializer, desc.identity, desc.affKeyFieldName,
+                desc.isEnum);
 
         BinaryInternalMapper globalMapper = resolveMapper(globalNameMapper, globalIdMapper);
 
@@ -1086,6 +1080,7 @@ public class BinaryContext {
      * @param clsName Class name.
      * @param mapper ID mapper.
      * @param serializer Serializer.
+     * @param identity Type identity.
      * @param affKeyFieldName Affinity key field name.
      * @param isEnum If enum.
      * @throws BinaryObjectException In case of error.
@@ -1094,6 +1089,7 @@ public class BinaryContext {
     public void registerUserType(String clsName,
         BinaryInternalMapper mapper,
         @Nullable BinarySerializer serializer,
+        @Nullable BinaryIdentityResolver identity,
         @Nullable String affKeyFieldName,
         boolean isEnum)
         throws BinaryObjectException {
@@ -1114,14 +1110,19 @@ public class BinaryContext {
 
         //Workaround for IGNITE-1358
         if (predefinedTypes.get(id) != null)
-            throw new BinaryObjectException("Duplicate type ID [clsName=" + clsName + ", id=" + id + ']');
+            throw duplicateTypeIdException(clsName, id);
 
         if (typeId2Mapper.put(id, mapper) != null)
-            throw new BinaryObjectException("Duplicate type ID [clsName=" + clsName + ", id=" + id + ']');
+            throw duplicateTypeIdException(clsName, id);
+
+        if (identity != null) {
+            if (identities.put(id, identity) != null)
+                throw duplicateTypeIdException(clsName, id);
+        }
 
         if (affKeyFieldName != null) {
             if (affKeyFieldNames.put(id, affKeyFieldName) != null)
-                throw new BinaryObjectException("Duplicate type ID [clsName=" + clsName + ", id=" + id + ']');
+                throw duplicateTypeIdException(clsName, id);
         }
 
         cls2Mappers.put(clsName, mapper);
@@ -1163,6 +1164,16 @@ public class BinaryContext {
     }
 
     /**
+     * Throw exception on class duplication.
+     *
+     * @param clsName Class name.
+     * @param id Type id.
+     */
+    private static BinaryObjectException duplicateTypeIdException(String clsName, int id) {
+        return new BinaryObjectException("Duplicate type ID [clsName=" + clsName + ", id=" + id + ']');
+    }
+
+    /**
      * Check whether reflective serializer can be used for class.
      *
      * @param cls Class.
@@ -1208,6 +1219,14 @@ public class BinaryContext {
 
     /**
      * @param typeId Type ID.
+     * @return Type identity.
+     */
+    public BinaryIdentityResolver identity(int typeId) {
+        return identities.get(typeId);
+    }
+
+    /**
+     * @param typeId Type ID.
      * @param meta Meta data.
      * @throws BinaryObjectException In case of error.
      */
@@ -1315,6 +1334,7 @@ public class BinaryContext {
          * @param clsName Class name.
          * @param mapper Mapper.
          * @param serializer Serializer.
+         * @param identity Key hashing mode.
          * @param affKeyFieldName Affinity key field name.
          * @param isEnum Enum flag.
          * @param canOverride Whether this descriptor can be override.
@@ -1323,6 +1343,7 @@ public class BinaryContext {
         private void add(String clsName,
             BinaryInternalMapper mapper,
             BinarySerializer serializer,
+            BinaryIdentityResolver identity,
             String affKeyFieldName,
             boolean isEnum,
             boolean canOverride)
@@ -1330,6 +1351,7 @@ public class BinaryContext {
             TypeDescriptor desc = new TypeDescriptor(clsName,
                 mapper,
                 serializer,
+                identity,
                 affKeyFieldName,
                 isEnum,
                 canOverride);
@@ -1365,6 +1387,9 @@ public class BinaryContext {
         /** Serializer. */
         private BinarySerializer serializer;
 
+        /** Type identity. */
+        private BinaryIdentityResolver identity;
+
         /** Affinity key field name. */
         private String affKeyFieldName;
 
@@ -1376,19 +1401,21 @@ public class BinaryContext {
 
         /**
          * Constructor.
-         *
          * @param clsName Class name.
          * @param mapper ID mapper.
          * @param serializer Serializer.
+         * @param identity Key hashing mode.
          * @param affKeyFieldName Affinity key field name.
          * @param isEnum Enum type.
          * @param canOverride Whether this descriptor can be override.
          */
         private TypeDescriptor(String clsName, BinaryInternalMapper mapper,
-            BinarySerializer serializer, String affKeyFieldName, boolean isEnum, boolean canOverride) {
+            BinarySerializer serializer, BinaryIdentityResolver identity, String affKeyFieldName, boolean isEnum,
+            boolean canOverride) {
             this.clsName = clsName;
             this.mapper = mapper;
             this.serializer = serializer;
+            this.identity = identity;
             this.affKeyFieldName = affKeyFieldName;
             this.isEnum = isEnum;
             this.canOverride = canOverride;
@@ -1406,6 +1433,7 @@ public class BinaryContext {
             if (canOverride) {
                 mapper = other.mapper;
                 serializer = other.serializer;
+                identity = other.identity;
                 affKeyFieldName = other.affKeyFieldName;
                 isEnum = other.isEnum;
                 canOverride = other.canOverride;

http://git-wip-us.apache.org/repos/asf/ignite/blob/86d143bb/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryFieldImpl.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryFieldImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryFieldImpl.java
index 78ed17a..59e79fb 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryFieldImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryFieldImpl.java
@@ -50,7 +50,6 @@ public class BinaryFieldImpl implements BinaryField {
     public BinaryFieldImpl(int typeId, BinarySchemaRegistry schemas, String fieldName, int fieldId) {
         assert typeId != 0;
         assert schemas != null;
-        assert fieldName != null;
         assert fieldId != 0;
 
         this.typeId = typeId;
@@ -64,6 +63,13 @@ public class BinaryFieldImpl implements BinaryField {
         return fieldName;
     }
 
+    /**
+     * @return Field ID.
+     */
+    public int fieldId() {
+        return fieldId;
+    }
+
     /** {@inheritDoc} */
     @Override public boolean exists(BinaryObject obj) {
         BinaryObjectExImpl obj0 = (BinaryObjectExImpl)obj;
@@ -87,7 +93,7 @@ public class BinaryFieldImpl implements BinaryField {
      * @param obj Object.
      * @return Field offset.
      */
-    private int fieldOrder(BinaryObjectExImpl obj) {
+    public int fieldOrder(BinaryObjectExImpl obj) {
         if (typeId != obj.typeId()) {
             throw new BinaryObjectException("Failed to get field because type ID of passed object differs" +
                 " from type ID this " + BinaryField.class.getSimpleName() + " belongs to [expected=" + typeId +