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); }