blob: 639282d08ec3bfe0062baa574be110effee4c25d [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.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.logging.log4j.Logger;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.query.AmbiguousNameException;
import org.apache.geode.cache.query.FunctionDomainException;
import org.apache.geode.cache.query.IndexType;
import org.apache.geode.cache.query.NameResolutionException;
import org.apache.geode.cache.query.QueryInvocationTargetException;
import org.apache.geode.cache.query.QueryService;
import org.apache.geode.cache.query.RegionNotFoundException;
import org.apache.geode.cache.query.SelectResults;
import org.apache.geode.cache.query.Struct;
import org.apache.geode.cache.query.TypeMismatchException;
import org.apache.geode.cache.query.internal.index.IndexData;
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.parse.OQLLexerTokenTypes;
import org.apache.geode.cache.query.internal.types.StructTypeImpl;
import org.apache.geode.cache.query.internal.types.TypeUtils;
import org.apache.geode.cache.query.types.ObjectType;
import org.apache.geode.cache.query.types.StructType;
import org.apache.geode.logging.internal.log4j.api.LogService;
import org.apache.geode.pdx.internal.PdxString;
public class CompiledIn extends AbstractCompiledValue implements Indexable {
private static final Logger logger = LogService.getLogger();
private CompiledValue elm;
private CompiledValue colln;
public CompiledIn(CompiledValue elm, CompiledValue colln) {
this.elm = elm;
this.colln = colln;
}
@Override
public List getChildren() {
List list = new ArrayList();
list.add(elm);
list.add(colln);
return list;
}
@Override
public int getType() {
return LITERAL_in;
}
/**
* We retrieve the collection from the context cache if it exists This allows us to not have to
* reevaluate the sub query on every iteration. This improves performance for queries such as
* "select * from /receipts r where r.type = 'large' and r.id in (select c.id from /customers c
* where c.status = 'preferred') The sub query would create a set that would not change and store
* it into the context if it does not yet exist
*/
private Object evaluateColln(ExecutionContext context) throws QueryInvocationTargetException,
NameResolutionException, TypeMismatchException, FunctionDomainException {
Object evalColln = null;
if (this.colln.isDependentOnCurrentScope(context)) {
evalColln = this.colln.evaluate(context);
} else {
evalColln = context.cacheGet(this.colln);
if (evalColln == null) {
evalColln = this.colln.evaluate(context);
context.cachePut(this.colln, evalColln);
}
}
return evalColln;
}
@Override
public Object evaluate(ExecutionContext context) throws FunctionDomainException,
TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
Object evalElm = this.elm.evaluate(context);
Object evalColln = evaluateColln(context);
if (evalColln == null || evalColln == QueryService.UNDEFINED) {
return QueryService.UNDEFINED;
}
// handle each type of collection that we support
if (evalColln instanceof Map) {
evalColln = ((Map) evalColln).entrySet();
}
if (evalColln instanceof Collection) {
Iterator iterator = ((Iterable) evalColln).iterator();
while (iterator.hasNext()) {
Object evalObj = evalElm;
Object collnObj = iterator.next();
if (evalElm instanceof PdxString && collnObj instanceof String) {
evalObj = ((PdxString) evalElm).toString();
} else if (collnObj instanceof PdxString && evalElm instanceof String) {
collnObj = ((PdxString) collnObj).toString();
}
if (TypeUtils.compare(evalObj, collnObj, OQLLexerTokenTypes.TOK_EQ).equals(Boolean.TRUE)) {
return Boolean.TRUE;
}
}
return Boolean.FALSE;
}
if (!evalColln.getClass().isArray()) {
throw new TypeMismatchException(
String.format("Operand of IN cannot be interpreted as a Collection. Is instance of %s",
evalColln.getClass().getName()));
}
if (evalColln.getClass().getComponentType().isPrimitive()) {
if (evalElm == null) {
throw new TypeMismatchException(
"IN operator, check for null IN primitive array");
}
}
int numElements = Array.getLength(evalColln);
for (int i = 0; i < numElements; i++) {
Object o = Array.get(evalColln, i);
if (TypeUtils.compare(evalElm, o, TOK_EQ).equals(Boolean.TRUE)) {
return Boolean.TRUE;
}
}
return Boolean.FALSE;
}
/**
* If the size of aray is two this implies that it is a relation ship index & so the key field
* will be null in both the indexes as key is not a meaningful entity. The 0th element will refer
* to LHS operand and 1th element will refer to RHS operannd
*/
@Override
public IndexInfo[] getIndexInfo(ExecutionContext context)
throws TypeMismatchException, AmbiguousNameException, NameResolutionException {
IndexInfo[] indexInfo = privGetIndexInfo(context);
if (indexInfo != null) {
// TODO: == check is identity only
if (indexInfo == NO_INDEXES_IDENTIFIER) {
return null;
} else {
return indexInfo;
}
}
if (!IndexUtils.indexesEnabled)
return null;
// get the path and index key to try
PathAndKey pAndK = getPathAndKey(context);
IndexInfo newIndexInfo[] = null;
if (pAndK != null) {
CompiledValue path = pAndK._path;
CompiledValue indexKey = pAndK._key;
IndexData indexData = QueryUtils.getAvailableIndexIfAny(path, context, TOK_EQ);
IndexProtocol index = null;
if (indexData != null) {
index = indexData.getIndex();
}
if (index != null && index.isValid()) {
newIndexInfo = new IndexInfo[1];
newIndexInfo[0] = new IndexInfo(indexKey, path, index, indexData.getMatchLevel(),
indexData.getMapping(), TOK_EQ);
}
}
if (newIndexInfo != null) {
privSetIndexInfo(newIndexInfo, context);
} else {
privSetIndexInfo(NO_INDEXES_IDENTIFIER, context);
}
return newIndexInfo;
}
/**
* _indexInfo is a transient field if this is just faulted in then can be null
*/
private IndexInfo[] privGetIndexInfo(ExecutionContext context) {
return (IndexInfo[]) context.cacheGet(this);
}
private void privSetIndexInfo(IndexInfo[] indexInfo, ExecutionContext context) {
context.cachePut(this, indexInfo);
}
/**
* Invariant: the receiver is dependent on the current iterator.
*/
@Override
protected PlanInfo protGetPlanInfo(ExecutionContext context)
throws TypeMismatchException, AmbiguousNameException, NameResolutionException {
PlanInfo result = new PlanInfo();
IndexInfo[] indexInfo = getIndexInfo(context);
if (indexInfo == null)
return result;
for (int i = 0; i < indexInfo.length; ++i) {
result.indexes.add(indexInfo[i]._index);
}
result.evalAsFilter = true;
String preferredCondn = (String) context.cacheGet(PREF_INDEX_COND);
if (preferredCondn != null) {
// This means that the system is having only one independent iterator so equi join is ruled
// out.
// thus the first index is guaranteed to be on the condition which may match our preferred
// index
if (indexInfo[0]._index.getCanonicalizedIndexedExpression().equals(preferredCondn)
&& (indexInfo[0]._index.getType() == IndexType.FUNCTIONAL
|| indexInfo[0]._index.getType() == IndexType.HASH)) {
result.isPreferred = true;
}
}
return result;
}
@Override
public Set computeDependencies(ExecutionContext context)
throws TypeMismatchException, AmbiguousNameException, NameResolutionException {
context.addDependencies(this, this.elm.computeDependencies(context));
return context.addDependencies(this, this.colln.computeDependencies(context));
}
/**
* specialized optimization for doing a bulk get on a region.
*
* @return a List of entries if optimization was performed, null if no match
*/
List optimizeBulkGet(CompiledRegion cRgn, ExecutionContext context)
throws RegionNotFoundException, TypeMismatchException, NameResolutionException,
FunctionDomainException, QueryInvocationTargetException {
// check the elm to see if it's the key on a map entry
boolean match = false;
CompiledValue resolution = null;
if (this.elm instanceof CompiledID) {
String id = ((CompiledID) this.elm).getId();
if (id.equals("key") || id.equals("getKey")) {
resolution = context.resolve(id);
if (resolution instanceof CompiledPath) {
resolution = ((CompiledPath) resolution).getReceiver();
}
}
} else if (this.elm instanceof CompiledPath) {
CompiledPath cPath = (CompiledPath) this.elm;
if (cPath.getTailID().equals("key") || cPath.getTailID().equals("getKey")) {
CompiledValue cVal = cPath.getReceiver();
if (cVal instanceof CompiledID) {
resolution = context.resolve(((CompiledID) cVal).getId());
}
}
} else if (this.elm instanceof CompiledOperation) {
CompiledOperation cOp = (CompiledOperation) this.elm;
if (cOp.getMethodName().equals("key") || cOp.getMethodName().equals("getKey")) {
if (cOp.getReceiver(context) instanceof CompiledID) {
resolution = context.resolve(((CompiledID) cOp.getReceiver(context)).getId());
} else if (cOp.getReceiver(context) == null) {
match = true; // implicit operation on the iterator
}
}
}
// only one possible iterator in this case, so it's a match if resolution
// is a RuntimeIterator
if (!match && !(resolution instanceof RuntimeIterator)) {
return null;
}
// element matches; finally, check to make sure the collection expression
// is independent of all iterators
if (context.isDependentOnAnyIterator(this.colln)) {
return null;
}
// evaluate the collection
Object evalColln = evaluateColln(context);
if (evalColln == null || evalColln == QueryService.UNDEFINED) {
return null;
}
// only handle an actual Collection or an Object[] for this optimization
Collection colln = null;
if (evalColln instanceof Collection) {
colln = (Collection) evalColln;
}
if (evalColln instanceof Object[]) {
colln = Arrays.asList((Object[]) evalColln);
}
if (colln != null) {
QRegion rgn = (QRegion) cRgn.evaluate(context);
// only do this optimization if the region keys is larger than the
// collection
if (rgn.keySet().size() < colln.size()) {
return null;
}
if (logger.isDebugEnabled()) {
logger.debug("Executing BULK GET on keys {}, in region {}", colln, rgn);
}
List results = new ArrayList(colln.size());
// now do the iteration over this collection
for (Iterator itr = colln.iterator(); itr.hasNext();) {
Object key = itr.next();
Region.Entry entry = rgn.getEntry(key);
if (entry != null) {
// the region contains this key, so add the entry to the results
results.add(entry);
}
}
return results;
}
return null;
}
/**
* get the path to see if there's an index for, and also determine which CompiledValue is the key
* while we're at it
*/
private PathAndKey getPathAndKey(ExecutionContext context)
throws TypeMismatchException, AmbiguousNameException {
boolean isLeftDependent = context.isDependentOnCurrentScope(this.elm);
boolean isRightDependent = context.isDependentOnCurrentScope(this.colln);
if (!isLeftDependent || isRightDependent)
return null;
CompiledValue indexKey;
CompiledValue path;
path = this.elm;
indexKey = this.colln;
// Do not worry about the nature of the collection. As long as it
// is not dependent on the current scope we should be fine
return new PathAndKey(path, indexKey);
}
/**
* Evaluates as a filter taking advantage of indexes if appropriate. This function has a
* meaningful implementation only in CompiledComparison & CompiledUndefined . It is unsupported in
* other classes. The additional parameters which it takes are a boolean which is used to indicate
* whether the index result set needs to be expanded to the top level or not. The second is a
* CompiledValue representing the operands which are only iter evaluatable. The CompiledValue
* passed will be null except if a GroupJunction has only one filter evaluatable condition & rest
* are iter operands. In such cases , the iter operands will be evaluated while expanding/cutting
* down the index resultset
*
*/
@Override
public SelectResults filterEvaluate(ExecutionContext context, SelectResults intermediateResults,
boolean completeExpansionNeeded, CompiledValue iterOperands, RuntimeIterator[] indpndntItrs,
boolean isIntersection, boolean conditioningNeeded, boolean evalProj)
throws FunctionDomainException, TypeMismatchException, NameResolutionException,
QueryInvocationTargetException {
// see if we're dependent on the current iterator
// if not let super handle it
if (!isDependentOnCurrentScope(context)) {
return super.filterEvaluate(context, intermediateResults);
}
IndexInfo[] idxInfo = getIndexInfo(context);
Support.Assert(idxInfo != null,
"a comparison that is dependent, not indexed, and filter evaluated is not possible");
Support.Assert(idxInfo.length == 1, "In operator should have only one index");
return singleBaseCollectionFilterEvaluate(context, intermediateResults, completeExpansionNeeded,
iterOperands, idxInfo[0], indpndntItrs, isIntersection, conditioningNeeded, evalProj);
}
@Override
public int getOperator() {
return TOK_EQ;
}
private void queryIndex(Object key, IndexInfo indexInfo, SelectResults results,
CompiledValue iterOperands, RuntimeIterator[] indpndntItrs, ExecutionContext context,
List projAttrib, boolean conditioningNeeded) throws TypeMismatchException,
FunctionDomainException, NameResolutionException, QueryInvocationTargetException {
assert indexInfo != null;
assert indexInfo._index != null;
// Get new IndexInfo to put in context as we dont want it to evaluate
// key collection again if its a CompiledSelect (Nested Query).
IndexInfo contextIndexInfo = new IndexInfo(new CompiledLiteral(key), indexInfo._path(),
indexInfo._getIndex(), 0, null, indexInfo._operator());
context.cachePut(CompiledValue.INDEX_INFO, contextIndexInfo);
indexInfo._index.query(key, TOK_EQ, results, !conditioningNeeded ? iterOperands : null,
indpndntItrs == null ? null : indpndntItrs[0], context, projAttrib, null, false);
}
/**
* evaluate as a filter, involving a single iterator. Use an index if possible.
*
* Invariant: the receiver is dependent on the current iterator.
*/
SelectResults singleBaseCollectionFilterEvaluate(ExecutionContext context,
SelectResults intermediateResults, boolean completeExpansionNeeded,
CompiledValue iterOperands, IndexInfo indexInfo, RuntimeIterator[] indpndntItr,
boolean isIntersection, boolean conditioningNeeded, boolean evalProj)
throws TypeMismatchException, AmbiguousNameException, FunctionDomainException,
NameResolutionException, QueryInvocationTargetException {
ObjectType resultType = indexInfo._index.getResultSetType();
int indexFieldsSize = -1;
SelectResults results = null;
if (resultType instanceof StructType) {
indexFieldsSize = ((StructTypeImpl) resultType).getFieldNames().length;
} else {
indexFieldsSize = 1;
}
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();
}
List projAttrib = null;
ObjectType projResultType = null;
if (!conditioningNeeded) {
projResultType = evalProj ? (ObjectType) context.cacheGet(RESULT_TYPE) : null;
if (projResultType != null) {
resultType = projResultType;
context.cachePut(RESULT_TYPE, Boolean.TRUE);
projAttrib = (List) context.cacheGet(PROJ_ATTRIB);
}
if (isIntersection) {
if (resultType instanceof StructType) {
context.getCache().getLogger()
.fine("StructType resultType.class=" + resultType.getClass().getName());
if (useLinkedDataStructure) {
results = context.isDistinct() ? new LinkedStructSet((StructTypeImpl) resultType)
: new SortedResultsBag<Struct>((StructTypeImpl) resultType, nullValuesAtStart);
} else {
results = QueryUtils.createStructCollection(context, (StructTypeImpl) resultType);
}
indexFieldsSize = ((StructTypeImpl) resultType).getFieldNames().length;
} else {
context.getCache().getLogger()
.fine("non-StructType resultType.class=" + resultType.getClass().getName());
if (useLinkedDataStructure) {
results = context.isDistinct() ? new LinkedResultSet(resultType)
: new SortedResultsBag(resultType, nullValuesAtStart);
} else {
results = QueryUtils.createResultCollection(context, resultType);
}
indexFieldsSize = 1;
}
} else {
if (intermediateResults != null && !completeExpansionNeeded) {
results = intermediateResults;
} else {
if (resultType instanceof StructType) {
context.getCache().getLogger()
.fine("StructType resultType.class=" + resultType.getClass().getName());
if (useLinkedDataStructure) {
results = context.isDistinct() ? new LinkedStructSet((StructTypeImpl) resultType)
: new SortedResultsBag<Struct>((StructTypeImpl) resultType, nullValuesAtStart);
} else {
results = QueryUtils.createStructCollection(context, (StructTypeImpl) resultType);
}
indexFieldsSize = ((StructTypeImpl) resultType).getFieldNames().length;
} else {
context.getCache().getLogger()
.fine("non-StructType resultType.class=" + resultType.getClass().getName());
if (useLinkedDataStructure) {
results = context.isDistinct() ? new LinkedResultSet(resultType)
: new SortedResultsBag(resultType, nullValuesAtStart);
} else {
results = QueryUtils.createResultCollection(context, resultType);
}
indexFieldsSize = 1;
}
}
}
} else {
if (resultType instanceof StructType) {
context.getCache().getLogger()
.fine("StructType resultType.class=" + resultType.getClass().getName());
if (useLinkedDataStructure) {
results = context.isDistinct() ? new LinkedStructSet((StructTypeImpl) resultType)
: new SortedResultsBag<Struct>((StructTypeImpl) resultType, nullValuesAtStart);
} else {
results = QueryUtils.createStructCollection(context, (StructTypeImpl) resultType);
}
indexFieldsSize = ((StructTypeImpl) resultType).getFieldNames().length;
} else {
context.getCache().getLogger()
.fine("non-StructType resultType.class=" + resultType.getClass().getName());
if (useLinkedDataStructure) {
results = context.isDistinct() ? new LinkedResultSet(resultType)
: new SortedResultsBag(resultType, nullValuesAtStart);
} else {
results = QueryUtils.createResultCollection(context, resultType);
}
indexFieldsSize = 1;
}
}
QueryObserver observer = QueryObserverHolder.getInstance();
try {
Object evalColln = evaluateColln(context);
observer.beforeIndexLookup(indexInfo._index, TOK_EQ, evalColln);
// We need to reset the result type just in case the colln turned out to
// be a compiled comparison which could change the result type
// Exec caches are incorrectly shared across all queries, this would result
// in overriding the result type. Once the result type was overridden
// multiple projections and class cast exceptions could result due to
// unexpected values overwriting expected values
if (!conditioningNeeded) {
if (projResultType != null) {
resultType = projResultType;
context.cachePut(RESULT_TYPE, Boolean.TRUE);
}
}
// handle each type of collection that we support
if (evalColln instanceof Map) {
Iterator itr = ((Map) evalColln).entrySet().iterator();
while (itr.hasNext()) {
this.queryIndex(itr.next(), indexInfo, results, iterOperands, indpndntItr, context,
projAttrib, conditioningNeeded);
}
} else if (evalColln instanceof Collection) {
Object key = indexInfo.evaluateIndexKey(context);
// If the index is a map index, the key is actually an object[] tuple that contains the map
// key in the [1]
// and the evalColln in the [0] position
if (key instanceof Object[]) {
Iterator iterator = ((Iterable) ((Object[]) key)[0]).iterator();
while (iterator.hasNext()) {
this.queryIndex(new Object[] {iterator.next(), ((Object[]) key)[1]}, indexInfo, results,
iterOperands, indpndntItr, context, projAttrib, conditioningNeeded);
}
} else {
// Removing duplicates from the collection
HashSet set = new HashSet((Collection) evalColln);
Iterator itr = set.iterator();
while (itr.hasNext()) {
this.queryIndex(itr.next(), indexInfo, results, iterOperands, indpndntItr, context,
projAttrib, conditioningNeeded);
}
}
} else {
if (!evalColln.getClass().isArray()) {
throw new TypeMismatchException("Operand of IN cannot be interpreted as a Collection. "
+ "Is instance of " + evalColln.getClass().getName());
}
int evalCollnLength = Array.getLength(evalColln);
for (int i = 0; i < evalCollnLength; ++i) {
this.queryIndex(Array.get(evalColln, i), indexInfo, results, iterOperands, indpndntItr,
context, projAttrib, conditioningNeeded);
}
}
if (conditioningNeeded) {
results = QueryUtils.getConditionedIndexResults(results, indexInfo, context,
indexFieldsSize, completeExpansionNeeded, iterOperands, indpndntItr);
} else {
if (isIntersection && intermediateResults != null) {
results = QueryUtils.intersection(intermediateResults, results, context);
}
}
return results;
} finally {
observer.afterIndexLookup(results);
}
}
@Override
public boolean isProjectionEvaluationAPossibility(ExecutionContext context) {
return true;
}
@Override
public boolean isLimitApplicableAtIndexLevel(ExecutionContext context) {
return true;
}
@Override
public boolean isOrderByApplicableAtIndexLevel(ExecutionContext context,
String canonicalizedOrderByClause) throws FunctionDomainException, TypeMismatchException,
NameResolutionException, QueryInvocationTargetException {
return false;
}
@Override
public boolean isConditioningNeededForIndex(RuntimeIterator independentIter,
ExecutionContext context, boolean completeExpnsNeeded)
throws AmbiguousNameException, TypeMismatchException, NameResolutionException {
IndexConditioningHelper ich = null;
IndexInfo[] idxInfo = getIndexInfo(context);
assert idxInfo.length == 1;
int indexFieldsSize = -1;
boolean conditioningNeeded = true;
ObjectType indexRsltType = idxInfo[0]._index.getResultSetType();
if (indexRsltType instanceof StructType) {
indexFieldsSize = ((StructTypeImpl) indexRsltType).getFieldNames().length;
} else {
indexFieldsSize = 1;
}
if (independentIter != null && indexFieldsSize == 1) {
ich = new IndexConditioningHelper(idxInfo[0], context, indexFieldsSize, completeExpnsNeeded,
null, independentIter);
}
conditioningNeeded = ich == null || ich.shufflingNeeded;
return conditioningNeeded;
}
/**
* evaluate as a filter, producing an intermediate result set. This may require iteration if there
* is no index available. The boolean true implies that CompiledComparison when existing on its
* own always requires a CompleteExpansion to top level iterators. This flag can get toggled to
* false only from inside a GroupJunction
*
* @param intermediateResults if this parameter is provided, and we have to iterate, then iterate
* over this result set instead of the entire base collection.
*/
@Override
public SelectResults filterEvaluate(ExecutionContext context, SelectResults intermediateResults)
throws FunctionDomainException, TypeMismatchException, NameResolutionException,
QueryInvocationTargetException {
// This function can be invoked only if the where clause contains
// a single condition which is CompiledComparison.
// If a CompiledComparison exists inside a GroupJunction, then it will
// always
// call the overloaded filterEvalauate with the RuntimeIterator passed
// as not null.
// Thus if the RuntimeIterator array passed is null then it is
// guaranteed
// that the condition was a isolatory condition ( directly in where
// clause)
// and the final iterators to which we need to expand to is all the
// iterators
// of the scope
RuntimeIterator indpndntItr = null;
List currentScopeIndpndntItrs = context.getAllIndependentIteratorsOfCurrentScope();
Set rntmItrs = QueryUtils.getCurrentScopeUltimateRuntimeIteratorsIfAny(this, context);
if (rntmItrs.size() == 1 && currentScopeIndpndntItrs.size() == 1) {
indpndntItr = (RuntimeIterator) rntmItrs.iterator().next();
}
/*
* It is safe to pass null as the independent iterator to which the condition belongs is
* required only if boolean complete expansion turns out to be false, which can happen only in
* case of CompiledComparison/CompiledUndefined called from GroupJunction or
* CompositeGroupJunction
*/
return filterEvaluate(context, intermediateResults, true, null,
indpndntItr != null ? new RuntimeIterator[] {indpndntItr} : null, true,
this.isConditioningNeededForIndex(indpndntItr, context, true), true);
}
/**
* This function should never get invoked as now if a CompiledJunction or GroupJunction contains a
* single filterable CompiledComparison it should directly call filterEvaluate rather than
* auxFilterEvalutae. Overriding this function just for ensuring that auxFilterEvaluate is not
* being called by mistake.
*/
@Override
public SelectResults auxFilterEvaluate(ExecutionContext context,
SelectResults intermediateResults) throws FunctionDomainException, TypeMismatchException,
NameResolutionException, QueryInvocationTargetException {
Support.assertionFailed(" This auxFilterEvaluate of CompiledIn should never have got invoked.");
return null;
}
@Override
public boolean isBetterFilter(Filter comparedTo, ExecutionContext context, final int thisSize)
throws FunctionDomainException, TypeMismatchException, NameResolutionException,
QueryInvocationTargetException {
// If the current filter is equality & comparedTo filter is also equality based , then
// return the one with lower size estimate is better
boolean isThisBetter = true;
int thatSize = comparedTo.getSizeEstimate(context);
int thatOperator = comparedTo.getOperator();
// Go with the lowest cost when hint is used.
if (context instanceof QueryExecutionContext && ((QueryExecutionContext) context).hasHints()) {
return thisSize <= thatSize;
}
switch (thatOperator) {
case TOK_EQ:
case TOK_NE:
case TOK_NE_ALT:
isThisBetter = thisSize < thatSize;
break;
case LITERAL_and:
// Give preference to IN . Is this right? It does not appear . Ideally we need to get
// some estimate on Range. This case is possible only in case of RangeJunction
break;
case TOK_LE:
case TOK_LT:
case TOK_GE:
case TOK_GT:
// Give preference to this rather than that as this is more deterministic
break;
default:
throw new IllegalArgumentException("The operator type =" + thatOperator + " is unknown");
}
return isThisBetter;
}
@Override
public int getSizeEstimate(ExecutionContext context) throws FunctionDomainException,
TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
IndexInfo[] idxInfo = getIndexInfo(context);
if (idxInfo == null) {
// This implies it is an independent condition. So evaluate it first in filter operand
return 0;
}
assert idxInfo.length == 1;
Object key = idxInfo[0].evaluateIndexKey(context);
if (key != null && key.equals(QueryService.UNDEFINED)) {
return 0;
}
if (context instanceof QueryExecutionContext) {
QueryExecutionContext qcontext = (QueryExecutionContext) context;
if (qcontext.isHinted(idxInfo[0]._index.getName())) {
return qcontext.getHintSize(idxInfo[0]._index.getName());
}
}
Object evalColln = evaluateColln(context);
int size = 0;
// handle each type of collection that we support
if (evalColln instanceof Map) {
Iterator itr = ((Map) evalColln).entrySet().iterator();
while (itr.hasNext()) {
size += idxInfo[0]._index.getSizeEstimate(itr.next(), TOK_EQ, idxInfo[0]._matchLevel);
}
} else if (evalColln instanceof Collection) {
if (key instanceof Object[]) {
Iterator iterator = ((ResultsSet) ((Object[]) key)[0]).iterator();
while (iterator.hasNext()) {
size += idxInfo[0]._index.getSizeEstimate(
new Object[] {iterator.next(), ((Object[]) key)[1]}, TOK_EQ, idxInfo[0]._matchLevel);
}
} else {
Iterator itr = ((Collection) evalColln).iterator();
while (itr.hasNext()) {
size += idxInfo[0]._index.getSizeEstimate(itr.next(), TOK_EQ, idxInfo[0]._matchLevel);
}
}
} else {
if (!evalColln.getClass().isArray()) {
throw new TypeMismatchException("Operand of IN cannot be interpreted as a Collection. "
+ "Is instance of " + evalColln.getClass().getName());
}
if (evalColln instanceof Object[]) {
Object[] arr = (Object[]) evalColln;
for (int i = 0; i < arr.length; ++i) {
size += idxInfo[0]._index.getSizeEstimate(arr[i], TOK_EQ, idxInfo[0]._matchLevel);
}
} else if (evalColln instanceof long[]) {
long[] a = (long[]) evalColln;
for (int i = 0; i < a.length; i++) {
size += idxInfo[0]._index.getSizeEstimate(a[i], TOK_EQ, idxInfo[0]._matchLevel);
}
} else if (evalColln instanceof double[]) {
double[] a = (double[]) evalColln;
for (int i = 0; i < a.length; i++) {
size += idxInfo[0]._index.getSizeEstimate(a[i], TOK_EQ, idxInfo[0]._matchLevel);
}
} else if (evalColln instanceof float[]) {
float[] a = (float[]) evalColln;
for (int i = 0; i < a.length; i++) {
size += idxInfo[0]._index.getSizeEstimate(a[i], TOK_EQ, idxInfo[0]._matchLevel);
}
} else if (evalColln instanceof int[]) {
int[] a = (int[]) evalColln;
for (int i = 0; i < a.length; i++) {
size += idxInfo[0]._index.getSizeEstimate(a[i], TOK_EQ, idxInfo[0]._matchLevel);
}
} else if (evalColln instanceof short[]) {
short[] a = (short[]) evalColln;
for (int i = 0; i < a.length; i++) {
size += idxInfo[0]._index.getSizeEstimate(a[i], TOK_EQ, idxInfo[0]._matchLevel);
}
} else if (evalColln instanceof char[]) {
char[] a = (char[]) evalColln;
for (int i = 0; i < a.length; i++) {
size += idxInfo[0]._index.getSizeEstimate(a[i], TOK_EQ, idxInfo[0]._matchLevel);
}
} else if (evalColln instanceof byte[]) {
byte[] a = (byte[]) evalColln;
for (int i = 0; i < a.length; i++) {
size += idxInfo[0]._index.getSizeEstimate(a[i], TOK_EQ, idxInfo[0]._matchLevel);
}
} else {
throw new TypeMismatchException(
"Operand of IN cannot be interpreted as a Comparable Object. Operand is of type ="
+ evalColln.getClass());
}
}
return size;
}
@Override
public boolean isRangeEvaluatable() {
return false;
}
static class PathAndKey {
CompiledValue _path;
CompiledValue _key;
PathAndKey(CompiledValue path, CompiledValue indexKey) {
_path = path;
_key = indexKey;
}
}
}