blob: 6fd3c3356a53837ee4e428e6b7c8f0507da1a803 [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.geode.cache.query.internal.index;
import static org.junit.Assert.assertTrue;
import java.io.Serializable;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.apache.geode.cache.AttributesFactory;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.DataPolicy;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionAttributes;
import org.apache.geode.cache.query.CacheUtils;
import org.apache.geode.cache.query.FunctionDomainException;
import org.apache.geode.cache.query.Index;
import org.apache.geode.cache.query.IndexExistsException;
import org.apache.geode.cache.query.IndexNameConflictException;
import org.apache.geode.cache.query.NameResolutionException;
import org.apache.geode.cache.query.QueryInvocationTargetException;
import org.apache.geode.cache.query.QueryService;
import org.apache.geode.cache.query.RegionNotFoundException;
import org.apache.geode.cache.query.SelectResults;
import org.apache.geode.cache.query.TypeMismatchException;
import org.apache.geode.cache.query.functional.StructSetOrResultsSet;
import org.apache.geode.test.junit.categories.OQLIndexTest;
@Category({OQLIndexTest.class})
public class EquiJoinIntegrationTest {
QueryService qs;
Region region1, region2, region3, region4;
@Before
public void setUp() throws java.lang.Exception {
CacheUtils.startCache();
qs = CacheUtils.getQueryService();
}
@After
public void tearDown() {
region2.destroyRegion();
region1.destroyRegion();
}
protected void createRegions() throws Exception {
region1 = createReplicatedRegion("region1");
region2 = createReplicatedRegion("region2");
}
protected void createAdditionalRegions() throws Exception {
region3 = createReplicatedRegion("region3");
region4 = createReplicatedRegion("region4");
}
protected void destroyAdditionalRegions() throws Exception {
if (region3 != null) {
region3.destroyRegion();
}
if (region4 != null) {
region4.destroyRegion();
}
}
@Test
public void testSingleFilterWithSingleEquijoinOneToOneMapping() throws Exception {
createRegions();
String[] queries = new String[] {
"<trace>select * from /region1 c, /region2 s where c.pkid=1 and c.pkid = s.pkid",
"<trace>select * from /region1 c, /region2 s where c.pkid=1 and s.pkid = c.pkid",
"<trace>select * from /region1 c, /region2 s where c.pkid = s.pkid and c.pkid=1",
"<trace>select * from /region1 c, /region2 s where s.pkid = c.pkid and c.pkid=1",};
for (int i = 0; i < 1000; i++) {
region1.put(i, new Customer(i, i));
region2.put(i, new Customer(i, i));
}
executeQueriesWithIndexCombinations(queries);
}
@Test
public void testSingleFilterWithSingleEquijoinOneToOneMappingWithAdditionalJoins()
throws Exception {
createRegions();
try {
createAdditionalRegions();
String[] queries = new String[] {
"<trace>select * from /region1 c, /region2 s, /region3 d where c.pkid=1 and c.pkid = s.pkid and d.pkid = s.pkid", // this
// should
// derive
// d
// after
// deriving
// s
// from
// c
"<trace>select * from /region1 c, /region2 s, /region3 d, /region4 f where c.pkid=1 and c.pkid = s.pkid and d.pkid = s.pkid and f.pkid = d.pkid", // this
// should
// f
// from
// d
// from
// s
// from
// c
"<trace>select * from /region1 c, /region2 s, /region3 d where c.pkid=1 and c.pkid = s.pkid and d.pkid = c.pkid", // this
// should
// derive
// d
// and
// s
// from
// c
"<trace>select * from /region1 c, /region2 s, /region3 d where c.pkid=1 and c.pkid = s.pkid and s.pkid = d.pkid", // this
// should
// derive
// d
// after
// deriving
// s
// from
// c
// (order
// is
// just
// switched
// in
// the
// query)
};
for (int i = 0; i < 30; i++) {
region1.put(i, new Customer(i, i));
region2.put(i, new Customer(i, i));
region3.put(i, new Customer(i, i));
region4.put(i, new Customer(i, i));
}
executeQueriesWithIndexCombinations(queries);
} finally {
destroyAdditionalRegions();
}
}
/**
* We do not want to test this with Primary Key on the many side or else only 1 result will be
* returned
*/
@Test
public void testSingleFilterWithSingleEquijoinOneToManyMapping() throws Exception {
createRegions();
String[] queries =
new String[] {"select * from /region1 c, /region2 s where c.pkid=1 and c.pkid = s.pkid",
"select * from /region1 c, /region2 s where c.pkid=1 and s.pkid = c.pkid",
"select * from /region1 c, /region2 s where c.pkid = s.pkid and c.pkid=1",
"select * from /region1 c, /region2 s where s.pkid = c.pkid and c.pkid=1",
"select distinct * from /region1 c, /region2 s where s.pkid = c.pkid and c.pkid=1"};
for (int i = 0; i < 1000; i++) {
region1.put(i, new Customer(i, i));
region2.put(i, new Customer(i % 100, i));
}
executeQueriesWithIndexCombinations(queries, new DefaultIndexCreatorCallback(qs) {
@Override
protected String[] createIndexTypesForRegion2() {
return new String[] {"Compact", "Hash"};
}
}, false);
}
@Test
public void testSingleFilterWithSingleEquijoinMultipleFiltersOnSameRegionOnSameIteratorMapping()
throws Exception {
createRegions();
String[] queries = new String[] {
"select * from /region1 c, /region2 s where c.pkid=1 and c.pkid = s.pkid and c.id = 1",
"select * from /region1 c, /region2 s where c.id = 1 and c.pkid=1 and s.pkid = c.pkid",
};
for (int i = 0; i < 1000; i++) {
region1.put(i, new Customer(i, i % 10));
region2.put(i, new Customer(i, i));
}
executeQueriesWithIndexCombinations(queries, new DefaultIndexCreatorCallback(qs) {
Index secondaryIndex;
@Override
public void createIndexForRegion1(int indexTypeId)
throws RegionNotFoundException, IndexExistsException, IndexNameConflictException {
secondaryIndex = qs.createIndex("region1 id", "p.id", "/region1 p");
super.createIndexForRegion1(indexTypeId);
}
@Override
public void destroyIndexForRegion1(int indexTypeId) {
qs.removeIndex(secondaryIndex);
super.destroyIndexForRegion1(indexTypeId);
}
}, false /* want to compare actual results and not size only */);
}
@Test
public void testSingleFilterWithSingleEquijoinWithRangeFilters() throws Exception {
createRegions();
String[] queries = new String[] {
"<trace>select * from /region1 c, /region2 s where c.pkid = 1 and c.id > 1 and c.id < 10 and c.pkid = s.pkid",
"<trace>select * from /region1 c, /region2 s where c.pkid >= 0 and c.pkid < 10 and c.id < 10 and c.pkid = s.pkid"};
// just need enough so that there are 1-10 ids per pkid
for (int i = 0; i < 1000; i++) {
region1.put(i, new Customer(i % 5, i % 10));
region2.put(i, new Customer(i, i));
}
executeQueriesWithIndexCombinations(queries, new DefaultIndexCreatorCallback(qs) {
@Override
protected String[] createIndexTypesForRegion1() {
return new String[] {"Compact", "Hash"};
}
}, false /* want to compare actual results and not size only */);
}
@Test
public void testSingleFilterWithSingleEquijoinLimit() throws Exception {
// In this test we are hoping the index being used will properly use the limit while taking into
// consideration the filters of c.id and c.pkid
// This test is set up so that if the pkid index is used and limit applied, if id is not taken
// into consideration until later stages, it will lead to incorrect results (0)
createRegions();
String[] queries = new String[] {
"select * from /region1 c, /region2 s where c.id = 3 and c.pkid > 2 and c.pkid = s.pkid limit 1",};
for (int i = 0; i < 1000; i++) {
region1.put(i, new Customer(i, i % 10));
region2.put(i, new Customer(i, i));
}
executeQueriesWithIndexCombinations(queries, new DefaultIndexCreatorCallback(qs) {
Index secondaryIndex;
@Override
public void createIndexForRegion1(int indexTypeId)
throws RegionNotFoundException, IndexExistsException, IndexNameConflictException {
secondaryIndex = qs.createIndex("region1 id", "p.id", "/region1 p");
super.createIndexForRegion1(indexTypeId);
}
@Override
public void destroyIndexForRegion1(int indexTypeId) {
qs.removeIndex(secondaryIndex);
super.destroyIndexForRegion1(indexTypeId);
}
}, true);
}
@Test
public void testSingleFilterWithSingleEquijoinNestedQuery() throws Exception {
createRegions();
String[] queries = new String[] {
"select * from /region1 c, /region2 s where c.pkid=1 and c.pkid = s.pkid and c.pkid in (select t.pkid from /region1 t,/region2 s where s.pkid=t.pkid and s.pkid = 1)",
"select * from /region1 c, /region2 s where c.pkid=1 and c.pkid = s.pkid or c.pkid in set (1,2,3,4)",};
for (int i = 0; i < 1000; i++) {
region1.put(i, new Customer(i, i));
region2.put(i, new Customer(i, i));
}
executeQueriesWithIndexCombinations(queries);
}
public static class Customer implements Serializable {
public int pkid;
public int id;
public String name;
public Map<String, Customer> nested = new HashMap<String, Customer>();
public Customer(int pkid, int id) {
this.pkid = pkid;
this.id = id;
this.name = "name" + pkid;
}
public String toString() {
return "Customer pkid = " + pkid + ", id: " + id + " name:" + name;
}
}
private Region createReplicatedRegion(String regionName) throws ParseException {
Cache cache = CacheUtils.getCache();
AttributesFactory attributesFactory = new AttributesFactory();
attributesFactory.setDataPolicy(DataPolicy.REPLICATE);
RegionAttributes regionAttributes = attributesFactory.create();
return cache.createRegion(regionName, regionAttributes);
}
protected void executeQueriesWithIndexCombinations(String[] queries)
throws RegionNotFoundException, IndexExistsException, IndexNameConflictException,
QueryInvocationTargetException, NameResolutionException, TypeMismatchException,
FunctionDomainException {
executeQueriesWithIndexCombinations(queries, new DefaultIndexCreatorCallback(qs), false);
}
protected void executeQueriesWithIndexCombinations(String[] queries,
IndexCreatorCallback indexCreator, boolean sizeOnly) throws RegionNotFoundException,
IndexExistsException, IndexNameConflictException, QueryInvocationTargetException,
NameResolutionException, TypeMismatchException, FunctionDomainException {
Object[] nonIndexedResults = executeQueries(queries);
for (int r1Index = 0; r1Index < indexCreator.getNumIndexTypesForRegion1(); r1Index++) {
indexCreator.createIndexForRegion1(r1Index);
for (int r2Index = 0; r2Index < indexCreator.getNumIndexTypesForRegion2(); r2Index++) {
indexCreator.createIndexForRegion2(r2Index);
Object[] indexedResults = executeQueries(queries);
compareResults(nonIndexedResults, indexedResults, queries, sizeOnly);
indexCreator.destroyIndexForRegion2(r2Index);
}
indexCreator.destroyIndexForRegion1(r1Index);
}
}
protected Object[] executeQueries(String[] queries) throws QueryInvocationTargetException,
NameResolutionException, TypeMismatchException, FunctionDomainException {
Object[] results = new SelectResults[queries.length];
for (int i = 0; i < queries.length; i++) {
results[i] = qs.newQuery(queries[i]).execute();
}
return results;
}
interface IndexCreatorCallback {
int getNumIndexTypesForRegion1();
int getNumIndexTypesForRegion2();
void createIndexForRegion1(int indexTypeId)
throws RegionNotFoundException, IndexExistsException, IndexNameConflictException;
void createIndexForRegion2(int indexTypeId)
throws RegionNotFoundException, IndexExistsException, IndexNameConflictException;
void destroyIndexForRegion1(int indexTypeId);
void destroyIndexForRegion2(int indexTypeId);
}
static class DefaultIndexCreatorCallback implements IndexCreatorCallback {
protected String[] indexTypesForRegion1 = createIndexTypesForRegion1();
protected String[] indexTypesForRegion2 = createIndexTypesForRegion2();
protected Index indexOnR1, indexOnR2;
protected QueryService qs;
DefaultIndexCreatorCallback(QueryService qs) {
this.qs = qs;
}
protected String[] createIndexTypesForRegion1() {
return new String[] {"Compact", "Hash", "PrimaryKey"};
}
protected String[] createIndexTypesForRegion2() {
return new String[] {"Compact", "Hash", "PrimaryKey"};
}
@Override
public int getNumIndexTypesForRegion1() {
return indexTypesForRegion1.length;
}
@Override
public int getNumIndexTypesForRegion2() {
return indexTypesForRegion2.length;
}
@Override
public void createIndexForRegion1(int indexTypeId)
throws RegionNotFoundException, IndexExistsException, IndexNameConflictException {
indexOnR1 = createIndex(indexTypesForRegion1[indexTypeId], "region1", "pkid");
}
@Override
public void createIndexForRegion2(int indexTypeId)
throws RegionNotFoundException, IndexExistsException, IndexNameConflictException {
indexOnR2 = createIndex(indexTypesForRegion2[indexTypeId], "region2", "pkid");
}
// Type id is not used here but at some future time we could store a map of indexes or find a
// use for this id?
@Override
public void destroyIndexForRegion1(int indexTypeId) {
qs.removeIndex(indexOnR1);
}
@Override
public void destroyIndexForRegion2(int indexTypeId) {
qs.removeIndex(indexOnR2);
}
private Index createIndex(String type, String regionName, String field)
throws RegionNotFoundException, IndexExistsException, IndexNameConflictException {
Index index = null;
switch (type) {
case "Compact":
index = createCompactRangeIndex(regionName, field);
break;
case "Range":
index = createRangeIndexOnFirstIterator(regionName, field);
break;
case "Hash":
index = createHashIndex(regionName, field);
break;
case "PrimaryKey":
index = createPrimaryKeyIndex(regionName, field);
break;
}
return index;
}
private Index createCompactRangeIndex(String regionName, String fieldName)
throws RegionNotFoundException, IndexExistsException, IndexNameConflictException {
String fromClause = "/" + regionName + " r";
String indexedExpression = "r." + fieldName;
return qs.createIndex("Compact " + fromClause + ":" + indexedExpression, indexedExpression,
fromClause);
}
private Index createHashIndex(String regionName, String fieldName)
throws RegionNotFoundException, IndexExistsException, IndexNameConflictException {
String fromClause = "/" + regionName + " r";
String indexedExpression = "r." + fieldName;
return qs.createHashIndex("Hash " + fromClause + ":" + indexedExpression, indexedExpression,
fromClause);
}
private Index createPrimaryKeyIndex(String regionName, String fieldName)
throws RegionNotFoundException, IndexExistsException, IndexNameConflictException {
String fromClause = "/" + regionName + " r";
String indexedExpression = "r." + fieldName;
return qs.createKeyIndex("PrimaryKey " + fromClause + ":" + indexedExpression,
indexedExpression, fromClause);
}
private Index createRangeIndexOnFirstIterator(String regionName, String fieldName)
throws RegionNotFoundException, IndexExistsException, IndexNameConflictException {
String fromClause = "/" + regionName + " r, r.nested.values v";
String indexedExpression = "r." + fieldName;
return qs.createIndex("Range " + fromClause + ":" + indexedExpression, indexedExpression,
fromClause);
}
private Index createRangeIndexOnSecondIterator(String regionName, String fieldName)
throws RegionNotFoundException, IndexExistsException, IndexNameConflictException {
String fromClause = "/" + regionName + " r, r.nested.values v";
String indexedExpression = "v." + fieldName;
return qs.createIndex("Range " + fromClause + ":" + indexedExpression, indexedExpression,
fromClause);
}
}
private void compareResults(Object[] nonIndexedResults, Object[] indexedResults, String[] queries,
boolean sizeOnly) {
if (sizeOnly) {
for (int i = 0; i < queries.length; i++) {
assertTrue(((SelectResults) nonIndexedResults[i])
.size() == ((SelectResults) indexedResults[i]).size());
assertTrue(((SelectResults) nonIndexedResults[i]).size() > 0);
}
} else {
StructSetOrResultsSet util = new StructSetOrResultsSet();
for (int i = 0; i < queries.length; i++) {
Object[][] resultsToCompare = new Object[1][2];
resultsToCompare[0][0] = nonIndexedResults[i];
resultsToCompare[0][1] = indexedResults[i];
util.CompareQueryResultsWithoutAndWithIndexes(resultsToCompare, 1,
new String[] {queries[i]});
assertTrue(((SelectResults) nonIndexedResults[i]).size() > 0);
}
}
}
}