blob: 2401162643f85cad67235b4ba5b67fd1621336ea [file] [log] [blame]
package com.gemstone.gemfire.cache.query.internal.index;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Set;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.query.CacheUtils;
import com.gemstone.gemfire.cache.query.Index;
import com.gemstone.gemfire.cache.query.IndexExistsException;
import com.gemstone.gemfire.cache.query.IndexNameConflictException;
import com.gemstone.gemfire.cache.query.Query;
import com.gemstone.gemfire.cache.query.QueryService;
import com.gemstone.gemfire.cache.query.RegionNotFoundException;
import com.gemstone.gemfire.cache.query.SelectResults;
import com.gemstone.gemfire.cache.query.data.Portfolio;
import com.gemstone.gemfire.cache.query.functional.StructSetOrResultsSet;
import com.gemstone.gemfire.cache.query.internal.DefaultQuery;
import com.gemstone.gemfire.cache.query.internal.QueryExecutionContext;
import com.gemstone.gemfire.cache.query.internal.QueryObserverAdapter;
import com.gemstone.gemfire.cache.query.internal.QueryObserverHolder;
import com.gemstone.gemfire.test.junit.categories.IntegrationTest;
@Category(IntegrationTest.class)
public class IndexHintJUnitTest {
private Region region;
@Before
public void setUp() throws Exception {
System.setProperty("gemfire.Query.VERBOSE", "true");
CacheUtils.startCache();
}
@After
public void tearDown() throws Exception {
CacheUtils.closeCache();
}
//tests the grammar for a hint with a single index name
@Test
public void testSingleIndexHint() throws Exception {
createRegion();
QueryService qs = CacheUtils.getQueryService();
DefaultQuery query = (DefaultQuery) qs
.newQuery("<hint 'FirstIndex'> select * from /Portfolios p where p.ID > 10");
QueryExecutionContext qec = new QueryExecutionContext(new Object[1],
CacheUtils.getCache(), query);
query.executeUsingContext(qec);
assertTrue(qec.isHinted("FirstIndex"));
assertEquals(-1, qec.getHintSize("FirstIndex"));
}
//Tests the grammar for a hint with two index names
@Test
public void testTwoIndexHint() throws Exception {
createRegion();
QueryService qs = CacheUtils.getQueryService();
DefaultQuery query = (DefaultQuery) qs
.newQuery("<hint 'FirstIndex', 'SecondIndex'> select * from /Portfolios p where p.ID > 10");
QueryExecutionContext qec = new QueryExecutionContext(new Object[1],
CacheUtils.getCache(), query);
query.executeUsingContext(qec);
assertTrue(qec.isHinted("FirstIndex"));
assertTrue(qec.isHinted("SecondIndex"));
}
//Tests that index hints are ordered correctly
@Test
public void testIndexHintOrdering() throws Exception {
createRegion();
QueryService qs = CacheUtils.getQueryService();
DefaultQuery query = (DefaultQuery)qs.newQuery("<hint 'FirstIndex','SecondIndex','ThirdIndex','FourthIndex'>select * from /Portfolios p where p.ID > 10");
QueryExecutionContext qec = new QueryExecutionContext(new Object[1], CacheUtils.getCache(), query);
query.executeUsingContext(qec);
assertTrue(qec.isHinted("FirstIndex"));
assertTrue(qec.isHinted("SecondIndex"));
assertTrue(qec.isHinted("ThirdIndex"));
assertTrue(qec.isHinted("FourthIndex"));
assertEquals(-4, qec.getHintSize("FirstIndex"));
assertEquals(-3, qec.getHintSize("SecondIndex"));
assertEquals(-2, qec.getHintSize("ThirdIndex"));
assertEquals(-1, qec.getHintSize("FourthIndex"));
}
//Tests that even though we would have chosen the index anyways, that the hint still
//allows us to chose the same index
@Test
public void testIndexUsageWithIndexHint() throws Exception {
createRegion();
populateData(1000);
//create index
createIndex("IDIndex", "p.ID", "/Portfolios p");
//set observer
QueryObserverImpl observer = new QueryObserverImpl();
QueryObserverHolder.setInstance(observer);
//execute query
QueryService qs = CacheUtils.getQueryService();
Query query = qs.newQuery("<hint 'IDIndex'>select * from /Portfolios p where p.ID > 10");
query.execute();
//verify index usage
assertTrue(observer.wasIndexUsed("IDIndex"));
}
//Tests scenario where we provide a hint that is unuseable for the query
//The other index should be used instead of the hint because the hint itself
//would not have helped
@Test
public void testIndexUsageWithUnusableIndexHint() throws Exception {
createRegion();
populateData(1000);
//create index
createIndex("IDIndex", "p.ID", "/Portfolios p");
createIndex("SecIndex", "p.secId", "/Portfolios p");
//set observer
QueryObserverImpl observer = new QueryObserverImpl();
QueryObserverHolder.setInstance(observer);
//execute query
QueryService qs = CacheUtils.getQueryService();
Query query = qs.newQuery("<hint 'SecIndex'>select * from /Portfolios p where p.ID > 10");
query.execute();
//verify index usage
assertFalse(observer.wasIndexUsed("SecIndex"));
assertTrue(observer.wasIndexUsed("IDIndex"));
}
//given a choice between two indexes, we hint on each one in different queries and verify
//that both indexes are used
@Test
public void testMultiIndexWithSingleIndexHint() throws Exception {
createRegion();
populateData(1000);
//create index
createIndex("IDIndex", "p.ID", "/Portfolios p");
createIndex("SecIndex", "p.status", "/Portfolios p");
//set observer
QueryObserverImpl observer = new QueryObserverImpl();
QueryObserverHolder.setInstance(observer);
//execute query
SelectResults[][] results = new SelectResults[1][2];
QueryService qs = CacheUtils.getQueryService();
Query query = qs.newQuery("<hint 'IDIndex'>select * from /Portfolios p where p.ID > 10 and p.status = 'inactive'");
results[0][0] = (SelectResults) query.execute();
//verify index usage
assertTrue(observer.wasIndexUsed("IDIndex"));
observer.reset();
query = qs.newQuery("<hint 'SecIndex'>select * from /Portfolios p where p.ID > 10 and p.status = 'inactive'");
results[0][1] = (SelectResults) query.execute();
//verify index usage
assertTrue(observer.wasIndexUsed("SecIndex"));
StructSetOrResultsSet ssOrrs = new StructSetOrResultsSet();
assertEquals(495, results[0][1].size());
//Not really with and without index but we can use this method to verify they are the same results
//regardless of which index used
ssOrrs.CompareQueryResultsWithoutAndWithIndexes(results, 1, new String[]{"<query with hints>"});
}
//Using junction, we will hint and make sure single index hints are functioning
@Test
public void testMultiIndexWithSingleIndexHintWithRangeJunction() throws Exception {
createRegion();
populateData(1000);
//create index
createIndex("IDIndex", "p.ID", "/Portfolios p");
createIndex("SecIndex", "p.status", "/Portfolios p");
//set observer
QueryObserverImpl observer = new QueryObserverImpl();
QueryObserverHolder.setInstance(observer);
//execute query
SelectResults[][] results = new SelectResults[1][2];
QueryService qs = CacheUtils.getQueryService();
Query query = qs.newQuery("<hint 'IDIndex'>select * from /Portfolios p where p.ID > 10 and p.ID < 200 and p.status = 'inactive'");
results[0][0] = (SelectResults) query.execute();
//verify index usage
assertTrue(observer.wasIndexUsed("IDIndex"));
observer.reset();
query = qs.newQuery("<hint 'SecIndex'>select * from /Portfolios p where p.ID > 10 and p.ID < 200 and p.status = 'inactive'");
results[0][1] = (SelectResults) query.execute();
//verify index usage
assertTrue(observer.wasIndexUsed("SecIndex"));
StructSetOrResultsSet ssOrrs = new StructSetOrResultsSet();
assertEquals(95, results[0][1].size());
//Not really with and without index but we can use this method to verify they are the same results
//regardless of which index used
ssOrrs.CompareQueryResultsWithoutAndWithIndexes(results, 1, new String[]{"<query with hints>"});
}
//Using junction, we will hint and make sure multi index hints are functioning
@Test
public void testMultiIndexWithMultiIndexHint() throws Exception {
createRegion();
populateData(1000);
//create index
createIndex("IDIndex", "p.ID", "/Portfolios p");
createIndex("SecIndex", "p.status", "/Portfolios p");
//set observer
QueryObserverImpl observer = new QueryObserverImpl();
QueryObserverHolder.setInstance(observer);
//execute query
SelectResults[][] results = new SelectResults[1][2];
QueryService qs = CacheUtils.getQueryService();
Query query = qs.newQuery("<hint 'IDIndex', 'SecIndex'>select * from /Portfolios p where p.ID > 10 and p.ID < 200 and p.status = 'inactive'");
results[0][0] = (SelectResults) query.execute();
//verify index usage
assertTrue(observer.wasIndexUsed("IDIndex"));
assertTrue(observer.wasIndexUsed("SecIndex"));
observer.reset();
query = qs.newQuery("<hint 'IDIndex'>select * from /Portfolios p where p.ID > 10 and p.ID < 200 and p.status = 'inactive'");
results[0][1] = (SelectResults) query.execute();
//verify index usage
assertTrue(observer.wasIndexUsed("IDIndex"));
StructSetOrResultsSet ssOrrs = new StructSetOrResultsSet();
assertEquals(95, results[0][1].size());
//Not really with and without index but we can use this method to verify they are the same results
//regardless of which index used
ssOrrs.CompareQueryResultsWithoutAndWithIndexes(results, 1, new String[]{"<query with hints>"});
}
@Test
public void testIndexWithCompiledInSet() throws Exception {
createRegion();
populateData(1000);
//create index
createIndex("IDIndex", "p.ID", "/Portfolios p");
createIndex("SecIndex", "p.status", "/Portfolios p");
createIndex("DescriptionIndex", "p.description", "/Portfolios p");
//set observer
QueryObserverImpl observer = new QueryObserverImpl();
QueryObserverHolder.setInstance(observer);
//execute query
SelectResults[][] results = new SelectResults[1][2];
QueryService qs = CacheUtils.getQueryService();
Query query = qs.newQuery("<hint 'IDIndex'>select * from /Portfolios p where p.ID > 10 and p.ID < 200 and p.status = 'inactive' and p.description IN SET ('XXXX', 'XXXY')");
results[0][0] = (SelectResults) query.execute();
//verify index usage
assertTrue(observer.wasIndexUsed("IDIndex"));
observer.reset();
query = qs.newQuery("<hint 'SecIndex'>select * from /Portfolios p where p.ID > 10 and p.ID < 200 and p.status = 'inactive' and p.description IN SET ('XXXX', 'XXXY')");
results[0][1] = (SelectResults) query.execute();
//verify index usage
assertTrue(observer.wasIndexUsed("SecIndex"));
observer.reset();
//Compare results with the first two index queries
StructSetOrResultsSet ssOrrs = new StructSetOrResultsSet();
assertEquals(95, results[0][1].size());
//Not really with and without index but we can use this method to verify they are the same results
//regardless of which index used
ssOrrs.CompareQueryResultsWithoutAndWithIndexes(results, 1, new String[]{"<query with hints>"});
query = qs.newQuery("<hint 'DescriptionIndex'>select * from /Portfolios p where p.ID > 10 and p.ID < 200 and p.status = 'inactive' and p.description IN SET ('XXXX', 'XXXY')");
results[0][1] = (SelectResults) query.execute();
//verify index usage
assertTrue(observer.wasIndexUsed("DescriptionIndex"));
//Compare results with the final index result
ssOrrs.CompareQueryResultsWithoutAndWithIndexes(results, 1, new String[]{"<query with hints>"});
}
@Test
public void testHintSingleIndexWithCompiledIn() throws Exception {
createRegion();
populateData(1000);
//create index
createIndex("IDIndex", "p.ID", "/Portfolios p");
createIndex("SecIndex", "p.status", "/Portfolios p");
createIndex("DescriptionIndex", "p.description", "/Portfolios p");
//set observer
QueryObserverImpl observer = new QueryObserverImpl();
QueryObserverHolder.setInstance(observer);
//execute query
SelectResults[][] results = new SelectResults[1][2];
QueryService qs = CacheUtils.getQueryService();
Query query = qs.newQuery("<hint 'IDIndex'>select * from /Portfolios p where p.ID > 10 and p.ID < 200 and p.status = 'inactive' and p.description IN (select p.description from /Portfolios p where p.ID > 10)");
results[0][0] = (SelectResults) query.execute();
//verify index usage
assertTrue(observer.wasIndexUsed("IDIndex"));
observer.reset();
query = qs.newQuery("<hint 'SecIndex'>select * from /Portfolios p where p.ID > 10 and p.ID < 200 and p.status = 'inactive' and p.description IN (select p.description from /Portfolios p where p.ID > 10)");
results[0][1] = (SelectResults) query.execute();
//verify index usage
assertTrue(observer.wasIndexUsed("SecIndex"));
observer.reset();
//Compare results with the first two index queries
StructSetOrResultsSet ssOrrs = new StructSetOrResultsSet();
assertEquals(95, results[0][1].size());
//Not really with and without index but we can use this method to verify they are the same results
//regardless of which index used
ssOrrs.CompareQueryResultsWithoutAndWithIndexes(results, 1, new String[]{"<query with hints>"});
query = qs.newQuery("<hint 'DescriptionIndex'>select * from /Portfolios p where p.ID > 10 and p.ID < 200 and p.status = 'inactive' and p.description IN (select p.description from /Portfolios p where p.ID > 10) ");
results[0][1] = (SelectResults) query.execute();
//verify index usage
assertTrue(observer.wasIndexUsed("DescriptionIndex"));
//Compare results with the final index result
ssOrrs.CompareQueryResultsWithoutAndWithIndexes(results, 1, new String[]{"<query with hints>"});
}
@Test
public void testHintMultiIndexWithCompiledIn() throws Exception {
createRegion();
populateData(1000);
//create index
createIndex("IDIndex", "p.ID", "/Portfolios p");
createIndex("SecIndex", "p.status", "/Portfolios p");
createIndex("DescriptionIndex", "p.description", "/Portfolios p");
//set observer
QueryObserverImpl observer = new QueryObserverImpl();
QueryObserverHolder.setInstance(observer);
//execute query
SelectResults[][] results = new SelectResults[1][2];
QueryService qs = CacheUtils.getQueryService();
Query query = qs.newQuery("select * from /Portfolios p where p.ID > 10 and p.ID < 200 and p.status = 'inactive' and p.description IN (<hint 'IDIndex', 'SecIndex', 'DescriptionIndex'>select p.description from /Portfolios p where p.ID > 10)");
results[0][0] = (SelectResults) query.execute();
//verify index usage
assertTrue(observer.wasIndexUsed("IDIndex"));
assertTrue(observer.wasIndexUsed("SecIndex"));
observer.reset();
query = qs.newQuery("select * from /Portfolios p where p.ID > 10 and p.ID < 200 and p.status = 'inactive' and p.description IN (select p.description from /Portfolios p where p.ID > 10)");
results[0][1] = (SelectResults) query.execute();
//verify index usage
assertTrue(observer.wasIndexUsed("SecIndex"));
assertFalse(observer.wasIndexUsed("DescriptionIndex"));
//We end up using IDIndex for this case.
observer.reset();
//Compare results with the first two index queries
StructSetOrResultsSet ssOrrs = new StructSetOrResultsSet();
assertEquals(95, results[0][1].size());
//Not really with and without index but we can use this method to verify they are the same results
//regardless of which index used
ssOrrs.CompareQueryResultsWithoutAndWithIndexes(results, 1, new String[]{"<query with hints>"});
query = qs.newQuery("select * from /Portfolios p where p.ID > 10 and p.ID < 200 and p.status = 'inactive' and p.description IN (<hint 'DescriptionIndex'>select p.description from /Portfolios p where p.ID > 10)");
results[0][1] = (SelectResults) query.execute();
//verify index usage
assertTrue(observer.wasIndexUsed("DescriptionIndex"));
assertFalse(observer.wasIndexUsed("SecIndex"));
//We end up using IDIndex for this case also
//Compare results with the final index result
ssOrrs.CompareQueryResultsWithoutAndWithIndexes(results, 1, new String[]{"<query with hints>"});
}
//Hints inside of a nested query will trigger index usage for that query only
//Unusable hints should behave the same for nested queries in that they are not used if unapplicable to the query
@Test
public void testHintNestedCompiledIn() throws Exception {
createRegion();
populateData(1000);
//create index
createIndex("IDIndex", "p.ID", "/Portfolios p");
createIndex("SecIndex", "p.status", "/Portfolios p");
createIndex("DescriptionIndex", "p.description", "/Portfolios p");
//set observer
QueryObserverImpl observer = new QueryObserverImpl();
QueryObserverHolder.setInstance(observer);
//execute query
SelectResults[][] results = new SelectResults[1][2];
QueryService qs = CacheUtils.getQueryService();
Query query = qs.newQuery("select * from /Portfolios p where p.ID > 10 and p.ID < 200 and p.status = 'inactive' and p.description IN (<hint 'IDIndex', 'SecIndex', 'DescriptionIndex'>select p.description from /Portfolios p where p.ID > 10)");
results[0][0] = (SelectResults) query.execute();
//verify index usage
assertTrue(observer.wasIndexUsed("IDIndex"));
assertTrue(observer.wasIndexUsed("SecIndex"));
//Because it was a hint for the inner query, it was an unuseable hint for that query
assertFalse(observer.wasIndexUsed("DescriptionIndex"));
observer.reset();
//query again with no hints for a "bare" comparison
query = qs.newQuery("select * from /Portfolios p where p.ID > 10 and p.ID < 200 and p.status = 'inactive' and p.description IN (select p.description from /Portfolios p where p.ID > 10)");
results[0][1] = (SelectResults) query.execute();
observer.reset();
//Compare results with the first two index queries
StructSetOrResultsSet ssOrrs = new StructSetOrResultsSet();
assertEquals(95, results[0][1].size());
//Not really with and without index but we can use this method to verify they are the same results
//regardless of which index used
ssOrrs.CompareQueryResultsWithoutAndWithIndexes(results, 1, new String[]{"<query with hints>"});
}
@Test
public void testJoinHint() throws Exception{
createRegion();
Region portfolios2 = createRegion("Portfolios_2");
populateData(1000);
populateData(portfolios2, 1000);
//create index
createIndex("IDIndex", "p.ID", "/Portfolios p");
createIndex("SecIndex", "p.status", "/Portfolios p");
createIndex("DescriptionIndex", "p.description", "/Portfolios p");
createIndex("IDIndexOnPortfolios2", "p.ID", "/Portfolios_2 p");
//set observer
QueryObserverImpl observer = new QueryObserverImpl();
QueryObserverHolder.setInstance(observer);
//execute query
SelectResults[][] results = new SelectResults[1][2];
QueryService qs = CacheUtils.getQueryService();
Query query = qs.newQuery("<hint 'SecIndex'>select * from /Portfolios p, /Portfolios_2 p2 where p.ID = p2.ID and p.status = 'inactive'");
results[0][0] = (SelectResults) query.execute();
//verify index usage
assertTrue(observer.wasIndexUsed("SecIndex"));
observer.reset();
//query again with no hints for a "bare" comparison
query = qs.newQuery("select * from /Portfolios p, /Portfolios_2 p2 where p.ID = p2.ID and p.status = 'inactive'");
results[0][1] = (SelectResults) query.execute();
observer.reset();
//Compare results with the first two index queries
StructSetOrResultsSet ssOrrs = new StructSetOrResultsSet();
assertEquals(500, results[0][1].size());
//Not really with and without index but we can use this method to verify they are the same results
//regardless of which index used
ssOrrs.CompareQueryResultsWithoutAndWithIndexes(results, 1, new String[]{"<query with hints>"});
}
class QueryObserverImpl extends QueryObserverAdapter
{
ArrayList indexesUsed = new ArrayList();
public void beforeIndexLookup(Index index, int oper, Object key) {
indexesUsed.add(index.getName());
}
public void beforeIndexLookup(Index index, int lowerBoundOperator,
Object lowerBoundKey, int upperBoundOperator, Object upperBoundKey,
Set NotEqualKeys) {
indexesUsed.add(index.getName());
}
public boolean wasIndexUsed(String indexName) {
return indexesUsed.contains(indexName);
}
public void reset() {
indexesUsed.clear();
}
}
private Region createRegion() {
region = createRegion("Portfolios");
return region;
}
private Region createRegion(String regionName) {
return CacheUtils.createRegion(regionName, Portfolio.class);
}
private void populateData(int numObjects) {
populateData(region, numObjects);
}
private void populateData(Region region, int numObjects) {
for (int i = 0; i < numObjects; i++) {
region.put("" + i, new Portfolio(i));
}
}
private void createIndex(String indexName, String indexedExpression, String regionPath) throws RegionNotFoundException, IndexExistsException, IndexNameConflictException{
CacheUtils.getQueryService().createIndex(indexName, indexedExpression, regionPath);
}
}