IGNITE-5255: Implement update methods for JDBC thin driver. This closes #2050. 
This closes #2068.


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

Branch: refs/heads/ignite-5267
Commit: 9d2ec1cc24652a14f5c37c0ba36ca5f7c19d2209
Parents: 4024cd3
Author: tledkov-gridgain <tled...@gridgain.com>
Authored: Fri Jun 2 18:06:58 2017 +0300
Committer: devozerov <voze...@gridgain.com>
Committed: Fri Jun 2 18:06:58 2017 +0300

----------------------------------------------------------------------
 .../jdbc/suite/IgniteJdbcDriverTestSuite.java   |  25 +-
 .../JdbcThinAbstractDmlStatementSelfTest.java   | 251 ++++++++++++++
 ...JdbcThinAbstractUpdateStatementSelfTest.java |  40 +++
 .../thin/JdbcThinDeleteStatementSelfTest.java   |  49 +++
 .../JdbcThinDynamicIndexAbstractSelfTest.java   | 326 +++++++++++++++++++
 ...namicIndexAtomicPartitionedNearSelfTest.java |  26 ++
 ...inDynamicIndexAtomicPartitionedSelfTest.java |  39 +++
 ...hinDynamicIndexAtomicReplicatedSelfTest.java |  39 +++
 ...dexTransactionalPartitionedNearSelfTest.java |  26 ++
 ...icIndexTransactionalPartitionedSelfTest.java |  39 +++
 ...micIndexTransactionalReplicatedSelfTest.java |  39 +++
 .../thin/JdbcThinInsertStatementSelfTest.java   | 185 +++++++++++
 .../thin/JdbcThinMergeStatementSelfTest.java    | 130 ++++++++
 .../thin/JdbcThinUpdateStatementSelfTest.java   |  50 +++
 .../jdbc/thin/JdbcThinPreparedStatement.java    |  34 +-
 .../internal/jdbc/thin/JdbcThinResultSet.java   |  16 +-
 .../internal/jdbc/thin/JdbcThinStatement.java   |  86 +++--
 17 files changed, 1356 insertions(+), 44 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/9d2ec1cc/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 e0e6f31..121b8df 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
@@ -32,13 +32,23 @@ import org.apache.ignite.jdbc.JdbcPreparedStatementSelfTest;
 import org.apache.ignite.jdbc.JdbcResultSetSelfTest;
 import org.apache.ignite.jdbc.JdbcStatementSelfTest;
 import org.apache.ignite.jdbc.thin.JdbcThinComplexQuerySelfTest;
-import org.apache.ignite.jdbc.thin.JdbcThinConnectionSelfTest;
+import org.apache.ignite.jdbc.thin.JdbcThinDeleteStatementSelfTest;
+import 
org.apache.ignite.jdbc.thin.JdbcThinDynamicIndexAtomicPartitionedNearSelfTest;
+import 
org.apache.ignite.jdbc.thin.JdbcThinDynamicIndexAtomicPartitionedSelfTest;
+import 
org.apache.ignite.jdbc.thin.JdbcThinDynamicIndexAtomicReplicatedSelfTest;
+import 
org.apache.ignite.jdbc.thin.JdbcThinDynamicIndexTransactionalPartitionedNearSelfTest;
+import 
org.apache.ignite.jdbc.thin.JdbcThinDynamicIndexTransactionalPartitionedSelfTest;
+import 
org.apache.ignite.jdbc.thin.JdbcThinDynamicIndexTransactionalReplicatedSelfTest;
 import org.apache.ignite.jdbc.thin.JdbcThinEmptyCacheSelfTest;
+import org.apache.ignite.jdbc.thin.JdbcThinInsertStatementSelfTest;
+import org.apache.ignite.jdbc.thin.JdbcThinMergeStatementSelfTest;
 import org.apache.ignite.jdbc.thin.JdbcThinMetadataSelfTest;
 import org.apache.ignite.jdbc.thin.JdbcThinNoDefaultSchemaTest;
 import org.apache.ignite.jdbc.thin.JdbcThinPreparedStatementSelfTest;
 import org.apache.ignite.jdbc.thin.JdbcThinResultSetSelfTest;
 import org.apache.ignite.jdbc.thin.JdbcThinStatementSelfTest;
+import org.apache.ignite.jdbc.thin.JdbcThinConnectionSelfTest;
+import org.apache.ignite.jdbc.thin.JdbcThinUpdateStatementSelfTest;
 
 /**
  * JDBC driver test suite.
@@ -102,6 +112,19 @@ public class IgniteJdbcDriverTestSuite extends TestSuite {
         suite.addTest(new TestSuite(JdbcThinEmptyCacheSelfTest.class));
         suite.addTest(new TestSuite(JdbcThinMetadataSelfTest.class));
 
+        suite.addTest(new TestSuite(JdbcThinInsertStatementSelfTest.class));
+        suite.addTest(new TestSuite(JdbcThinUpdateStatementSelfTest.class));
+        suite.addTest(new TestSuite(JdbcThinMergeStatementSelfTest.class));
+        suite.addTest(new TestSuite(JdbcThinDeleteStatementSelfTest.class));
+
+        // New thin JDBC driver, DDL tests
+        suite.addTest(new 
TestSuite(JdbcThinDynamicIndexAtomicPartitionedNearSelfTest.class));
+        suite.addTest(new 
TestSuite(JdbcThinDynamicIndexAtomicPartitionedSelfTest.class));
+        suite.addTest(new 
TestSuite(JdbcThinDynamicIndexAtomicReplicatedSelfTest.class));
+        suite.addTest(new 
TestSuite(JdbcThinDynamicIndexTransactionalPartitionedNearSelfTest.class));
+        suite.addTest(new 
TestSuite(JdbcThinDynamicIndexTransactionalPartitionedSelfTest.class));
+        suite.addTest(new 
TestSuite(JdbcThinDynamicIndexTransactionalReplicatedSelfTest.class));
+
         return suite;
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/9d2ec1cc/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinAbstractDmlStatementSelfTest.java
----------------------------------------------------------------------
diff --git 
a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinAbstractDmlStatementSelfTest.java
 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinAbstractDmlStatementSelfTest.java
new file mode 100644
index 0000000..eddf61b
--- /dev/null
+++ 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinAbstractDmlStatementSelfTest.java
@@ -0,0 +1,251 @@
+/*
+ * 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.jdbc.thin;
+
+import java.io.Serializable;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.util.Collections;
+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.configuration.OdbcConfiguration;
+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 static org.apache.ignite.cache.CacheMode.PARTITIONED;
+import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_SYNC;
+
+/**
+ * Statement test.
+ */
+public abstract class JdbcThinAbstractDmlStatementSelfTest extends 
JdbcThinAbstractSelfTest {
+    /** IP finder. */
+    private static final TcpDiscoveryIpFinder IP_FINDER = new 
TcpDiscoveryVmIpFinder(true);
+
+    /** URL. */
+    private static final String URL = "jdbc:ignite:thin://127.0.0.1/";
+
+    /** SQL SELECT query for verification. */
+    static final String SQL_SELECT = "select _key, id, firstName, lastName, 
age from Person";
+
+    /** Connection. */
+    protected Connection conn;
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTestsStarted() throws Exception {
+        super.beforeTestsStarted();
+
+        Class.forName("org.apache.ignite.IgniteJdbcThinDriver");
+
+        startGridsMultiThreaded(3);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTestsStopped() throws Exception {
+        stopAllGrids();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        ignite(0).getOrCreateCache(cacheConfig());
+
+        conn = DriverManager.getConnection(URL);
+
+        conn.setSchema(DEFAULT_CACHE_NAME);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        grid(0).destroyCache(DEFAULT_CACHE_NAME);
+
+        conn.close();
+
+        assertTrue(conn.isClosed());
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String 
igniteInstanceName) throws Exception {
+        return getConfiguration0(igniteInstanceName);
+    }
+
+    /**
+     * @param igniteInstanceName Ignite instance name.
+     * @return Grid configuration used for starting the grid.
+     * @throws Exception If failed.
+     */
+    private IgniteConfiguration getConfiguration0(String igniteInstanceName) 
throws Exception {
+        IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
+
+        TcpDiscoverySpi disco = new TcpDiscoverySpi();
+
+        disco.setIpFinder(IP_FINDER);
+
+        cfg.setDiscoverySpi(disco);
+
+        cfg.setConnectorConfiguration(new ConnectorConfiguration());
+
+        cfg.setOdbcConfiguration(new OdbcConfiguration());
+
+        return cfg;
+    }
+
+    /**
+     * @param igniteInstanceName Ignite instance name.
+     * @return Grid configuration used for starting the grid ready for 
manipulating binary objects.
+     * @throws Exception If failed.
+     */
+    IgniteConfiguration getBinaryConfiguration(String igniteInstanceName) 
throws Exception {
+        IgniteConfiguration cfg = getConfiguration0(igniteInstanceName);
+
+        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;
+    }
+
+    /**
+     * @return Cache configuration for non binary marshaller tests.
+     */
+    private CacheConfiguration nonBinCacheConfig() {
+        CacheConfiguration<?,?> cache = defaultCacheConfiguration();
+
+        cache.setCacheMode(PARTITIONED);
+        cache.setBackups(1);
+        cache.setWriteSynchronizationMode(FULL_SYNC);
+        cache.setIndexedTypes(
+            String.class, Person.class
+        );
+
+        return cache;
+    }
+
+    /**
+     * @return Cache configuration for binary marshaller tests.
+     */
+    final CacheConfiguration binaryCacheConfig() {
+        CacheConfiguration<?,?> cache = defaultCacheConfiguration();
+
+        cache.setCacheMode(PARTITIONED);
+        cache.setBackups(1);
+        cache.setWriteSynchronizationMode(FULL_SYNC);
+
+        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);
+
+        cache.setQueryEntities(Collections.singletonList(e));
+
+        return cache;
+    }
+
+    /**
+     * @return Configuration of cache to create.
+     */
+    CacheConfiguration cacheConfig() {
+        return nonBinCacheConfig();
+    }
+
+    /**
+     * 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/9d2ec1cc/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinAbstractUpdateStatementSelfTest.java
----------------------------------------------------------------------
diff --git 
a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinAbstractUpdateStatementSelfTest.java
 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinAbstractUpdateStatementSelfTest.java
new file mode 100644
index 0000000..f71d18a
--- /dev/null
+++ 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinAbstractUpdateStatementSelfTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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.jdbc.thin;
+
+import java.sql.Statement;
+
+/**
+ *
+ */
+public abstract class JdbcThinAbstractUpdateStatementSelfTest extends 
JdbcThinAbstractDmlStatementSelfTest {
+    /** 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/9d2ec1cc/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDeleteStatementSelfTest.java
----------------------------------------------------------------------
diff --git 
a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDeleteStatementSelfTest.java
 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDeleteStatementSelfTest.java
new file mode 100644
index 0000000..9d0665b
--- /dev/null
+++ 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDeleteStatementSelfTest.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.jdbc.thin;
+
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.HashSet;
+
+/**
+ *
+ */
+public class JdbcThinDeleteStatementSelfTest extends 
JdbcThinAbstractUpdateStatementSelfTest {
+    /**
+     * @throws SQLException If failed.
+     */
+    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"))));
+    }
+
+    /**
+     * @throws SQLException If failed.
+     */
+    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/9d2ec1cc/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexAbstractSelfTest.java
----------------------------------------------------------------------
diff --git 
a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexAbstractSelfTest.java
 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexAbstractSelfTest.java
new file mode 100644
index 0000000..3f762fc
--- /dev/null
+++ 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexAbstractSelfTest.java
@@ -0,0 +1,326 @@
+/*
+ * 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.jdbc.thin;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheMode;
+import org.apache.ignite.cache.CacheWriteSynchronizationMode;
+import org.apache.ignite.cache.query.SqlFieldsQuery;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.NearCacheConfiguration;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.testframework.GridTestUtils;
+
+/**
+ * Test that checks indexes handling with JDBC.
+ */
+public abstract class JdbcThinDynamicIndexAbstractSelfTest extends 
JdbcThinAbstractDmlStatementSelfTest {
+    /** */
+    private static final String CREATE_INDEX = "create index idx on Person (id 
desc)";
+
+    /** */
+    private static final String DROP_INDEX = "drop index idx";
+
+    /** */
+    private static final String CREATE_INDEX_IF_NOT_EXISTS = "create index if 
not exists idx on Person (id desc)";
+
+    /** */
+    private static final String DROP_INDEX_IF_EXISTS = "drop index idx if 
exists";
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        super.beforeTest();
+
+        try (PreparedStatement ps =
+            conn.prepareStatement("INSERT INTO Person (_key, id, age, 
firstName, lastName) values (?, ?, ?, ?, ?)")) {
+
+            ps.setString(1, "j");
+            ps.setInt(2, 1);
+            ps.setInt(3, 10);
+            ps.setString(4, "John");
+            ps.setString(5, "Smith");
+            ps.executeUpdate();
+
+            ps.setString(1, "m");
+            ps.setInt(2, 2);
+            ps.setInt(3, 20);
+            ps.setString(4, "Mark");
+            ps.setString(5, "Stone");
+            ps.executeUpdate();
+
+            ps.setString(1, "s");
+            ps.setInt(2, 3);
+            ps.setInt(3, 30);
+            ps.setString(4, "Sarah");
+            ps.setString(5, "Pazzi");
+            ps.executeUpdate();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    @Override CacheConfiguration cacheConfig() {
+        CacheConfiguration ccfg = super.cacheConfig();
+
+        
ccfg.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC);
+
+        ccfg.setCacheMode(cacheMode());
+        ccfg.setAtomicityMode(atomicityMode());
+
+        if (nearCache())
+            ccfg.setNearConfiguration(new NearCacheConfiguration());
+
+        return ccfg;
+    }
+
+    /**
+     * @return Cache mode to use.
+     */
+    protected abstract CacheMode cacheMode();
+
+    /**
+     * @return Cache atomicity mode to use.
+     */
+    protected abstract CacheAtomicityMode atomicityMode();
+
+    /**
+     * @return Whether to use near cache.
+     */
+    protected abstract boolean nearCache();
+
+    /**
+     * Execute given SQL statement.
+     * @param sql Statement.
+     * @throws SQLException if failed.
+     */
+    private void jdbcRun(String sql) throws SQLException {
+        try (Statement stmt = conn.createStatement()) {
+            stmt.execute(sql);
+        }
+    }
+
+    /**
+     * @param rs Result set.
+     * @return The value of the first column at the first row from result set.
+     * @throws SQLException If failed.
+     */
+    private Object getSingleValue(ResultSet rs) throws SQLException {
+        assertEquals(1, rs.getMetaData().getColumnCount());
+
+        assertTrue(rs.next());
+
+        Object res = rs.getObject(1);
+
+        assertTrue(rs.isLast());
+
+        return res;
+    }
+
+    /**
+     * Test that after index creation index is used by queries.
+     * @throws SQLException If failed.
+     */
+    public void testCreateIndex() throws SQLException {
+        assertSize(3);
+
+        assertColumnValues(30, 20, 10);
+
+        jdbcRun(CREATE_INDEX);
+
+        // Test that local queries on all server nodes use new index.
+        for (int i = 0 ; i < 3; i++) {
+            List<List<?>> locRes = 
ignite(i).cache(DEFAULT_CACHE_NAME).query(new SqlFieldsQuery("explain select id 
from " +
+                "Person where id = 5").setLocal(true)).getAll();
+
+            assertEquals(F.asList(
+                Collections.singletonList("SELECT\n" +
+                    "    ID\n" +
+                    "FROM \"" + DEFAULT_CACHE_NAME + "\".PERSON\n" +
+                    "    /* \"" + DEFAULT_CACHE_NAME + "\".IDX: ID = 5 */\n" +
+                    "WHERE ID = 5")
+            ), locRes);
+        }
+
+        assertSize(3);
+
+        assertColumnValues(30, 20, 10);
+    }
+
+    /**
+     * Test that creating an index with duplicate name yields an error.
+     * @throws SQLException If failed.
+     */
+    public void testCreateIndexWithDuplicateName() throws SQLException {
+        jdbcRun(CREATE_INDEX);
+
+        GridTestUtils.assertThrowsAnyCause(log, new Callable<Void>() {
+            @Override public Void call() throws Exception {
+                jdbcRun(CREATE_INDEX);
+
+                return null;
+            }
+        }, IgniteCheckedException.class, "Index already exists: IDX");
+    }
+
+    /**
+     * Test that creating an index with duplicate name does not yield an error 
with {@code IF NOT EXISTS}.
+     * @throws SQLException If failed.
+     */
+    public void testCreateIndexIfNotExists() throws SQLException {
+        jdbcRun(CREATE_INDEX);
+
+        // Despite duplicate name, this does not yield an error.
+        jdbcRun(CREATE_INDEX_IF_NOT_EXISTS);
+    }
+
+    /**
+     * Test that after index drop there are no attempts to use it, and data 
state remains intact.
+     * @throws SQLException If failed.
+     */
+    public void testDropIndex() throws SQLException {
+        assertSize(3);
+
+        jdbcRun(CREATE_INDEX);
+
+        assertSize(3);
+
+        jdbcRun(DROP_INDEX);
+
+        // Test that no local queries on server nodes use new index.
+        for (int i = 0 ; i < 3; i++) {
+            List<List<?>> locRes = 
ignite(i).cache(DEFAULT_CACHE_NAME).query(new SqlFieldsQuery("explain select id 
from " +
+                "Person where id = 5").setLocal(true)).getAll();
+
+            assertEquals(F.asList(
+                Collections.singletonList("SELECT\n" +
+                    "    ID\n" +
+                    "FROM \"" + DEFAULT_CACHE_NAME + "\".PERSON\n" +
+                    "    /* \"" + DEFAULT_CACHE_NAME + "\".PERSON.__SCAN_ 
*/\n" +
+                    "WHERE ID = 5")
+            ), locRes);
+        }
+
+        assertSize(3);
+    }
+
+    /**
+     * Test that dropping a non-existent index yields an error.
+     */
+    public void testDropMissingIndex() {
+        GridTestUtils.assertThrowsAnyCause(log, new Callable<Void>() {
+            @Override public Void call() throws Exception {
+                jdbcRun(DROP_INDEX);
+
+                return null;
+            }
+        }, IgniteCheckedException.class, "Index doesn't exist: IDX");
+    }
+
+    /**
+     * Test that dropping a non-existent index does not yield an error with 
{@code IF EXISTS}.
+     * @throws SQLException If failed.
+     */
+    public void testDropMissingIndexIfExists() throws SQLException {
+        // Despite index missing, this does not yield an error.
+        jdbcRun(DROP_INDEX_IF_EXISTS);
+    }
+
+    /**
+     * Test that changes in cache affect index, and vice versa.
+     * @throws SQLException If failed.
+     */
+    public void testIndexState() throws SQLException {
+        IgniteCache<String, Person> cache = cache();
+
+        assertSize(3);
+
+        assertColumnValues(30, 20, 10);
+
+        jdbcRun(CREATE_INDEX);
+
+        assertSize(3);
+
+        assertColumnValues(30, 20, 10);
+
+        cache.remove("m");
+
+        assertColumnValues(30, 10);
+
+        cache.put("a", new Person(4, "someVal", "a", 5));
+
+        assertColumnValues(5, 30, 10);
+
+        jdbcRun(DROP_INDEX);
+
+        assertColumnValues(5, 30, 10);
+    }
+
+    /**
+     * Check that values of {@code field1} match what we expect.
+     * @param vals Expected values.
+     * @throws SQLException If failed.
+     */
+    private void assertColumnValues(int... vals) throws SQLException {
+        try (Statement stmt = conn.createStatement()) {
+            try (ResultSet rs = stmt.executeQuery("SELECT age FROM Person 
ORDER BY id desc")) {
+                assertEquals(1, rs.getMetaData().getColumnCount());
+
+                for (int i = 0; i < vals.length; i++) {
+                    assertTrue("Result set must have " + vals.length + " rows, 
got " + i, rs.next());
+
+                    assertEquals(vals[i], rs.getInt(1));
+                }
+
+                assertFalse("Result set must have exactly " + vals.length + " 
rows", rs.next());
+            }
+        }
+    }
+
+    /**
+     * Do a {@code SELECT COUNT(*)} query to check index state correctness.
+     * @param expSize Expected number of items in table.
+     * @throws SQLException If failed.
+     */
+    private void assertSize(long expSize) throws SQLException {
+        assertEquals(expSize, cache().size());
+
+        try (Statement stmt = conn.createStatement()) {
+            conn.setSchema(DEFAULT_CACHE_NAME);
+
+            try (ResultSet rs = stmt.executeQuery("SELECT COUNT(*) from 
Person")) {
+                assertEquals(expSize, getSingleValue(rs));
+            }
+        }
+    }
+
+    /**
+     * @return Cache.
+     */
+    private IgniteCache<String, Person> cache() {
+        return grid(0).cache(DEFAULT_CACHE_NAME);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/9d2ec1cc/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexAtomicPartitionedNearSelfTest.java
----------------------------------------------------------------------
diff --git 
a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexAtomicPartitionedNearSelfTest.java
 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexAtomicPartitionedNearSelfTest.java
new file mode 100644
index 0000000..e6bf0cb
--- /dev/null
+++ 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexAtomicPartitionedNearSelfTest.java
@@ -0,0 +1,26 @@
+/*
+ * 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.jdbc.thin;
+
+/** */
+public class JdbcThinDynamicIndexAtomicPartitionedNearSelfTest extends 
JdbcThinDynamicIndexAtomicPartitionedSelfTest {
+    /** {@inheritDoc} */
+    @Override protected boolean nearCache() {
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/9d2ec1cc/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexAtomicPartitionedSelfTest.java
----------------------------------------------------------------------
diff --git 
a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexAtomicPartitionedSelfTest.java
 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexAtomicPartitionedSelfTest.java
new file mode 100644
index 0000000..826fc6b
--- /dev/null
+++ 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexAtomicPartitionedSelfTest.java
@@ -0,0 +1,39 @@
+/*
+ * 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.jdbc.thin;
+
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheMode;
+
+/** */
+public class JdbcThinDynamicIndexAtomicPartitionedSelfTest extends 
JdbcThinDynamicIndexAbstractSelfTest {
+    /** {@inheritDoc} */
+    @Override protected CacheMode cacheMode() {
+        return CacheMode.PARTITIONED;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected CacheAtomicityMode atomicityMode() {
+        return CacheAtomicityMode.ATOMIC;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected boolean nearCache() {
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/9d2ec1cc/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexAtomicReplicatedSelfTest.java
----------------------------------------------------------------------
diff --git 
a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexAtomicReplicatedSelfTest.java
 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexAtomicReplicatedSelfTest.java
new file mode 100644
index 0000000..7729d2e
--- /dev/null
+++ 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexAtomicReplicatedSelfTest.java
@@ -0,0 +1,39 @@
+/*
+ * 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.jdbc.thin;
+
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheMode;
+
+/** */
+public class JdbcThinDynamicIndexAtomicReplicatedSelfTest extends 
JdbcThinDynamicIndexAbstractSelfTest {
+    /** {@inheritDoc} */
+    @Override protected CacheMode cacheMode() {
+        return CacheMode.REPLICATED;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected CacheAtomicityMode atomicityMode() {
+        return CacheAtomicityMode.ATOMIC;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected boolean nearCache() {
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/9d2ec1cc/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexTransactionalPartitionedNearSelfTest.java
----------------------------------------------------------------------
diff --git 
a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexTransactionalPartitionedNearSelfTest.java
 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexTransactionalPartitionedNearSelfTest.java
new file mode 100644
index 0000000..2f72108
--- /dev/null
+++ 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexTransactionalPartitionedNearSelfTest.java
@@ -0,0 +1,26 @@
+/*
+ * 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.jdbc.thin;
+
+/** */
+public class JdbcThinDynamicIndexTransactionalPartitionedNearSelfTest extends 
JdbcThinDynamicIndexTransactionalPartitionedSelfTest {
+    /** {@inheritDoc} */
+    @Override protected boolean nearCache() {
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/9d2ec1cc/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexTransactionalPartitionedSelfTest.java
----------------------------------------------------------------------
diff --git 
a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexTransactionalPartitionedSelfTest.java
 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexTransactionalPartitionedSelfTest.java
new file mode 100644
index 0000000..e2c56d7
--- /dev/null
+++ 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexTransactionalPartitionedSelfTest.java
@@ -0,0 +1,39 @@
+/*
+ * 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.jdbc.thin;
+
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheMode;
+
+/** */
+public class JdbcThinDynamicIndexTransactionalPartitionedSelfTest extends 
JdbcThinDynamicIndexAbstractSelfTest {
+    /** {@inheritDoc} */
+    @Override protected CacheMode cacheMode() {
+        return CacheMode.PARTITIONED;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected CacheAtomicityMode atomicityMode() {
+        return CacheAtomicityMode.TRANSACTIONAL;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected boolean nearCache() {
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/9d2ec1cc/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexTransactionalReplicatedSelfTest.java
----------------------------------------------------------------------
diff --git 
a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexTransactionalReplicatedSelfTest.java
 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexTransactionalReplicatedSelfTest.java
new file mode 100644
index 0000000..ba6e1b6
--- /dev/null
+++ 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinDynamicIndexTransactionalReplicatedSelfTest.java
@@ -0,0 +1,39 @@
+/*
+ * 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.jdbc.thin;
+
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheMode;
+
+/** */
+public class JdbcThinDynamicIndexTransactionalReplicatedSelfTest extends 
JdbcThinDynamicIndexAbstractSelfTest {
+    /** {@inheritDoc} */
+    @Override protected CacheMode cacheMode() {
+        return CacheMode.REPLICATED;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected CacheAtomicityMode atomicityMode() {
+        return CacheAtomicityMode.TRANSACTIONAL;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected boolean nearCache() {
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/9d2ec1cc/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinInsertStatementSelfTest.java
----------------------------------------------------------------------
diff --git 
a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinInsertStatementSelfTest.java
 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinInsertStatementSelfTest.java
new file mode 100644
index 0000000..3b86757
--- /dev/null
+++ 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinInsertStatementSelfTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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.jdbc.thin;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+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.IgniteCheckedException;
+import org.apache.ignite.testframework.GridTestUtils;
+
+/**
+ * Statement test.
+ */
+public class JdbcThinInsertStatementSelfTest extends 
JdbcThinAbstractDmlStatementSelfTest {
+    /** 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 " +
+        "(?, ?, ?, ?, ?), (?, ?, ?, ?, ?), (?, ?, ?, ?, ?)";
+
+    /** Arguments for prepared statement. */
+    private final Object [][] args = new Object[][] {
+        {"p1", 1, "John", "White", 25},
+        {"p3", 3, "Mike", "Green", 40},
+        {"p2", 2, "Joe", "Black", 35}
+    };
+
+    /** 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());
+
+        int paramCnt = 1;
+
+        for (Object[] arg : args) {
+            prepStmt.setString(paramCnt++, (String)arg[0]);
+            prepStmt.setInt(paramCnt++, (Integer)arg[1]);
+            prepStmt.setString(paramCnt++, (String)arg[2]);
+            prepStmt.setString(paramCnt++, (String)arg[3]);
+            prepStmt.setInt(paramCnt++, (Integer)arg[4]);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        try (Statement selStmt = conn.createStatement()) {
+            assertTrue(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;
+                }
+            }
+        }
+
+        if (stmt != null && !stmt.isClosed())
+            stmt.close();
+
+        if (prepStmt != null && !prepStmt.isClosed())
+            prepStmt.close();
+
+        assertTrue(prepStmt.isClosed());
+        assertTrue(stmt.isClosed());
+
+        super.afterTest();
+    }
+
+    /**
+     * @throws SQLException If failed.
+     */
+    public void testExecuteUpdate() throws SQLException {
+        assertEquals(3, stmt.executeUpdate(SQL));
+    }
+
+    /**
+     * @throws SQLException If failed.
+     */
+    public void testPreparedExecuteUpdate() throws SQLException {
+        assertEquals(3, prepStmt.executeUpdate());
+    }
+
+    /**
+     * @throws SQLException If failed.
+     */
+    public void testExecute() throws SQLException {
+        assertFalse(stmt.execute(SQL));
+    }
+
+    /**
+     * @throws SQLException If failed.
+     */
+    public void testPreparedExecute() throws SQLException {
+        assertFalse(prepStmt.execute());
+    }
+
+    /**
+     *
+     */
+    public void testDuplicateKeys() {
+        jcache(0).put("p2", new Person(2, "Joe", "Black", 35));
+
+        GridTestUtils.assertThrowsAnyCause(log, new Callable<Object>() {
+            /** {@inheritDoc} */
+            @Override public Object call() throws Exception {
+                return stmt.execute(SQL);
+            }
+        }, IgniteCheckedException.class,
+            "Failed to INSERT some keys because they are already in cache 
[keys=[p2]]");
+
+        assertEquals(3, jcache(0).withKeepBinary().getAll(new 
HashSet<>(Arrays.asList("p1", "p2", "p3"))).size());
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/9d2ec1cc/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMergeStatementSelfTest.java
----------------------------------------------------------------------
diff --git 
a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMergeStatementSelfTest.java
 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMergeStatementSelfTest.java
new file mode 100644
index 0000000..9d9467f
--- /dev/null
+++ 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMergeStatementSelfTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.jdbc.thin;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+/**
+ * MERGE statement test.
+ */
+public class JdbcThinMergeStatementSelfTest extends 
JdbcThinAbstractDmlStatementSelfTest {
+    /** 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 {
+        try (Statement selStmt = conn.createStatement()) {
+            assertTrue(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;
+                }
+            }
+        }
+
+        if (stmt != null && !stmt.isClosed())
+            stmt.close();
+
+        if (prepStmt != null && !prepStmt.isClosed())
+            prepStmt.close();
+
+        assertTrue(prepStmt.isClosed());
+        assertTrue(stmt.isClosed());
+
+        super.afterTest();
+    }
+
+    /**
+     * @throws SQLException If failed.
+     */
+    public void testExecuteUpdate() throws SQLException {
+        assertEquals(3, stmt.executeUpdate(SQL));
+    }
+
+    /**
+     * @throws SQLException If failed.
+     */
+    public void testExecute() throws SQLException {
+        assertFalse(stmt.execute(SQL));
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/9d2ec1cc/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinUpdateStatementSelfTest.java
----------------------------------------------------------------------
diff --git 
a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinUpdateStatementSelfTest.java
 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinUpdateStatementSelfTest.java
new file mode 100644
index 0000000..f749dbe
--- /dev/null
+++ 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinUpdateStatementSelfTest.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.jdbc.thin;
+
+import java.sql.SQLException;
+import java.util.Arrays;
+import org.apache.ignite.cache.query.SqlFieldsQuery;
+import org.apache.ignite.internal.util.typedef.F;
+
+/**
+ *
+ */
+public class JdbcThinUpdateStatementSelfTest extends 
JdbcThinAbstractUpdateStatementSelfTest {
+    /**
+     * @throws SQLException If failed.
+     */
+    public void testExecute() throws SQLException {
+        conn.createStatement().execute("update Person set firstName = 'Jack' 
where " +
+            "cast(substring(_key, 2, 1) as int) % 2 = 0");
+
+        assertEquals(Arrays.asList(F.asList("John"), F.asList("Jack"), 
F.asList("Mike")),
+            jcache(0).query(new SqlFieldsQuery("select firstName from Person 
order by _key")).getAll());
+    }
+
+    /**
+     * @throws SQLException If failed.
+     */
+    public void testExecuteUpdate() throws SQLException {
+        conn.createStatement().executeUpdate("update Person set firstName = 
'Jack' where " +
+                "cast(substring(_key, 2, 1) as int) % 2 = 0");
+
+        assertEquals(Arrays.asList(F.asList("John"), F.asList("Jack"), 
F.asList("Mike")),
+                jcache(0).query(new SqlFieldsQuery("select firstName from 
Person order by _key")).getAll());
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/9d2ec1cc/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinPreparedStatement.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinPreparedStatement.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinPreparedStatement.java
index 0b1cad8..49a78b6 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinPreparedStatement.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinPreparedStatement.java
@@ -48,6 +48,9 @@ public class JdbcThinPreparedStatement extends 
JdbcThinStatement implements Prep
     /** SQL query. */
     private final String sql;
 
+    /** Query arguments. */
+    protected ArrayList<Object> args;
+
     /**
      * Creates new prepared statement.
      *
@@ -62,9 +65,12 @@ public class JdbcThinPreparedStatement extends 
JdbcThinStatement implements Prep
 
     /** {@inheritDoc} */
     @Override public ResultSet executeQuery() throws SQLException {
-        ResultSet rs = super.executeQuery(sql);
+        executeWithArguments();
 
-        args = null;
+        ResultSet rs = getResultSet();
+
+        if (rs == null)
+            throw new SQLException("The query isn't SELECT query: " + sql);
 
         return rs;
     }
@@ -76,9 +82,14 @@ public class JdbcThinPreparedStatement extends 
JdbcThinStatement implements Prep
 
     /** {@inheritDoc} */
     @Override public int executeUpdate() throws SQLException {
-        ensureNotClosed();
+        executeWithArguments();
 
-        throw new SQLFeatureNotSupportedException("Updates are not 
supported.");
+        int res = getUpdateCount();
+
+        if (res == -1)
+            throw new SQLException("The query is not DML statememt: " + sql);
+
+        return res;
     }
 
     /** {@inheritDoc} */
@@ -196,7 +207,20 @@ public class JdbcThinPreparedStatement extends 
JdbcThinStatement implements Prep
 
     /** {@inheritDoc} */
     @Override public boolean execute() throws SQLException {
-        return super.execute(sql);
+        executeWithArguments();
+
+        return rs.isQuery();
+    }
+
+    /**
+     * Execute query with arguments and nullify them afterwards.
+     *
+     * @throws SQLException If failed.
+     */
+    private void executeWithArguments() throws SQLException {
+        execute0(sql, args);
+
+        args = null;
     }
 
     /** {@inheritDoc} */

http://git-wip-us.apache.org/repos/asf/ignite/blob/9d2ec1cc/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinResultSet.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinResultSet.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinResultSet.java
index f4ab444..36f938b 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinResultSet.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinResultSet.java
@@ -107,20 +107,26 @@ public class JdbcThinResultSet implements ResultSet {
      * @param rows Rows.
      * @param isQuery Is Result ser for Select query
      */
-    @SuppressWarnings("OverlyStrongTypeCast") 
JdbcThinResultSet(JdbcThinStatement stmt, long qryId, int fetchSize, boolean 
finished,
-        List<List<Object>> rows, boolean isQuery) {
+    @SuppressWarnings("OverlyStrongTypeCast")
+    JdbcThinResultSet(JdbcThinStatement stmt, long qryId, int fetchSize, 
boolean finished,
+        List<List<Object>> rows, boolean isQuery, long updCnt) {
         assert stmt != null;
         assert fetchSize > 0;
 
         this.stmt = stmt;
         this.qryId = qryId;
         this.fetchSize = fetchSize;
-        this.rows = rows;
         this.finished = finished;
+        this.isQuery = isQuery;
 
-        rowsIter = rows.iterator();
+        if (isQuery) {
+            this.fetchSize = fetchSize;
+            this.rows = rows;
 
-        this.isQuery = isQuery;
+            rowsIter = rows.iterator();
+        }
+        else
+            this.updCnt = updCnt;
     }
 
     /** {@inheritDoc} */

http://git-wip-us.apache.org/repos/asf/ignite/blob/9d2ec1cc/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinStatement.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinStatement.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinStatement.java
index 2bcd4d1..91b8b06 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinStatement.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinStatement.java
@@ -24,7 +24,7 @@ import java.sql.SQLException;
 import java.sql.SQLFeatureNotSupportedException;
 import java.sql.SQLWarning;
 import java.sql.Statement;
-import java.util.ArrayList;
+import java.util.List;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.cache.query.SqlQuery;
 import org.apache.ignite.internal.processors.odbc.jdbc.JdbcQueryExecuteResult;
@@ -54,14 +54,12 @@ public class JdbcThinStatement implements Statement {
     private int timeout;
 
     /** Current result set. */
-    private JdbcThinResultSet rs;
-
-    /** Query arguments. */
-    protected ArrayList<Object> args;
+    protected JdbcThinResultSet rs;
 
     /** Fetch size. */
     private int pageSize = DFLT_PAGE_SIZE;
 
+    /** */
     private boolean alreadyRead;
 
     /**
@@ -77,20 +75,23 @@ public class JdbcThinStatement implements Statement {
 
     /** {@inheritDoc} */
     @Override public ResultSet executeQuery(String sql) throws SQLException {
-        JdbcThinResultSet rs = execute0(sql);
+        execute0(sql, null);
+
+        ResultSet rs = getResultSet();
 
-        if (!rs.isQuery())
-            throw new SQLException("The query isn't SELECT query: [sql=" + sql 
+']');
+        if (rs == null)
+            throw new SQLException("The query isn't SELECT query: " + sql);
 
         return rs;
     }
 
     /**
      * @param sql Sql query.
-     * @return Result set.
+     * @param args Query parameters.
+     *
      * @throws SQLException Onj error.
      */
-    protected JdbcThinResultSet execute0(String sql) throws SQLException {
+    protected void execute0(String sql, List<Object> args) throws SQLException 
{
         ensureNotClosed();
 
         if (rs != null) {
@@ -110,9 +111,8 @@ public class JdbcThinStatement implements Statement {
 
             assert res != null;
 
-            rs = new JdbcThinResultSet(this, res.getQueryId(), pageSize, 
res.last(), res.items(), res.isQuery());
-
-            return rs;
+            rs = new JdbcThinResultSet(this, res.getQueryId(), pageSize, 
res.last(), res.items(),
+                res.isQuery(), res.updateCount());
         }
         catch (IOException e) {
             conn.close();
@@ -126,9 +126,14 @@ public class JdbcThinStatement implements Statement {
 
     /** {@inheritDoc} */
     @Override public int executeUpdate(String sql) throws SQLException {
-        ensureNotClosed();
+        execute0(sql, null);
 
-        throw new SQLFeatureNotSupportedException("Updates are not 
supported.");
+        int res = getUpdateCount();
+
+        if (res == -1)
+            throw new SQLException("The query is not DML statememt: " + sql);
+
+        return res;
     }
 
     /** {@inheritDoc} */
@@ -217,33 +222,48 @@ public class JdbcThinStatement implements Statement {
     @Override public boolean execute(String sql) throws SQLException {
         ensureNotClosed();
 
-        rs = execute0(sql);
+        execute0(sql, null);
 
         return rs.isQuery();
     }
 
     /** {@inheritDoc} */
     @Override public ResultSet getResultSet() throws SQLException {
-        ensureNotClosed();
+        JdbcThinResultSet rs = lastResultSet();
 
-        if (rs == null || !rs.isQuery() || alreadyRead)
-            return null;
+        ResultSet res = rs == null || !rs.isQuery() ? null : rs;
 
-        alreadyRead = true;
+        if (res != null)
+            alreadyRead = true;
 
-        return rs;
+        return res;
     }
 
     /** {@inheritDoc} */
     @Override public int getUpdateCount() throws SQLException {
-        ensureNotClosed();
+        JdbcThinResultSet rs = lastResultSet();
+
+        int res = rs == null || rs.isQuery() ? -1 : (int)rs.updatedCount();
 
-        if (rs == null || rs.isQuery() || alreadyRead)
-            return -1;
+        if (res != -1)
+            alreadyRead = true;
 
-        alreadyRead = true;
+        return res;
+    }
 
-        return (int)rs.updatedCount();
+    /**
+     * Get last result set if any.
+     *
+     * @return Result set or null.
+     * @throws SQLException If failed.
+     */
+    private JdbcThinResultSet lastResultSet() throws SQLException {
+        ensureNotClosed();
+
+        if (rs == null || alreadyRead)
+            return null;
+
+        return rs;
     }
 
     /** {@inheritDoc} */
@@ -341,28 +361,28 @@ public class JdbcThinStatement implements Statement {
     @Override public ResultSet getGeneratedKeys() throws SQLException {
         ensureNotClosed();
 
-        throw new SQLFeatureNotSupportedException("Updates are not 
supported.");
+        throw new SQLFeatureNotSupportedException("Auto-generated columns are 
not supported .");
     }
 
     /** {@inheritDoc} */
     @Override public int executeUpdate(String sql, int autoGeneratedKeys) 
throws SQLException {
         ensureNotClosed();
 
-        throw new SQLFeatureNotSupportedException("Updates are not 
supported.");
+        throw new SQLFeatureNotSupportedException("Auto-generated columns are 
not supported .");
     }
 
     /** {@inheritDoc} */
     @Override public int executeUpdate(String sql, int[] colIndexes) throws 
SQLException {
         ensureNotClosed();
 
-        throw new SQLFeatureNotSupportedException("Updates are not 
supported.");
+        throw new SQLFeatureNotSupportedException("Auto-generated columns are 
not supported .");
     }
 
     /** {@inheritDoc} */
     @Override public int executeUpdate(String sql, String[] colNames) throws 
SQLException {
         ensureNotClosed();
 
-        throw new SQLFeatureNotSupportedException("Updates are not 
supported.");
+        throw new SQLFeatureNotSupportedException("Auto-generated columns are 
not supported .");
     }
 
     /** {@inheritDoc} */
@@ -370,7 +390,7 @@ public class JdbcThinStatement implements Statement {
         ensureNotClosed();
 
         if (autoGeneratedKeys == RETURN_GENERATED_KEYS)
-            throw new SQLFeatureNotSupportedException("Updates are not 
supported.");
+            throw new SQLFeatureNotSupportedException("Auto-generated columns 
are not supported .");
 
         return execute(sql);
     }
@@ -380,7 +400,7 @@ public class JdbcThinStatement implements Statement {
         ensureNotClosed();
 
         if (colIndexes != null && colIndexes.length > 0)
-            throw new SQLFeatureNotSupportedException("Updates are not 
supported.");
+            throw new SQLFeatureNotSupportedException("Auto-generated columns 
are not supported .");
 
         return execute(sql);
     }
@@ -390,7 +410,7 @@ public class JdbcThinStatement implements Statement {
         ensureNotClosed();
 
         if (colNames != null && colNames.length > 0)
-            throw new SQLFeatureNotSupportedException("Updates are not 
supported.");
+            throw new SQLFeatureNotSupportedException("Auto-generated columns 
are not supported .");
 
         return execute(sql);
     }

Reply via email to