blob: 04d50b4a8014040494ee26069e1fc79e4cbbe094 [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.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.CacheClosedException;
import org.apache.geode.cache.EntryDestroyedException;
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.Index;
import org.apache.geode.cache.query.NameNotFoundException;
import org.apache.geode.cache.query.NameResolutionException;
import org.apache.geode.cache.query.Query;
import org.apache.geode.cache.query.QueryInvalidException;
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.AbstractIndex;
import org.apache.geode.cache.query.internal.index.PartitionedIndex;
import org.apache.geode.cache.query.internal.types.ObjectTypeImpl;
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.CollectionType;
import org.apache.geode.cache.query.types.ObjectType;
import org.apache.geode.cache.query.types.StructType;
import org.apache.geode.internal.cache.InternalCache;
import org.apache.geode.pdx.PdxInstance;
import org.apache.geode.pdx.internal.PdxString;
public class CompiledSelect extends AbstractCompiledValue {
protected List<CompiledSortCriterion> orderByAttrs; // order by attributes: list of CompiledValue
private CompiledValue whereClause; // can be null if there isn't one
private List iterators; // fromClause: list of CompiledIteratorDefs
protected List projAttrs; // projection attributes: list of Object[2]:
// 0 is projection name, 1 is the CompiledValue for the expression
private boolean distinct;
private boolean count;
// limits the SelectResults by the number specified.
private CompiledValue limit;
// counts the no of results satisfying where condition for
// count(*) non-distinct queries where no indexes are used.
private int countStartQueryResult = 0;
protected List<CompiledValue> groupBy = null;
// Are not serialized and are recreated when compiling the query
private List<String> hints;
protected boolean transformationDone = false;
protected ObjectType cachedElementTypeForOrderBy = null;
private boolean hasUnmappedOrderByCols = false;
// used as a key in a context to identify the scope of this CompiledSelect
private Object scopeID = new Object();
/*
* Set in context for the where clause to signify that it has been evaluated at least one time for
* any other CompiledValue that may use precalculated indexes we want to mark this as Evaluated so
* that we don't unlock locks that don't belong to this iteration of evaluate. This is similar to
* how CompiledComparisons store their IndexInfo in the context but for example a CompiledJunction
* that uses 2 Comparisons would have unlocked the readlocks because we check to see if the clause
* has a mapped value in the context. Because CompiledJunctions did not, we unlocked the read
* locks. Now we set a value so that it will not do this. See where we use this value to see how
* unlock is determined
*/
private static final String CLAUSE_EVALUATED = "Evaluated";
public CompiledSelect(boolean distinct, boolean count, CompiledValue whereClause, List iterators,
List projAttrs, List<CompiledSortCriterion> orderByAttrs, CompiledValue limit,
List<String> hints, List<CompiledValue> groupByClause) {
this.orderByAttrs = orderByAttrs;
this.whereClause = whereClause;
this.iterators = iterators;
this.projAttrs = projAttrs;
this.distinct = distinct;
this.count = count;
this.limit = limit;
this.hints = hints;
this.groupBy = groupByClause;
}
@Override
public List getChildren() {
List list = new ArrayList();
if (this.whereClause != null) {
list.add(this.whereClause);
}
list.addAll(iterators);
if (this.projAttrs != null) {
// extract the CompiledValues out of the projAttrs (each of which are Object[2])
for (Iterator itr = this.projAttrs.iterator(); itr.hasNext();) {
list.add(((Object[]) itr.next())[1]);
}
}
if (this.orderByAttrs != null) {
list.addAll(this.orderByAttrs);
}
return list;
}
public boolean isDistinct() {
return this.distinct;
}
public boolean isGroupBy() {
return this.groupBy != null;
}
public boolean isOrderBy() {
return this.orderByAttrs != null;
}
public void setDistinct(boolean distinct) {
this.distinct = distinct;
}
public boolean isCount() {
return this.count;
}
public void setCount(boolean count) {
this.count = count;
}
@Override
public int getType() {
return LITERAL_select;
}
public CompiledValue getWhereClause() {
return this.whereClause;
}
public List getIterators() {
return this.iterators;
}
public List getProjectionAttributes() {
return this.projAttrs;
}
public List<CompiledSortCriterion> getOrderByAttrs() {
return this.orderByAttrs;
}
@Override
public Set computeDependencies(ExecutionContext context)
throws TypeMismatchException, NameResolutionException {
// bind iterators in new scope in order to determine dependencies
context.cachePut(scopeID, context.associateScopeID());
context.newScope((Integer) context.cacheGet(scopeID));
context.pushExecCache((Integer) context.cacheGet(scopeID));
try {
Iterator iter = this.iterators.iterator();
while (iter.hasNext()) {
CompiledIteratorDef iterDef = (CompiledIteratorDef) iter.next();
// compute dependencies on this iter first before adding its
// RuntimeIterator to the current scope.
// this makes sure it doesn't bind attributes to itself
context.addDependencies(this, iterDef.computeDependencies(context));
RuntimeIterator rIter = iterDef.getRuntimeIterator(context);
context.addToIndependentRuntimeItrMap(iterDef);
context.bindIterator(rIter);
}
// is the where clause dependent on itr?
if (this.whereClause != null) {
context.addDependencies(this, this.whereClause.computeDependencies(context));
}
// are any projections dependent on itr?
if (this.projAttrs != null) {
Set totalDependencySet = null;
for (iter = this.projAttrs.iterator(); iter.hasNext();) {
// unwrap the projection expressions, they are in 2-element Object[]
// with first element the fieldName and second the projection
// expression
Object[] prj = (Object[]) TypeUtils.checkCast(iter.next(), Object[].class);
CompiledValue prjExpr = (CompiledValue) TypeUtils.checkCast(prj[1], CompiledValue.class);
totalDependencySet = context.addDependencies(this, prjExpr.computeDependencies(context));
}
this.doTreeTransformation(context);
return totalDependencySet;
} else {
this.doTreeTransformation(context);
return context.getDependencySet(this, true);
}
// is the where clause dependent on itr?
/*
* if (this.whereClause != null) { return context.addDependencies(this,
* this.whereClause.computeDependencies(context)); } else { return
* context.getDependencySet(this, true); }
*/
} finally {
context.popExecCache();
context.popScope();
}
}
protected void doTreeTransformation(ExecutionContext context)
throws TypeMismatchException, NameResolutionException {
if (!this.transformationDone) {
this.cachedElementTypeForOrderBy = prepareResultType(context);
this.mapOrderByColumns(context);
this.transformGroupByIfPossible(context);
}
this.transformationDone = true;
}
/**
* Transforms the group by clause into distinct order by clause, if possible
*/
private void transformGroupByIfPossible(ExecutionContext context)
throws TypeMismatchException, NameResolutionException {
// for time being assume that the group by cols are explicitly mentioned in proj
if (this.groupBy != null) {
List projAttribs = this.projAttrs;
if (projAttribs == null) {
projAttribs = new ArrayList();
List currentIters = context.getCurrentIterators();
for (Object o : currentIters) {
RuntimeIterator rIter = (RuntimeIterator) o;
String name = rIter.getName();
projAttribs.add(new Object[] {name, rIter});
}
}
if (projAttribs != null && projAttribs.size() != this.groupBy.size()) {
throw new QueryInvalidException(
"Query contains projected column not present in group by clause or "
+ "Query contains group by columns not present in projected fields");
}
boolean shouldTransform = true;
StringBuilder lhsBuffer = new StringBuilder();
StringBuilder rhsBuffer = new StringBuilder();
outer: for (int i = 0; i < projAttribs.size(); ++i) {
Object[] prj = (Object[]) TypeUtils.checkCast(projAttribs.get(i), Object[].class);
CompiledValue groupByAttr = this.groupBy.get(i);
if (prj[0] != null) {
if (groupByAttr instanceof CompiledID) {
if (prj[0].equals(((CompiledID) groupByAttr).getId())) {
lhsBuffer.delete(0, lhsBuffer.length());
rhsBuffer.delete(0, rhsBuffer.length());
continue;
}
}
}
CompiledValue cvProj = (CompiledValue) TypeUtils.checkCast(prj[1], CompiledValue.class);
cvProj.generateCanonicalizedExpression(lhsBuffer, context);
groupByAttr.generateCanonicalizedExpression(rhsBuffer, context);
if (lhsBuffer.length() == rhsBuffer.length()) {
for (int indx = 0; indx < lhsBuffer.length(); ++indx) {
if (lhsBuffer.charAt(indx) != rhsBuffer.charAt(indx)) {
shouldTransform = false;
break outer;
}
}
} else {
shouldTransform = false;
break;
}
lhsBuffer.delete(0, lhsBuffer.length());
rhsBuffer.delete(0, rhsBuffer.length());
}
// check if the order by clause is null or order by clause is same as proj.
// for now check if order by is null
if (shouldTransform && this.orderByAttrs == null) {
this.modifyGroupByToOrderBy(true, context);
} else {
throw new QueryInvalidException(
"Query contains projected column not present in group by clause or "
+ "Query contains group by columns not present in projected fields");
}
}
}
protected void modifyGroupByToOrderBy(boolean setDistinct, ExecutionContext context)
throws TypeMismatchException, NameResolutionException {
if (setDistinct) {
this.distinct = setDistinct;
}
this.orderByAttrs = new ArrayList<CompiledSortCriterion>(this.groupBy.size());
int colIndex = 0;
for (CompiledValue cv : this.groupBy) {
CompiledSortCriterion csc = new CompiledSortCriterion(false, cv);
csc.mapExpressionToProjectionField(projAttrs, context);
this.orderByAttrs.add(csc);
}
this.groupBy = null;
}
private void mapOrderByColumns(ExecutionContext context)
throws TypeMismatchException, NameResolutionException {
if (this.orderByAttrs != null) {
Iterator<CompiledSortCriterion> iter = this.orderByAttrs.iterator();
while (iter.hasNext()) {
CompiledSortCriterion csc = iter.next();
// Ideally for replicated regions, the requirement that
// projected columns should
// contain order by fields ( directly or derivable on it),
// is not needed. But for PR , the query gathers only projected
// columns, so applying order by on the query node
// will need order by values ( which we dont send). So this
// restriction is needed.
// Also if this restriction is assumed to be correct, then the order
// by comparator can be optimized as
// it does not need to keep the mapping of evaluated order by clause,
// for comparison
if (!csc.mapExpressionToProjectionField(this.projAttrs, context)) {
this.hasUnmappedOrderByCols = true;
}
}
}
}
private void evalCanonicalizedExpressionForCSC(CompiledSortCriterion csc,
ExecutionContext context, StringBuilder buffer)
throws TypeMismatchException, NameResolutionException {
csc.getExpr().generateCanonicalizedExpression(buffer, context);
}
/*
* Gets the appropriate empty results set when outside of actual query evalutaion.
*
* @param parameters the parameters that will be passed into the query when evaluated
*
* @param cache the cache the query will be executed in the context of
*
* @return the empty result set of the appropriate type
*/
public SelectResults getEmptyResultSet(Object[] parameters, InternalCache cache, Query query)
throws FunctionDomainException, TypeMismatchException, NameResolutionException,
QueryInvocationTargetException {
ExecutionContext context = new QueryExecutionContext(parameters, cache, query);
computeDependencies(context);
context.newScope((Integer) context.cacheGet(scopeID));
context.pushExecCache((Integer) context.cacheGet(scopeID));
SelectResults results = null;
try {
Iterator iter = iterators.iterator();
while (iter.hasNext()) {
CompiledIteratorDef iterDef = (CompiledIteratorDef) iter.next();
RuntimeIterator rIter = iterDef.getRuntimeIterator(context);
context.bindIterator(rIter);
}
results = prepareEmptyResultSet(context, false);
} finally {
context.popScope();
context.popExecCache();
}
return results;
}
public ObjectType getElementTypeForOrderByQueries() {
return this.cachedElementTypeForOrderBy;
}
@Override
public SelectResults evaluate(ExecutionContext context) throws FunctionDomainException,
TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
context.newScope((Integer) context.cacheGet(scopeID));
context.pushExecCache((Integer) context.cacheGet(scopeID));
context.setDistinct(this.distinct);
if (this.hasUnmappedOrderByCols && context.getBucketList() != null) {
throw new QueryInvalidException(
"Query contains atleast one order by field which is not present in projected fields.");
}
if (hints != null) {
context.cachePut(QUERY_INDEX_HINTS, hints);
}
try {
// set flag to keep objects serialized for "select *" queries
if (context.getQuery() != null) {
((DefaultQuery) context.getQuery()).keepResultsSerialized(this, context);
}
Iterator iter = iterators.iterator();
while (iter.hasNext()) {
CompiledIteratorDef iterDef = (CompiledIteratorDef) iter.next();
RuntimeIterator rIter = iterDef.getRuntimeIterator(context);
context.bindIterator(rIter);
// Ideally the function below should always be called after binding has occurred
// So that the internal ID gets set during binding to the scope. If not so then chances
// are that internal_id is still null causing index_internal_id to be null.
// Though in our case it may not be an issue as the compute dependency phase must have
// already set the index id
}
Integer limitValue = evaluateLimitValue(context, this.limit);
SelectResults result = null;
boolean evalAsFilters = false;
if (this.whereClause == null) {
result = doIterationEvaluate(context, false);
} else {
if (!this.whereClause.isDependentOnCurrentScope(context)) { // independent
// where
// @todo
// check
// for
// dependency
// on
// current
// scope
// only?
// clause
Object b = this.whereClause.evaluate(context);
if (b == null || b == QueryService.UNDEFINED) {
// treat as if all elements are undefined
result = prepareEmptyResultSet(context, false);
// ResultsSet.emptyResultsSet(resultSet, 0);
// return result;
} else if (!(b instanceof Boolean)) {
throw new TypeMismatchException(
String.format("The WHERE clause was type ' %s ' instead of boolean",
b.getClass().getName()));
} else if ((Boolean) b) {
result = doIterationEvaluate(context, false);
} else {
result = prepareEmptyResultSet(context, false);
// ResultsSet.emptyResultsSet(resultSet, 0);
// return result;
}
} else {
// Check the numer of independent iterators
int numInd = context.getAllIndependentIteratorsOfCurrentScope().size();
// If order by clause is defined, then the first column should be the preferred index
if (this.orderByAttrs != null && numInd == 1) {
CompiledSortCriterion csc = orderByAttrs.get(0);
StringBuilder preferredIndexCondn = new StringBuilder();
this.evalCanonicalizedExpressionForCSC(csc, context, preferredIndexCondn);
context.cachePut(PREF_INDEX_COND, preferredIndexCondn.toString());
}
boolean unlock = true;
Object obj = context.cacheGet(this.whereClause);
if (obj != null && (obj instanceof IndexInfo[] || obj.equals(CLAUSE_EVALUATED))) {
// if indexinfo is cached means the read lock
// is not being taken this time, so releasing
// the lock is not required
unlock = false;
}
// see if we should evaluate as filters,
// and count how many actual index lookups will be performed
PlanInfo planInfo = this.whereClause.getPlanInfo(context);
if (context.cacheGet(this.whereClause) == null) {
context.cachePut(this.whereClause, CLAUSE_EVALUATED);
}
try {
evalAsFilters = planInfo.evalAsFilter;
// let context know if there is exactly one index lookup
context.setOneIndexLookup(planInfo.indexes.size() == 1);
if (evalAsFilters) {
((QueryExecutionContext) context).setIndexUsed(true);
// Ignore order by attribs for a while
boolean canApplyOrderByAtIndex = false;
if (limitValue >= 0 && numInd == 1
&& ((Filter) this.whereClause).isLimitApplicableAtIndexLevel(context)) {
context.cachePut(CAN_APPLY_LIMIT_AT_INDEX, Boolean.TRUE);
}
StringBuilder temp = null;
if (this.orderByAttrs != null) {
temp = new StringBuilder();
CompiledSortCriterion csc = this.orderByAttrs.get(0);
this.evalCanonicalizedExpressionForCSC(csc, context, temp);
}
boolean needsTopLevelOrdering = true;
if (temp != null && numInd == 1 && ((Filter) this.whereClause)
.isOrderByApplicableAtIndexLevel(context, temp.toString())) {
context.cachePut(CAN_APPLY_ORDER_BY_AT_INDEX, Boolean.TRUE);
context.cachePut(ORDERBY_ATTRIB, this.orderByAttrs);
canApplyOrderByAtIndex = true;
if (this.orderByAttrs.size() == 1) {
needsTopLevelOrdering = false;
// If there is a limit present and we are executing on a partitioned region
// we should use a sorted set
if (this.limit != null) {
// Currently check bucket list to determine if it's a pr query
if (context.getBucketList() != null && context.getBucketList().size() > 0) {
needsTopLevelOrdering = true;
}
}
}
} else if (temp != null) {
// If order by is present but cannot be applied at index level,
// then limit also cannot be applied
// at index level
context.cachePut(CAN_APPLY_LIMIT_AT_INDEX, Boolean.FALSE);
}
context.cachePut(RESULT_LIMIT, limitValue);
if (numInd == 1
&& ((Filter) this.whereClause).isProjectionEvaluationAPossibility(context)
&& (this.orderByAttrs == null
|| (canApplyOrderByAtIndex && !needsTopLevelOrdering))
&& this.projAttrs != null) {
// Possibility of evaluating the resultset as filter itself
ObjectType resultType = this.cachedElementTypeForOrderBy != null
? this.cachedElementTypeForOrderBy : this.prepareResultType(context);
context.cachePut(RESULT_TYPE, resultType);
context.cachePut(PROJ_ATTRIB, this.projAttrs);
}
result = ((Filter) this.whereClause).filterEvaluate(context, null);
if (!(context.cacheGet(RESULT_TYPE) instanceof Boolean)) {
QueryObserverHolder.getInstance()
.beforeApplyingProjectionOnFilterEvaluatedResults(result);
result = applyProjectionOnCollection(result, context, !needsTopLevelOrdering);
}
} else {
// otherwise iterate over the single from var to evaluate
result = doIterationEvaluate(context, true);
}
} finally {
// The Read lock is acquired in {@link
// IndexManager#getBestMatchIndex()},
// because we need to select index which can be read-locked.
if (unlock) {
releaseReadLockOnUsedIndex(planInfo);
}
}
}
}
// TODO: It does not appear that results would be null ever.
// if (result == null) { return QueryService.UNDEFINED; }
assert result != null;
// drop duplicates if this is DISTINCT
if (result instanceof SelectResults) {
SelectResults sr = result;
CollectionType colnType = sr.getCollectionType();
// if (this.distinct && colnType.allowsDuplicates()) {
if (this.distinct) {
Collection r;
// Set s = sr.asSet();
if (colnType.allowsDuplicates()) {
// don't just convert to a ResultsSet (or StructSet), since
// the bags can convert themselves to a Set more efficiently
r = sr.asSet();
} else {
r = sr;
}
result = new ResultsCollectionWrapper(colnType.getElementType(), r, limitValue);
if (r instanceof Bag.SetView) {
((ResultsCollectionWrapper) result).setModifiable(false);
}
} else {
// SelectResults is of type
if (limitValue > -1) {
((Bag) sr).applyLimit(limitValue);
}
}
/*
* We still have to get size of SelectResults in some cases like, if index was used OR query
* is a distinct query.
*
* If SelectResult size is zero then we need to put Integer for 0 count.
*/
if (this.count) {
SelectResults res = result;
if ((this.distinct || evalAsFilters || countStartQueryResult == 0)) {
// Retrun results as it is as distinct is applied
// at coordinator node for PR queries.
if (context.getBucketList() != null && this.distinct) {
return result;
}
// Take size and empty the results
int resultCount = res.size();
res.clear();
ResultsBag countResult =
new ResultsBag(new ObjectTypeImpl(Integer.class), context.getCachePerfStats());
countResult.addAndGetOccurence(resultCount);
result = countResult;
} else {
((Bag) res).addAndGetOccurence(countStartQueryResult);
}
}
}
return result;
} finally {
context.popScope();
context.popExecCache();
}
}
/**
* The index is locked during query to prevent it from being removed by another thread. So we have
* to release the lock only after whole query is finished as one query can use an index multiple
* times.
*/
private void releaseReadLockOnUsedIndex(PlanInfo planInfo) {
List inds = planInfo.indexes;
for (Object obj : inds) {
Index index = (Index) obj;
Index prIndex = ((AbstractIndex) index).getPRIndex();
if (prIndex != null) {
((PartitionedIndex) prIndex).releaseIndexReadLockForRemove();
} else {
((AbstractIndex) index).releaseIndexReadLockForRemove();
}
}
}
/**
* Returns the size of region iterator for count(*) on a region without whereclause.
*
* @since GemFire 6.6.2
*/
private int getRegionIteratorSize(ExecutionContext context, CompiledValue collExpr)
throws RegionNotFoundException {
Region region;
String regionPath = ((CompiledRegion) collExpr).getRegionPath();
if (context.getBucketRegion() == null) {
region = context.getCache().getRegion(regionPath);
} else {
region = context.getBucketRegion();
}
if (region != null) {
return region.size();
} else {
// if we couldn't find the region because the cache is closed, throw
// a CacheClosedException
Cache cache = context.getCache();
if (cache.isClosed()) {
throw new CacheClosedException();
}
throw new RegionNotFoundException(
String.format("Region not found: %s", regionPath));
}
}
public int getLimitValue(Object[] bindArguments) throws FunctionDomainException,
TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
// if evaluation of the limit fails, we default to no limit
return evaluateLimitValue(bindArguments);
}
// returns null if result is UNDEFINED
private SelectResults doIterationEvaluate(ExecutionContext context, boolean evaluateWhereClause)
throws TypeMismatchException, FunctionDomainException, NameResolutionException,
QueryInvocationTargetException {
SelectResults results = prepareEmptyResultSet(context, false);
// TODO: SELF : Work on limit implementation on bulk get
// check for bulk get optimization
if (evaluateWhereClause) {
List tmpResults = optimizeBulkGet(context);
if (tmpResults != null) {
// (has only one iterator)
RuntimeIterator rIter = (RuntimeIterator) context.getCurrentIterators().get(0);
for (Iterator itr = tmpResults.iterator(); itr.hasNext();) {
Object currObj = itr.next();
rIter.setCurrent(currObj);
QueryObserver observer = QueryObserverHolder.getInstance();
observer.beforeIterationEvaluation(rIter, currObj);
applyProjectionAndAddToResultSet(context, results, this.orderByAttrs == null);
}
return results;
}
}
int numElementsInResult = 0;
try {
doNestedIterations(0, results, context, evaluateWhereClause, numElementsInResult);
} catch (CompiledSelect.NullIteratorException ignore) {
return null;
}
return results;
}
// TODO: make this more general to work for any kind of map, not just regions
/**
* Check for the bulk-get pattern and if it applies do an optimized execution. The pattern is:
* SELECT ?? FROM <Region>.entrySet e WHERE e.key IN <Collection>.
*
* @return a List of entries if optimization was executed, or null if it wasn't because the
* optimization pattern didn't match
*/
private List optimizeBulkGet(ExecutionContext context) throws TypeMismatchException,
FunctionDomainException, NameResolutionException, QueryInvocationTargetException {
List iterList = context.getCurrentIterators();
// must be one iterator
if (iterList.size() != 1) {
return null;
}
// where clause must be an IN operation
if (!(this.whereClause instanceof CompiledIn)) {
return null;
}
RuntimeIterator rIter = (RuntimeIterator) iterList.get(0);
CompiledIteratorDef cIterDef = rIter.getCmpIteratorDefn();
CompiledValue colnExpr = cIterDef.getCollectionExpr();
// check for region.entrySet or region.entrySet()
boolean match = false;
CompiledRegion rgn = null;
if (colnExpr instanceof CompiledPath) {
CompiledPath cPath = (CompiledPath) colnExpr;
CompiledValue rcvr = cPath.getReceiver();
if (rcvr instanceof CompiledRegion) {
rgn = (CompiledRegion) rcvr;
String attr = cPath.getTailID();
match = attr.equals("entrySet");
}
}
if (!match && (colnExpr instanceof CompiledOperation)) {
CompiledOperation cOp = (CompiledOperation) colnExpr;
CompiledValue rcvr = cOp.getReceiver(context);
if (rcvr instanceof CompiledRegion) {
rgn = (CompiledRegion) rcvr;
match = cOp.getMethodName().equals("entrySet");
}
}
if (!match) {
return null;
}
// check for IN expression
CompiledIn cIn = (CompiledIn) this.whereClause;
// defer to the CompiledIn for rest of pattern match and
// evaluation
return cIn.optimizeBulkGet(rgn, context);
}
// returns the number of elements added in the return ResultSet
private int doNestedIterations(int level, SelectResults results, ExecutionContext context,
boolean evaluateWhereClause, int numElementsInResult)
throws TypeMismatchException, FunctionDomainException, NameResolutionException,
QueryInvocationTargetException, CompiledSelect.NullIteratorException {
List iterList = context.getCurrentIterators();
if (level == iterList.size()) {
boolean addToResults = true;
if (evaluateWhereClause) {
Object result = this.whereClause.evaluate(context);
QueryObserver observer = QueryObserverHolder.getInstance();
observer.afterIterationEvaluation(result);
if (result == null) {
addToResults = false;
} else if (result instanceof Boolean) {
addToResults = (Boolean) result;
} else if (result == QueryService.UNDEFINED) {
// add UNDEFINED to results only for NOT EQUALS queries
if (this.whereClause.getType() == COMPARISON) {
int operator = ((Filter) this.whereClause).getOperator();
if ((operator != TOK_NE && operator != TOK_NE_ALT)) {
addToResults = false;
}
} else {
addToResults = false;
}
} else {
throw new TypeMismatchException(
String.format("The WHERE clause was type ' %s ' instead of boolean",
result.getClass().getName()));
}
}
if (addToResults) {
int occurrence =
applyProjectionAndAddToResultSet(context, results, this.orderByAttrs == null);
// If the occurrence is greater than 1, then only in case of
// non distinct query should it be treated as contributing to size
// else duplication will be eliminated when making it distinct using
// ResultsCollectionWrapper and we will fall short of limit
if (occurrence == 1 || (occurrence > 1 && !this.distinct)) {
// (Unique i.e first time occurrence) or subsequent occurrence
// for non distinct query
++numElementsInResult;
}
}
} else {
RuntimeIterator rIter = (RuntimeIterator) iterList.get(level);
SelectResults sr = rIter.evaluateCollection(context);
if (sr == null) {
return 0; // continue iteration if a collection evaluates to UNDEFINED
}
// Check if its a non-distinct count(*) query without where clause, in that case,
// we can size directly on the region.
if (this.whereClause == null && iterators.size() == 1 && isCount() && !isDistinct()
&& sr instanceof QRegion) {
QRegion qr = (QRegion) sr;
countStartQueryResult = qr.getRegion().size();
return 1;
}
// #44807: select * query should not deserialize objects
// In case of "select *" queries we can keep the results in serialized
// form and send it to the client.
if (context.getQuery() != null && ((DefaultQuery) context.getQuery()).isKeepSerialized()
&& sr instanceof QRegion) {
((QRegion) sr).setKeepSerialized(true);
}
// Iterate through the data set.
for (Object aSr : sr) {
// Check if query execution on this thread is canceled.
QueryMonitor.throwExceptionIfQueryOnCurrentThreadIsCanceled();
Object currObj = aSr;
rIter.setCurrent(currObj);
QueryObserver observer = QueryObserverHolder.getInstance();
observer.beforeIterationEvaluation(rIter, currObj);
numElementsInResult = doNestedIterations(level + 1, results, context, evaluateWhereClause,
numElementsInResult);
Integer limitValue = evaluateLimitValue(context, this.limit);
if (this.orderByAttrs == null && limitValue > -1 && numElementsInResult == limitValue) {
break;
}
}
}
return numElementsInResult;
}
private SelectResults applyProjectionOnCollection(SelectResults resultSet,
ExecutionContext context, boolean ignoreOrderBy) throws TypeMismatchException,
FunctionDomainException, NameResolutionException, QueryInvocationTargetException {
List iterators = context.getCurrentIterators();
if (projAttrs == null && (this.orderByAttrs == null || ignoreOrderBy)) {
// If the projection attribute is null (ie specified as *) & there is only one Runtime
// Iterator we can return the set as it is. But if the projection attribute is null & multiple
// Iterators are defined we need to rectify the StructBag that is returned. It is to be noted
// that in case of single from clause where the from clause itself is defined as nested select
// query with multiple from clauses, the result set returned will be a StructBag which we have
// to return as it is.
if (iterators.size() > 1) {
StructType type = createStructTypeForNullProjection(iterators, context);
resultSet.setElementType(type);
}
return resultSet;
} else {
int numElementsAdded = 0;
SelectResults pResultSet = prepareEmptyResultSet(context, ignoreOrderBy);
boolean isStructType = resultSet.getCollectionType().getElementType() != null
&& resultSet.getCollectionType().getElementType().isStructType();
if (isStructType) {
Iterator resultsIter = resultSet.iterator();
// Apply limit if there is no order by
Integer limitValue = evaluateLimitValue(context, this.limit);
while (((this.orderByAttrs != null && !ignoreOrderBy) || limitValue < 0
|| (numElementsAdded < limitValue)) && resultsIter.hasNext()) {
// Check if query execution on this thread is canceled
QueryMonitor.throwExceptionIfQueryOnCurrentThreadIsCanceled();
Object values[] = ((Struct) resultsIter.next()).getFieldValues();
for (int i = 0; i < values.length; i++) {
((RuntimeIterator) iterators.get(i)).setCurrent(values[i]);
}
int occurrence = applyProjectionAndAddToResultSet(context, pResultSet, ignoreOrderBy);
if (occurrence == 1 || (occurrence > 1 && !this.distinct)) {
// (Unique i.e first time occurrence) or subsequent occurrence
// for non distinct query
++numElementsAdded;
}
}
// return pResultSet;
} else if (iterators.size() == 1) {
RuntimeIterator rIter = (RuntimeIterator) iterators.get(0);
Iterator resultsIter = resultSet.iterator();
// Apply limit if there is no order by.
Integer limitValue = evaluateLimitValue(context, this.limit);
while (((this.orderByAttrs != null && !ignoreOrderBy) || limitValue < 0
|| (numElementsAdded < limitValue)) && resultsIter.hasNext()) {
rIter.setCurrent(resultsIter.next());
int occurrence = applyProjectionAndAddToResultSet(context, pResultSet, ignoreOrderBy);
if (occurrence == 1 || (occurrence > 1 && !this.distinct)) {
// (Unique i.e first time occurrence) or subsequent occurrence
// for non distinct query
++numElementsAdded;
}
}
} else {
throw new RuntimeException(
"Result Set does not match with iterator definitions in from clause");
}
return pResultSet;
}
}
public enum DataContainerType {
// isOrdered, distinct, elementType.isStructType(), ignoreOrderBy
UNORDERED_DISTINCT_STRUCT(false, true, true, true),
UNORDERED_DISTINCT_RESULTS(false, true, false, true),
UNORDERED_INDISTINCT_STRUCT(false, false, true, true),
UNORDERED_INDISTINCT_RESULTS(false, false, false, true),
ORDERED_DISTINCT_STRUCT_IGNORED(true, true, true, true),
ORDERED_INDISTINCT_STRUCT_IGNORED(true, false, true, true),
ORDERED_DISTINCT_STRUCT_UNIGNORED(true, true, true, false),
ORDERED_INDISTINCT_STRUCT_UNIGNORED(true, false, true, false),
ORDERED_DISTINCT_RESULTS_IGNORED(true, true, false, true),
ORDERED_INDISTINCT_RESULTS_IGNORED(true, false, false, true),
ORDERED_DISTINCT_RESULTS_UNIGNORED(true, true, false, false),
ORDERED_INDISTINCT_RESULTS_UNIGNORED(true, false, false, false);
public static DataContainerType determineDataContainerType(boolean getOrdered,
boolean getDistinct, boolean getStructType, boolean getIgnoreOrderBy)
throws TypeMismatchException {
// if not isOrdered, then isIgnoreOrderBy is irrelevant
return Arrays.stream(DataContainerType.values()).filter(type -> type.isOrdered == getOrdered)
.filter(type -> type.isDistinct == getDistinct)
.filter(type -> type.isStructType == getStructType)
.filter(type -> type.isIgnoreOrderBy == getIgnoreOrderBy || !type.isOrdered).findFirst()
.orElseThrow(() -> new TypeMismatchException("Logical inconsistency in CompiledSelect"));
}
DataContainerType(boolean isOrdered, boolean isDistinct, boolean isStructType,
boolean isIgnoreOrderBy) {
this.isOrdered = isOrdered;
this.isDistinct = isDistinct;
this.isStructType = isStructType;
this.isIgnoreOrderBy = isIgnoreOrderBy;
}
private final boolean isOrdered, isDistinct, isStructType, isIgnoreOrderBy;
}
private SelectResults prepareEmptyResultSet(ExecutionContext context, boolean ignoreOrderBy)
throws TypeMismatchException, AmbiguousNameException {
// If no projection attributes or '*' as projection attribute & more than one/RunTimeIterator
// then create a StructSet.
// If attribute is null or '*' & only one RuntimeIterator then create a ResultSet.
// If single attribute is present without alias name, then create ResultSet.
// Else if more than on attribute or single attribute with alias is present then return a
// StructSet.
// Create StructSet which will contain root objects of all iterators in from clause.
ObjectType elementType = this.cachedElementTypeForOrderBy != null
? this.cachedElementTypeForOrderBy : prepareResultType(context);
SelectResults results;
if (!this.distinct && this.count) {
// Shobhit: If it's a 'COUNT' query and no End processing required Like for 'DISTINCT'
// we can directly keep count in ResultSet and ResultBag is good enough for that.
results = new ResultsBag(new ObjectTypeImpl(Integer.class), 1, context.getCachePerfStats());
countStartQueryResult = 0;
return results;
}
// Potential edge-case: Could this be non-null but empty?
boolean nullValuesAtStart = orderByAttrs != null && !orderByAttrs.get(0).getCriterion();
OrderByComparator comparator;
boolean isOrdered = this.orderByAttrs != null;
switch (DataContainerType.determineDataContainerType(isOrdered, distinct,
elementType.isStructType(), ignoreOrderBy)) {
case UNORDERED_DISTINCT_STRUCT:
return new StructSet((StructType) elementType);
case UNORDERED_DISTINCT_RESULTS:
return new ResultsSet(elementType);
case UNORDERED_INDISTINCT_STRUCT:
return new StructBag((StructType) elementType, context.getCachePerfStats());
case UNORDERED_INDISTINCT_RESULTS:
return new ResultsBag(elementType, context.getCachePerfStats());
case ORDERED_DISTINCT_STRUCT_IGNORED:
return new LinkedStructSet((StructTypeImpl) elementType);
case ORDERED_INDISTINCT_STRUCT_IGNORED:
return new SortedResultsBag(elementType, nullValuesAtStart);
case ORDERED_DISTINCT_STRUCT_UNIGNORED:
comparator = this.hasUnmappedOrderByCols
? new OrderByComparatorMapped(this.orderByAttrs, elementType, context)
: new OrderByComparator(this.orderByAttrs, elementType, context);
return new SortedStructSet(comparator, (StructTypeImpl) elementType);
case ORDERED_INDISTINCT_STRUCT_UNIGNORED:
comparator = this.hasUnmappedOrderByCols
? new OrderByComparatorMapped(this.orderByAttrs, elementType, context)
: new OrderByComparator(this.orderByAttrs, elementType, context);
return new SortedStructBag(comparator, (StructType) elementType, nullValuesAtStart);
case ORDERED_DISTINCT_RESULTS_IGNORED:
results = new LinkedResultSet();
results.setElementType(elementType);
return results;
case ORDERED_INDISTINCT_RESULTS_IGNORED:
results = new SortedResultsBag(nullValuesAtStart);
results.setElementType(elementType);
return results;
case ORDERED_DISTINCT_RESULTS_UNIGNORED:
comparator = this.hasUnmappedOrderByCols
? new OrderByComparatorMapped(this.orderByAttrs, elementType, context)
: new OrderByComparator(this.orderByAttrs, elementType, context);
results = new SortedResultSet(comparator);
results.setElementType(elementType);
return results;
case ORDERED_INDISTINCT_RESULTS_UNIGNORED:
comparator = this.hasUnmappedOrderByCols
? new OrderByComparatorMapped(this.orderByAttrs, elementType, context)
: new OrderByComparator(this.orderByAttrs, elementType, context);
results = new SortedResultsBag(comparator, nullValuesAtStart);
results.setElementType(elementType);
return results;
}
throw new TypeMismatchException("Logical inconsistency in CompiledSelect");
}
protected ObjectType prepareResultType(ExecutionContext context)
throws TypeMismatchException, AmbiguousNameException {
// if no projection attributes or '*'as projection attribute
// & more than one/RunTimeIterator then create a StrcutSet.
// If attribute is null or '*' & only one RuntimeIterator then create a
// ResultSet.
// If single attribute is present without alias name , then create
// ResultSet
// Else if more than on attribute or single attribute with alias is
// present then return a StrcutSet
// create StructSet which will contain root objects of all iterators in
// from clause
ObjectType elementType = null;
SelectResults sr = null;
List currentIterators = context.getCurrentIterators();
if (projAttrs == null) {
if (currentIterators.size() == 1) {
RuntimeIterator iter = (RuntimeIterator) currentIterators.get(0);
elementType = iter.getElementType();
} else {
elementType = createStructTypeForNullProjection(currentIterators, context);
}
} else {
// Create StructType for projection attributes
int projCount = projAttrs.size();
String fieldNames[] = new String[projCount];
ObjectType fieldTypes[] = new ObjectType[projCount];
boolean createStructSet = false;
String fldName = null;
for (int i = 0; i < projCount; i++) {
Object[] projDef = (Object[]) projAttrs.get(i);
fldName = (String) projDef[0];
if (!createStructSet) {
if (fldName != null || projCount > 1) {
createStructSet = true;
}
}
fieldNames[i] = (fldName == null && createStructSet)
? generateProjectionName((CompiledValue) projDef[1], context) : fldName;
fieldTypes[i] = getFieldTypeOfProjAttrib(context, (CompiledValue) projDef[1]);
// fieldTypes[i] = TypeUtils.OBJECT_TYPE;
}
if (createStructSet) {
elementType = new StructTypeImpl(fieldNames, fieldTypes);
} else {
elementType = fieldTypes[0];
}
}
return elementType;
}
/**
* This function should be used to create a StructType for those queries which have * as
* projection attribute (implying null projection attribute) & multiple from clauses
*/
private StructTypeImpl createStructTypeForNullProjection(List currentIterators,
ExecutionContext context) {
int len = currentIterators.size();
String fieldNames[] = new String[len];
ObjectType fieldTypes[] = new ObjectType[len];
String fldName = null;
for (int i = 0; i < len; i++) {
RuntimeIterator iter = (RuntimeIterator) currentIterators.get(i);
// fieldNames[i] = iter.getName();
if ((fldName = iter.getName()) == null) {
fldName = generateProjectionName(iter, context);
}
fieldNames[i] = fldName;
fieldTypes[i] = iter.getElementType();
}
return new StructTypeImpl(fieldNames, fieldTypes);
}
private ObjectType getFieldTypeOfProjAttrib(ExecutionContext context, CompiledValue cv)
throws TypeMismatchException, AmbiguousNameException {
// Identify the RuntimeIterator for the compiled value
ObjectType retType = TypeUtils.OBJECT_TYPE;
try {
RuntimeIterator rit = context.findRuntimeIterator(cv);
List pathOnItr = cv.getPathOnIterator(rit, context);
if (pathOnItr != null) {
String path[] = (String[]) pathOnItr.toArray(new String[pathOnItr.size()]);
ObjectType ot[] = PathUtils.calculateTypesAlongPath(context, rit.getElementType(), path);
retType = ot[ot.length - 1];
}
} catch (NameNotFoundException ignore) {
// Unable to determine the type Of attribute.It will default to
// ObjectType
}
return retType;
}
// resultSet could be a set or a bag (we have set constructor, or there
// could be a distinct subquery)
// in future, it would be good to simplify this to always work with a bag
// (converting all sets to bags) until the end when we enforce distinct
// The number returned indicates the occurrence of the data in the SelectResults
// Thus if the SelectResults is of type ResultsSet or StructSet
// then 1 will indicate that data was added to the results & that was the
// first occurrence. For this 0 will indicate that the data was not added
// because it was a duplicate
// If the SelectResults is an instance ResultsBag or StructsBag , the number will
// indicate the occurrence. Thus 1 will indicate it being added for first time
// Currently orderBy is present only for StructSet & ResultSet which are
// unique object holders. So the occurrence for them can be either 0 or 1 only
private int applyProjectionAndAddToResultSet(ExecutionContext context, SelectResults resultSet,
boolean ignoreOrderBy) throws FunctionDomainException, TypeMismatchException,
NameResolutionException, QueryInvocationTargetException {
List currrentRuntimeIters = context.getCurrentIterators();
int occurrence = 0;
ObjectType elementType = resultSet.getCollectionType().getElementType();
boolean isStruct = elementType != null && elementType.isStructType();
// TODO: Optimize this condition in some clean way
boolean isLinkedStructure =
resultSet instanceof Ordered && ((Ordered) resultSet).dataPreordered();
ArrayList evaluatedOrderByClause = null;
OrderByComparator comparator = null;
boolean applyOrderBy = false;
if (this.orderByAttrs != null && !ignoreOrderBy) {
// In case PR order-by will get applied on the coordinator node
// on the cumulative results. Apply the order-by on PR only if
// limit is specified.
Integer limitValue = evaluateLimitValue(context, this.limit);
if (context.getPartitionedRegion() != null && limitValue < 0) {
applyOrderBy = false;
}
applyOrderBy = true;
}
if (this.orderByAttrs != null && !ignoreOrderBy) {
comparator = (OrderByComparator) ((Ordered) resultSet).comparator();
}
if (projAttrs == null) {
int len = currrentRuntimeIters.size();
Object values[] = new Object[len];
for (int i = 0; i < len; i++) {
RuntimeIterator iter = (RuntimeIterator) currrentRuntimeIters.get(i);
values[i] = iter.evaluate(context);
// For local queries with distinct, deserialize all PdxInstances
// as we do not have a way to compare Pdx and non Pdx objects in case
// the cache has a mix of pdx and non pdx objects.
// We still have to honor the cache level readserialized flag in
// case of all Pdx objects in cache
if (this.distinct && !((DefaultQuery) context.getQuery()).isRemoteQuery()
&& !context.getCache().getPdxReadSerialized() && (values[i] instanceof PdxInstance)) {
values[i] = ((PdxInstance) values[i]).getObject();
}
}
// Shobhit: Add count value to the counter for this select expression.
// Don't care about Order By for count(*).
if (isCount() && !this.distinct) {
// Counter is local to CompileSelect and not available in ResultSet
// until
// the end of evaluate call to this CompiledSelect object.
this.countStartQueryResult++;
occurrence = 1;
} else {
// if order by is present
if (applyOrderBy) {
StructImpl structImpl;
if (this.distinct) {
if (isStruct) {
if (values.length == 1 && values[0] instanceof StructImpl) {
structImpl = (StructImpl) values[0];
comparator.addEvaluatedSortCriteria(structImpl.getFieldValues(), context);
occurrence = resultSet.add(structImpl) ? 1 : 0;
} else {
comparator.addEvaluatedSortCriteria(values, context);
occurrence = ((StructFields) resultSet).addFieldValues(values) ? 1 : 0;
}
// TODO:Instead of a normal Map containing which holds
// StructImpl object
// use a THashObject with Object[] array hashing stragtegy as we
// are unnnecessarily
// creating objects of type Object[]
} else {
comparator.addEvaluatedSortCriteria(values[0], context);
occurrence = resultSet.add(values[0]) ? 1 : 0;
}
} else {
if (isStruct) {
if (values.length == 1 && values[0] instanceof StructImpl) {
structImpl = (StructImpl) values[0];
comparator.addEvaluatedSortCriteria(structImpl.getFieldValues(), context);
occurrence = ((Bag) resultSet).addAndGetOccurence(structImpl.getFieldValues());
} else {
comparator.addEvaluatedSortCriteria(values, context);
occurrence = ((Bag) resultSet).addAndGetOccurence(values);
}
} else {
comparator.addEvaluatedSortCriteria(values[0], context);
occurrence = ((Bag) resultSet).addAndGetOccurence(values[0]);
}
}
} else {
if (isLinkedStructure) {
if (isStruct) {
StructImpl structImpl;
if (values.length == 1 && values[0] instanceof StructImpl) {
structImpl = (StructImpl) values[0];
} else {
structImpl = new StructImpl((StructTypeImpl) elementType, values);
}
if (this.distinct) {
occurrence = resultSet.add(structImpl) ? 1 : 0;
} else {
occurrence = ((Bag) resultSet).addAndGetOccurence(structImpl);
}
} else {
if (this.distinct) {
occurrence = resultSet.add(values[0]) ? 1 : 0;
} else {
occurrence = ((Bag) resultSet).addAndGetOccurence(values[0]);
}
}
} else {
if (this.distinct) {
if (isStruct) {
occurrence = ((StructFields) resultSet).addFieldValues(values) ? 1 : 0;
} else {
occurrence = resultSet.add(values[0]) ? 1 : 0;
}
} else {
if (isStruct) {
occurrence = ((Bag) resultSet).addAndGetOccurence(values);
} else {
boolean add = true;
if (context.isCqQueryContext()) {
if (values[0] instanceof Region.Entry) {
Region.Entry e = (Region.Entry) values[0];
if (!e.isDestroyed()) {
try {
values[0] = new CqEntry(e.getKey(), e.getValue());
} catch (EntryDestroyedException ignore) {
// Even though isDestory() check is made, the entry could throw
// EntryDestroyedException if the value becomes null.
add = false;
}
} else {
add = false;
}
}
}
if (add) {
occurrence = ((Bag) resultSet).addAndGetOccurence(values[0]);
}
}
}
}
}
}
} else { // One or more projection attributes
int projCount = projAttrs.size();
Object[] values = new Object[projCount];
for (int i = 0; i < projCount; i++) {
Object projDef[] = (Object[]) projAttrs.get(i);
values[i] = ((CompiledValue) projDef[1]).evaluate(context);
// For local queries with distinct, deserialize all PdxInstances
// as we do not have a way to compare Pdx and non Pdx objects in case
// the cache has a mix of pdx and non pdx objects.
// We still have to honor the cache level readserialized flag in
// case of all Pdx objects in cache.
// Also always convert PdxString to String before adding to resultset
// for remote queries
if (!((DefaultQuery) context.getQuery()).isRemoteQuery()) {
if (this.distinct && values[i] instanceof PdxInstance
&& !context.getCache().getPdxReadSerialized()) {
values[i] = ((PdxInstance) values[i]).getObject();
} else if (values[i] instanceof PdxString) {
values[i] = values[i].toString();
}
}
}
// if order by is present
if (applyOrderBy) {
if (distinct) {
if (isStruct) {
comparator.addEvaluatedSortCriteria(values, context);
// Occurrence field is used to identify the corrcet number of
// iterations
// required to implement the limit based on the presence or absence
// of distinct clause
occurrence = ((StructFields) resultSet).addFieldValues(values) ? 1 : 0;
} else {
comparator.addEvaluatedSortCriteria(values[0], context);
occurrence = resultSet.add(values[0]) ? 1 : 0;
}
} else {
if (isStruct) {
comparator.addEvaluatedSortCriteria(values, context);
occurrence = ((Bag) resultSet).addAndGetOccurence(values);
} else {
comparator.addEvaluatedSortCriteria(values[0], context);
occurrence = ((Bag) resultSet).addAndGetOccurence(values[0]);
}
}
} else {
if (isLinkedStructure) {
if (isStruct) {
StructImpl structImpl = new StructImpl((StructTypeImpl) elementType, values);
if (this.distinct) {
occurrence = resultSet.add(structImpl) ? 1 : 0;
} else {
occurrence = ((Bag) resultSet).addAndGetOccurence(structImpl);
}
} else {
if (this.distinct) {
occurrence = resultSet.add(values[0]) ? 1 : 0;
} else {
occurrence = ((Bag) resultSet).addAndGetOccurence(values[0]);
}
}
} else {
if (this.distinct) {
if (isStruct) {
occurrence = ((StructFields) resultSet).addFieldValues(values) ? 1 : 0;
} else {
occurrence = resultSet.add(values[0]) ? 1 : 0;
}
} else {
if (isStruct) {
occurrence = ((Bag) resultSet).addAndGetOccurence(values);
} else {
occurrence = ((Bag) resultSet).addAndGetOccurence(values[0]);
}
}
}
}
}
return occurrence;
}
private String generateProjectionName(CompiledValue projExpr, ExecutionContext context) {
String name = null;
if (projExpr instanceof RuntimeIterator) {
RuntimeIterator rIter = (RuntimeIterator) projExpr;
name = rIter.getDefinition();
int index = name.lastIndexOf('.');
if (index > 0) {
name = name.substring(index + 1);
} else if (name.charAt(0) == '/') {
index = name.lastIndexOf('/');
name = name.substring(index + 1);
} else {
name = rIter.getInternalId();
}
} else {
int type = projExpr.getType();
if (type == PATH) {
name = ((CompiledPath) projExpr).getTailID();
} else if (type == Identifier) {
name = ((CompiledID) projExpr).getId();
} else if (type == LITERAL) {
name = (((CompiledLiteral) projExpr)._obj).toString();
} else if (type == METHOD_INV) {
name = ((CompiledOperation) projExpr).getMethodName();
} else {
name = new StringBuilder("field$").append(context.nextFieldNum()).toString();
// name = projExpr.toString();
}
}
return name;
}
/**
* Optimized evaluate for CQ execution.
*/
public boolean evaluateCq(ExecutionContext context) throws FunctionDomainException,
TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
if (this.whereClause == null) {
return true;
}
context.newScope((Integer) context.cacheGet(scopeID));
context.pushExecCache((Integer) context.cacheGet(scopeID));
try {
CompiledIteratorDef iterDef = (CompiledIteratorDef) iterators.get(0);
RuntimeIterator rIter = iterDef.getRuntimeIterator(context);
context.bindIterator(rIter);
Collection coll;
{
Object evalResult = iterDef.getCollectionExpr().evaluate(context);
if (evalResult == null || evalResult == QueryService.UNDEFINED) {
return false;
}
coll = (Collection) evalResult;
}
if (coll.isEmpty()) {
return false;
}
if (this.whereClause.isDependentOnCurrentScope(context)) {
Iterator cIter = coll.iterator();
Object currObj = cIter.next();
rIter.setCurrent(currObj);
}
Object b = this.whereClause.evaluate(context);
if (b == null) {
return false;
} else if (b == QueryService.UNDEFINED) {
// add UNDEFINED to results only for NOT EQUALS queries
if (this.whereClause.getType() == COMPARISON) {
int operator = ((Filter) this.whereClause).getOperator();
return operator == TOK_NE || operator == TOK_NE_ALT;
} else {
return false;
}
} else {
return (Boolean) b;
}
} finally {
context.popExecCache();
context.popScope();
}
}
/*
* A special evaluation of limit for when limit needs to be evaluated before an execution context
* is created.
*
* It assumes the limit is either a CompiledBindArgument or a CompiledLiteral
*
*
*
*
*
*
*/
private Integer evaluateLimitValue(Object[] bindArguments) throws FunctionDomainException,
TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
Integer limitValue = -1;
if (this.limit != null) {
if (this.limit instanceof CompiledBindArgument) {
limitValue = (Integer) ((CompiledBindArgument) this.limit).evaluate(bindArguments);
} else {
// Assume limit is a compiled literal which does not need a context
limitValue = (Integer) this.limit.evaluate(null);
}
}
return limitValue;
}
protected static Integer evaluateLimitValue(ExecutionContext context, CompiledValue limit)
throws FunctionDomainException, TypeMismatchException, NameResolutionException,
QueryInvocationTargetException {
Integer limitValue = -1;
if (limit != null) {
limitValue = (Integer) limit.evaluate(context);
if (limitValue == null) {
// This is incase an object array was passed in but no param was set for the limit
limitValue = -1;
}
}
return limitValue;
}
private static class NullIteratorException extends Exception {
}
}