blob: 0dd8159c98f5b07271577afdf35c6317bf512146 [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.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.geode.InternalGemFireError;
import org.apache.geode.cache.EntryDestroyedException;
import org.apache.geode.cache.query.AmbiguousNameException;
import org.apache.geode.cache.query.FunctionDomainException;
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.IndexManager;
import org.apache.geode.cache.query.internal.index.IndexProtocol;
import org.apache.geode.cache.query.internal.parse.OQLLexerTokenTypes;
import org.apache.geode.cache.query.internal.types.StructTypeImpl;
import org.apache.geode.cache.query.types.ObjectType;
import org.apache.geode.internal.Assert;
/**
* Conjunctions and Disjunctions (LITERAL_and LITERAL_or) As a part of feature development to ensure
* index usage for multiple regions & index usage in equi join conditions across the regions , a
* CompiledJunction's organized operands method creates internal structures like GroupJunction,
* AllGroupJunction & CompositeGroupJunction. The method createJunction is where the decision of
* creating an AllGroupJunction ( wrapping multiple GroupJunctions and CompositeGroupJunctions) or a
* single GroupJunction or a singe CompositeGroupJunction is taken.
*
* CompiledJunction -----> on organization of Operands may result in any of the following cases
*
* 1) CompiledJunction | | -------------------------------- | | CompiledJunction GroupJunction
* (Filter evaluable) (Filter evaluable)
*
* 2) CompiledJunction | | -------------------------------- | | CompiledJunction RangeJunction
* (Filter evaluable same index operands. May contain iter operands belonging to the same Group)
* (Filter evaluable)
*
*
* 3) CompiledJunction | | -------------------------------- | | CompiledJunction GroupJunction
* (Filter evaluable) (Filter evaluable) | | ---------------------------------------------- | |
* CompiledComparison RangeJunction (at least one filter evaluable compiled Comparison using an
* index shared by no other condition)
*
*
* 5) CompiledJunction | | -------------------------------- | | CompiledJunction GroupJunction
* (Filter evaluable) (Filter evaluable) | | --------------------------------------- | | |
* RangeJunction RangeJunction CompiledComparison (zero or more Filter evaluable or iter evaluable )
*
* 6) CompiledJunction | ---------------------------------- | | CompiledJunction AllGroupJunction
* (filter evaluable) | ------------------------------------------------ | | | GroupJunction
* GroupJunction CompositeGroupJunction | --------------------------------------- | | |
* GroupJunction equi join conditions GroupJunction
*
*
*
* @version $Revision: 1.2 $
*/
public class CompiledJunction extends AbstractCompiledValue implements Negatable {
/** left operand */
private final CompiledValue[] _operands;
private int _operator = 0;
private List unevaluatedFilterOperands = null;
// A token to place into the samesort map. This is to let the engine know there is more than one
// index
// being used for this junction but allows actual operands to form range junctions if enough
// exist.
// The mechanism checks to see if the mapped object is an integer, if so, it increments, if it's
// not it sets as 1
// Because we are a string place holder, the next actual operand would just start at one. If the
// join is added
// after a valid operand has already set the counter to an integer, we instead just ignore and do
// not set the place holder
private static final String PLACEHOLDER_FOR_JOIN = "join";
CompiledJunction(CompiledValue[] operands, int operator) {
// invariant: operator must be LITERAL_and or LITERAL_or
// invariant: at least two operands
if (!((operator == LITERAL_or || operator == LITERAL_and) && operands.length >= 2)) {
throw new InternalGemFireError(
"operator=" + operator + "operands.length =" + operands.length);
}
_operator = operator;
_operands = operands;
}
@Override
public List getChildren() {
return Arrays.asList(this._operands);
}
@Override
public int getType() {
return JUNCTION;
}
@Override
public Object evaluate(ExecutionContext context) throws FunctionDomainException,
TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
Object r = _operands[0].evaluate(context); // UNDEFINED, null, or a Boolean
// if it's true, and op is or then return true immediately
// if it's false and the op is and then return false immediately
if (r instanceof Boolean)
if (((Boolean) r).booleanValue() && _operator == LITERAL_or)
return r;
else if (!((Boolean) r).booleanValue() && _operator == LITERAL_and)
return r;
if (r == null || r == QueryService.UNDEFINED)
r = QueryService.UNDEFINED; // keep going to see if we hit a
// short-circuiting truth value
else if (!(r instanceof Boolean))
throw new TypeMismatchException(
String.format(
"LITERAL_and/LITERAL_or operands must be of type boolean, not type ' %s '",
r.getClass().getName()));
for (int i = 1; i < _operands.length; i++) {
Object ri = null;
try {
ri = _operands[i].evaluate(context); // UNDEFINED, null, or
} catch (EntryDestroyedException ede) {
continue;
}
// Boolean
if (ri instanceof Boolean)
if (((Boolean) ri).booleanValue() && _operator == LITERAL_or)
return ri;
else if (!((Boolean) ri).booleanValue() && _operator == LITERAL_and)
return ri;
if (ri == null || ri == QueryService.UNDEFINED || r == QueryService.UNDEFINED) {
r = QueryService.UNDEFINED;
continue; // keep going to see if we hit a short-circuiting truth value
} else if (!(ri instanceof Boolean))
throw new TypeMismatchException(
String.format(
"LITERAL_and/LITERAL_or operands must be of type boolean, not type ' %s '",
ri.getClass().getName()));
// now do the actual and/or
if (_operator == LITERAL_and)
r = Boolean.valueOf(((Boolean) r).booleanValue() && ((Boolean) ri).booleanValue());
else
// LITERAL_or
r = Boolean.valueOf(((Boolean) r).booleanValue() || ((Boolean) ri).booleanValue());
}
return r;
}
@Override
public Set computeDependencies(ExecutionContext context)
throws TypeMismatchException, AmbiguousNameException, NameResolutionException {
for (int i = 0; i < _operands.length; i++) {
context.addDependencies(this, this._operands[i].computeDependencies(context));
}
return context.getDependencySet(this, true);
}
@Override
public SelectResults filterEvaluate(ExecutionContext context, SelectResults intermediateResults)
throws FunctionDomainException, TypeMismatchException, NameResolutionException,
QueryInvocationTargetException {
OrganizedOperands newOperands = organizeOperands(context);
SelectResults result = intermediateResults;
Support.Assert(newOperands.filterOperand != null);
// evaluate directly on the operand
result =
newOperands.isSingleFilter ? (newOperands.filterOperand).filterEvaluate(context, result)
: (newOperands.filterOperand).auxFilterEvaluate(context, result);
if (!newOperands.isSingleFilter) {
List unevaluatedOps =
((CompiledJunction) newOperands.filterOperand).unevaluatedFilterOperands;
if (unevaluatedOps != null) {
if (newOperands.iterateOperand == null) {
if (unevaluatedOps.size() == 1) {
newOperands.iterateOperand = (CompiledValue) unevaluatedOps.get(0);
} else {
int len = unevaluatedOps.size();
CompiledValue[] iterOps = new CompiledValue[len];
for (int i = 0; i < len; i++) {
iterOps[i] = (CompiledValue) unevaluatedOps.get(i);
}
newOperands.iterateOperand = new CompiledJunction(iterOps, getOperator());
}
} else {
// You cannot have two CompiledJunctions a the same level
if (newOperands.iterateOperand instanceof CompiledJunction) {
CompiledJunction temp = (CompiledJunction) newOperands.iterateOperand;
List prevOps = temp.getOperands();
unevaluatedOps.addAll(prevOps);
} else {
unevaluatedOps.add(newOperands.iterateOperand);
}
int totalLen = unevaluatedOps.size();
CompiledValue[] combinedOps = new CompiledValue[totalLen];
Iterator itr = unevaluatedOps.iterator();
int j = 0;
while (itr.hasNext()) {
combinedOps[j++] = (CompiledValue) itr.next();
}
newOperands.iterateOperand = new CompiledJunction(combinedOps, getOperator());
}
}
}
if (newOperands.iterateOperand != null)
// call private method here to evaluate
result = auxIterateEvaluate(newOperands.iterateOperand, context, result);
return result;
}
private List getCondtionsSortedOnIncreasingEstimatedIndexResultSize(ExecutionContext context)
throws FunctionDomainException, TypeMismatchException, NameResolutionException,
QueryInvocationTargetException {
// The checks invoked before this function have ensured that all the
// operands are of type ComparisonQueryInfo and of the form 'var = constant'.
// Also need for sorting will not arise if there are only two operands
int len = this._operands.length;
List sortedList = new ArrayList(len);
for (int i = 0; i < len; ++i) {
Filter toSort = (Filter) this._operands[i];
int indxRsltToSort = toSort.getSizeEstimate(context);
int sortedListLen = sortedList.size();
int j = 0;
for (; j < sortedListLen; ++j) {
Filter currSorted = (Filter) sortedList.get(j);
if (currSorted.getSizeEstimate(context) > indxRsltToSort) {
break;
}
}
sortedList.add(j, toSort);
}
return sortedList;
}
/**
* invariant: all operands are known to be evaluated as a filter no operand organization is
* necessary
*/
@Override
public SelectResults auxFilterEvaluate(ExecutionContext context,
SelectResults intermediateResults) throws FunctionDomainException, TypeMismatchException,
NameResolutionException, QueryInvocationTargetException {
// evaluate the result set from the indexed values
// using the intermediate results so far (passed in)
// put results into new intermediate results
List sortedConditionsList =
this.getCondtionsSortedOnIncreasingEstimatedIndexResultSize(context);
// Sort the operands in increasing order of resultset size
Iterator sortedConditionsItr = sortedConditionsList.iterator();
while (sortedConditionsItr.hasNext()) {
// Asif:TODO The intermediate ResultSet should be passed as null when invoking
// filterEvaluate. Just because filterEvaluate is being called, itself
// guarantees that there will be at least on auxFilterEvalaute call.
// The filterEvalaute will return after invoking auxIterEvaluate.
// The auxIterEvaluate cannot get invoked for an OR junction.
// The approach of calculation of resultset should be Bubble UP ( i.e
// bottom to Top of the tree. This implies that for filterEvaluate, we
// should not be passing IntermediateResultSet.
// Each invocation of filterEvaluate will be complete in itself with the
// recursion being ended by evaluating auxIterEvaluate if any. The passing
// of IntermediateResult in filterEvalaute causes AND junction evaluation
// to be corrupted , if the intermediateResultset contains some value.
SelectResults filterResults =
((Filter) sortedConditionsItr.next()).filterEvaluate(context, null);
if (_operator == LITERAL_and) {
if (filterResults != null && filterResults.isEmpty()) {
return filterResults;
} else if (filterResults != null) {
intermediateResults = (intermediateResults == null) ? filterResults
: QueryUtils.intersection(intermediateResults, filterResults, context);
sortedConditionsItr.remove();
if (intermediateResults.size() <= indexThresholdSize) {
// Abort further intersection , the remaining filter operands will be
// transferred for
// iter evaluation
break;
}
}
} else {
// Asif : In case of OR clause, the filterEvaluate cannot return a
// null value ,as a null value implies Cartesian of Iterators
// in current scope which can happen only if the operand of OR
// clause evaluates to true (& which we are not allowing).
// Though if the operand evaluates to false,it is permissible case
// for filterEvaluation.
Assert.assertTrue(filterResults != null);
intermediateResults = intermediateResults == null ? filterResults
: QueryUtils.union(intermediateResults, filterResults, context);
}
}
if (_operator == LITERAL_and && !sortedConditionsList.isEmpty()) {
this.unevaluatedFilterOperands = sortedConditionsList;
}
return intermediateResults;
}
/** invariant: the operand is known to be evaluated by iteration */
SelectResults auxIterateEvaluate(CompiledValue operand, ExecutionContext context,
SelectResults intermediateResults) throws FunctionDomainException, TypeMismatchException,
NameResolutionException, QueryInvocationTargetException {
// Asif: This can be a valid value if we have an AND condition like ID=1 AND
// true = true. In such cases , if an index is not available on ID still we
// have at least one expression which is independent of current scope. In
// such cases a value of null means ignore the null ( because it means all
// the results ) thus a the result-set of current auxIterate which will be
// the subset of total result-set , will be the right data.
// auxIterEvalaute can be called only from an AND junction.
// This also implies that there will be at least one operand which will be
// evaluated using auxFilterEvaluate.Thus the result-set given to this
// function can be empty ( obtained using auxFilterEvaluate ,meaning no need
// to do iterations below ) but it can never be null. As null itself
// signifies that the junction cannot be evaluated as a filter.
if (intermediateResults == null)
throw new RuntimeException(
"intermediateResults can not be null");
if (intermediateResults.isEmpty()) // short circuit
return intermediateResults;
List currentIters = context.getCurrentIterators();
RuntimeIterator rIters[] = new RuntimeIterator[currentIters.size()];
currentIters.toArray(rIters);
ObjectType elementType = intermediateResults.getCollectionType().getElementType();
SelectResults resultSet;
if (elementType.isStructType()) {
resultSet = QueryUtils.createStructCollection(context, (StructTypeImpl) elementType);
} else {
resultSet = QueryUtils.createResultCollection(context, elementType);
}
QueryObserver observer = QueryObserverHolder.getInstance();
try {
observer.startIteration(intermediateResults, operand);
Iterator iResultsIter = intermediateResults.iterator();
while (iResultsIter.hasNext()) {
Object tuple = iResultsIter.next();
if (tuple instanceof Struct) {
Object values[] = ((Struct) tuple).getFieldValues();
for (int i = 0; i < values.length; i++) {
rIters[i].setCurrent(values[i]);
}
} else {
rIters[0].setCurrent(tuple);
}
Object result = null;
try {
result = operand.evaluate(context);
} finally {
observer.afterIterationEvaluation(result);
}
if (result instanceof Boolean) {
if (((Boolean) result).booleanValue())
resultSet.add(tuple);
} 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()));
}
} finally {
observer.endIteration(resultSet);
}
return resultSet;
}
/**
* invariant: all operands are known to be evaluated as a filter no operand organization is
* necessary
*/
@Override
public void negate() {
_operator = inverseOperator(_operator);
for (int i = 0; i < _operands.length; i++) {
if (_operands[i] instanceof Negatable)
((Negatable) _operands[i]).negate();
else
_operands[i] = new CompiledNegation(_operands[i]);
}
}
@Override
protected PlanInfo protGetPlanInfo(ExecutionContext context) throws FunctionDomainException,
TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
Support.Assert(_operator == LITERAL_or || _operator == LITERAL_and);
PlanInfo resultPlanInfo = new PlanInfo();
// set default evalAsFilter depending on operator
boolean isOr = (_operator == LITERAL_or);
resultPlanInfo.evalAsFilter = isOr;
// collect indexes
// for LITERAL_and operator, if any say yes to filter,
// then change default evalAsFilter from false to true
// of LITERAL_or operator, if any say no to filter, change to false
for (int i = 0; i < _operands.length; i++) {
PlanInfo opPlanInfo = _operands[i].getPlanInfo(context);
resultPlanInfo.indexes.addAll(opPlanInfo.indexes);
if (!isOr && opPlanInfo.evalAsFilter) {
resultPlanInfo.evalAsFilter = true;
} else if (isOr && !opPlanInfo.evalAsFilter) {
resultPlanInfo.evalAsFilter = false;
}
}
return resultPlanInfo;
}
/* Package methods */
@Override
public int getOperator() {
return _operator;
}
List getOperands() {
// return unmodifiable copy
return Collections.unmodifiableList(Arrays.asList(_operands));
}
/**
* TODO: Should composite operands be part of iterator operands of CompiledJunction or should it
* be part of AllGroupJunction Write a unit Test for this function.
*
* Asif: The iterators which can be considered part of GroupJunction are those which are
* exclusively dependent only on the independent iterator of the group. Any iterator which is
* ultimately dependent on more than one independent iterators cannot be assumed to be part of the
* GroupJunction, even if one of independent iterator belongs to a different scope.
*
* @return New combination of AbstractCompiledValue(s) in form of CompiledJunction.
*/
OrganizedOperands organizeOperands(ExecutionContext context) throws FunctionDomainException,
TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
// get the list of operands to evaluate, and evaluate operands that can use
// indexes first.
List evalOperands = new ArrayList(_operands.length);
int indexCount = 0;
// TODO: Check if we can defer the creation of this array list only
// if there exists an eval operand
List compositeIterOperands = new ArrayList(_operands.length);
// Asif: This Map will contain as key the composite filter operand & as
// value , the set containing independent RuntimeIterators ( which will
// necessarily be two )
Map compositeFilterOpsMap = new LinkedHashMap();
Map iterToOperands = new HashMap();
CompiledValue operand = null;
boolean isJunctionNeeded = false;
boolean indexExistsOnNonJoinOp = false;
for (int i = 0; i < _operands.length; i++) {
// Asif : If we are inside this function this itself indicates
// that there exists at least on operand which can be evaluated
// as an auxFilterEvaluate. If any operand even if its flag of
// evalAsFilter happens to be false, but if it is independently
// evaluable, we should attach it with the auxFilterevaluable
// operands irrespective of its value ( either true or false.)
// This is because if it is true or false, it can always be paired
// up with other filter operands for AND junction. But for
// OR junction it can get paired with the filterOperands only
// if it is false. But we do not have to worry about whether
// the junction is AND or OR because, if the independent operand's
// value is true & was under an OR junction , it would not have
// been filterEvaluated. Instead it would have gone for auxIterEvaluate.
// We are here itself implies, that any independent operand can be
// either true or false for an AND junction but always false for an
// OR Junction.
operand = this._operands[i];
if (!operand.isDependentOnCurrentScope(context)) {
indexCount++;
// Asif Ensure that independent operands are always at the start
evalOperands.add(0, operand);
} else if (operand instanceof CompiledJunction) {
if (operand.getPlanInfo(context).evalAsFilter) {
// Asif Ensure that independent operands are always at the start
evalOperands.add(indexCount++, operand);
} else {
evalOperands.add(operand);
}
} else {
// If the operand happens to be a Like predicate , this is the point
// where we can expand as now the structures created are all for the current
// execution. We expand only if the current junction is AND because if its
// OR we cannot bring Like at level of OR , because like itself is an AND
// Also cannot expand for NOT LIKE because CCs generated by CompiledLike with AND
// will be converted to OR by negation
CompiledValue expandedOperands[] = null;
if (operand.getType() == LIKE && this._operator == OQLLexerTokenTypes.LITERAL_and
&& ((CompiledLike) operand).getOperator() != OQLLexerTokenTypes.TOK_NE) {
expandedOperands =
((CompiledLike) operand).getExpandedOperandsWithIndexInfoSetIfAny(context);
} else {
expandedOperands = new CompiledValue[] {operand};
}
for (CompiledValue expndOperand : expandedOperands) {
boolean operandEvalAsFilter = expndOperand.getPlanInfo(context).evalAsFilter;
isJunctionNeeded = isJunctionNeeded || operandEvalAsFilter;
Set set = QueryUtils.getCurrentScopeUltimateRuntimeIteratorsIfAny(expndOperand, context);
if (set.size() != 1) {
// Asif: A set of size not equal to 1 ( which can mean anything more
// than 1, will mean a composite condition. For a Composite
// Condition which is filter evaluable that means necessarily that
// RHS is dependent on one independent iterator & LHS on the other.
if (operandEvalAsFilter) {
Support.Assert(set.size() == 2,
" The no of independent iterators should be equal to 2");
compositeFilterOpsMap.put(expndOperand, set);
} else {
compositeIterOperands.add(expndOperand);
}
} else {
Support.Assert(set.size() == 1,
"The size has to be 1 & cannot be zero as that would mean it is independent");
RuntimeIterator rIter = (RuntimeIterator) set.iterator().next();
List operandsList = (List) iterToOperands.get(rIter);
if (operandsList == null) {
operandsList = new ArrayList();
iterToOperands.put(rIter, operandsList);
}
if (operandEvalAsFilter && _operator == LITERAL_and) {
indexExistsOnNonJoinOp = true;
}
operandsList.add(expndOperand);
}
}
}
}
/*
* Asif : If there exists a SingleGroupJunction & no other Filter operand , then all remaining
* eval operands ( even if they are CompiledJunction which are not evaluable as Filter) &
* composite operands should become part of GroupJunction. If there exists only
* SingleGroupJunction & at least one Filter operand, then all conditions of GroupJunction which
* are not filter evaluable should become iter operand of CompiledJunction. If there exists an
* AllGroupJunction & no Filter operand, then all remaining iter operands of CompiledJunction
* along with Composite operands should be part of AllGroupJunction. If there exists at least
* one Filter operand , all remaining operands of AllGroupJunction ( including those
* GroupJunction not filter evaluable should become part of eval operand of CompiledJunction)
* The index count will give the idea of number of filter operands
*/
if (isJunctionNeeded) {
// There exists at least one condition which must have an index available.
Filter junction = createJunction(compositeIterOperands, compositeFilterOpsMap, iterToOperands,
context, indexCount, evalOperands, indexExistsOnNonJoinOp);
// Asif Ensure that independent operands are always at the start
evalOperands.add(indexCount++, junction);
} else {
// if(compositeIterOperands != null) {
if (!compositeIterOperands.isEmpty()) {
evalOperands.addAll(compositeIterOperands);
}
Iterator itr = iterToOperands.values().iterator();
while (itr.hasNext()) {
evalOperands.addAll((List) itr.next());
}
}
OrganizedOperands result = new OrganizedOperands();
// Group the operands into those that use indexes and those that don't,
// so we can evaluate all the operands that don't use indexes together in
// one iteration first get filter operands
Filter filterOperands = null;
// there must be at least one filter operand or else we wouldn't be here
Support.Assert(indexCount > 0);
if (indexCount == 1) {
filterOperands = (Filter) evalOperands.get(0);
result.isSingleFilter = true;
} else {
CompiledValue[] newOperands = new CompiledValue[indexCount];
for (int i = 0; i < indexCount; i++)
newOperands[i] = (CompiledValue) evalOperands.get(i);
filterOperands = new CompiledJunction(newOperands, _operator);
}
// get iterating operands
// INVARIANT: the number of iterating operands when the _operator is
// LITERAL_or must be zero. This is an invariant on filter evaluation
CompiledValue iterateOperands = null;
// int numIterating = _operands.length - indexCount;
int numIterating = evalOperands.size() - indexCount;
Support.Assert(_operator == LITERAL_and || numIterating == 0);
if (numIterating > 0) {
if (numIterating == 1)
iterateOperands = (CompiledValue) evalOperands.get(indexCount);
else {
CompiledValue[] newOperands = new CompiledValue[numIterating];
for (int i = 0; i < numIterating; i++)
newOperands[i] = (CompiledValue) evalOperands.get(i + indexCount);
iterateOperands = new CompiledJunction(newOperands, _operator);
}
}
result.filterOperand = filterOperands;
result.iterateOperand = iterateOperands;
return result;
}
/**
* Creates a GroupJunction or a RangeJunction based on the operands passed. The operands are
* either Filter Operands belonging to single independent RuntimeIterator or are iter evaluable on
* the RuntimeIterator[] passed as parameter. If all the filter evaluable operands use a single
* Index , than a RangeJunction is created with iter operand as part of RangeJunction. If there
* exists operands using multiple indexes, then a GroupJunction is created. In that case , the
* operands using the same index are grouped in a RangeJunction & that Range Junction becomes part
* of the GroupJunction. A GroupJunction may contain one or more RangeJunction
*
* @param needsCompacting boolean indicating if there is a possibility of RangeJunction or not. If
* needsCompacting is false , then a GroupJunction will be formed, without any
* RangeJunction
* @param grpIndpndntItr Array of RuntimeIterator which represents the independent iterator for
* the Group. for all cases , except a CompositeGroupJunction containing a single
* GroupJunction , will have a single RuntimeIterator in this array.
* @param completeExpnsn boolean indicating whether the Group or Range Junction needs to be
* expanded to the query from clause level or the group level
* @param cv Array of CompiledValue containing operands belonging to a group
* @param sameIndexOperands Map object containing Index as the key & as a value an Integer object
* or a List. If it contains an Integer as value against Index, it implies that there is
* only one operand which uses that type of index & hence cannot form a RangeJunction. If
* it contains a List then the Lis contains the operands which use same index & thus can
* form a RangeJunction
* @return Object of GroupJunction or RangeJunction as the case may be
*/
private AbstractGroupOrRangeJunction createGroupJunctionOrRangeJunction(boolean needsCompacting,
RuntimeIterator[] grpIndpndntItr, boolean completeExpnsn, CompiledValue[] cv,
Map sameIndexOperands) {
AbstractGroupOrRangeJunction junction;
if (needsCompacting) {
Iterator itr = sameIndexOperands.values().iterator();
if (sameIndexOperands.size() == 1) {
// This means that there exists only one type of Index for the whole
// Group. Thus overall we should just create a RangeJunction. It also
// means that the rest of the operands are the Iter Operands ( they may
// contain those operands which are not index evaluable but evaluate to
// true/false ( not dependent on current scope)
List sameIndexOps = (List) itr.next();
Iterator sameIndexOpsItr = sameIndexOps.iterator();
for (int i = 0; i < cv.length; ++i) {
if (cv[i] == null) {
cv[i] = (CompiledValue) sameIndexOpsItr.next();
}
}
junction = new RangeJunction(this._operator, grpIndpndntItr, completeExpnsn, cv);
} else {
int numRangeJunctions = 0;
CompiledValue[] rangeJunctions = new CompiledValue[sameIndexOperands.size()];
int nullifiedFields = 0;
while (itr.hasNext()) {
Object listOrPosition = itr.next();
if (listOrPosition instanceof List) {
List ops = (List) listOrPosition;
nullifiedFields += ops.size();
CompiledValue operands[] = (CompiledValue[]) ops.toArray(new CompiledValue[ops.size()]);
rangeJunctions[numRangeJunctions++] =
new RangeJunction(this._operator, grpIndpndntItr, completeExpnsn, operands);
}
}
int totalOperands = cv.length - nullifiedFields + numRangeJunctions;
CompiledValue[] allOperands = new CompiledValue[totalOperands];
// Fill the Non RangeJunction operands first
int k = 0;
for (int i = 0; i < cv.length; ++i) {
CompiledValue tempCV = cv[i];
if (tempCV != null) {
allOperands[k++] = tempCV;
}
}
for (int i = 0; i < numRangeJunctions; ++i) {
allOperands[k++] = rangeJunctions[i];
}
junction = new GroupJunction(this._operator, grpIndpndntItr, completeExpnsn, allOperands);
}
} else {
junction = new GroupJunction(this._operator, grpIndpndntItr, completeExpnsn, cv);
}
return junction;
}
private boolean sortSameIndexOperandsForGroupJunction(CompiledValue cv[], List operandsList,
Map sameIndexOperands, ExecutionContext context)
throws AmbiguousNameException, TypeMismatchException, NameResolutionException,
FunctionDomainException, QueryInvocationTargetException {
int size = operandsList.size();
CompiledValue tempOp = null;
boolean needsCompacting = false;
for (int i = 0; i < size; ++i) {
tempOp = (CompiledValue) operandsList.get(i);
IndexInfo[] indx = null;
Object listOrPosition = null;
boolean evalAsFilter = tempOp.getPlanInfo(context).evalAsFilter;
// TODO:Do not club Like predicate in an existing range
if (evalAsFilter) {
indx = ((Indexable) tempOp).getIndexInfo(context);
// We are now sorting these for joins, therefore we need to weed out the join indexes
if (!IndexManager.JOIN_OPTIMIZATION || indx.length == 1) {
Assert.assertTrue(indx.length == 1,
"There should have been just one index for the condition");
listOrPosition = sameIndexOperands.get(indx[0]._index);
}
}
if (listOrPosition != null) {
if (listOrPosition instanceof Integer) {
int position = ((Integer) listOrPosition).intValue();
List operands = new ArrayList(size);
operands.add(cv[position]);
operands.add(tempOp);
cv[position] = null;
sameIndexOperands.put(indx[0]._index, operands);
needsCompacting = true;
} else if (listOrPosition instanceof List) {
List operands = (List) listOrPosition;
operands.add(tempOp);
} else {
// a join was present here, let's now occupy that spot and remove the placeholder
listOrPosition = null;
}
}
if (listOrPosition == null) {
cv[i] = tempOp;
if (indx != null && indx.length == 1) {
// TODO: Enable only for AND junction for now
if (evalAsFilter && this._operator == OQLLexerTokenTypes.LITERAL_and) {
sameIndexOperands.put(indx[0]._index, Integer.valueOf(i));
}
} else if (indx != null && indx.length == 2) {
if (evalAsFilter && this._operator == OQLLexerTokenTypes.LITERAL_and) {
if (!sameIndexOperands.containsKey(indx[0]._index)) {
sameIndexOperands.put(indx[0]._index, PLACEHOLDER_FOR_JOIN);
}
if (!sameIndexOperands.containsKey(indx[1]._index)) {
sameIndexOperands.put(indx[1]._index, PLACEHOLDER_FOR_JOIN);
}
}
}
}
}
return needsCompacting;
}
// This is called only if the CompiledJunction was either independent or filter evaluable.
@Override
public int getSizeEstimate(ExecutionContext context) throws FunctionDomainException,
TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
if (this.isDependentOnCurrentScope(context)) {
return Integer.MAX_VALUE;
} else {
return 0;
}
}
// TODO: Optmize this function further in terms of creation of Arrays &
// Lists
private Filter createJunction(List compositeIterOperands, Map compositeFilterOpsMap,
Map iterToOperands, ExecutionContext context, int indexCount, List evalOperands,
boolean indexExistsOnNonJoinOp) throws FunctionDomainException, TypeMismatchException,
NameResolutionException, QueryInvocationTargetException {
Support.Assert(!(iterToOperands.isEmpty() && compositeFilterOpsMap.isEmpty()),
" There should not have been any need to create a Junction");
CompiledValue junction = null;
int size;
/*---------- Create only a GroupJunction */
if (iterToOperands.size() == 1 && (compositeFilterOpsMap.isEmpty()
|| (indexExistsOnNonJoinOp && IndexManager.JOIN_OPTIMIZATION))) {
if ((indexExistsOnNonJoinOp && IndexManager.JOIN_OPTIMIZATION)) {
// For the optimization we will want to add the compositeFilterOpsMap 848
// without the optimization we only fall into here if it's empty anyways, but have not
// tested the removal of this if clause
evalOperands.addAll(compositeFilterOpsMap.keySet());
}
// Asif :Create only a GroupJunction. The composite conditions can be
// evaluated as iter operands inside GroupJunction.
Map.Entry entry = (Map.Entry) iterToOperands.entrySet().iterator().next();
List operandsList = (List) entry.getValue();
// Asif: If there exists no other filter operand, transfer all the
// remaining iter operands to the Group Junction. Else transfer all the
// remaining operands to eval operands of CompiledJunctio. This means that
// we are following the logic that as far as possible minimize the
// result-set used for iter evaluation of conditions without index.
// This way we also save on extra iterations for eval conditions if they
// existed both in GroupJunction as well as CompiledJunction. The only
// exception in this logic is the iter evaluable conditions belonging to a
// GroupJunction.
// If a condition is dependent on a Single Group & is not filter evaluable
// we do not push it out to iter operands of AllGroupJunction or Compiled
// Junction , but make it part of GroupJunction itself. This helps bcoz we
// are sure that the condition will be completely evaluable by the
// iterators of that group itself.
if (indexCount == 0) {
operandsList.addAll(evalOperands);
evalOperands.clear();
if (!compositeIterOperands.isEmpty()) {
operandsList.addAll(compositeIterOperands);
}
} else {
if (!compositeIterOperands.isEmpty()) {
evalOperands.addAll(compositeIterOperands);
}
Iterator itr = operandsList.iterator();
while (itr.hasNext()) {
CompiledValue cv = (CompiledValue) itr.next();
// Asif : Those conditions not filter evaluable are transferred to
// eval operand of CompiledJunction.
if (!cv.getPlanInfo(context).evalAsFilter) {
itr.remove();
evalOperands.add(cv);
}
}
}
size = operandsList.size();
Map sameIndexOperands = new HashMap(size);
CompiledValue cv[] = new CompiledValue[size];
// Enable only for AND junction
boolean needsCompacting =
sortSameIndexOperandsForGroupJunction(cv, operandsList, sameIndexOperands, context);
junction = createGroupJunctionOrRangeJunction(needsCompacting,
new RuntimeIterator[] {(RuntimeIterator) entry.getKey()},
true /* need a Complete expansion */, cv, sameIndexOperands);
}
/*
* Asif : An AllGroupJunction or a CompositeGroupJunction can get created. An AllGroupJunction
* will get created , if there exists a need to create more than one CompositeGroupJunction
* AND/OR more than one GroupJunctions ( not part of CompositeGroupJunction) If suppose the
* where clause contained two sets of conditions each dependent exclusively on a separate
* independent group ( implying two independent groups) then we should create an
* AllGroupJunction iff the two Groups are both filter evaluable. If an AllGroupJunction
* contains only one filter evaluable Group & no CompositeGroupJunction then we should just
* create a singleGroupJunction with complete expansion as true
*/
else {
// Asif : First create the required amount of CompositeGroupJunctions
// A map which keeps track of no. of independent iterators pointing to the
// same CompositeGroupJunction. The integer count will keep track of
// number of different CompositeGroupJunctions which got created.
Map tempMap = new HashMap();
Set entries = compositeFilterOpsMap.entrySet();
Set cgjs = new HashSet(compositeFilterOpsMap.size());
Iterator entryItr = entries.iterator();
while (entryItr.hasNext()) {
Map.Entry entry = (Map.Entry) entryItr.next();
CompiledValue op = (CompiledValue) entry.getKey();
Set indpndtItrSet = (Set) entry.getValue();
Iterator itr = indpndtItrSet.iterator();
CompositeGroupJunction cgj = null;
RuntimeIterator[] absentItrs = new RuntimeIterator[2];
int k = 0;
while (itr.hasNext()) {
RuntimeIterator ritr = (RuntimeIterator) itr.next();
CompositeGroupJunction temp = (CompositeGroupJunction) tempMap.get(ritr);
if (temp == null) {
absentItrs[k++] = ritr;
}
if (cgj == null && temp != null) {
cgj = temp;
cgj.addFilterableCompositeCondition(op);
} else if (cgj != null && temp != null && cgj != temp) {
// Asif A peculiar case wherein we have some thing like four regions
// with relation ship conditions like r1 = r2 and r3 = r4 and r4 =
// r1. In such situations we should just have a single
// CompositeJunction containing r1 r2 r3 and r4 We will merge temp
// into CompositeGroupJunction & change all the values in tempMap
// which contained temp object to CompositeGroupJunction.
// Also we will remove the entry of temp from CompositeGroupJunctions.
cgj.mergeFilterableCCsAndIndependentItrs(temp);
cgjs.remove(temp);
Iterator entriesItr = tempMap.entrySet().iterator();
Map.Entry tempEntry = null;
while (entriesItr.hasNext()) {
tempEntry = (Map.Entry) entriesItr.next();
if (tempEntry.getValue() == temp) {
tempEntry.setValue(cgj);
}
}
}
}
if (cgj == null) {
// Asif Create a new CompositeGroupJunction
cgj = new CompositeGroupJunction(this._operator, op /* CompositeFilterOperand */);
cgjs.add(cgj);
}
for (int i = 0; i < k; ++i) {
tempMap.put(absentItrs[i], cgj);
cgj.addIndependentIterators(absentItrs[i]);
}
}
// Asif :If no of cgj is zero definitely there will be an AllGroupJunction
// If no. of cgj is more than 1 , then also there will be an AllGroupJunction.
// But if no. of cgj is excactly one & all the GroupJunctions can be
// accomodated within the CGJ , then we will have CompsiteGroupJunction.
// Also , those composite conditions which are not filter evaluable will
// be added to CompositeGroupJunction as iter operands only if we don't
// have any additional filter & CompositeGroupJunction is the only
// junction which will get created. If there exists any other filter then
// it will be part of CompiledJunction's iter operand. Basically we will
// not distinguish between various CompositeConditions which are not
// filter evaluable & will not try to place it in various
// ComspoiteGroupJunctions to which they belong.
List gjs = new ArrayList(iterToOperands.size());
Iterator itr = iterToOperands.entrySet().iterator();
Map.Entry entry;
while (itr.hasNext()) {
entry = (Map.Entry) itr.next();
List operandsList = (List) entry.getValue();
CompiledValue cv[] = new CompiledValue[size = operandsList.size()];
int j = 0;
// Asif:An individual GroupJunction is Filter evaluable if at least one
// operand is filter evaluable.
boolean evalAsFilter = false;
boolean needsCompacting = false;
CompiledValue tempOp = null;
Map sameIndexOperands = new HashMap(size);
for (; j < size; ++j) {
tempOp = (CompiledValue) operandsList.get(j);
boolean isFilterevaluable = tempOp.getPlanInfo(context).evalAsFilter;
evalAsFilter = evalAsFilter || isFilterevaluable;
IndexInfo[] indx = null;
Object listOrPosition = null;
if (isFilterevaluable) {
indx = ((Indexable) tempOp).getIndexInfo(context);
Assert.assertTrue(indx.length == 1,
"There should have been just one index for the condition");
listOrPosition = sameIndexOperands.get(indx[0]._index);
}
if (listOrPosition != null) {
if (listOrPosition instanceof Integer) {
int position = ((Integer) listOrPosition).intValue();
List operands = new ArrayList(size);
operands.add(cv[position]);
operands.add(tempOp);
cv[position] = null;
sameIndexOperands.put(indx[0]._index, operands);
needsCompacting = true;
} else {
List operands = (List) listOrPosition;
operands.add(tempOp);
}
} else {
cv[j] = tempOp;
if (isFilterevaluable && this._operator == OQLLexerTokenTypes.LITERAL_and) {
sameIndexOperands.put(indx[0]._index, Integer.valueOf(j));
}
}
}
if (!evalAsFilter) {
// Asif : If there exists at least one Filter then those GroupJunction
// which are not Filter evaluable should become part of eval operands
// of CompiledJunction else add the Group operands which is not filter
// evaluable to the Composite iterator oprands
List toAddTo = null;
if (indexCount > 0) {
toAddTo = evalOperands;
} else {
toAddTo = compositeIterOperands;
}
for (int i = 0; i < size; ++i) {
toAddTo.add(cv[i]);
}
} else {
RuntimeIterator grpIndpndtItr = (RuntimeIterator) entry.getKey();
AbstractGroupOrRangeJunction gj = createGroupJunctionOrRangeJunction(needsCompacting,
new RuntimeIterator[] {(RuntimeIterator) entry.getKey()},
false/* Expand only to Group Level */, cv, sameIndexOperands);
CompositeGroupJunction cgj = null;
if ((cgj = (CompositeGroupJunction) tempMap.get(grpIndpndtItr)) != null) {
cgj.addGroupOrRangeJunction(gj);
} else {
gjs.add(gj);
}
}
}
if (indexCount == 0) {
// Asif : all the eval operands of CompiledJunction should be part of
// AllGroupJunction
compositeIterOperands.addAll(evalOperands);
evalOperands.clear();
} else {
// Asif : Add the composite operands to the eval operands so that they
// are evaluated as part of CompiledJunction.
if (!compositeIterOperands.isEmpty()) {
evalOperands.addAll(compositeIterOperands);
}
}
// Asif : An AllGroupJunction will be created iff either the List
// containing GroupJunctions is not empty ot there exists multiple
// CompositeGroupJunctions Else a CompositeGroupJunction will be created
// Convert the List of Indpendent Runtime Iterators of
// CompositeGroupJunction into Array of Indpendent RuntimeIterators
Iterator cgjItr = cgjs.iterator();
while (cgjItr.hasNext()) {
((CompositeGroupJunction) cgjItr.next()).setArrayOfIndependentItrs();
}
int cgjSize = cgjs.size();
if (gjs.isEmpty() && cgjSize == 1) {
CompositeGroupJunction compGrpJnc = (CompositeGroupJunction) cgjs.iterator().next();
compGrpJnc.addIterOperands(compositeIterOperands);
compGrpJnc.setCompleteExpansionOn();
junction = compGrpJnc;
} else if (gjs.size() == 1 && cgjSize == 0) {
// Asif: There exists only a single Filterable GroupJunction & no
// CompositeGroupJunction so we should just create a GroupJunction
// instead of an AllGroupJunction . ( In this way the remaining iter
// operands will get evaluated during the filter evaluation of
// GroupJunction) For such GroupJunction complete expansion needs to be
// true & the list of composite iter operands ( which at this point
// contains the iter operands, should be added to the operands of
// GroupJunction where they will get orgainzed as iter operands during
// the call of organizeOperands in GroupJunction
AbstractGroupOrRangeJunction gjTemp = (AbstractGroupOrRangeJunction) gjs.get(0);
compositeIterOperands.addAll(gjTemp.getOperands());
CompiledValue newOps[] = new CompiledValue[compositeIterOperands.size()];
compositeIterOperands.toArray(newOps);
// Asif : If gjTemp is a RangeJunction, we will get an instance of
// RangeJunction else we will get an instance of GroupJunction.
junction =
gjTemp.createNewOfSameType(this._operator, gjTemp.getIndependentIteratorForGroup(),
true/* The expansion needs to be complete */, newOps);
} else {
gjs.addAll(cgjs);
junction = new AllGroupJunction(gjs, this._operator, compositeIterOperands);
}
}
return (Filter) junction;
}
// Asif : This function provides package visbility for testing the
// output of organizeOperands function.
OrganizedOperands testOrganizedOperands(ExecutionContext context) throws FunctionDomainException,
TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
return organizeOperands(context);
}
@Override
public boolean isProjectionEvaluationAPossibility(ExecutionContext context)
throws FunctionDomainException, TypeMismatchException, NameResolutionException,
QueryInvocationTargetException {
for (int i = 0; i < this._operands.length; ++i) {
// LIKE gives rise to a JUNCTION in CompiledLike whether wildcard is present or not
if ((this._operands[i].getType() == JUNCTION || this._operands[i].getType() == LIKE)
&& this._operands[i].getPlanInfo(context).evalAsFilter) {
return false;
}
}
return true;
}
/*
* This method checks the limit applicability on index intermediate results for junctions and
* optimizes the limit on index intermediate results ONLY if ONE index is used for whole query and
* all conditions in where clause use that index. Look at the call hierarchy of this function.
* There are two cases:
*
* Literal_OR: A junction with OR will contain operands which are CompiledComparison or
* CompiledJunction (or subjunction). we recursively check if all of those use the same index and
* if ANY one of those comparisons or subjunctions does not use the index, it retruns false.
*
* Literal_AND: If we get combination of comparisons and subjunctions then limit is NOT applicable
* on index results. Like, "where ID != 10 AND (ID < 5 OR ID > 10) LIMIT 5". If we get only
* comparisons ONLY then if all comparisons use the same index then limit is applicable and this
* returns true. Like, "where ID != 10 AND (ID < 5 AND ID > 10) LIMIT 5".
*
*/
@Override
public boolean isLimitApplicableAtIndexLevel(ExecutionContext context)
throws FunctionDomainException, TypeMismatchException, NameResolutionException,
QueryInvocationTargetException {
if (this._operator == LITERAL_or) {
// There is a slight inefficiency in the sense that if the subjunction ( say AND) cannot apply
// limit,
// then limit would not be applied at remaining conditions of OR. But since we have single
// flag
// governing the behaviour of applying limit at index level, we cannot make it true for
// specific clauses
for (int i = 0; i < this._operands.length; ++i) {
if (!this._operands[i].getPlanInfo(context).evalAsFilter
|| ((Filter) this._operands[i]).isLimitApplicableAtIndexLevel(context)) {
return false;
}
}
return true;
} else {
// For limit to be applicable on a AND junction, there should be only one type of index used
// and rest iter evaluable
// But since the selection of which index to use happens in GroupJunction created at runtimke,
// we do not have that
// information yet, and at this point there would be multiple indexes. Ideally we should
// invoke this function in GroupJunction,
// in case we want to support multi index usage again at some point. Till then since it is
// hard coded to use 1 index
// we can for the time being return true if there exists atleast one indexable condition
boolean foundIndex = false;
for (int i = 0; i < this._operands.length; ++i) {
if (this._operands[i].getPlanInfo(context).evalAsFilter
&& this._operands[i].getType() == JUNCTION) {
return false;
} else if (this._operands[i].getPlanInfo(context).evalAsFilter) {
foundIndex = true;
}
}
return foundIndex;
}
}
@Override
public boolean isOrderByApplicableAtIndexLevel(ExecutionContext context,
String canonicalizedOrderByClause) throws FunctionDomainException, TypeMismatchException,
NameResolutionException, QueryInvocationTargetException {
if (this._operator == LITERAL_and) {
// Set<IndexProtocol> usedIndex = new HashSet<IndexProtocol>();
boolean foundRightIndex = false;
for (int i = 0; i < this._operands.length; ++i) {
PlanInfo pi = this._operands[i].getPlanInfo(context);
if (pi.evalAsFilter && this._operands[i].getType() == JUNCTION) {
return false;
} else if (pi.evalAsFilter) {
if (!foundRightIndex) {
IndexProtocol ip =
(IndexProtocol) this._operands[i].getPlanInfo(context).indexes.get(0);
if (ip.getCanonicalizedIndexedExpression().equals(canonicalizedOrderByClause)
&& pi.isPreferred) {
foundRightIndex = true;
}
}
// usedIndex.addAll(this._operands[i].getPlanInfo(context).indexes);
}
}
return foundRightIndex;
// if(usedIndex.size() == 1 &&
// usedIndex.iterator().next().getCanonicalizedIndexedExpression().equals(canonicalizedOrderByClause))
// {
// return true;
// }
}
return false;
}
/*
* class CGJData { RuntimeIterator [] indpndItrs = null; List iterOperands = null; List
* groupJunctions = null; CompiledValue [] compositeCompiledComparisons = null;
*
* CGJData(RuntimeIterator [] indpndItrs, List iterOperands,List groupJunctions, CompiledValue []
* compositeCompiledComparisons) { this.indpndItrs = indpndItrs; this.iterOperands = iterOperands;
* this.groupJunctions = groupJunctions; this.compositeCompiledComparisons =
* compositeCompiledComparisons; } }
*/
}