/* * 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. */ /** * Abstract Super class of the Group or Range Junction. The common functionality of Group or Range * Junction is present in this class. */ package org.apache.geode.cache.query.internal; import org.apache.geode.cache.query.*; 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.distributed.internal.DistributionConfig; import org.apache.geode.internal.Assert; import org.apache.geode.internal.i18n.LocalizedStrings; import java.util.*; /** * */ public abstract class AbstractGroupOrRangeJunction extends AbstractCompiledValue implements Filter, OQLLexerTokenTypes { /** left operand */ final CompiledValue[] _operands; private static final int INDEX_RESULT_THRESHOLD_DEFAULT = 100; public static final String INDX_THRESHOLD_PROP_STR = DistributionConfig.GEMFIRE_PREFIX + "Query.INDEX_THRESHOLD_SIZE"; private static final int indexThresholdSize = Integer.getInteger(INDX_THRESHOLD_PROP_STR, INDEX_RESULT_THRESHOLD_DEFAULT).intValue(); private int _operator = 0; private CompiledValue iterOperands; // Asif: In normal circumstances , there will be only one indpendent // RuntimeIterator for a GroupJunction. But if inside a // CompositeGroupJunction, there exists only a single GroupJunction , then it // is advantageous to evaluate the the GroupJunction directly to the level of // ComspositeGroupJunction or complete expansion ( rather than expanding it // within CompositeGroupJunction). This will save some extra iteration. In // such cases , the GroupJunction may contain multiple Iterators ( this will // happen only if called from CompositeGroupJunction private RuntimeIterator indpndntItr[] = null; private boolean completeExpansion = false; AbstractGroupOrRangeJunction(int operator, RuntimeIterator[] indpndntItr, boolean isCompleteExpansion, CompiledValue[] operands) { this.indpndntItr = indpndntItr; _operator = operator; this.completeExpansion = isCompleteExpansion; this._operands = operands; } AbstractGroupOrRangeJunction(AbstractGroupOrRangeJunction oldGJ, boolean completeExpansion, RuntimeIterator indpnds[], CompiledValue iterOp) { this._operator = oldGJ._operator; this.completeExpansion = completeExpansion; this.indpndntItr = indpnds; if (iterOp != null) { if (iterOp instanceof CompiledComparison) { int finalSize = 1 + oldGJ._operands.length; this._operands = new CompiledValue[finalSize]; System.arraycopy(oldGJ._operands, 0, this._operands, 0, finalSize - 1); this._operands[finalSize - 1] = iterOp; } else { // Instance of CompiledJunction CompiledJunction temp = (CompiledJunction) iterOp; int operator = temp.getOperator(); if (_operator == operator) { int tempOpSize = temp.getOperands().size(); int oldGJOpSize = oldGJ._operands.length; this._operands = new CompiledValue[tempOpSize + oldGJOpSize]; System.arraycopy(oldGJ._operands, 0, this._operands, 0, oldGJOpSize); Iterator itr = temp.getOperands().iterator(); int i = oldGJOpSize; while (itr.hasNext()) { this._operands[i++] = (CompiledValue) itr.next(); } } else { int oldGJOpSize = oldGJ._operands.length; this._operands = new CompiledValue[1 + oldGJOpSize]; System.arraycopy(oldGJ._operands, 0, this._operands, 0, oldGJOpSize); this._operands[oldGJOpSize] = temp; } } } else { this._operands = oldGJ._operands; } } public Object evaluate(ExecutionContext context) throws FunctionDomainException, TypeMismatchException, NameResolutionException, QueryInvocationTargetException { throw new AssertionError("Should not have come here"); } public int getType() { return GROUPJUNCTION; } void setCompleteExpansionOn() { this.completeExpansion = true; } void addIterOperands(CompiledValue iterOps) { this.iterOperands = iterOps; } CompiledValue getIterOperands() { return this.iterOperands; } @Override public SelectResults filterEvaluate(ExecutionContext context, SelectResults iterationLimit, boolean completeExpansionNeeded, CompiledValue iterOperands, RuntimeIterator[] indpndntItrs, boolean isIntersection, boolean conditioningNeeded, boolean evalProj) throws FunctionDomainException, TypeMismatchException, NameResolutionException, QueryInvocationTargetException { if (iterOperands != null) { this.addIterOperands(iterOperands); } return filterEvaluate(context, null /* The intermediate results is null */); } @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); if (newOperands.isSingleFilter) { // The below assertion used to hold true previously that if there used to be only // a RangeJunction & all iter operands , then GroupJunction would never be formed // in the first place & we would be calling the organizeOperands of RangeJunction. // But now even if a GroupJunction has two RangeJUnctions, we use only one of them // as filter & so the other RangeJunction acts as an iter operand, thus a GroupJunction // is formed with a single RanegJunction as filter operand & the other RangeJunction // as iter operand. // Asif : This means that new operand is either a CompiledComparison or // CompiledUndefined or RangeJunctionEvaluator, BUT it cannot be // a single RangeJunction within a GroupJunction. // The interpretation of isConditioning etc is not provided for RangeEvaluators result = (newOperands.filterOperand).filterEvaluate(context, result, this.completeExpansion, newOperands.iterateOperand, this.indpndntItr, true /* this is necessarily an ANDjunction */, newOperands.filterOperand.isConditioningNeededForIndex( this.indpndntItr.length == 1 ? this.indpndntItr[0] : null, context, this.completeExpansion), true /* evaluate projection */); } else { // With multiple filter conditions, the newOperands.filterOperand is bound // to be GroupJunction assert newOperands.filterOperand instanceof GroupJunction; // evaluate directly on the operand result = (newOperands.filterOperand).auxFilterEvaluate(context, result); List unevaluatedFilterOps = ((GroupJunction) newOperands.filterOperand).getUnevaluatedFilterOperands(); if (unevaluatedFilterOps != null) { if (newOperands.iterateOperand == null) { if (unevaluatedFilterOps.size() == 1) { newOperands.iterateOperand = (CompiledValue) unevaluatedFilterOps.get(0); } else { int len = unevaluatedFilterOps.size(); CompiledValue[] iterOps = new CompiledValue[len]; for (int i = 0; i < len; i++) { iterOps[i] = (CompiledValue) unevaluatedFilterOps.get(i); } newOperands.iterateOperand = new CompiledJunction(iterOps, getOperator()); } } else { if (newOperands.iterateOperand instanceof CompiledJunction && ((CompiledJunction) newOperands.iterateOperand).getOperator() == getOperator()) { CompiledJunction temp = (CompiledJunction) newOperands.iterateOperand; List prevOps = temp.getOperands(); unevaluatedFilterOps.addAll(prevOps); } else { unevaluatedFilterOps.add(newOperands.iterateOperand); } int totalLen = unevaluatedFilterOps.size(); CompiledValue[] combinedOps = new CompiledValue[totalLen]; Iterator itr = unevaluatedFilterOps.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 before this function is invoked // 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 List sortedList = new ArrayList(this._operands.length); int len = this._operands.length; 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; } /** * Asif : This function is always invoked on a DummyGroupJunction object formed as a part of * organization of operands of a GroupJunction . This also guranatees that the operands are all of * type CompiledComparison or CompiledUndefined */ @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 i = sortedConditionsList.iterator(); // SortedSet intersectionSet = new TreeSet(new SelectResultsComparator()); while (i.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. // If the parent object is a GroupJunction then the filter may be a // RangeJunction or a CompiledComparison. But if the parent Object is a // RangeJunction then the Filter is a RangeJunctionEvaluator SelectResults filterResults = null; Filter filter = (Filter) i.next(); boolean isConditioningNeeded = filter.isConditioningNeededForIndex( this.indpndntItr.length == 1 ? this.indpndntItr[0] : null, context, this.completeExpansion); // TODO:Asif: For RangeJunction I am right now returning true as // isConditioningNeeded because there is no provision right now to pass // intermediate results from RangeJunction & also no code to utilize the // intermediate results in the evaluator created out of RangeJunction. filterResults = filter.filterEvaluate(context, !isConditioningNeeded ? intermediateResults : null, this.completeExpansion, null/* * Asif * Asif :The iter operands passed are null, as a not null value can exists only * if there exists a single Filter operand in original GroupJunction */, this.indpndntItr, _operator == LITERAL_and, isConditioningNeeded, false /* do not evaluate projection */); if (_operator == LITERAL_and) { if (filterResults != null && filterResults.isEmpty()) { return filterResults; } else if (filterResults != null) { intermediateResults = (intermediateResults == null || !isConditioningNeeded) ? filterResults : QueryUtils.intersection(intermediateResults, filterResults, context); i.remove(); if (intermediateResults.size() <= indexThresholdSize) { // Abort further intersection , the residual 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); boolean isDistinct = (DefaultQuery) context.getQuery() != null ? ((DefaultQuery) context.getQuery()).getSelect().isDistinct() : false; /* * Shobhit: Only for distinct query union can be avoided. For non-distinct queries * filterResults and intermediateResults are different for OR conditions and final result * can contain duplicate objects based on occurrences. */ if (intermediateResults == null) { intermediateResults = filterResults; } else if (isDistinct && !isConditioningNeeded) { intermediateResults = filterResults; } else { intermediateResults = QueryUtils.union(intermediateResults, filterResults, context); } } } if (_operator == LITERAL_and && !sortedConditionsList.isEmpty()) { this.addUnevaluatedFilterOperands(sortedConditionsList); } return intermediateResults; } /** invariant: the operand is known to be evaluated by iteration */ private 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 resultset of current auxIterate which will be the // subset of total resultset , 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 resultset 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( LocalizedStrings.AbstractGroupOrRangeJunction_INTERMEDIATERESULTS_CAN_NOT_BE_NULL .toLocalizedString()); if (intermediateResults.isEmpty()) // short circuit return intermediateResults; List currentIters = (this.completeExpansion) ? context.getCurrentIterators() : QueryUtils.getDependentItrChainForIndpndntItrs(this.indpndntItr, context); SelectResults resultSet = null; RuntimeIterator rIters[] = new RuntimeIterator[currentIters.size()]; currentIters.toArray(rIters); ObjectType elementType = intermediateResults.getCollectionType().getElementType(); 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("AND/OR operands must be of type boolean, not type '" + result.getClass().getName() + "'"); } } finally { observer.endIteration(resultSet); } return resultSet; } /* Package methods */ public int getOperator() { return _operator; } List getOperands() { // return unmodifiable copy return Collections.unmodifiableList(Arrays.asList(_operands)); } boolean getExpansionFlag() { return this.completeExpansion; } RuntimeIterator[] getIndependentIteratorForGroup() { return this.indpndntItr; } /** * Segregates the Where clause operands of a Range or Group Junction into filter evaluatable & * iter evaluatable operands by creating an Object of Organizedoperands. This method is invoked * from organizeOperands of Group or Range Junction. * * @param indexCount Indicates the number of operands of the evalOperands List which are filter * evaluatable. The filter evaluatable operands are present at the start of the List * @param evalOperands List containing the operands of the Group or Range Junction. The operands * may be filter or iter evaluatable * @return Object of OrganziedOperands type having filter & iter operand segregated */ OrganizedOperands createOrganizedOperandsObject(int indexCount, List evalOperands) { 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); // Asif : Toggle the flag of OrgaizedOperands to indicate a single Filter // condition result.isSingleFilter = true; } else { CompiledValue[] newOperands = new CompiledValue[indexCount]; for (int i = 0; i < indexCount; i++) newOperands[i] = (CompiledValue) evalOperands.get(i); filterOperands = new GroupJunction(getOperator(), getIndependentIteratorForGroup(), getExpansionFlag(), newOperands); } // 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; // Commenting this assert as for CompiledLike there could be an // iterOperand in a GroupJunction with OR condition. // For example, query with WHERE clause as // NOT (status like 'a%' or pkid like '1%') // results in GroupJunction for status (status<'a'||status>'b'). // In this case (pkid like '1') is iterating condition for the GJ. // Support.Assert(getOperator() == 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, getOperator()); } } result.filterOperand = filterOperands; result.iterateOperand = iterateOperands; return result; } /** * Helper method which creates a new RangeJunction or Group Junction from the old junction. Thus * if the invoking junction is a RangeJunction , the new object created will be a RangeJunction * else if a GroupJunction then that will be created. This method retains the operands of the old * junction while creating the new junction and adds the iter operands passed, to the existing * operands. * * @param completeExpansion if true indicates that the junction on evaluation should be expanded * to the query from clause level. * @param indpnds The independent runtime iterators to which this group belongs to. Normally it * will always be a single iterator, but if there exists a single GroupJunction in a * CompositeGroupJunction then this would be more than 1 ,as in that case the GroupJunction * will be evaluated to the CompositeGroupJunction level * @param iterOp The Iter operands which along with the operands of the old junction should be * present in the new junction. These operands are definitely not filter evaluatable * @return RangeJunction or GroupJunction depending on the nature of the old junction ( Range or * Group) */ abstract AbstractGroupOrRangeJunction recreateFromOld(boolean completeExpansion, RuntimeIterator indpnds[], CompiledValue iterOp); /** * Helper method which creates a new RangeJunction or Group Junction from the old junction. Thus * if the invoking junction is a RangeJunction , the new object created will be a RangeJunction * else if a GroupJunction then that will be created. This method discards the operands of the old * junction while creating the new junction . The new junction will only have the operands passed * to it. * * @param operator The junction type ( AND or OR ) of the old junction * @param indpndntItr Array of RuntimeIterator representing the Independent Iterator for the Group * or Range Junction * @param isCompleteExpansion * @param operands * @return Object of type GroupJunction or RangeJunction depending on the type of the invoking * Object */ abstract AbstractGroupOrRangeJunction createNewOfSameType(int operator, RuntimeIterator[] indpndntItr, boolean isCompleteExpansion, CompiledValue[] operands); /** * Segregates the conditions into those which are filter evaluatable and which are iter * evaluatable. This method is appropriately overridden in the GroupJunction and RangeJunction * class. * * @param context ExecutionContext object * @return Object of type OrganizedOperands * @throws FunctionDomainException * @throws TypeMismatchException * @throws NameResolutionException * @throws QueryInvocationTargetException */ abstract OrganizedOperands organizeOperands(ExecutionContext context) throws FunctionDomainException, TypeMismatchException, NameResolutionException, QueryInvocationTargetException; abstract void addUnevaluatedFilterOperands(List unevaluatedFilterOps); }