blob: 5be500bd5fcfb389ce52cacaf89a028b3ff66df6 [file] [log] [blame]
/*=========================================================================
* Copyright (c) 2010-2014 Pivotal Software, Inc. All Rights Reserved.
* This product is protected by U.S. and international copyright
* and intellectual property laws. Pivotal products are covered by
* one or more patents listed at http://www.pivotal.io/patents.
*=========================================================================
*/
package com.gemstone.gemfire.cache.query.internal.index;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import util.TestException;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.query.Index;
import com.gemstone.gemfire.cache.query.QueryService;
import com.gemstone.gemfire.cache.query.QueryTestUtils;
import com.gemstone.gemfire.cache.query.SelectResults;
import com.gemstone.gemfire.cache.query.data.Portfolio;
import com.gemstone.gemfire.cache.query.internal.DefaultQuery;
import com.gemstone.gemfire.cache.query.internal.DefaultQuery.TestHook;
import com.gemstone.gemfire.internal.cache.persistence.query.CloseableIterator;
import com.gemstone.gemfire.test.junit.categories.IntegrationTest;
/**
*
* @author Tejas Nomulwar
*
*/
@Category(IntegrationTest.class)
public class CompactRangeIndexJUnitTest {
private QueryTestUtils utils;
private Index index;
@Before
public void setUp() {
System.setProperty("index_elemarray_threshold", "3");
utils = new QueryTestUtils();
Properties props = new Properties();
props.setProperty("mcast-port", "0");
utils.createCache(props);
utils.createReplicateRegion("exampleRegion");
}
@Test
public void testCompactRangeIndex() throws Exception{
System.setProperty("index_elemarray_threshold", "3");
index = utils.createIndex("type", "\"type\"", "/exampleRegion");
putValues(9);
isUsingIndexElemArray("type1");
putValues(10);
isUsingConcurrentHashSet("type1");
utils.removeIndex("type", "/exampleRegion");
executeQueryWithAndWithoutIndex(4);
updateValues(2);
executeQueryWithCount();
executeQueryWithAndWithoutIndex(3);
executeRangeQueryWithDistinct(8);
executeRangeQueryWithoutDistinct(9);
}
/*
* Tests adding entries to compact range index where the key is null
* fixes bug 47151 where null keyed entries would be removed after being added
*/
@Test
public void testNullKeyCompactRangeIndex() throws Exception {
index = utils.createIndex("indexName", "status", "/exampleRegion");
Region region = utils.getCache().getRegion("exampleRegion");
//create objects
int numObjects = 10;
for (int i = 1; i <= numObjects; i++) {
Portfolio p = new Portfolio(i);
p.status = null;
region.put("KEY-"+ i, p);
}
//execute query and check result size
QueryService qs = utils.getCache().getQueryService();
SelectResults results = (SelectResults) qs.newQuery("Select * from /exampleRegion r where r.status = null").execute();
assertEquals("Null matched Results expected", numObjects, results.size());
}
//Tests race condition where we possibly were missing remove calls due to transitioning
//to an empty index elem before adding the entries
//the fix is to add the entries to the elem and then transition to that elem
@Test
public void testCompactRangeIndexMemoryIndexStoreMaintenance() throws Exception {
try {
index = utils.createIndex("compact range index", "p.status", "/exampleRegion p");
final Region r = utils.getCache().getRegion("/exampleRegion");
Portfolio p0 = new Portfolio(0);
p0.status = "active";
final Portfolio p1 = new Portfolio(1);
p1.status = "active";
r.put("0", p0);
DefaultQuery.testHook = new MemoryIndexStoreREToIndexElemTestHook();
final CountDownLatch threadsDone = new CountDownLatch(2);
Thread t1 = new Thread(new Runnable() {
public void run() {
r.put("1", p1);
threadsDone.countDown();
}
});
t1.start();
Thread t0 = new Thread(new Runnable() {
public void run() {
r.remove("0");
threadsDone.countDown();
}
});
t0.start();
threadsDone.await(90, TimeUnit.SECONDS);
QueryService qs = utils.getCache().getQueryService();
SelectResults results = (SelectResults) qs.newQuery("Select * from /exampleRegion r where r.status='active'").execute();
//the remove should have happened
assertEquals(1, results.size());
results = (SelectResults) qs.newQuery("Select * from /exampleRegion r where r.status!='inactive'").execute();
assertEquals(1, results.size());
CompactRangeIndex cindex = (CompactRangeIndex)index;
MemoryIndexStore indexStore = (MemoryIndexStore)cindex.getIndexStorage();
CloseableIterator iterator = indexStore.get("active");
int count = 0;
while (iterator.hasNext()) {
count++;
iterator.next();
}
assertEquals("incorrect number of entries in collection", 1, count);
}
finally {
DefaultQuery.testHook = null;
}
}
//Tests race condition when we are transitioning index collection from elem array to concurrent hash set
//The other thread could remove from the empty concurrent hash set.
//Instead we now set a token, do all the puts into a collection and then unsets the token to the new collection
@Test
public void testMemoryIndexStoreMaintenanceTransitionFromElemArrayToTokenToConcurrentHashSet() throws Exception {
try {
index = utils.createIndex("compact range index", "p.status", "/exampleRegion p");
final Region r = utils.getCache().getRegion("/exampleRegion");
Portfolio p0 = new Portfolio(0);
p0.status = "active";
Portfolio p1 = new Portfolio(1);
p1.status = "active";
final Portfolio p2 = new Portfolio(2);
p2.status = "active";
Portfolio p3 = new Portfolio(3);
p3.status = "active";
r.put("0", p0);
r.put("1", p1);
r.put("3", p3);
//now we set the test hook. That way previous calls would not affect the test hooks
DefaultQuery.testHook = new MemoryIndexStoreIndexElemToTokenToConcurrentHashSetTestHook();
final CountDownLatch threadsDone = new CountDownLatch(2);
Thread t2 = new Thread(new Runnable() {
public void run() {
r.put("2", p2);
threadsDone.countDown();
}
});
t2.start();
Thread t0 = new Thread(new Runnable() {
public void run() {
r.remove("0");
threadsDone.countDown();
}
});
t0.start();
threadsDone.await(90, TimeUnit.SECONDS);
QueryService qs = utils.getCache().getQueryService();
SelectResults results = (SelectResults) qs.newQuery("Select * from /exampleRegion r where r.status='active'").execute();
//the remove should have happened
assertEquals(3, results.size());
results = (SelectResults) qs.newQuery("Select * from /exampleRegion r where r.status!='inactive'").execute();
assertEquals(3, results.size());
CompactRangeIndex cindex = (CompactRangeIndex)index;
MemoryIndexStore indexStore = (MemoryIndexStore)cindex.getIndexStorage();
CloseableIterator iterator = indexStore.get("active");
int count = 0;
while (iterator.hasNext()) {
count++;
iterator.next();
}
assertEquals("incorrect number of entries in collection", 3, count);
}
finally {
DefaultQuery.testHook = null;
System.setProperty("index_elemarray_threshold", "100");
}
}
@Test
public void testInvalidTokens() throws Exception {
final Region r = utils.getCache().getRegion("/exampleRegion");
r.put("0", new Portfolio(0));
r.invalidate("0");
index = utils.createIndex("compact range index", "p.status", "/exampleRegion p");
QueryService qs = utils.getCache().getQueryService();
SelectResults results = (SelectResults) qs.newQuery("Select * from /exampleRegion r where r.status='active'").execute();
//the remove should have happened
assertEquals(0, results.size());
results = (SelectResults) qs.newQuery("Select * from /exampleRegion r where r.status!='inactive'").execute();
assertEquals(0, results.size());
CompactRangeIndex cindex = (CompactRangeIndex)index;
MemoryIndexStore indexStore = (MemoryIndexStore)cindex.getIndexStorage();
CloseableIterator iterator = indexStore.get(QueryService.UNDEFINED);
int count = 0;
while (iterator.hasNext()) {
count++;
iterator.next();
}
assertEquals("incorrect number of entries in collection", 0, count);
}
private class MemoryIndexStoreREToIndexElemTestHook implements TestHook {
private CountDownLatch readyToStartRemoveLatch;
private CountDownLatch waitForRemoveLatch;
private CountDownLatch waitForTransitioned;
public MemoryIndexStoreREToIndexElemTestHook() {
waitForRemoveLatch = new CountDownLatch(1);
waitForTransitioned = new CountDownLatch(1);
readyToStartRemoveLatch = new CountDownLatch(1);
}
public void doTestHook(int spot) {
}
public void doTestHook(String description) {
try {
if (description.equals("ATTEMPT_REMOVE")) {
if (!readyToStartRemoveLatch.await(21, TimeUnit.SECONDS)) {
throw new TestException("Time ran out waiting for other thread to initiate put");
}
}
else if (description.equals("TRANSITIONED_FROM_REGION_ENTRY_TO_ELEMARRAY")) {
readyToStartRemoveLatch.countDown();
if (!waitForRemoveLatch.await(21, TimeUnit.SECONDS)) {
throw new TestException("Time ran out waiting for other thread to initiate remove");
}
}
else if (description.equals("BEGIN_REMOVE_FROM_ELEM_ARRAY")) {
waitForRemoveLatch.countDown();
if (waitForTransitioned.await(21, TimeUnit.SECONDS)) {
throw new TestException("Time ran out waiting for transition from region entry to elem array");
}
}
else if (description.equals("TRANSITIONED_FROM_REGION_ENTRY_TO_ELEMARRAY")) {
waitForTransitioned.countDown();
}
else if (description.equals("REMOVE_CALLED_FROM_ELEM_ARRAY")) {
}
}
catch (InterruptedException e) {
throw new TestException("Interrupted while waiting for test to complete");
}
}
}
//Test hook that waits for another thread to begin removing
//The current thread should then continue to set the token
//then continue and convert to chs while holding the lock to the elem array still
//After the conversion of chs, the lock is released and then remove can proceed
private class MemoryIndexStoreIndexElemToTokenToConcurrentHashSetTestHook implements TestHook {
private CountDownLatch waitForRemoveLatch;
private CountDownLatch waitForTransitioned;
private CountDownLatch waitForRetry;
private CountDownLatch readyToStartRemoveLatch;
public MemoryIndexStoreIndexElemToTokenToConcurrentHashSetTestHook() {
waitForRemoveLatch = new CountDownLatch(1);
waitForTransitioned = new CountDownLatch(1);
waitForRetry = new CountDownLatch(1);
readyToStartRemoveLatch = new CountDownLatch(1);
}
public void doTestHook(int spot) {
}
public void doTestHook(String description) {
try {
if (description.equals("ATTEMPT_REMOVE")) {
if (!readyToStartRemoveLatch.await(21, TimeUnit.SECONDS)) {
throw new TestException("Time ran out waiting for other thread to initiate put");
}
}
else if (description.equals("BEGIN_TRANSITION_FROM_ELEMARRAY_TO_CONCURRENT_HASH_SET")) {
readyToStartRemoveLatch.countDown();
if (!waitForRemoveLatch.await(21, TimeUnit.SECONDS)) {
throw new TestException("Time ran out waiting for other thread to initiate remove");
}
}
else if (description.equals("BEGIN_REMOVE_FROM_ELEM_ARRAY")) {
waitForRemoveLatch.countDown();
if (!waitForTransitioned.await(21, TimeUnit.SECONDS)) {
throw new TestException("Time ran out waiting for transition from elem array to token");
}
}
else if (description.equals("TRANSITIONED_FROM_ELEMARRAY_TO_TOKEN")) {
waitForTransitioned.countDown();
}
}
catch (InterruptedException e) {
throw new TestException("Interrupted while waiting for test to complete");
}
}
}
public void putValues(int num) {
long start = System.currentTimeMillis();
utils.createValuesStringKeys("exampleRegion", num);
}
private void updateValues(int num){
utils.createDiffValuesStringKeys("exampleRegion", num);
}
public void executeQueryWithCount() throws Exception{
String[] queries = { "520" };
for (Object result : utils.executeQueries(queries)) {
if (result instanceof Collection) {
for (Object e : (Collection) result) {
if(e instanceof Integer) {
assertEquals(10,((Integer) e).intValue());
}
}
}
}
}
private void isUsingIndexElemArray(String key){
if(index instanceof CompactRangeIndex){
assertEquals("Expected IndexElemArray but instanceForKey is " + getValuesFromMap(key).getClass().getName(), getValuesFromMap(key) instanceof IndexElemArray, true);
}
else{
fail("Should have used CompactRangeIndex");
}
}
private void isUsingConcurrentHashSet(String key){
if(index instanceof CompactRangeIndex){
assertEquals("Expected concurrent hash set but instanceForKey is " + getValuesFromMap(key).getClass().getName(), getValuesFromMap(key) instanceof IndexConcurrentHashSet, true);
}
else{
fail("Should have used CompactRangeIndex");
}
}
private Object getValuesFromMap(String key){
MemoryIndexStore ind = (MemoryIndexStore) ((CompactRangeIndex)index).getIndexStorage();
Map map = ind.valueToEntriesMap;
Object entryValue = map.get(key);
return entryValue;
}
public void executeQueryWithAndWithoutIndex(int expectedResults) {
try {
executeSimpleQuery(expectedResults);
} catch (Exception e) {
fail("Query execution failed. : "+e);
}
index = utils.createIndex("type", "\"type\"", "/exampleRegion");
try {
executeSimpleQuery( expectedResults);
} catch (Exception e) {
fail("Query execution failed. : " +e);
}
utils.removeIndex("type", "/exampleRegion");
}
private int executeSimpleQuery( int expResults) throws Exception{
String[] queries = { "519" }; //SELECT * FROM /exampleRegion WHERE \"type\" = 'type1'
int results = 0;
for (Object result : utils.executeQueries(queries)) {
if (result instanceof SelectResults) {
Collection<?> collection = ((SelectResults<?>) result).asList();
results = collection.size();
assertEquals(expResults, results);
for (Object e : collection) {
if(e instanceof Portfolio){
assertEquals("type1",((Portfolio)e).getType());
}
}
}
}
return results;
}
private int executeRangeQueryWithDistinct( int expResults) throws Exception{
String[] queries = { "181" };
int results = 0;
for (Object result : utils.executeQueries(queries)) {
if (result instanceof SelectResults) {
Collection<?> collection = ((SelectResults<?>) result).asList();
results = collection.size();
assertEquals(expResults, results);
int[] ids = {};
List expectedIds = new ArrayList(Arrays.asList( 10, 9, 8, 7, 6, 5, 4, 3, 2 ));
for (Object e : collection) {
if (e instanceof Portfolio) {
assertTrue(expectedIds.contains(((Portfolio) e).getID()));
expectedIds.remove((Integer)((Portfolio) e).getID());
}
}
}
}
return results;
}
private int executeRangeQueryWithoutDistinct( int expResults){
String[] queries = { "181" };
int results = 0;
for (Object result : utils.executeQueriesWithoutDistinct(queries)) {
if (result instanceof SelectResults) {
Collection<?> collection = ((SelectResults<?>) result).asList();
results = collection.size();
assertEquals(expResults, results);
List expectedIds = new ArrayList(Arrays.asList( 10, 9, 8, 7, 6, 5, 4, 3, 3 ));
for (Object e : collection) {
if(e instanceof Portfolio){
assertTrue(expectedIds.contains(((Portfolio) e).getID()));
expectedIds.remove((Integer)((Portfolio) e).getID());
}
}
}
}
return results;
}
@After
public void tearDown() throws Exception{
utils.closeCache();
}
}