blob: 29446bffd45221553361c568ece599ae5268a6a4 [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;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import org.apache.logging.log4j.Logger;
import org.apache.geode.cache.query.AmbiguousNameException;
import org.apache.geode.cache.query.FunctionDomainException;
import org.apache.geode.cache.query.Index;
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.SelectResults;
import org.apache.geode.cache.query.Struct;
import org.apache.geode.cache.query.TypeMismatchException;
import org.apache.geode.cache.query.internal.index.AbstractIndex;
import org.apache.geode.cache.query.internal.index.IndexData;
import org.apache.geode.cache.query.internal.index.IndexManager;
import org.apache.geode.cache.query.internal.index.IndexProtocol;
import org.apache.geode.cache.query.internal.index.IndexUtils;
import org.apache.geode.cache.query.internal.index.PartitionedIndex;
import org.apache.geode.cache.query.internal.parse.OQLLexerTokenTypes;
import org.apache.geode.cache.query.internal.types.ObjectTypeImpl;
import org.apache.geode.cache.query.internal.types.StructTypeImpl;
import org.apache.geode.cache.query.types.CollectionType;
import org.apache.geode.cache.query.types.ObjectType;
import org.apache.geode.cache.query.types.StructType;
import org.apache.geode.internal.Assert;
import org.apache.geode.internal.cache.BucketRegion;
import org.apache.geode.internal.cache.CachePerfStats;
import org.apache.geode.internal.cache.PartitionedRegion;
import org.apache.geode.internal.cache.partitioned.Bucket;
import org.apache.geode.logging.internal.log4j.api.LogService;
public class QueryUtils {
private static final Logger logger = LogService.getLogger();
/**
* Return a SelectResults that is the intersection of c1 and c2. May or may not return a modified
* c1 or c2.
*/
public static SelectResults intersection(SelectResults c1, SelectResults c2,
ExecutionContext contextOrNull) {
QueryObserverHolder.getInstance().invokedQueryUtilsIntersection(c1, c2);
assertCompatible(c1, c2);
if (c1.isEmpty()) {
return c1;
}
if (c2.isEmpty()) {
return c2;
}
// iterate on the smallest one
if (c1.size() < c2.size()) {
return sizeSortedIntersection(c1, c2, contextOrNull);
} else {
return sizeSortedIntersection(c2, c1, contextOrNull);
}
}
/**
* Return a SelectResults that is the union of c1 and c2. May or may not return a modified c1 or
* c2.
*/
public static SelectResults union(SelectResults c1, SelectResults c2,
ExecutionContext contextOrNull) {
QueryObserverHolder.getInstance().invokedQueryUtilsUnion(c1, c2);
assertCompatible(c1, c2);
// iterate on the smallest one
if (c1.size() < c2.size()) {
return sizeSortedUnion(c1, c2, contextOrNull);
} else {
return sizeSortedUnion(c2, c1, contextOrNull);
}
}
private static void assertCompatible(SelectResults sr1, SelectResults sr2) {
Assert.assertTrue(
sr1.getCollectionType().getElementType().equals(sr2.getCollectionType().getElementType()));
}
public static SelectResults createResultCollection(ExecutionContext context,
ObjectType elementType) {
return context.isDistinct() ? new ResultsSet(elementType)
: new ResultsBag(elementType, context.getCachePerfStats());
}
public static SelectResults createStructCollection(ExecutionContext context,
StructType elementType) {
return context.isDistinct() ? new StructSet(elementType)
: new StructBag(elementType, context.getCachePerfStats());
}
public static SelectResults createResultCollection(boolean distinct, ObjectType elementType,
ExecutionContext context) {
return distinct ? new ResultsSet(elementType)
: new ResultsBag(elementType, context.getCachePerfStats());
}
public static SelectResults createStructCollection(boolean distinct, StructType elementType,
ExecutionContext context) {
return distinct ? new StructSet(elementType)
: new StructBag(elementType, context.getCachePerfStats());
}
/**
* Returns an appropriate, empty {@code SelectResults}
*
* @param objectType The {@code ObjectType} of the query results
* @return an appropriate, empty {@code SelectResults}
*/
public static SelectResults getEmptySelectResults(ObjectType objectType,
CachePerfStats statsOrNull) {
SelectResults emptyResults = null;
if (objectType instanceof StructType) {
emptyResults = new StructBag((StructTypeImpl) objectType, statsOrNull);
} else {
emptyResults = new ResultsBag(objectType, statsOrNull);
}
return emptyResults;
}
/**
* Returns an appropriate, empty {@code SelectResults}
*
* TODO: statsOrNull is always null
*
* @param collectionType The {@code CollectionType} of the query results
* @return an appropriate, empty {@code SelectResults}
*/
public static SelectResults getEmptySelectResults(CollectionType collectionType,
CachePerfStats statsOrNull) {
SelectResults emptyResults = null;
if (collectionType.isOrdered()) {
// The collectionType is ordered.
// The 'order by' clause was used in the query.
// Wrap an ArrayList with a ResultsCollectionWrapper
emptyResults = new ResultsCollectionWrapper(collectionType.getElementType(), new ArrayList());
} else if (!collectionType.allowsDuplicates()) {
// The collectionType does not allow duplicates.
// The distinct keyword was used in the query.
// Wrap a HashSet with a ResultsCollectionWrapper
emptyResults = new ResultsCollectionWrapper(collectionType.getElementType(), new HashSet());
} else {
// Use ObjectType to determine the correct SelectResults implementation
emptyResults = getEmptySelectResults(collectionType.getElementType(), statsOrNull);
}
return emptyResults;
}
// convenience method
private static boolean isBag(SelectResults coln) {
return coln.getCollectionType().allowsDuplicates();
}
/** collections are passed in from smallest to largest */
// consider: taking intersection of two bags, only retain
// the number of occurrences in the intersection equal to the
// minimum number between the two bags
private static SelectResults sizeSortedIntersection(SelectResults small, SelectResults large,
ExecutionContext contextOrNull) {
// if one is a set and one is a bag, then treat the set like a bag (and return a bag)
boolean smallModifiable = small.isModifiable() && (isBag(small) || !isBag(large));
boolean largeModifiable = large.isModifiable() && (isBag(large) || !isBag(small));
if (smallModifiable) {
try {
for (Iterator itr = small.iterator(); itr.hasNext();) {
Object element = itr.next();
int count = large.occurrences(element);
if (small.occurrences(element) > count) {
// bag intersection: only retain smaller number of dups
itr.remove();
}
}
return small;
} catch (UnsupportedOperationException ignore) {
// didn't succeed because small is actually unmodifiable
}
}
if (largeModifiable) {
try {
for (Iterator itr = large.iterator(); itr.hasNext();) {
Object element = itr.next();
int count = small.occurrences(element);
if (large.occurrences(element) > count) {
// bag intersection: only retain smaller number of dups
itr.remove();
}
}
return large;
} catch (UnsupportedOperationException ignore) {
// didn't succeed because large is actually unmodifiable
}
}
SelectResults rs;
if (contextOrNull != null) {
rs = contextOrNull.isDistinct() ? new ResultsSet(small)
: new ResultsBag(small, contextOrNull.getCachePerfStats());
} else {
rs = new ResultsBag(small, null);
}
for (Iterator itr = rs.iterator(); itr.hasNext();) {
Object element = itr.next();
int count = large.occurrences(element);
if (rs.occurrences(element) > count) { // bag intersection: only retain smaller number of dups
itr.remove();
}
}
return rs;
}
/** collections are passed in from smallest to largest */
// assume we're dealing with bags and/or sets here, number of occurrences in the
// union should be the sum of the occurrences in the two bags
// Is this Ok? There may be tuples which are actually common to both set so
// union in such cases should not increase count. right.?
private static SelectResults sizeSortedUnion(SelectResults small, SelectResults large,
ExecutionContext contextOrNull) {
// if one is a set and one is a bag, then treat the set like a bag (and return a bag)
boolean smallModifiable = small.isModifiable() && (isBag(small) || !isBag(large));
boolean largeModifiable = large.isModifiable() && (isBag(large) || !isBag(small));
if (largeModifiable) {
try {
for (Object element : small) {
int count = small.occurrences(element);
if (large.occurrences(element) < count) {
large.add(element);
}
}
return large;
} catch (UnsupportedOperationException ignore) {
// didn't succeed because large is actually unmodifiable
}
}
if (smallModifiable) {
try {
for (Object element : large) {
int count = large.occurrences(element);
if (small.occurrences(element) < count) {
small.add(element);
}
}
return small;
} catch (UnsupportedOperationException ignore) {
// didn't succeed because small is actually unmodifiable
}
}
SelectResults rs;
if (contextOrNull != null) {
rs = contextOrNull.isDistinct() ? new ResultsSet(large)
: new ResultsBag(large, contextOrNull.getCachePerfStats());
} else {
rs = new ResultsBag(large, null);
}
for (Iterator itr = small.iterator(); itr.hasNext();) {
Object element = itr.next();
rs.add(element);
}
return rs;
}
/**
* This function returns a list of runtime iterators in current scope which are exclusively
* dependent on given independent RuntimeIterators. The order of dependent iterators in the List
* is based on the order of independent Iterators present in the array . For each group the first
* iterator is its independent iterator
*
* @param indpndntItrs array of independent RuntimeIterators
*/
static List getDependentItrChainForIndpndntItrs(RuntimeIterator[] indpndntItrs,
ExecutionContext context) {
List ret = new ArrayList();
for (RuntimeIterator indpndntItr : indpndntItrs) {
ret.addAll(context.getCurrScopeDpndntItrsBasedOnSingleIndpndntItr(indpndntItr));
}
return ret;
}
/**
* This util function does a cartesian of the array of SelectResults object , expanding the
* resultant set to the number of iterators passed in expansionList. The position of the iterator
* fields in the final result is governed by the order of RuntimeIterators present in the
* finalList. If any condition needs to be evaluated during cartesian , it can be passed as
* operand
*
* @param results Array of SelectResults object which are to be cartesianed
* @param itrsForResultFields A two dimensional array of RuntimeIterator. Each row of this two
* dimensional RuntimeIterator array , maps to a SelectResults object in the results array.
* Thus the 0th row of two dimensional RuntimeIterator array will map to the 0th element of
* the SelectResults array. The order of RuntimeIterator in a row will map to the fields in
* the SelectResults object. The 0th RuntimeIterator will map to the 0th field of the
* corresponding SelectResults object. The number of rows in the two dimensional array of
* RuntimeIterator should be equal to the size of array of SelectResults object passed and
* the number of RuntimeIterators in each row should be equal to the number of fields in
* the SelectResults object . The SelectResults object itself may be a ResultBag object or
* a StructBag object.
*
* @param expansionList List containing RunimeIterators to which the final Results should be
* expanded to.
* @param finalList List containing RuntimeIterators which define the number of fields to be
* present in the resultant SelectResults and their relative positions. The Runtime
* Iterators present in the List should be either available in the expansion List or should
* be present in each row of the two dimensional RuntimeIterator array.
*
* @param context ExecutionContext object
* @param operand The CompiledValue which needs to be iter evaluated during cartesian. Only those
* tuples will be selected in the final Result for which oerand evaluates to true.
* @return SelectResults object representing the final result of the cartesian
*/
public static SelectResults cartesian(SelectResults[] results,
RuntimeIterator[][] itrsForResultFields, List expansionList, List finalList,
ExecutionContext context, CompiledValue operand) throws FunctionDomainException,
TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
SelectResults returnSet = null;
if (finalList.size() == 1) {
ObjectType type = ((RuntimeIterator) finalList.iterator().next()).getElementType();
if (type instanceof StructType) {
returnSet = QueryUtils.createStructCollection(context, (StructTypeImpl) type);
} else {
return QueryUtils.createResultCollection(context, type);
}
} else {
StructType structType = createStructTypeForRuntimeIterators(finalList);
returnSet = QueryUtils.createStructCollection(context, structType);
}
ListIterator expnItr = expansionList.listIterator();
doNestedIterations(0, returnSet, results, itrsForResultFields, finalList, expnItr,
results.length + expansionList.size(), context, operand);
return returnSet;
}
// TODO:Optimize the function further in terms of reducing the
// parameters passed in the function, if possible
private static void doNestedIterations(int level, SelectResults returnSet,
SelectResults[] results, RuntimeIterator[][] itrsForResultFields, List finalItrs,
ListIterator expansionItrs, int finalLevel, ExecutionContext context, CompiledValue operand)
throws FunctionDomainException, TypeMismatchException, NameResolutionException,
QueryInvocationTargetException {
if (level == finalLevel) {
// end recusrion
boolean select = true;
if (operand != null) {
select = applyCondition(operand, context);
}
Iterator itr = finalItrs.iterator();
int len = finalItrs.size();
if (len > 1) {
Object values[] = new Object[len];
int j = 0;
while (itr.hasNext()) {
values[j++] = ((RuntimeIterator) itr.next()).evaluate(context);
}
if (select) {
((StructFields) returnSet).addFieldValues(values);
}
} else {
if (select)
returnSet.add(((RuntimeIterator) itr.next()).evaluate(context));
}
} else if (level < results.length) {
SelectResults individualResultSet = results[level];
RuntimeIterator[] itrsForFields = itrsForResultFields[level];
int len = itrsForFields.length;
for (Object anIndividualResultSet : individualResultSet) {
// Check if query execution on this thread is canceled.
QueryMonitor.throwExceptionIfQueryOnCurrentThreadIsCanceled();
if (len == 1) {
// this means we have a ResultSet
itrsForFields[0].setCurrent(anIndividualResultSet);
} else {
Struct struct = (Struct) anIndividualResultSet;
Object[] fieldValues = struct.getFieldValues();
int size = fieldValues.length;
for (int i = 0; i < size; ++i) {
itrsForFields[i].setCurrent(fieldValues[i]);
}
}
doNestedIterations(level + 1, returnSet, results, itrsForResultFields, finalItrs,
expansionItrs, finalLevel, context, operand);
}
} else {
RuntimeIterator currLevel = (RuntimeIterator) expansionItrs.next();
SelectResults c = currLevel.evaluateCollection(context);
if (c == null) {
expansionItrs.previous();
return;
}
for (Object aC : c) {
currLevel.setCurrent(aC);
doNestedIterations(level + 1, returnSet, results, itrsForResultFields, finalItrs,
expansionItrs, finalLevel, context, operand);
}
expansionItrs.previous();
}
}
public static boolean applyCondition(CompiledValue operand, ExecutionContext context)
throws FunctionDomainException, TypeMismatchException, NameResolutionException,
QueryInvocationTargetException {
if (operand == null)
return true;
Object result = operand.evaluate(context);
if (result instanceof Boolean) {
return (Boolean) result;
} else if (result != null && result != QueryService.UNDEFINED) {
throw new TypeMismatchException(
String.format("AND/OR operands must be of type boolean, not type ' %s '",
result.getClass().getName()));
} else {
return false;
}
}
/**
* NOTE: intermediateResults should be a single element array
* <p>
* NOTE: itrsForIntermediateResults should be a two dimensional array but with only one row
* <p>
* TODO: This function is used to do cartesian of index resultset while
* <p>
* expanding/cutting down index resultset with the intermediate resultset
*/
private static void mergeRelationshipIndexResultsWithIntermediateResults(SelectResults returnSet,
SelectResults[] intermediateResults, RuntimeIterator[][] itrsForIntermediateResults,
Object[][] indexResults, RuntimeIterator[][] indexFieldToItrsMapping,
ListIterator expansionListItr, List finalItrs, ExecutionContext context, List[] checkList,
CompiledValue iterOps, IndexCutDownExpansionHelper[] icdeh, int level,
int maxExpnCartesianDepth) throws FunctionDomainException, TypeMismatchException,
NameResolutionException, QueryInvocationTargetException {
int resultSize = indexResults[level].length;
// TODO: Since this is constant for a given merge call, pass it as a
// parameter to
// the function rather than calling it every time
for (int j = 0; j < resultSize; ++j) {
if (setIndexFieldValuesInRespectiveIterators(indexResults[level][j],
indexFieldToItrsMapping[level], icdeh[level])) {
if (level == indexResults.length - 1) {
// Set the values in the Intermedaite Resultset
doNestedIterations(0, returnSet, intermediateResults, itrsForIntermediateResults,
finalItrs, expansionListItr, maxExpnCartesianDepth, context, iterOps);
} else {
mergeRelationshipIndexResultsWithIntermediateResults(returnSet, intermediateResults,
itrsForIntermediateResults, indexResults, indexFieldToItrsMapping, expansionListItr,
finalItrs, context, checkList, iterOps, icdeh, level + 1, maxExpnCartesianDepth);
if (icdeh[level + 1].cutDownNeeded) {
icdeh[level + 1].checkSet.clear();
}
}
}
}
}
// TODO: Test the function & write expnanation of the parameters
private static void mergeAndExpandCutDownRelationshipIndexResults(Object[][] values,
SelectResults result, RuntimeIterator[][] indexFieldToItrsMapping,
ListIterator expansionListIterator, List finalItrs, ExecutionContext context,
CompiledValue iterOps, IndexCutDownExpansionHelper[] icdeh, int level)
throws FunctionDomainException, TypeMismatchException, NameResolutionException,
QueryInvocationTargetException {
int resultSize = values[level].length;
// We do not know if the first X results might or might not fulfill all operands.
Boolean applyLimit = (Boolean) context.cacheGet(CompiledValue.CAN_APPLY_LIMIT_AT_INDEX);
int limit = (applyLimit != null && applyLimit) ? getLimitValue(context) : -1;
// Stops recursion if limit has already been met AND limit can be applied to index.
if (limit != -1 && result.size() >= limit) {
return;
}
for (int j = 0; j < resultSize; ++j) {
// Check if query execution on this thread is canceled.
QueryMonitor.throwExceptionIfQueryOnCurrentThreadIsCanceled();
if (setIndexFieldValuesInRespectiveIterators(values[level][j], indexFieldToItrsMapping[level],
icdeh[level])) {
if (level == values.length - 1) {
doNestedIterationsForIndex(expansionListIterator.hasNext(), result, finalItrs,
expansionListIterator, context, iterOps, limit, null);
if (limit != -1 && result.size() >= limit) {
break;
}
} else {
mergeAndExpandCutDownRelationshipIndexResults(values, result, indexFieldToItrsMapping,
expansionListIterator, finalItrs, context, iterOps, icdeh, level + 1);
if (icdeh[level + 1].cutDownNeeded) {
icdeh[level + 1].checkSet.clear();
}
}
}
}
}
// TODO: Explain the function & write test cases. A boolean false means by pass i.e the set value
// to be ignored
// End result if we have not already expanded is that we have created a new struct and added to a
// set to prevent future expansions of the same object
// It also advances the current object for the iterator.
private static boolean setIndexFieldValuesInRespectiveIterators(Object value,
RuntimeIterator[] indexFieldToItrsMapping, IndexCutDownExpansionHelper icdeh) {
boolean select = true;
int len = indexFieldToItrsMapping.length;
if (len == 1) {
// this means we have a ResultSet
Support.Assert(!icdeh.cutDownNeeded,
"If the index fields to iter mapping is of of size 1 then cut down cannot occur");
indexFieldToItrsMapping[0].setCurrent(value);
} else {
Struct struct = (Struct) value;
Object[] fieldValues = struct.getFieldValues();
int size = fieldValues.length;
Object[] checkFields = null;
if (icdeh.cutDownNeeded)
checkFields = new Object[icdeh.checkSize];
// Object values[] = new Object[numItersInResultSet];
int j = 0;
RuntimeIterator rItr = null;
for (int i = 0; i < size; i++) {
rItr = indexFieldToItrsMapping[i];
if (rItr != null) {
rItr.setCurrent(fieldValues[i]);
if (icdeh.cutDownNeeded) {
checkFields[j++] = fieldValues[i];
}
}
}
if (icdeh.cutDownNeeded) {
Object temp = null;
if (icdeh.checkSize == 1) {
temp = checkFields[0];
} else {
temp = new StructImpl((StructTypeImpl) icdeh.checkType, checkFields);
}
if (icdeh.checkSet.contains(temp)) {
select = false;
} else {
icdeh.checkSet.add(temp);
}
}
}
return select;
}
// creates the returned set and then calls other methods to do actual work
private static SelectResults cutDownAndExpandIndexResults(SelectResults result,
RuntimeIterator[] indexFieldToItrsMapping, List expansionList, List finalItrs,
ExecutionContext context, List checkList, CompiledValue iterOps, IndexInfo theFilteringIndex)
throws FunctionDomainException, TypeMismatchException, NameResolutionException,
QueryInvocationTargetException {
boolean useLinkedDataStructure = false;
boolean nullValuesAtStart = true;
Boolean orderByClause = (Boolean) context.cacheGet(CompiledValue.CAN_APPLY_ORDER_BY_AT_INDEX);
if (orderByClause != null && orderByClause) {
List orderByAttrs = (List) context.cacheGet(CompiledValue.ORDERBY_ATTRIB);
useLinkedDataStructure = orderByAttrs.size() == 1;
nullValuesAtStart = !((CompiledSortCriterion) orderByAttrs.get(0)).getCriterion();
}
SelectResults returnSet = null;
if (finalItrs.size() == 1) {
ObjectType resultType = ((RuntimeIterator) finalItrs.iterator().next()).getElementType();
if (useLinkedDataStructure) {
returnSet = context.isDistinct() ? new LinkedResultSet(resultType)
: new SortedResultsBag(resultType, nullValuesAtStart);
} else {
returnSet = QueryUtils.createResultCollection(context, resultType);
}
} else {
StructTypeImpl resultType = (StructTypeImpl) createStructTypeForRuntimeIterators(finalItrs);
if (useLinkedDataStructure) {
returnSet = context.isDistinct() ? new LinkedStructSet(resultType)
: new SortedResultsBag<Struct>((StructTypeImpl) resultType, nullValuesAtStart);
} else {
returnSet = QueryUtils.createStructCollection(context, resultType);
}
}
cutDownAndExpandIndexResults(returnSet, result, indexFieldToItrsMapping, expansionList,
finalItrs, context, checkList, iterOps, theFilteringIndex);
return returnSet;
}
// TODO: Explain the parameters passed
private static void cutDownAndExpandIndexResults(SelectResults returnSet, SelectResults result,
RuntimeIterator[] indexFieldToItrsMapping, List expansionList, List finalItrs,
ExecutionContext context, List checkList, CompiledValue iterOps, IndexInfo theFilteringIndex)
throws FunctionDomainException, TypeMismatchException, NameResolutionException,
QueryInvocationTargetException {
IndexCutDownExpansionHelper icdeh = new IndexCutDownExpansionHelper(checkList, context);
int len = indexFieldToItrsMapping.length;
// don't call instanceof ResultsBag here, since a StructBag is a subtype of ResultsBag
if (result.getClass() == ResultsBag.class) {
Support.Assert(len == 1, "The array size of iterators should be 1 here, but got " + len);
}
Iterator itr = result.iterator();
ListIterator expansionListIterator = expansionList.listIterator();
int limit = getLimitValue(context);
while (itr.hasNext()) {
DerivedInfo derivedInfo = null;
if (IndexManager.JOIN_OPTIMIZATION) {
derivedInfo = new DerivedInfo();
derivedInfo.setExpansionList(expansionList);
}
Object value = itr.next();
if (setIndexFieldValuesInRespectiveIterators(value, indexFieldToItrsMapping, icdeh)) {
// does that mean we don't get dupes even if they exist in the index?
// DO NESTED LOOPING
if (IndexManager.JOIN_OPTIMIZATION) {
derivedInfo.computeDerivedJoinResults(theFilteringIndex, context, iterOps);
}
doNestedIterationsForIndex(expansionListIterator.hasNext(), returnSet, finalItrs,
expansionListIterator, context, iterOps, limit, derivedInfo.derivedResults);
if (limit != -1 && returnSet.size() >= limit) {
break;
}
}
}
}
// returns the limit value from the context. This was set in CompiledSelect evaluate
// We do not apply limit if we have an order by attribute at this time
// it may be possible but we need better understanding of when ordering is taking place
// If it's at the index level, we may be able to apply limits at this point
// however a lot of the code in this class is fragile/unreadable/hard to maintain
private static int getLimitValue(ExecutionContext context) {
int limit = -1;
if (context.cacheGet(CompiledValue.ORDERBY_ATTRIB) == null) {
limit = context.cacheGet(CompiledValue.RESULT_LIMIT) != null
? (Integer) context.cacheGet(CompiledValue.RESULT_LIMIT) : -1;
}
return limit;
}
static CompiledID getCompiledIdFromPath(CompiledValue path) {
int type = path.getType();
if (type == OQLLexerTokenTypes.Identifier) {
return (CompiledID) path;
}
return getCompiledIdFromPath(path.getReceiver());
}
private static void doNestedIterationsForIndex(boolean continueRecursion, SelectResults resultSet,
List finalItrs, ListIterator expansionItrs, ExecutionContext context, CompiledValue iterOps,
int limit, Map<String, SelectResults> derivedResults) throws FunctionDomainException,
TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
if (!continueRecursion) {
// end recusrion
Iterator itr = finalItrs.iterator();
int len = finalItrs.size();
boolean select = true;
if (iterOps != null) {
select = applyCondition(iterOps, context);
}
if (len > 1) {
boolean isOrdered = resultSet.getCollectionType().isOrdered();
StructTypeImpl elementType =
(StructTypeImpl) resultSet.getCollectionType().getElementType();
// TODO: Optimize the LinkedStructSet implementation so that Object[] can be added rather
// than Struct
Object[] values = new Object[len];
int j = 0;
// creates tuple
while (itr.hasNext()) {
// Check if query execution on this thread is canceled.
QueryMonitor.throwExceptionIfQueryOnCurrentThreadIsCanceled();
values[j++] = ((RuntimeIterator) itr.next()).evaluate(context);
}
if (select) {
if (isOrdered) {
// ((LinkedStructSet) resultSet).add(new StructImpl(elementType, values));
// Can be LinkedStructSet or SortedResultsBag ( containing underlying LinkedHashMap)
resultSet.add(new StructImpl(elementType, values));
} else {
((StructFields) resultSet).addFieldValues(values);
}
}
} else {
if (select)
resultSet.add(((RuntimeIterator) itr.next()).evaluate(context));
}
} else {
RuntimeIterator currentLevel = (RuntimeIterator) expansionItrs.next();
// Calculate the key to find the derived join results. If we are a non nested lookup it will
// be a Compiled Region otherwise it will be a CompiledPath that
// we can extract the id from. In the end the result will be the alias which is used as a
// prefix
String key = null;
boolean useDerivedResults = true;
if (currentLevel.getCmpIteratorDefn().getCollectionExpr()
.getType() == OQLLexerTokenTypes.RegionPath) {
key = currentLevel.getCmpIteratorDefn().getName() + ':' + currentLevel.getDefinition();
} else if (currentLevel.getCmpIteratorDefn().getCollectionExpr()
.getType() == OQLLexerTokenTypes.LITERAL_select) {
useDerivedResults = false;
}
SelectResults c;
CompiledValue path = currentLevel.getCmpIteratorDefn().getCollectionExpr();
if (useDerivedResults && derivedResults != null && path.hasIdentifierAtLeafNode()) {
key = getCompiledIdFromPath(path).getId()
+ ':' + currentLevel.getDefinition();
if (derivedResults.containsKey(key)) {
c = derivedResults.get(key);
} else {
c = currentLevel.evaluateCollection(context);
}
} else {
c = currentLevel.evaluateCollection(context);
}
if (c == null) {
expansionItrs.previous();
return;
}
for (Object aC : c) {
// Check if query execution on this thread is canceled.
QueryMonitor.throwExceptionIfQueryOnCurrentThreadIsCanceled();
currentLevel.setCurrent(aC);
doNestedIterationsForIndex(expansionItrs.hasNext(), resultSet, finalItrs, expansionItrs,
context, iterOps, limit, derivedResults);
if (limit != -1 && resultSet.size() >= limit) {
break;
}
}
expansionItrs.previous();
}
}
/**
* This function will evaluate the starting CompiledValue for a given CompliedValue. The value
* returned will always be either the original CompiledValue, or a CompiledID, or a
* CompiledRegion, or a CompiledBindArgument, or a CompiledOperation. The ExecutionContext passed
* can be null. If it is null, then for a CompiledOperation , if supposed to get resolved
* implicitly, will have its receiver as null. This is because in normal cases , a CompiledID
* marks the termination, but in case of CompiledOperation this is not the case
*
* @param expr CompiledValue object
*/
static CompiledValue obtainTheBottomMostCompiledValue(CompiledValue expr) {
boolean toContinue = true;
int exprType = expr.getType();
while (toContinue) {
switch (exprType) {
case OQLLexerTokenTypes.RegionPath:
toContinue = false;
break;
case OQLLexerTokenTypes.METHOD_INV:
CompiledOperation operation = (CompiledOperation) expr;
// pass the ExecutionContext as null, thus never implicitly resolving to RuntimeIterator
expr = operation.getReceiver(null);
if (expr == null) {
expr = operation;
toContinue = false;
}
break;
case CompiledValue.PATH:
expr = expr.getReceiver();
break;
case OQLLexerTokenTypes.TOK_LBRACK:
expr = expr.getReceiver();
break;
default:
toContinue = false;
break;
}
if (toContinue) {
exprType = expr.getType();
}
}
return expr;
}
/**
* This function creates a StructType using Internal IDs of the iterators as the field names for
* the StructType. It should be invoked iff the iterators size is greater than 1
*
* @param runTimeIterators List of RuntimeIterator objects
* @return StructType object
*
*/
static StructType createStructTypeForRuntimeIterators(List runTimeIterators) {
int len = runTimeIterators.size();
String[] fieldNames = new String[len];
String[] indexAlternativeFieldNames = new String[len];
ObjectType[] fieldTypes = new ObjectType[len];
// use an Iterator as the chances are that we will be sending
// LinkedList rather than ArrayList
Iterator itr = runTimeIterators.iterator();
int i = 0;
while (itr.hasNext()) {
RuntimeIterator iter = (RuntimeIterator) itr.next();
fieldNames[i] = iter.getInternalId();
indexAlternativeFieldNames[i] = iter.getIndexInternalID();
fieldTypes[i++] = iter.getElementType();
}
return new StructTypeImpl(fieldNames, indexAlternativeFieldNames, fieldTypes);
}
/**
* This function returns the ultimate independent RuntimeIterators of current scope on which the
* CompiledValue passed is dependent upon. This does not return the RuntimeIterators on which it
* may be dependent but are not part of the current scope. If no such RuntimeIterator exists it
* returns empty set.
*
* @param compiledValue CompiledValue Object
* @param context ExecutionContextobject
* @return Set containing the ultimate independent RuntimeIterators of current scope
*/
static Set getCurrentScopeUltimateRuntimeIteratorsIfAny(CompiledValue compiledValue,
ExecutionContext context) {
Set set = new HashSet();
context.computeUltimateDependencies(compiledValue, set);
Iterator iter = set.iterator();
while (iter.hasNext()) {
RuntimeIterator rIter = (RuntimeIterator) iter.next();
if (rIter.getScopeID() != context.currentScope().getScopeID()/* context.getScopeCount() */)
iter.remove();
}
return set;
}
/**
* Returns the pair of RangeIndexes available for a composite condition ( equi join across the
* region). It will either return two indexes or will return null. *
*
* @param lhs One of the operands of the equi-join condition
* @param rhs The other operand of the equi-join condition
* @param context ExecutionContext object
* @param operator The operator which necesarily has to be an equality ( ' = ' )
* @return An array of IndexData object with 0th IndexData for the lhs operand & 1th object for
* rhs operad
*/
static IndexData[] getRelationshipIndexIfAny(CompiledValue lhs, CompiledValue rhs,
ExecutionContext context, int operator)
throws AmbiguousNameException, TypeMismatchException, NameResolutionException {
if (operator != OQLLexerTokenTypes.TOK_EQ) {
// Operator must be '='
return null;
}
// Do not use PrimaryKey Index
IndexData lhsIndxData = QueryUtils.getAvailableIndexIfAny(lhs, context, false);
if (lhsIndxData == null) {
return null;
}
// Do not use PrimaryKey Index
IndexData rhsIndxData = QueryUtils.getAvailableIndexIfAny(rhs, context, false);
if (rhsIndxData == null) {
// release the lock held on lhsIndex as it will not be used
Index index = lhsIndxData.getIndex();
Index prIndex = ((AbstractIndex) index).getPRIndex();
if (prIndex != null) {
((PartitionedIndex) prIndex).releaseIndexReadLockForRemove();
} else {
((AbstractIndex) index).releaseIndexReadLockForRemove();
}
return null;
}
IndexProtocol lhsIndx = lhsIndxData.getIndex();
IndexProtocol rhsIndx = rhsIndxData.getIndex();
if (lhsIndx.isValid() && rhsIndx.isValid()) {
return new IndexData[] {lhsIndxData, rhsIndxData};
}
return null;
}
/**
* Gets an Index available for the condition
*
* @param cv Condition on which index needs to be obtained
* @param context ExecutionContext object
* @param operator int argument identifying the type of operator
* @return IndexData object
*/
static IndexData getAvailableIndexIfAny(CompiledValue cv, ExecutionContext context, int operator)
throws AmbiguousNameException, TypeMismatchException, NameResolutionException {
// If operator is = or != then first search for PRIMARY_KEY Index
boolean usePrimaryIndex =
operator == OQLLexerTokenTypes.TOK_EQ || operator == OQLLexerTokenTypes.TOK_NE;
return getAvailableIndexIfAny(cv, context, usePrimaryIndex);
}
private static IndexData getAvailableIndexIfAny(CompiledValue cv, ExecutionContext context,
boolean usePrimaryIndex)
throws AmbiguousNameException, TypeMismatchException, NameResolutionException {
Set set = new HashSet();
context.computeUltimateDependencies(cv, set);
if (set.size() != 1)
return null;
RuntimeIterator rIter = (RuntimeIterator) set.iterator().next();
String regionPath = null;
// An Index is not available if the ultimate independent RuntimeIterator is
// of different scope or if the underlying
// collection is not a Region
if (rIter.getScopeID() != context.currentScope().getScopeID() /* context.getScopeCount() */
|| (regionPath = context.getRegionPathForIndependentRuntimeIterator(rIter)) == null) {
return null;
}
// The independent iterator is added as the first element
List groupRuntimeItrs = context.getCurrScopeDpndntItrsBasedOnSingleIndpndntItr(rIter);
String[] definitions = new String[groupRuntimeItrs.size()];
Iterator iterator = groupRuntimeItrs.iterator();
int i = 0;
while (iterator.hasNext()) {
RuntimeIterator rIterator = (RuntimeIterator) iterator.next();
definitions[i++] = rIterator.getDefinition();
}
IndexData indexData = IndexUtils.findIndex(regionPath, definitions, cv, "*", context.getCache(),
usePrimaryIndex, context);
if (indexData != null) {
if (logger.isDebugEnabled()) {
logger.debug("Indexed expression for indexed data : {} for region : {}",
indexData.getIndex().getCanonicalizedIndexedExpression(), regionPath);
}
}
return indexData;
}
/**
* Conditions the raw index result obtained on a non composite condition ( i.e a condition with a
* format of variable = constant . A constant may be either a CompiledLiteral or an expression
* which is completely dependent on iterators other than the current scope. The variable is a path
* expression which is completely dependent on iterators belonging only to a single region ( i.e
* iterators belonging to a Group of iterators only dependent on a single indpendent iterator for
* the region). The raw index result is appropriately expanded / cutdown with evaluation of iter
* operand if any , StructType/ObjectType appropriately set, Shuffling of the fields appropriately
* done, such that the final result is compatible, in terms of the position and names of the
* fields of SelectResults( StructBag) , with the Iterators of the query from clause ( if complete
* expansion flag is true) or the chain of iterators identified by the indpendent iterator for the
* group.
*
* @param indexResults The raw index results which may be a ResultBag object or an StructBag
* object
* @param indexInfo IndexInfo object containing data such as match level & the mapping of the
* position of Runtime Iterators of the group to the position of the corresponding field in
* the index result ( StructBag)
* @param context ExecutionContext object
* @param indexFieldsSize The number of fields contained in the raw index resultset
* @param completeExpansion The boolean indicating whether the index resultset needs to be
* expanded to the query from clause level ( i.e top level)
* @param iterOperands The CompiledValue representing the iter operand which needs to be evaluated
* during conditioning of index resultset
* @param grpIndpndntItr An Array of Independent Iterators representing their respective groups.
* The conditioned Index Resultset will be created as per the chain of dependent iterators
* for each group.
* @return SelectResults object representing the conditioned Results
*/
static SelectResults getConditionedIndexResults(SelectResults indexResults, IndexInfo indexInfo,
ExecutionContext context, int indexFieldsSize, boolean completeExpansion,
CompiledValue iterOperands, RuntimeIterator[] grpIndpndntItr) throws FunctionDomainException,
TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
if (!completeExpansion && grpIndpndntItr != null && grpIndpndntItr.length > 1) {
// If for a Single Base Collection Index usage we are having
// independent
// group of iterators with size greater than 1 , that implies it is being
// invoked
// from CompositeGroupJunction with a single GroupJunction present in it.
// If it so happens that Complete expansion is false, this implies that we
// need to expand
// it to CompositeGroupJunction level. We will esnure in
// CompositeGroupJunction that
// we pass an array of independent iterators of size > 1 only if complete
// expansion is false
IndexConditioningHelper ich = new IndexConditioningHelper(indexInfo, context, indexFieldsSize,
completeExpansion, iterOperands, null);
// We will definitely need shuffling as a CompositeGroupJunction itself
// indicates
// that there will be at least one independent group to which we need to
// expand to
ich.finalList = getDependentItrChainForIndpndntItrs(grpIndpndntItr, context);
// Add the iterators of remaining independent grp to the expansion list
List newExpList = new ArrayList();
int len = grpIndpndntItr.length;
RuntimeIterator tempItr = null;
for (RuntimeIterator aGrpIndpndntItr : grpIndpndntItr) {
tempItr = aGrpIndpndntItr;
if (tempItr != ich.indpndntItr) {
newExpList.addAll(context.getCurrScopeDpndntItrsBasedOnSingleIndpndntItr(tempItr));
}
}
newExpList.addAll(ich.expansionList);
ich.expansionList = newExpList;
QueryObserver observer = QueryObserverHolder.getInstance();
try {
observer.beforeCutDownAndExpansionOfSingleIndexResult(indexInfo._index, indexResults);
indexResults =
QueryUtils.cutDownAndExpandIndexResults(indexResults, ich.indexFieldToItrsMapping,
ich.expansionList, ich.finalList, context, ich.checkList, iterOperands, indexInfo);
} finally {
observer.afterCutDownAndExpansionOfSingleIndexResult(indexResults);
}
} else {
IndexConditioningHelper ich = new IndexConditioningHelper(indexInfo, context, indexFieldsSize,
completeExpansion, iterOperands, grpIndpndntItr != null ? grpIndpndntItr[0] : null);
if (ich.shufflingNeeded) {
QueryObserver observer = QueryObserverHolder.getInstance();
try {
observer.beforeCutDownAndExpansionOfSingleIndexResult(indexInfo._index, indexResults);
indexResults = QueryUtils.cutDownAndExpandIndexResults(indexResults,
ich.indexFieldToItrsMapping, ich.expansionList, ich.finalList, context, ich.checkList,
iterOperands, indexInfo);
} finally {
observer.afterCutDownAndExpansionOfSingleIndexResult(indexResults);
}
} else if (indexInfo.mapping.length > 1) {
indexResults.setElementType(ich.structType);
}
}
return indexResults;
}
/**
* This function is used to evaluate a filter evaluatable CompositeCondition(ie Range Indexes
* available on both LHS & RHS operands).This function is invoked from AND junction evaluation of
* CompositeGroupJunction. It expands the intermediate resultset passed , to the level of groups
* determined by the LHS & RHS operand, using the range indexes. It is possible that the group of
* iterators for an operand of condition already exists in the intermediate resultset passed. In
* such situation, the intermediate resultset is iterated & the operand ( whose group of iterators
* are available in the intermediate resultset ) is evaluated. For each such evaluated value , the
* other operand's Range Index is queried & the Range Index's results are appropriately expanded &
* cut down & a final tuple obtained( which includes the previously existing fields of
* intermediate resultset). The array of independent iterators passed from the Composite Group
* junction will be null, except for the final condition ( subject to the fact that complete
* expansion flag is false. Otherwise even for final condition , the array will be null) as that
* array will be used to get the final position of iterators in the resultant StructBag
*
* TODO: break this method up
*
* @param intermediateResults SelectResults object containing the intermediate resultset obtained
* by evaluation of previous filter evaluatable composite conditions of the
* CompositeGroupJunction
* @param indxInfo Array of IndexInfo objects ( size 2), representing the range index for the two
* operands of the condition
* @param context ExecutionContext object
* @param completeExpansionNeeded A boolean when true indicates that the final result from
* Composite GroupJunction needs to be evaluated to the query from clause ( top ) level.
* @param iterOperands CompiledValue representing the conditions which are to be iter evaluated.
* This can exist only if instead of AllGroupJunction we have a single
* CompositeGroupJunction
* @param indpdntItrs Array of RuntimeIterators representing the independent iterators of their
* representative groups forming the CompositeGroupJunction *
* @return SelectResults The Result object created by evaluating the filter evaluatable condition
* merged with the intermediate results
*/
static SelectResults getRelationshipIndexResultsMergedWithIntermediateResults(
SelectResults intermediateResults, IndexInfo[] indxInfo, ExecutionContext context,
boolean completeExpansionNeeded, CompiledValue iterOperands, RuntimeIterator[] indpdntItrs)
throws FunctionDomainException, TypeMismatchException, NameResolutionException,
QueryInvocationTargetException {
ObjectType resultType1 = indxInfo[0]._index.getResultSetType();
int indexFieldsSize1 = resultType1 instanceof StructType
? ((StructTypeImpl) resultType1).getFieldNames().length : 1;
ObjectType resultType2 = indxInfo[1]._index.getResultSetType();
int indexFieldsSize2 = resultType2 instanceof StructType
? ((StructTypeImpl) resultType2).getFieldNames().length : 1;
/*
* even if the complete expansion is needed pass the flag of complete expansion as false. Thus
* for LHS & RHS we will get the expansionList for that individual group.
*/
// NOTE: use false for completeExpansion irrespective of actual value
IndexConditioningHelper ich1 = new IndexConditioningHelper(indxInfo[0], context,
indexFieldsSize1, false, iterOperands, null);
// NOTE: use false for completeExpansion irrespective of actual value
IndexConditioningHelper ich2 = new IndexConditioningHelper(indxInfo[1], context,
indexFieldsSize2, false, iterOperands, null);
// We cannot have a condition where in intermediateResultset is empty
// or null & complete
// expansion flag true because in that case instead of this function we should
// have called
int noOfIndexesToUse = intermediateResults == null || intermediateResults.isEmpty() ? 2 : 0;
RuntimeIterator[] resultFieldsItrMapping = null;
List allItrs = context.getCurrentIterators();
IndexConditioningHelper singleUsableICH = null;
IndexConditioningHelper nonUsableICH = null;
List finalList =
completeExpansionNeeded ? allItrs : indpdntItrs == null ? new ArrayList() : null;
// the set will contain those iterators which we don't have to expand to either because they are
// already present ( because of intermediate results or because index result already contains
// them
Set expnItrsToIgnore = null;
if (noOfIndexesToUse == 0) {
// If the intermediate Resultset is not empty then check if the resultset
// fields of intermediate
// resultset contains any independent iterator of the current condition
noOfIndexesToUse = 2;
StructType stype = (StructType) intermediateResults.getCollectionType().getElementType();
String[] fieldNames = stype.getFieldNames();
int len = fieldNames.length;
resultFieldsItrMapping = new RuntimeIterator[len];
String fieldName = null;
String lhsID = ich1.indpndntItr.getInternalId();
String rhsID = ich2.indpndntItr.getInternalId();
for (int i = 0; i < len; ++i) {
fieldName = fieldNames[i];
if (noOfIndexesToUse != 0) {
if (fieldName.equals(lhsID)) {
--noOfIndexesToUse;
singleUsableICH = ich2;
nonUsableICH = ich1;
} else if (fieldName.equals(rhsID)) {
--noOfIndexesToUse;
singleUsableICH = ich1;
nonUsableICH = ich2;
}
}
int pos = Integer.parseInt(fieldName.substring(4));
RuntimeIterator itrPrsntInIntermdtRes = (RuntimeIterator) allItrs.get(pos - 1);
resultFieldsItrMapping[i] = itrPrsntInIntermdtRes;
// the iterator below is already present in resultset so needs to be ignored for expansion
if (completeExpansionNeeded) {
if (expnItrsToIgnore == null) {
expnItrsToIgnore = new HashSet();
}
expnItrsToIgnore.add(itrPrsntInIntermdtRes);
} else if (indpdntItrs == null) {
// We will need to know the intermediate iterators so as to know
// the final list which will be used to obtain the correct structset.
// But if the independent group of iterators is passed, the final list needs
// to be calculated
// on its basis
finalList.add(itrPrsntInIntermdtRes);
}
}
if (noOfIndexesToUse == 0) {
singleUsableICH = null;
}
}
QueryObserver observer = QueryObserverHolder.getInstance();
if (noOfIndexesToUse == 2) {
List data = null;
try {
ArrayList resultData = new ArrayList();
observer.beforeIndexLookup(indxInfo[0]._index, OQLLexerTokenTypes.TOK_EQ, null);
observer.beforeIndexLookup(indxInfo[1]._index, OQLLexerTokenTypes.TOK_EQ, null);
if (context.getBucketList() != null) {
data = queryEquijoinConditionBucketIndexes(indxInfo, context);
} else {
data = indxInfo[0]._index.queryEquijoinCondition(indxInfo[1]._index, context);
}
} finally {
observer.afterIndexLookup(data);
}
// For sure we need to evaluate both the conditions & expand it only to
// its own respective
// Ignore the boolean of reshuffling needed etc for this case
List totalExpList = new ArrayList();
totalExpList.addAll(ich1.expansionList);
totalExpList.addAll(ich2.expansionList);
if (completeExpansionNeeded) {
if (expnItrsToIgnore == null) {
// The expnItrsToIgnore set being null at this point implies that though complete
// expansion flag is true but intermediate result set is empty
Support.Assert(intermediateResults == null || intermediateResults.isEmpty(),
"expnItrsToIgnore should not have been null if the intermediate result set is not empty");
expnItrsToIgnore = new HashSet();
}
expnItrsToIgnore.addAll(ich1.finalList);
expnItrsToIgnore.addAll(ich2.finalList);
// identify the iterators which we need to expand to
// TODO: Make the code compact by using a common function to take care of this
int size = finalList.size();
for (int i = 0; i < size; ++i) {
RuntimeIterator currItr = (RuntimeIterator) finalList.get(i);
// If the runtimeIterators of scope not present in CheckSet add it to the expansion list
if (!expnItrsToIgnore.contains(currItr)) {
totalExpList.add(currItr);
}
}
} else {
// If the independent itrs passed is not null, this implies that we are evaluating the last
// filterable cc of the AND junction so the final resultset should have placement of
// iterators in the order of indpendent iterators present in CGJ. Otherwise we will have
// struct set mismatch while doing intersection with GroupJunction results
if (indpdntItrs != null) {
finalList = getDependentItrChainForIndpndntItrs(indpdntItrs, context);
} else {
finalList.addAll(ich1.finalList);
finalList.addAll(ich2.finalList);
}
}
List[] checkList = new List[] {ich1.checkList, ich2.checkList};
StructType stype = createStructTypeForRuntimeIterators(finalList);
SelectResults returnSet = QueryUtils.createStructCollection(context, stype);
RuntimeIterator[][] mappings = new RuntimeIterator[2][];
mappings[0] = ich1.indexFieldToItrsMapping;
mappings[1] = ich2.indexFieldToItrsMapping;
List[] totalCheckList = new List[] {ich1.checkList, ich2.checkList};
RuntimeIterator[][] resultMappings = new RuntimeIterator[1][];
resultMappings[0] = resultFieldsItrMapping;
Iterator dataItr = data.iterator();
IndexCutDownExpansionHelper[] icdeh = new IndexCutDownExpansionHelper[] {
new IndexCutDownExpansionHelper(ich1.checkList, context),
new IndexCutDownExpansionHelper(ich2.checkList, context)};
ListIterator expansionListIterator = totalExpList.listIterator();
if (dataItr.hasNext()) {
observer = QueryObserverHolder.getInstance();
try {
observer.beforeMergeJoinOfDoubleIndexResults(indxInfo[0]._index, indxInfo[1]._index,
data);
boolean doMergeWithIntermediateResults =
intermediateResults != null && !intermediateResults.isEmpty();
int maxCartesianDepth = totalExpList.size() + (doMergeWithIntermediateResults ? 1 : 0);
while (dataItr.hasNext()) {
// TODO: Change the code in range Index so that while collecting data instead of
// creating two dimensional object array , we create one dimensional Object array of
// size 2, & each elemnt stores an Object array
Object[][] values = (Object[][]) dataItr.next();
// Before doing the cartesian of the Results , we need to clear the CheckSet of
// InexCutDownExpansionHelper. This is needed because for a new key , the row of sets
// needs to be considered fresh as presence of old row in checkset may cause us to
// wrongly skip the similar row of a set , even when the row in its entirety is unique (
// made by different data in the other set)
if (doMergeWithIntermediateResults) {
mergeRelationshipIndexResultsWithIntermediateResults(returnSet,
new SelectResults[] {intermediateResults}, resultMappings, values, mappings,
expansionListIterator, finalList, context, checkList, iterOperands, icdeh, 0,
maxCartesianDepth);
} else {
mergeAndExpandCutDownRelationshipIndexResults(values, returnSet, mappings,
expansionListIterator, finalList, context, iterOperands, icdeh,
0);
}
if (icdeh[0].cutDownNeeded)
icdeh[0].checkSet.clear();
}
} finally {
observer.afterMergeJoinOfDoubleIndexResults(returnSet);
}
}
return returnSet;
} else if (noOfIndexesToUse == 1) {
// There exists one independent iterator in the current condition which is also a part of the
// intermediate resultset Identify the final List which will depend upon the complete
// expansion flag Identify the iterators to be expanded to, which will also depend upon
// complete expansion flag..
List totalExpList = new ArrayList(singleUsableICH.expansionList);
if (completeExpansionNeeded) {
Support.Assert(expnItrsToIgnore != null,
"expnItrsToIgnore should not have been null as we are in this block itself indicates that intermediate results was not null");
expnItrsToIgnore.addAll(singleUsableICH.finalList);
// identify the iterators which we need to expand to
// TODO: Make the code compact by using a common function to take care of this
int size = finalList.size();
for (int i = 0; i < size; ++i) {
RuntimeIterator currItr = (RuntimeIterator) finalList.get(i);
// If the runtimeIterators of scope not present in CheckSet add it to the expansion list
if (!expnItrsToIgnore.contains(currItr)) {
totalExpList.add(currItr);
}
}
} else {
// If the independent itrs passed is not null, this implies that we are evaluating the last
// filterable cc of the AND junction so the final resultset should have placement of
// iterators in the order of indpendent iterators present in CGJ. Otherwise we will havve
// struct set mismatch while doing intersection with GroupJunction results
if (indpdntItrs != null) {
finalList = getDependentItrChainForIndpndntItrs(indpdntItrs, context);
} else {
finalList.addAll(singleUsableICH.finalList);
}
}
StructType stype = createStructTypeForRuntimeIterators(finalList);
SelectResults returnSet = QueryUtils.createStructCollection(context, stype);
// Obtain the empty resultset for the single usable index
IndexProtocol singleUsblIndex = singleUsableICH.indxInfo._index;
CompiledValue nonUsblIndxPath = nonUsableICH.indxInfo._path;
ObjectType singlUsblIndxResType = singleUsblIndex.getResultSetType();
SelectResults singlUsblIndxRes = null;
if (singlUsblIndxResType instanceof StructType) {
singlUsblIndxRes =
QueryUtils.createStructCollection(context, (StructTypeImpl) singlUsblIndxResType);
} else {
singlUsblIndxRes = QueryUtils.createResultCollection(context, singlUsblIndxResType);
}
// iterate over the intermediate structset
Iterator intrmdtRsItr = intermediateResults.iterator();
observer = QueryObserverHolder.getInstance();
try {
observer.beforeIndexLookup(singleUsblIndex, OQLLexerTokenTypes.TOK_EQ, null);
observer.beforeIterJoinOfSingleIndexResults(singleUsblIndex, nonUsableICH.indxInfo._index);
while (intrmdtRsItr.hasNext()) {
Struct strc = (Struct) intrmdtRsItr.next();
Object[] val = strc.getFieldValues();
int len = val.length;
for (int i = 0; i < len; ++i) {
resultFieldsItrMapping[i].setCurrent(val[i]);
}
// TODO: Issue relevant index use callbacks to QueryObserver
Object key = nonUsblIndxPath.evaluate(context);
// TODO: Check this logic out
if (key != null && key.equals(QueryService.UNDEFINED)) {
continue;
}
singleUsblIndex.query(key, OQLLexerTokenTypes.TOK_EQ, singlUsblIndxRes, context);
cutDownAndExpandIndexResults(returnSet, singlUsblIndxRes,
singleUsableICH.indexFieldToItrsMapping, totalExpList, finalList, context,
singleUsableICH.checkList, iterOperands, singleUsableICH.indxInfo);
singlUsblIndxRes.clear();
}
} finally {
observer.afterIterJoinOfSingleIndexResults(returnSet);
observer.afterIndexLookup(returnSet);
}
return returnSet;
} else {
// This condition is filter evaluatable but both the RHS group as
// well as
// LHS group of iterators are present in the intermediate resultset. As a
// result indexes
// cannot be used for this condition. This condition needs to be iter
// evaluated.
// For BETTER PERF INDEXES SHOULD BE REMOVED FROM THIS CONDITION SO THAT
// IT BECOMES
// PART OF ITER OPERANDS
if (logger.isDebugEnabled()) {
StringBuilder tempBuffLhs = new StringBuilder();
StringBuilder tempBuffRhs = new StringBuilder();
ich1.indxInfo._path.generateCanonicalizedExpression(tempBuffLhs, context);
ich2.indxInfo._path.generateCanonicalizedExpression(tempBuffRhs, context);
logger.debug("For better performance indexes are not used for the condition {} = {}",
tempBuffLhs, tempBuffRhs);
}
CompiledValue reconstructedVal = new CompiledComparison(ich1.indxInfo._path,
ich2.indxInfo._path, OQLLexerTokenTypes.TOK_EQ);
// Add this reconstructed value to the iter operand if any
CompiledValue finalVal = reconstructedVal;
if (iterOperands != null) {
// The type of CompiledJunction has to be AND junction as this function gets invoked only
// for AND . Also it is OK if we have iterOperands which itself is a CompiledJunction. We
// can have a tree of CompiledJunction with its operands being a CompiledComparison & a
// CompiledJunction. We can live without creating a flat structure
finalVal = new CompiledJunction(new CompiledValue[] {iterOperands, reconstructedVal},
OQLLexerTokenTypes.LITERAL_and);
}
RuntimeIterator[][] resultMappings = new RuntimeIterator[1][];
resultMappings[0] = resultFieldsItrMapping;
return cartesian(new SelectResults[] {intermediateResults}, resultMappings,
Collections.emptyList(), finalList, context, finalVal);
}
}
/**
* This function is used to evaluate a filter evaluatable composite condition. It gets invoked
* either from a CompositeGroupJunction of "OR" type or a where clause containing single composite
* condition. In the later case the boolean completeExpansion flag is always true. While in the
* former case it may be true or false. If it is false, the array of independent iterators passed
* is not null.
*
* @param data A List object whose elements are two dimensional object array. Each element of the
* List represent a value which satisfies the equi-join condition. Since there may be more
* than one tuples on either side of the equality condition which meet the criteria for a
* given value, we require a 2 dimensional Object array. The cartesian of the two rows will
* give us the set of tuples satisfying the join criteria. Each element of the row of
* Object Array may be either an Object or a Struct object.
* @param indxInfo An array of IndexInfo objects of size 2 , representing the range indexes of the
* two operands. The other Index maps to the 0th Object array row of the List object ( data
* ) & so on.
* @param context ExecutionContext object
* @param completeExpansionNeeded boolean if true indicates that the CGJ needs to be expanded to
* the query from clause ( top level )
* @param iterOperands This will be null as for OR junction we cannot have iter operand
* @param indpdntItrs Array of independent iterators representing the various Groups forming the
* composite group junction. It will be null, if complete expansion flag is true
* @return SelectResults objet representing the result obtained by evaluating a filter evaluatable
* composite condition in an OR junction. The returned Result is expanded either to the
* CompositeGroupJunction level or to the top level as the case may be
*/
static SelectResults getConditionedRelationshipIndexResultsExpandedToTopOrCGJLevel(List data,
IndexInfo[] indxInfo, ExecutionContext context, boolean completeExpansionNeeded,
CompiledValue iterOperands, RuntimeIterator[] indpdntItrs) throws FunctionDomainException,
TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
ObjectType resultType1 = indxInfo[0]._index.getResultSetType();
int indexFieldsSize1 = resultType1 instanceof StructType
? ((StructTypeImpl) resultType1).getFieldNames().length : 1;
ObjectType resultType2 = indxInfo[1]._index.getResultSetType();
int indexFieldsSize2 = resultType2 instanceof StructType
? ((StructTypeImpl) resultType2).getFieldNames().length : 1;
/*
* even if th complete expansion is needed pass the flag of complete expansion as false. Thus
* for LHS & RHS we will get the expansionList for that individual group. Thus the total
* expansion List wil contain sum of the individual expansion lists plus all the iterators of
* the current scope which are dependent on any other groups or are composite iterators ( i.e
* dependent on both the independent groups currently under consideration
*/
// pass completeExpansion as false, irrespective of actual value
IndexConditioningHelper ich1 = new IndexConditioningHelper(indxInfo[0], context,
indexFieldsSize1, false, iterOperands, null);
// pass completeExpansion as false, irrespective of actual value
IndexConditioningHelper ich2 = new IndexConditioningHelper(indxInfo[1], context,
indexFieldsSize2, false, iterOperands, null);
List totalExpList = new ArrayList();
totalExpList.addAll(ich1.expansionList);
totalExpList.addAll(ich2.expansionList);
List totalFinalList = null;
if (completeExpansionNeeded) {
totalFinalList = context.getCurrentIterators();
Set expnItrsAlreadyAccounted = new HashSet();
expnItrsAlreadyAccounted.addAll(ich1.finalList);
expnItrsAlreadyAccounted.addAll(ich2.finalList);
int size = totalFinalList.size();
for (int i = 0; i < size; ++i) {
RuntimeIterator currItr = (RuntimeIterator) totalFinalList.get(i);
// If the runtimeIterators of scope not present in CheckSet add it to the expansion list
if (!expnItrsAlreadyAccounted.contains(currItr)) {
totalExpList.add(currItr);
}
}
} else {
totalFinalList = new ArrayList();
for (int i = 0; i < indpdntItrs.length; ++i) {
RuntimeIterator indpndntItr = indpdntItrs[i];
if (indpndntItr == ich1.finalList.get(0)) {
totalFinalList.addAll(ich1.finalList);
} else if (indpndntItr == ich2.finalList.get(0)) {
totalFinalList.addAll(ich2.finalList);
} else {
List temp = context.getCurrScopeDpndntItrsBasedOnSingleIndpndntItr(indpndntItr);
totalFinalList.addAll(temp);
totalExpList.addAll(temp);
}
}
}
SelectResults returnSet;
StructType stype = createStructTypeForRuntimeIterators(totalFinalList);
if (totalFinalList.size() == 1) {
returnSet = QueryUtils.createResultCollection(context, new ObjectTypeImpl(stype.getClass()));
} else {
returnSet = QueryUtils.createStructCollection(context, stype);
}
RuntimeIterator[][] mappings = new RuntimeIterator[2][];
mappings[0] = ich1.indexFieldToItrsMapping;
mappings[1] = ich2.indexFieldToItrsMapping;
Iterator dataItr = data.iterator();
IndexCutDownExpansionHelper[] icdeh =
new IndexCutDownExpansionHelper[] {new IndexCutDownExpansionHelper(ich1.checkList, context),
new IndexCutDownExpansionHelper(ich2.checkList, context)};
ListIterator expansionListIterator = totalExpList.listIterator();
if (dataItr.hasNext()) {
QueryObserver observer = QueryObserverHolder.getInstance();
try {
observer.beforeMergeJoinOfDoubleIndexResults(ich1.indxInfo._index, ich2.indxInfo._index,
data);
while (dataItr.hasNext()) {
// TODO: Change the code in range Index so that while collecting data instead of creating
// two dimensional object array , we create one dimensional Object array of size 2, & each
// elemnt stores an Object array
Object[][] values = (Object[][]) dataItr.next();
// Before doing the cartesian of the Results , we need to clear the CheckSet of
// IndexCutDownExpansionHelper. This is needed because for a new key , the row of sets
// needs to be considered fresh as presence of old row in checkset may cause us to wrongly
// skip the similar row of a set , even when the row in its entirety is unique ( made by
// different data in the other set)
mergeAndExpandCutDownRelationshipIndexResults(values, returnSet, mappings,
expansionListIterator, totalFinalList, context, iterOperands, icdeh,
0 /* Level */);
if (icdeh[0].cutDownNeeded)
icdeh[0].checkSet.clear();
}
} finally {
observer.afterMergeJoinOfDoubleIndexResults(returnSet);
}
}
return returnSet;
}
/**
* This function is used ony for testing the private visibility function
*/
static SelectResults testCutDownAndExpandIndexResults(List dataList)
throws FunctionDomainException, TypeMismatchException, NameResolutionException,
QueryInvocationTargetException {
return cutDownAndExpandIndexResults((SelectResults) dataList.get(0),
(RuntimeIterator[]) dataList.get(1), (List) dataList.get(2), (List) dataList.get(3),
(ExecutionContext) dataList.get(4), (List) dataList.get(5), null, null);
}
static List queryEquijoinConditionBucketIndexes(IndexInfo[] indxInfo, ExecutionContext context)
throws QueryInvocationTargetException, TypeMismatchException, FunctionDomainException,
NameResolutionException {
List resultData = new ArrayList();
AbstractIndex index0 = (AbstractIndex) indxInfo[0]._index;
AbstractIndex index1 = (AbstractIndex) indxInfo[1]._index;
PartitionedRegion pr0 = null;
if (index0.getRegion() instanceof BucketRegion) {
pr0 = ((Bucket) index0.getRegion()).getPartitionedRegion();
}
PartitionedRegion pr1 = null;
if (index1.getRegion() instanceof BucketRegion) {
pr1 = ((Bucket) index1.getRegion()).getPartitionedRegion();
}
List data = null;
IndexProtocol i0 = null;
IndexProtocol i1 = null;
for (Object b : context.getBucketList()) {
i0 = pr0 != null ? PartitionedIndex.getBucketIndex(pr0, index0.getName(), (Integer) b)
: indxInfo[0]._index;
i1 = pr1 != null ? PartitionedIndex.getBucketIndex(pr1, index1.getName(), (Integer) b)
: indxInfo[1]._index;
if (i0 == null || i1 == null) {
continue;
}
data = i0.queryEquijoinCondition(i1, context);
resultData.addAll(data);
}
data = resultData;
return data;
}
}