/* * 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.*; import org.apache.geode.cache.query.*; import org.apache.geode.cache.query.internal.IndexInfo; 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); } public int getType() { return FUNCTION; } 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 * * @return SelectResults */ @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); } 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); } 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)); } 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; } 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(StringBuffer 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); } public boolean isRangeEvaluatable() { return false; } public boolean isProjectionEvaluationAPossibility(ExecutionContext context) { return true; } // TODO:Asif: This should ideally be treated like CompiledComparison in terms evaluation of // iter operands etc public boolean isConditioningNeededForIndex(RuntimeIterator independentIter, ExecutionContext context, boolean completeExpnsNeeded) throws AmbiguousNameException, TypeMismatchException, NameResolutionException { return true; } 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; } }