blob: c591f36eef84229355ec1af34649911f1be72409 [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.Collections;
import java.util.List;
import java.util.Set;
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.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.types.StructTypeImpl;
import org.apache.geode.cache.query.types.ObjectType;
import org.apache.geode.cache.query.types.StructType;
/**
* Predefined function for identity of the UNDEFINED literal
*
* @version $Revision: 1.2 $
*/
public class CompiledUndefined extends AbstractCompiledValue implements Negatable, Indexable {
private CompiledValue _value;
private boolean _is_defined;
public CompiledUndefined(CompiledValue value, boolean is_defined) {
_value = value;
_is_defined = is_defined;
}
@Override
public List getChildren() {
return Collections.singletonList(this._value);
}
@Override
public int getType() {
return FUNCTION;
}
@Override
public Object evaluate(ExecutionContext context) throws FunctionDomainException,
TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
boolean b = _value.evaluate(context) == QueryService.UNDEFINED;
return Boolean.valueOf(_is_defined ? !b : b);
}
/**
* Asif : 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 evaluateProjAttrib)
throws FunctionDomainException, TypeMismatchException, NameResolutionException,
QueryInvocationTargetException {
// this method is called if we are independent of the iterator,
// or if we can use an index.
// if we are independent, then we should not have been here in the first
// place
Support.Assert(this._value.isDependentOnCurrentScope(context),
"For a condition which does not depend on any RuntimeIterator of current scope , we should not have been in this function");
IndexInfo idxInfo[] = getIndexInfo(context);
ObjectType resultType = idxInfo[0]._index.getResultSetType();
int indexFieldsSize = -1;
SelectResults set = null;
if (resultType instanceof StructType) {
set = QueryUtils.createStructCollection(context, (StructTypeImpl) resultType);
indexFieldsSize = ((StructTypeImpl) resultType).getFieldNames().length;
} else {
set = QueryUtils.createResultCollection(context, resultType);
indexFieldsSize = 1;
}
int op = _is_defined ? TOK_NE : TOK_EQ;
Object key = QueryService.UNDEFINED;
QueryObserver observer = QueryObserverHolder.getInstance();
try {
observer.beforeIndexLookup(idxInfo[0]._index, op, key);
context.cachePut(CompiledValue.INDEX_INFO, idxInfo[0]);
idxInfo[0]._index.query(key, op, set, context);
} finally {
observer.afterIndexLookup(set);
}
return QueryUtils.getConditionedIndexResults(set, idxInfo[0], context, indexFieldsSize,
completeExpansionNeeded, iterOperands, indpndntItrs);
}
@Override
public int getSizeEstimate(ExecutionContext context) throws FunctionDomainException,
TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
IndexInfo[] idxInfo = getIndexInfo(context);
assert idxInfo.length == 1;
if (context instanceof QueryExecutionContext) {
QueryExecutionContext qcontext = (QueryExecutionContext) context;
if (qcontext.isHinted(idxInfo[0]._index.getName())) {
return qcontext.getHintSize(idxInfo[0]._index.getName());
}
}
int op = _is_defined ? TOK_NE : TOK_EQ;
return idxInfo[0]._index.getSizeEstimate(QueryService.UNDEFINED, op, idxInfo[0]._matchLevel);
}
@Override
public int getOperator() {
return _is_defined ? TOK_NE : TOK_EQ;
}
/**
* evaluate as a filter, producing an intermediate result set. This may require iteration if there
* is no index available. Asif :The boolean true implies that CompiledComparsion when existing on
* its own always requires a Complete expansion to top level iterators. This flag can get toggled
* to false only from inside a GroupJunction
*
* <p>
* 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 iterationLimit)
throws FunctionDomainException, TypeMismatchException, NameResolutionException,
QueryInvocationTargetException {
return filterEvaluate(context, iterationLimit, true/* Complete Expansion needed */, null, null,
true, isConditioningNeededForIndex(null, context, true), false);
}
/*
* Asif : This function should never get invoked as now if a CompiledJunction or GroupJunction
* contains a single filterable CompiledUndefined, 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 CompiledComparison should never have got invoked.");
return null;
}
@Override
public Set computeDependencies(ExecutionContext context)
throws TypeMismatchException, AmbiguousNameException, NameResolutionException {
return context.addDependencies(this, this._value.computeDependencies(context));
}
@Override
public void negate() {
_is_defined = !_is_defined;
}
// 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;
Support.Assert(indexInfo.length == 1,
"For a CompiledUndefined we cannot have a join of two indexes. There should be only a single index to use");
result.indexes.add(indexInfo[0]._index);
result.evalAsFilter = true;
return result;
}
@Override
public IndexInfo[] getIndexInfo(ExecutionContext context)
throws TypeMismatchException, AmbiguousNameException, NameResolutionException {
IndexInfo[] indexInfo = privGetIndexInfo(context);
if (indexInfo != null) {
if (indexInfo == NO_INDEXES_IDENTIFIER) {
return null;
} else {
return indexInfo;
}
}
if (!IndexUtils.indexesEnabled)
return null;
// TODO:Asif : Check if the condition is such that Primary Key Index is used
// & its key is DEFINED
// , then are we returning all the values of the region ?
// & that if the key is UNDEFINED are we returning an empty set.?
IndexData indexData =
QueryUtils.getAvailableIndexIfAny(this._value, context, _is_defined ? TOK_NE : TOK_EQ);
IndexProtocol index = null;
IndexInfo[] newIndexInfo = null;
if (indexData != null) {
index = indexData.getIndex();
}
if (index != null && index.isValid()) {
newIndexInfo = new IndexInfo[1];
/*
* Pass the Key as null as the key is not of type CompiledValue( but of type
* QueryService.UNDEFINED)
*/
newIndexInfo[0] = new IndexInfo(null, this._value, index, indexData.getMatchLevel(),
indexData.getMapping(), _is_defined ? TOK_NE : TOK_EQ);
}
if (newIndexInfo != null) {
privSetIndexInfo(newIndexInfo, context);
} else {
privSetIndexInfo(NO_INDEXES_IDENTIFIER, context);
}
return newIndexInfo;
}
@Override
public void generateCanonicalizedExpression(StringBuilder clauseBuffer, ExecutionContext context)
throws AmbiguousNameException, TypeMismatchException, NameResolutionException {
clauseBuffer.insert(0, ')');
_value.generateCanonicalizedExpression(clauseBuffer, context);
if (_is_defined)
clauseBuffer.insert(0, "IS_DEFINED(");
else
clauseBuffer.insert(0, "IS_UNDEFINED(");
}
// _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);
}
@Override
public boolean isRangeEvaluatable() {
return false;
}
@Override
public boolean isProjectionEvaluationAPossibility(ExecutionContext context) {
return true;
}
// TODO:Asif: This should ideally be treated like CompiledComparison in terms evaluation of
// iter operands etc
@Override
public boolean isConditioningNeededForIndex(RuntimeIterator independentIter,
ExecutionContext context, boolean completeExpnsNeeded)
throws AmbiguousNameException, TypeMismatchException, NameResolutionException {
return true;
}
@Override
public boolean isBetterFilter(Filter comparedTo, ExecutionContext context, 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 thisOperator = this.getOperator();
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:
// This is possible only in case of RangeJunction
if (thisOperator == TOK_NE || thisOperator == TOK_NE_ALT) {
// Asif: Give preference to range as I am assuming that range will fetch less data
// as compared to NOT EQUALs
isThisBetter = false;
}
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;
}
}