blob: 3f0a3aaeb33dbd9ecb06919ff1cffa8f3eb9f218 [file] [log] [blame]
/*
* 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.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;
import org.junit.Test;
/**
* 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.
*/
@Test
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.
*/
@Test
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;
}
}, SQLException.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.
*/
@Test
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.
*/
@Test
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.
*/
@Test
public void testDropMissingIndex() {
GridTestUtils.assertThrowsAnyCause(log, new Callable<Void>() {
@Override public Void call() throws Exception {
jdbcRun(DROP_INDEX);
return null;
}
}, SQLException.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.
*/
@Test
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.
*/
@Test
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);
}
}