Index: modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcAbstractDmlStatementSelfTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcAbstractDmlStatementSelfTest.java (revision ) +++ modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcAbstractDmlStatementSelfTest.java (revision ) @@ -0,0 +1,245 @@ +/* + * 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; + +import java.io.Serializable; +import java.sql.Connection; +import java.sql.DriverManager; +import java.util.Collections; +import org.apache.ignite.cache.CachePeekMode; +import org.apache.ignite.cache.QueryEntity; +import org.apache.ignite.cache.query.annotations.QuerySqlField; +import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.configuration.ConnectorConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.binary.BinaryMarshaller; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; +import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder; +import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder; +import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; + +import static org.apache.ignite.cache.CacheMode.PARTITIONED; +import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_SYNC; + +/** + * Statement test. + */ +public abstract class JdbcAbstractDmlStatementSelfTest extends GridCommonAbstractTest { + /** IP finder. */ + private static final TcpDiscoveryIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true); + + /** URL. */ + private static final String URL = "jdbc:ignite://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 { + startGridsMultiThreaded(3); + + Class.forName("org.apache.ignite.IgniteJdbcDriver"); + } + + /** {@inheritDoc} */ + @Override protected void afterTestsStopped() throws Exception { + stopAllGrids(); + } + + /** {@inheritDoc} */ + @Override protected void beforeTest() throws Exception { + ignite(0).getOrCreateCache(cacheConfig()); + + conn = DriverManager.getConnection(URL); + } + + /** {@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()); + + 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; + } + } +} Index: modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcDynamicIndexTransactionalPartitionedNearSelfTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcDynamicIndexTransactionalPartitionedNearSelfTest.java (revision ) +++ modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcDynamicIndexTransactionalPartitionedNearSelfTest.java (revision ) @@ -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; + +/** */ +public class JdbcDynamicIndexTransactionalPartitionedNearSelfTest extends JdbcDynamicIndexTransactionalPartitionedSelfTest { + /** {@inheritDoc} */ + @Override protected boolean nearCache() { + return true; + } +} Index: modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcStatement.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcStatement.java (revision 3a917572c9732795d1c027721d79a1a271e3f4cc) +++ modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcStatement.java (revision ) @@ -27,6 +27,8 @@ import java.util.List; import java.util.UUID; import org.apache.ignite.internal.client.GridClientException; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.T2; import org.apache.ignite.internal.util.typedef.internal.U; import static java.sql.ResultSet.CONCUR_READ_ONLY; @@ -70,6 +72,10 @@ /** Fetch size. */ private int fetchSize = DFLT_FETCH_SIZE; + /** Current updated items count. */ + long updateCnt = -1; + + /** * Creates new statement. * @@ -83,10 +89,37 @@ /** {@inheritDoc} */ @Override public ResultSet executeQuery(String sql) throws SQLException { + T2 res = execute0(sql); + + rs = res.get2(); + + return rs; + } + + /** {@inheritDoc} */ + @Override public int executeUpdate(String sql) throws SQLException { + T2 res = execute0(sql); + + // select statement + if (res.get1()) + return 0; + + return (int)updateCnt; + } + + /** + * Internal execute query. + * @param sql Sql statement. + * @return Tuple with (isQuery flag, results set). + * @throws SQLException If failed. + */ + private T2 execute0(String sql) throws SQLException { ensureNotClosed(); rs = null; + updateCnt = -1; + if (sql == null || sql.isEmpty()) throw new SQLException("SQL query is empty"); @@ -105,7 +138,7 @@ else { List msg = JdbcUtils.unmarshal(data); - assert msg.size() == 7; + assert msg.size() == 8; UUID nodeId = (UUID)msg.get(0); UUID futId = (UUID)msg.get(1); @@ -114,8 +147,12 @@ List types = (List)msg.get(4); Collection> fields = (Collection>)msg.get(5); boolean finished = (Boolean)msg.get(6); + boolean isQuery = (Boolean)msg.get(7); - return new JdbcResultSet(this, nodeId, futId, tbls, cols, types, fields, finished, fetchSize); + if (!isQuery) + updateCnt = updateCounterFromQueryResult(fields); + + return new T2(isQuery, new JdbcResultSet(this, nodeId, futId, tbls, cols, types, fields, finished, fetchSize)); } } catch (GridClientException e) { @@ -123,11 +160,29 @@ } } - /** {@inheritDoc} */ - @Override public int executeUpdate(String sql) throws SQLException { - ensureNotClosed(); - - throw new SQLFeatureNotSupportedException("Updates are not supported."); + /** + * @param rows query result. + * @return update counter, if found + * @throws SQLException if getting an update counter from result proved to be impossible. + */ + private static long updateCounterFromQueryResult(Collection> rows) throws SQLException { + if (F.isEmpty(rows)) + return -1; + + if (rows.size() != 1) + throw new SQLException("Expected fetch size of 1 for update operation"); + + List row = rows.iterator().next(); + + if (row.size() != 1) + throw new SQLException("Expected row size of 1 for update operation"); + + Object objRes = row.get(0); + + if (!(objRes instanceof Long)) + throw new SQLException("Unexpected update result type"); + + return (Long)objRes; } /** {@inheritDoc} */ @@ -212,9 +267,11 @@ @Override public boolean execute(String sql) throws SQLException { ensureNotClosed(); - rs = executeQuery(sql); + T2 res = execute0(sql); - return true; + rs = res.get2(); + + return res.get1(); } /** {@inheritDoc} */ @@ -232,7 +289,11 @@ @Override public int getUpdateCount() throws SQLException { ensureNotClosed(); - return -1; + int res = (int)updateCnt; + + updateCnt = -1; + + return res; } /** {@inheritDoc} */ @@ -292,21 +353,21 @@ @Override public void addBatch(String sql) throws SQLException { ensureNotClosed(); - throw new SQLFeatureNotSupportedException("Updates are not supported."); + throw new SQLFeatureNotSupportedException("Batch statements are not supported yet."); } /** {@inheritDoc} */ @Override public void clearBatch() throws SQLException { ensureNotClosed(); - throw new SQLFeatureNotSupportedException("Updates are not supported."); + throw new SQLFeatureNotSupportedException("Batch statements are not supported yet."); } /** {@inheritDoc} */ @Override public int[] executeBatch() throws SQLException { ensureNotClosed(); - throw new SQLFeatureNotSupportedException("Updates are not supported."); + throw new SQLFeatureNotSupportedException("Batch statements are not supported yet."); } /** {@inheritDoc} */ @@ -330,28 +391,37 @@ @Override public ResultSet getGeneratedKeys() throws SQLException { ensureNotClosed(); - throw new SQLFeatureNotSupportedException("Updates are not supported."); + throw new SQLFeatureNotSupportedException("Auto generated keys are not supported."); } /** {@inheritDoc} */ @Override public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { ensureNotClosed(); - throw new SQLFeatureNotSupportedException("Updates are not supported."); + if (autoGeneratedKeys == RETURN_GENERATED_KEYS) + throw new SQLFeatureNotSupportedException("Auto generated keys are not supported."); + + return executeUpdate(sql); } /** {@inheritDoc} */ @Override public int executeUpdate(String sql, int[] colIndexes) throws SQLException { ensureNotClosed(); - throw new SQLFeatureNotSupportedException("Updates are not supported."); + if (!F.isEmpty(colIndexes)) + throw new SQLFeatureNotSupportedException("Auto generated keys are not supported."); + + return executeUpdate(sql); } /** {@inheritDoc} */ @Override public int executeUpdate(String sql, String[] colNames) throws SQLException { ensureNotClosed(); - throw new SQLFeatureNotSupportedException("Updates are not supported."); + if (!F.isEmpty(colNames)) + throw new SQLFeatureNotSupportedException("Auto generated keys are not supported."); + + return executeUpdate(sql); } /** {@inheritDoc} */ @@ -359,7 +429,7 @@ ensureNotClosed(); if (autoGeneratedKeys == RETURN_GENERATED_KEYS) - throw new SQLFeatureNotSupportedException("Updates are not supported."); + throw new SQLFeatureNotSupportedException("Auto generated keys are not supported."); return execute(sql); } @@ -368,8 +438,8 @@ @Override public boolean execute(String sql, int[] colIndexes) throws SQLException { ensureNotClosed(); - if (colIndexes != null && colIndexes.length > 0) - throw new SQLFeatureNotSupportedException("Updates are not supported."); + if (!F.isEmpty(colIndexes)) + throw new SQLFeatureNotSupportedException("Auto generated keys are not supported."); return execute(sql); } @@ -378,8 +448,8 @@ @Override public boolean execute(String sql, String[] colNames) throws SQLException { ensureNotClosed(); - if (colNames != null && colNames.length > 0) - throw new SQLFeatureNotSupportedException("Updates are not supported."); + if (!F.isEmpty(colNames)) + throw new SQLFeatureNotSupportedException("Auto generated keys are not supported."); return execute(sql); } Index: modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcDynamicIndexAtomicReplicatedSelfTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcDynamicIndexAtomicReplicatedSelfTest.java (revision ) +++ modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcDynamicIndexAtomicReplicatedSelfTest.java (revision ) @@ -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; + +import org.apache.ignite.cache.CacheAtomicityMode; +import org.apache.ignite.cache.CacheMode; + +/** */ +public class JdbcDynamicIndexAtomicReplicatedSelfTest extends JdbcDynamicIndexAbstractSelfTest { + /** {@inheritDoc} */ + @Override protected CacheMode cacheMode() { + return CacheMode.REPLICATED; + } + + /** {@inheritDoc} */ + @Override protected CacheAtomicityMode atomicityMode() { + return CacheAtomicityMode.ATOMIC; + } + + /** {@inheritDoc} */ + @Override protected boolean nearCache() { + return false; + } +} Index: modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcDynamicIndexTransactionalPartitionedSelfTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcDynamicIndexTransactionalPartitionedSelfTest.java (revision ) +++ modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcDynamicIndexTransactionalPartitionedSelfTest.java (revision ) @@ -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; + +import org.apache.ignite.cache.CacheAtomicityMode; +import org.apache.ignite.cache.CacheMode; + +/** */ +public class JdbcDynamicIndexTransactionalPartitionedSelfTest extends JdbcDynamicIndexAbstractSelfTest { + /** {@inheritDoc} */ + @Override protected CacheMode cacheMode() { + return CacheMode.PARTITIONED; + } + + /** {@inheritDoc} */ + @Override protected CacheAtomicityMode atomicityMode() { + return CacheAtomicityMode.TRANSACTIONAL; + } + + /** {@inheritDoc} */ + @Override protected boolean nearCache() { + return false; + } +} Index: modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcDynamicIndexAtomicPartitionedSelfTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcDynamicIndexAtomicPartitionedSelfTest.java (revision ) +++ modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcDynamicIndexAtomicPartitionedSelfTest.java (revision ) @@ -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; + +import org.apache.ignite.cache.CacheAtomicityMode; +import org.apache.ignite.cache.CacheMode; + +/** */ +public class JdbcDynamicIndexAtomicPartitionedSelfTest extends JdbcDynamicIndexAbstractSelfTest { + /** {@inheritDoc} */ + @Override protected CacheMode cacheMode() { + return CacheMode.PARTITIONED; + } + + /** {@inheritDoc} */ + @Override protected CacheAtomicityMode atomicityMode() { + return CacheAtomicityMode.ATOMIC; + } + + /** {@inheritDoc} */ + @Override protected boolean nearCache() { + return false; + } +} Index: modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcMergeStatementSelfTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcMergeStatementSelfTest.java (revision ) +++ modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcMergeStatementSelfTest.java (revision ) @@ -0,0 +1,131 @@ +/* + * 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; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import org.apache.ignite.cache.CachePeekMode; + +/** + * MERGE statement test. + */ +public class JdbcMergeStatementSelfTest extends JdbcAbstractDmlStatementSelfTest { + /** SQL query. */ + private static final String SQL = "merge into Person(_key, id, firstName, lastName, age) values " + + "('p1', 1, 'John', 'White', 25), " + + "('p2', 2, 'Joe', 'Black', 35), " + + "('p3', 3, 'Mike', 'Green', 40)"; + + /** SQL query. */ + protected static final String SQL_PREPARED = "merge into Person(_key, id, firstName, lastName, age) values " + + "(?, ?, ?, ?, ?), (?, ?, ?, ?, ?)"; + + /** Statement. */ + protected Statement stmt; + + /** Prepared statement. */ + protected PreparedStatement prepStmt; + + /** {@inheritDoc} */ + @Override protected void beforeTest() throws Exception { + super.beforeTest(); + stmt = conn.createStatement(); + prepStmt = conn.prepareStatement(SQL_PREPARED); + + assertNotNull(stmt); + assertFalse(stmt.isClosed()); + + assertNotNull(prepStmt); + assertFalse(prepStmt.isClosed()); + } + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + 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)); + } +} Index: modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheQueryCacheDestroyAndCreateSelfTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheQueryCacheDestroyAndCreateSelfTest.java (revision ) +++ modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheQueryCacheDestroyAndCreateSelfTest.java (revision ) @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.cache; + +import org.apache.ignite.cache.query.SqlFieldsQuery; +import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; + +import static org.apache.ignite.cache.CacheMode.PARTITIONED; +import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_SYNC; + +/** + * Check NPE at the DmlStatementsProcessor on DML queries after cache destroy and re-create. + */ +public class IgniteCacheQueryCacheDestroyAndCreateSelfTest extends GridCommonAbstractTest { + /** */ + public static final int GRID_CNT = 2; + + /** + * @throws Exception If failed. + */ + public void testSqlSelectWithCacheRecreate() throws Exception { + executeSqlWithCacheRecreate("select * from Integer"); + } + + /** + * @throws Exception If failed. + */ + public void testSqlInsertWithCacheRecreate() throws Exception { + executeSqlWithCacheRecreate("insert into Integer(_key, _val) values " + + "('p1', 1)"); + } + + /** + * @throws Exception If failed. + */ + public void testSqlInsertWithCacheRecreateOnNewNode() throws Exception { + executeSqlWithCacheRecreateOnNewNode("insert into Integer(_key, _val) values " + + "('p1', 1)"); + } + + /** + * @param sql Sql statement. + * @throws Exception If failed. + */ + private void executeSqlWithCacheRecreate(String sql) throws Exception { + try { + startGridsMultiThreaded(GRID_CNT); + + ignite(0).getOrCreateCache(personCacheConfig()); + + grid(0).cache(DEFAULT_CACHE_NAME).query(new SqlFieldsQuery(sql)); + + grid(0).destroyCache(DEFAULT_CACHE_NAME); + + grid(0).getOrCreateCache(personCacheConfig()); + + grid(0).cache(DEFAULT_CACHE_NAME).query(new SqlFieldsQuery(sql)); + } finally { + stopAllGrids(); + } + } + + /** + * @param sql Sql statement. + * @throws Exception If failed. + */ + private void executeSqlWithCacheRecreateOnNewNode(String sql) throws Exception { + try { + startGridsMultiThreaded(GRID_CNT); + + ignite(0).getOrCreateCache(personCacheConfig()); + + grid(0).cache(DEFAULT_CACHE_NAME).query(new SqlFieldsQuery(sql)); + + grid(0).destroyCache(DEFAULT_CACHE_NAME); + + grid(0).getOrCreateCache(personCacheConfig()); + + startGrid(GRID_CNT); + + grid(GRID_CNT).cache(DEFAULT_CACHE_NAME).query(new SqlFieldsQuery(sql)); + } finally { + stopAllGrids(); + } + } + + /** + * @return Cache configuration for non binary marshaller tests. + */ + private CacheConfiguration personCacheConfig() { + CacheConfiguration cache = defaultCacheConfiguration(); + + cache.setCacheMode(PARTITIONED); + cache.setBackups(1); + cache.setWriteSynchronizationMode(FULL_SYNC); + cache.setIndexedTypes( + String.class, Integer.class + ); + + return cache; + } +} Index: modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java (revision 3a917572c9732795d1c027721d79a1a271e3f4cc) +++ modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java (revision ) @@ -23,9 +23,18 @@ import org.apache.ignite.internal.jdbc2.JdbcDistributedJoinsQueryTest; import org.apache.ignite.jdbc.JdbcComplexQuerySelfTest; import org.apache.ignite.jdbc.JdbcConnectionSelfTest; +import org.apache.ignite.jdbc.JdbcDeleteStatementSelfTest; +import org.apache.ignite.jdbc.JdbcDynamicIndexAtomicPartitionedNearSelfTest; +import org.apache.ignite.jdbc.JdbcDynamicIndexAtomicPartitionedSelfTest; +import org.apache.ignite.jdbc.JdbcDynamicIndexAtomicReplicatedSelfTest; +import org.apache.ignite.jdbc.JdbcDynamicIndexTransactionalPartitionedNearSelfTest; +import org.apache.ignite.jdbc.JdbcDynamicIndexTransactionalPartitionedSelfTest; +import org.apache.ignite.jdbc.JdbcDynamicIndexTransactionalReplicatedSelfTest; import org.apache.ignite.jdbc.JdbcDefaultNoOpCacheTest; import org.apache.ignite.jdbc.JdbcEmptyCacheSelfTest; +import org.apache.ignite.jdbc.JdbcInsertStatementSelfTest; import org.apache.ignite.jdbc.JdbcLocalCachesSelfTest; +import org.apache.ignite.jdbc.JdbcMergeStatementSelfTest; import org.apache.ignite.jdbc.JdbcMetadataSelfTest; import org.apache.ignite.jdbc.JdbcNoDefaultCacheTest; import org.apache.ignite.jdbc.JdbcPojoLegacyQuerySelfTest; @@ -33,6 +42,7 @@ import org.apache.ignite.jdbc.JdbcPreparedStatementSelfTest; import org.apache.ignite.jdbc.JdbcResultSetSelfTest; import org.apache.ignite.jdbc.JdbcStatementSelfTest; +import org.apache.ignite.jdbc.JdbcUpdateStatementSelfTest; import org.apache.ignite.jdbc.thin.JdbcThinAutoCloseServerCursorTest; import org.apache.ignite.jdbc.thin.JdbcThinBatchSelfTest; import org.apache.ignite.jdbc.thin.JdbcThinComplexQuerySelfTest; @@ -81,6 +91,17 @@ suite.addTest(new TestSuite(JdbcPojoQuerySelfTest.class)); suite.addTest(new TestSuite(JdbcPojoLegacyQuerySelfTest.class)); suite.addTest(new TestSuite(JdbcConnectionReopenTest.class)); + suite.addTest(new TestSuite(JdbcInsertStatementSelfTest.class)); + suite.addTest(new TestSuite(JdbcUpdateStatementSelfTest.class)); + suite.addTest(new TestSuite(JdbcMergeStatementSelfTest.class)); + suite.addTest(new TestSuite(JdbcDeleteStatementSelfTest.class)); + + suite.addTest(new TestSuite(JdbcDynamicIndexAtomicPartitionedNearSelfTest.class)); + suite.addTest(new TestSuite(JdbcDynamicIndexAtomicPartitionedSelfTest.class)); + suite.addTest(new TestSuite(JdbcDynamicIndexAtomicReplicatedSelfTest.class)); + suite.addTest(new TestSuite(JdbcDynamicIndexTransactionalPartitionedNearSelfTest.class)); + suite.addTest(new TestSuite(JdbcDynamicIndexTransactionalPartitionedSelfTest.class)); + suite.addTest(new TestSuite(JdbcDynamicIndexTransactionalReplicatedSelfTest.class)); // Ignite client node based driver tests suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcConnectionSelfTest.class)); Index: modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcDynamicIndexAtomicPartitionedNearSelfTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcDynamicIndexAtomicPartitionedNearSelfTest.java (revision ) +++ modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcDynamicIndexAtomicPartitionedNearSelfTest.java (revision ) @@ -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; + +/** */ +public class JdbcDynamicIndexAtomicPartitionedNearSelfTest extends JdbcDynamicIndexAtomicPartitionedSelfTest { + /** {@inheritDoc} */ + @Override protected boolean nearCache() { + return true; + } +} Index: modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcDynamicIndexAbstractSelfTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcDynamicIndexAbstractSelfTest.java (revision ) +++ modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcDynamicIndexAbstractSelfTest.java (revision ) @@ -0,0 +1,367 @@ +/* + * 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; + +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 org.apache.ignite.IgniteCache; +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.processors.cache.query.IgniteQueryErrorCode; +import org.apache.ignite.internal.processors.query.IgniteSQLException; +import org.apache.ignite.internal.util.typedef.F; + +/** + * Test that checks indexes handling with JDBC. + */ +public abstract class JdbcDynamicIndexAbstractSelfTest extends JdbcAbstractDmlStatementSelfTest { + /** */ + 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> locRes = ignite(i).cache(null).query(new SqlFieldsQuery("explain select id from " + + "Person where id = 5").setLocal(true)).getAll(); + + assertEquals(F.asList( + Collections.singletonList("SELECT\n" + + " ID\n" + + "FROM \"\".PERSON\n" + + " /* \"\".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); + + assertSqlException(new RunnableX() { + /** {@inheritDoc} */ + @Override public void run() throws Exception { + jdbcRun(CREATE_INDEX); + } + }, IgniteQueryErrorCode.INDEX_ALREADY_EXISTS); + } + + /** + * 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> locRes = ignite(i).cache(null).query(new SqlFieldsQuery("explain select id from " + + "Person where id = 5").setLocal(true)).getAll(); + + assertEquals(F.asList( + Collections.singletonList("SELECT\n" + + " ID\n" + + "FROM \"\".PERSON\n" + + " /* \"\".PERSON.__SCAN_ */\n" + + "WHERE ID = 5") + ), locRes); + } + + assertSize(3); + } + + /** + * Test that dropping a non-existent index yields an error. + */ + public void testDropMissingIndex() { + assertSqlException(new RunnableX() { + /** {@inheritDoc} */ + @Override public void run() throws Exception { + jdbcRun(DROP_INDEX); + } + }, IgniteQueryErrorCode.INDEX_NOT_FOUND); + } + + /** + * 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 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()) { + try (ResultSet rs = stmt.executeQuery("SELECT COUNT(*) from Person")) { + assertEquals(expSize, getSingleValue(rs)); + } + } + } + + /** + * @return Cache. + */ + private IgniteCache cache() { + return grid(0).cache(null); + } + + /** + * Ensure that SQL exception is thrown. + * + * @param r Runnable. + * @param expCode Error code. + */ + private static void assertSqlException(RunnableX r, int expCode) { + // We expect IgniteSQLException with given code inside CacheException inside JDBC SQLException. + + try { + r.run(); + } + catch (SQLException e) { + for(Throwable t = e.getCause(); t != null; t = t.getCause()) { + if (t instanceof IgniteSQLException) { + IgniteSQLException sqlExecption = (IgniteSQLException)t; + + assertEquals("Unexpected error code [expected=" + expCode + + ", actual=" + sqlExecption.statusCode() + ']', + expCode, sqlExecption.statusCode()); + + return; + } + } + + fail("Unexpected exception: " + e); + } + catch (Exception e) { + fail("Unexpected exception: " + e); + } + + fail(IgniteSQLException.class.getSimpleName() + " is not thrown."); + } + + /** + * Runnable which can throw checked exceptions. + */ + private interface RunnableX { + /** + * Do run. + * + * @throws Exception If failed. + */ + public void run() throws Exception; + } +} Index: modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcDynamicIndexTransactionalReplicatedSelfTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcDynamicIndexTransactionalReplicatedSelfTest.java (revision ) +++ modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcDynamicIndexTransactionalReplicatedSelfTest.java (revision ) @@ -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; + +import org.apache.ignite.cache.CacheAtomicityMode; +import org.apache.ignite.cache.CacheMode; + +/** */ +public class JdbcDynamicIndexTransactionalReplicatedSelfTest extends JdbcDynamicIndexAbstractSelfTest { + /** {@inheritDoc} */ + @Override protected CacheMode cacheMode() { + return CacheMode.REPLICATED; + } + + /** {@inheritDoc} */ + @Override protected CacheAtomicityMode atomicityMode() { + return CacheAtomicityMode.TRANSACTIONAL; + } + + /** {@inheritDoc} */ + @Override protected boolean nearCache() { + return false; + } +} Index: modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcPreparedStatement.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcPreparedStatement.java (revision 3a917572c9732795d1c027721d79a1a271e3f4cc) +++ modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcPreparedStatement.java (revision ) @@ -74,9 +74,11 @@ /** {@inheritDoc} */ @Override public int executeUpdate() throws SQLException { - ensureNotClosed(); + int res = executeUpdate(sql); - throw new SQLFeatureNotSupportedException("Updates are not supported."); + args = null; + + return res; } /** {@inheritDoc} */ Index: modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcInsertStatementSelfTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcInsertStatementSelfTest.java (revision ) +++ modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcInsertStatementSelfTest.java (revision ) @@ -0,0 +1,187 @@ +/* + * 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; + +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.IgniteException; +import org.apache.ignite.cache.CachePeekMode; +import org.apache.ignite.cache.query.SqlFieldsQuery; +import org.apache.ignite.cache.query.SqlQuery; +import org.apache.ignite.internal.jdbc2.JdbcSqlFieldsQuery; +import org.apache.ignite.internal.processors.query.IgniteSQLException; +import org.apache.ignite.testframework.GridTestUtils; + +/** + * Statement test. + */ +public class JdbcInsertStatementSelfTest extends JdbcAbstractDmlStatementSelfTest { + /** SQL query. */ + private static final String SQL = "insert into Person(_key, id, firstName, lastName, age) values " + + "('p1', 1, 'John', 'White', 25), " + + "('p2', 2, 'Joe', 'Black', 35), " + + "('p3', 3, 'Mike', 'Green', 40)"; + + /** SQL query. */ + private static final String SQL_PREPARED = "insert into Person(_key, id, firstName, lastName, age) values " + + "(?, ?, ?, ?, ?), (?, ?, ?, ?, ?), (?, ?, ?, ?, ?)"; + + /** 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() { + /** {@inheritDoc} */ + @Override public Object call() throws Exception { + return stmt.execute(SQL); + } + }, IgniteSQLException.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()); + } +} Index: modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcAbstractUpdateStatementSelfTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcAbstractUpdateStatementSelfTest.java (revision ) +++ modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcAbstractUpdateStatementSelfTest.java (revision ) @@ -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; + +import java.sql.Statement; + +/** + * + */ +public abstract class JdbcAbstractUpdateStatementSelfTest extends JdbcAbstractDmlStatementSelfTest { + /** SQL query to populate cache. */ + private static final String ITEMS_SQL = "insert into Person(_key, id, firstName, lastName, age) values " + + "('p1', 1, 'John', 'White', 25), " + + "('p2', 2, 'Joe', 'Black', 35), " + + "('p3', 3, 'Mike', 'Green', 40)"; + + /** {@inheritDoc} */ + @Override protected void beforeTest() throws Exception { + super.beforeTest(); + jcache(0).clear(); + try (Statement s = conn.createStatement()) { + s.executeUpdate(ITEMS_SQL); + } + } +} Index: modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcUpdateStatementSelfTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcUpdateStatementSelfTest.java (revision ) +++ modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcUpdateStatementSelfTest.java (revision ) @@ -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; + +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 JdbcUpdateStatementSelfTest extends JdbcAbstractUpdateStatementSelfTest { + /** + * @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()); + } +} Index: modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcDeleteStatementSelfTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcDeleteStatementSelfTest.java (revision ) +++ modules/clients/src/test/java/org/apache/ignite/jdbc/JdbcDeleteStatementSelfTest.java (revision ) @@ -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; + +import java.sql.SQLException; +import java.util.Arrays; +import java.util.HashSet; + +/** + * + */ +public class JdbcDeleteStatementSelfTest extends JdbcAbstractUpdateStatementSelfTest { + /** + * @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(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(Arrays.asList("p1", "p3")))); + } +} Index: modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcConnection.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcConnection.java (revision 3a917572c9732795d1c027721d79a1a271e3f4cc) +++ modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcConnection.java (revision ) @@ -89,7 +89,7 @@ import static org.apache.ignite.IgniteJdbcDriver.PROP_STREAMING_PER_NODE_PAR_OPS; /** - * JDBC connection implementation. + * JDBC connection implementation based on Ignite client node. */ public class JdbcConnection implements Connection { /** Null stub. */ Index: modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/jdbc/GridCacheQueryJdbcTask.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/jdbc/GridCacheQueryJdbcTask.java (revision 3a917572c9732795d1c027721d79a1a271e3f4cc) +++ modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/jdbc/GridCacheQueryJdbcTask.java (revision ) @@ -172,7 +172,7 @@ else { status = 1; - bytes = U.marshal(MARSHALLER, new SQLException(res.getException().getMessage())); + bytes = U.marshal(MARSHALLER, new SQLException(res.getException())); } byte[] packet = new byte[bytes.length + 1]; @@ -243,6 +243,7 @@ Collection tbls = null; Collection cols = null; Collection types = null; + boolean isQuery = false; if (first) { assert sql != null; @@ -279,6 +280,7 @@ tbls = new ArrayList<>(meta.size()); cols = new ArrayList<>(meta.size()); types = new ArrayList<>(meta.size()); + isQuery = ((QueryCursorImpl>)cursor).isQuery(); for (GridQueryFieldMetadata desc : meta) { tbls.add(desc.typeName()); @@ -339,7 +341,8 @@ else remove(futId, c); - return first ? F.asList(ignite.cluster().localNode().id(), futId, tbls, cols, types, rows, finished) : + return first ? + F.asList(ignite.cluster().localNode().id(), futId, tbls, cols, types, rows, finished, isQuery) : F.asList(rows, finished); }