/*
* 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 java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.cache.Cache;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.cache.query.Query;
import org.apache.ignite.cache.query.QueryCursor;
import org.apache.ignite.cache.query.SqlFieldsQuery;
import org.apache.ignite.cache.query.SqlQuery;
import org.apache.ignite.cache.query.annotations.QuerySqlField;
import org.apache.ignite.cache.query.annotations.QuerySqlFunction;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.internal.IgniteInternalFuture;
import org.apache.ignite.internal.processors.query.GridQueryProcessor;
import org.apache.ignite.internal.processors.query.GridRunningQueryInfo;
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;
/**
* Tests modification of values returned by query iterators with enabled copy on read.
*/
public class CacheSqlQueryValueCopySelfTest extends GridCommonAbstractTest {
/** */
private static final TcpDiscoveryIpFinder ipFinder = new TcpDiscoveryVmIpFinder(true);
/** */
private static final int KEYS = 100;
/** {@inheritDoc} */
@Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
if ("client".equals(cfg.getIgniteInstanceName()))
cfg.setClientMode(true);
((TcpDiscoverySpi)cfg.getDiscoverySpi()).setIpFinder(ipFinder);
CacheConfiguration<Integer, Value> cc = new CacheConfiguration<>(DEFAULT_CACHE_NAME);
cc.setCopyOnRead(true);
cc.setIndexedTypes(Integer.class, Value.class);
cc.setSqlFunctionClasses(TestSQLFunctions.class);
cfg.setCacheConfiguration(cc);
return cfg;
}
/** {@inheritDoc} */
@Override protected void beforeTestsStarted() throws Exception {
super.beforeTestsStarted();
startGridsMultiThreaded(3);
}
/** {@inheritDoc} */
@Override protected void beforeTest() throws Exception {
IgniteCache<Integer, Value> cache = grid(0).cache(DEFAULT_CACHE_NAME);
for (int i = 0; i < KEYS; i++)
cache.put(i, new Value(i, "before-" + i));
}
/** {@inheritDoc} */
@Override protected void afterTest() throws Exception {
IgniteCache<Integer, Value> cache = grid(0).cache(DEFAULT_CACHE_NAME);
cache.removeAll();
super.afterTest();
}
/** {@inheritDoc} */
@Override protected void afterTestsStopped() throws Exception {
super.afterTestsStopped();
stopAllGrids();
}
/**
* Tests two step query from dedicated client.
*
* @throws Exception If failed.
*/
public void testTwoStepSqlClientQuery() throws Exception {
try (Ignite client = startGrid("client")) {
IgniteCache<Integer, Value> cache = client.cache(DEFAULT_CACHE_NAME);
List<Cache.Entry<Integer, Value>> all = cache.query(
new SqlQuery<Integer, Value>(Value.class, "select * from Value")).getAll();
assertEquals(KEYS, all.size());
for (Cache.Entry<Integer, Value> entry : all)
entry.getValue().str = "after";
check(cache);
QueryCursor<List<?>> qry = cache.query(new SqlFieldsQuery("select _val from Value"));
List<List<?>> all0 = qry.getAll();
assertEquals(KEYS, all0.size());
for (List<?> entry : all0)
((Value)entry.get(0)).str = "after";
check(cache);
}
}
/**
* Test two step query without local reduce phase.
*/
public void testTwoStepSkipReduceSqlQuery() {
IgniteCache<Integer, Value> cache = grid(0).cache(DEFAULT_CACHE_NAME);
List<Cache.Entry<Integer, Value>> all = cache.query(
new SqlQuery<Integer, Value>(Value.class, "select * from Value").setPageSize(3)).getAll();
assertEquals(KEYS, all.size());
for (Cache.Entry<Integer, Value> entry : all)
entry.getValue().str = "after";
check(cache);
}
/**
* Test two step query value copy.
*/
public void testTwoStepReduceSqlQuery() {
IgniteCache<Integer, Value> cache = grid(0).cache(DEFAULT_CACHE_NAME);
QueryCursor<List<?>> qry = cache.query(new SqlFieldsQuery("select _val from Value order by _key"));
List<List<?>> all = qry.getAll();
assertEquals(KEYS, all.size());
for (List<?> entry : all)
((Value)entry.get(0)).str = "after";
check(cache);
}
/**
* Tests local sql query.
*/
public void testLocalSqlQuery() {
IgniteCache<Integer, Value> cache = grid(0).cache(DEFAULT_CACHE_NAME);
SqlQuery<Integer, Value> qry = new SqlQuery<>(Value.class.getSimpleName(), "select * from Value");
qry.setLocal(true);
List<Cache.Entry<Integer, Value>> all = cache.query(qry).getAll();
assertFalse(all.isEmpty());
for (Cache.Entry<Integer, Value> entry : all)
entry.getValue().str = "after";
check(cache);
}
/**
* Tests local sql query.
*/
public void testLocalSqlFieldsQuery() {
IgniteCache<Integer, Value> cache = grid(0).cache(DEFAULT_CACHE_NAME);
QueryCursor<List<?>> cur = cache.query(new SqlFieldsQuery("select _val from Value").setLocal(true));
List<List<?>> all = cur.getAll();
assertFalse(all.isEmpty());
for (List<?> entry : all)
((Value)entry.get(0)).str = "after";
check(cache);
}
/**
* Run specified query in separate thread.
*
* @param qry Query to execute.
*/
private IgniteInternalFuture<?> runQueryAsync(final Query<?> qry) throws Exception {
return multithreadedAsync(new Runnable() {
@Override public void run() {
try {
log.info(">>> Query started");
grid(0).cache(DEFAULT_CACHE_NAME).query(qry).getAll();
log.info(">>> Query finished");
}
catch (Throwable e) {
e.printStackTrace();
}
}
}, 1, "run-query");
}
/**
* Test collecting info about running.
*
* @throws Exception If failed.
*/
public void testRunningSqlFieldsQuery() throws Exception {
IgniteInternalFuture<?> fut = runQueryAsync(new SqlFieldsQuery("select _val, sleep(1000) from Value limit 3"));
Thread.sleep(500);
GridQueryProcessor qryProc = grid(0).context().query();
Collection<GridRunningQueryInfo> queries = qryProc.runningQueries(0);
assertEquals(1, queries.size());
fut.get();
queries = qryProc.runningQueries(0);
assertEquals(0, queries.size());
SqlFieldsQuery qry = new SqlFieldsQuery("select _val, sleep(1000) from Value limit 3");
qry.setLocal(true);
fut = runQueryAsync(qry);
Thread.sleep(500);
queries = qryProc.runningQueries(0);
assertEquals(1, queries.size());
fut.get();
queries = qryProc.runningQueries(0);
assertEquals(0, queries.size());
}
/**
* Test collecting info about running.
*
* @throws Exception If failed.
*/
public void testRunningSqlQuery() throws Exception {
IgniteInternalFuture<?> fut = runQueryAsync(new SqlQuery<Integer, Value>(Value.class, "id > sleep(100)"));
Thread.sleep(500);
GridQueryProcessor qryProc = grid(0).context().query();
Collection<GridRunningQueryInfo> queries = qryProc.runningQueries(0);
assertEquals(1, queries.size());
fut.get();
queries = qryProc.runningQueries(0);
assertEquals(0, queries.size());
SqlQuery<Integer, Value> qry = new SqlQuery<>(Value.class, "id > sleep(100)");
qry.setLocal(true);
fut = runQueryAsync(qry);
Thread.sleep(500);
queries = qryProc.runningQueries(0);
assertEquals(1, queries.size());
fut.get();
queries = qryProc.runningQueries(0);
assertEquals(0, queries.size());
}
/**
* Test collecting info about running.
*
* @throws Exception If failed.
*/
public void testCancelingSqlFieldsQuery() throws Exception {
runQueryAsync(new SqlFieldsQuery("select * from (select _val, sleep(100) from Value limit 50)"));
Thread.sleep(500);
final GridQueryProcessor qryProc = grid(0).context().query();
Collection<GridRunningQueryInfo> queries = qryProc.runningQueries(0);
assertEquals(1, queries.size());
final Collection<GridRunningQueryInfo> finalQueries = queries;
for (GridRunningQueryInfo query : finalQueries)
qryProc.cancelQueries(Collections.singleton(query.id()));
int n = 100;
// Give cluster some time to cancel query and cleanup resources.
while (n > 0) {
Thread.sleep(100);
queries = qryProc.runningQueries(0);
if (queries.isEmpty())
break;
log.info(">>>> Wait for cancel: " + n);
n--;
}
queries = qryProc.runningQueries(0);
assertEquals(0, queries.size());
}
/**
* @param cache Cache.
*/
private void check(IgniteCache<Integer, Value> cache) {
int cnt = 0;
// Value should be not modified by previous assignment.
for (Cache.Entry<Integer, Value> entry : cache) {
cnt++;
assertEquals("before-" + entry.getKey(), entry.getValue().str);
}
assertEquals(KEYS, cnt);
}
/** */
private static class Value {
/** */
@QuerySqlField
private int id;
/** */
@QuerySqlField
private String str;
/**
* @param id ID.
* @param str String.
*/
public Value(int id, String str) {
this.id = id;
this.str = str;
}
}
/**
* Utility class with custom SQL functions.
*/
public static class TestSQLFunctions {
/**
* Sleep function to simulate long running queries.
*
* @param x Time to sleep.
* @return Return specified argument.
*/
@QuerySqlFunction
public static long sleep(long x) {
if (x >= 0)
try {
Thread.sleep(x);
}
catch (InterruptedException ignored) {
// No-op.
}
return x;
}
}
}