blob: 6d7769536d58814433674ee9795413f78d7e60ef [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.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.logging.log4j.Logger;
import org.apache.geode.cache.CacheClosedException;
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.NameResolutionException;
import org.apache.geode.cache.query.QueryExecutionTimeoutException;
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.TypeMismatchException;
import org.apache.geode.cache.query.internal.parse.OQLLexerTokenTypes;
import org.apache.geode.cache.query.internal.types.TypeUtils;
import org.apache.geode.cache.query.types.CollectionType;
import org.apache.geode.cache.query.types.MapType;
import org.apache.geode.cache.query.types.ObjectType;
import org.apache.geode.internal.logging.LogService;
import org.apache.geode.security.NotAuthorizedException;
public class CompiledIteratorDef extends AbstractCompiledValue {
private static final Logger logger = LogService.getLogger();
private String name;
private ObjectType elementType;
private CompiledValue collectionExpr;
/**
* Creates a new instance of CompiledIteratorDef name and type can be null
*/
public CompiledIteratorDef(String name, ObjectType elementType, CompiledValue collectionExpr) {
this.name = name;
this.elementType = elementType == null ? TypeUtils.OBJECT_TYPE : elementType;
this.collectionExpr = collectionExpr;
}
@Override
public List getChildren() {
return Collections.singletonList(collectionExpr);
}
/**
* Returns a RuntimeIterator (or null if evaluates to null or UNDEFINED); the collection expr is
* evaluated lazily after dependencies are known
*/
@Override
public Object evaluate(ExecutionContext context) throws FunctionDomainException,
TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
throw new UnsupportedOperationException(
"Not to be evaluated directly");
}
public RuntimeIterator getRuntimeIterator(ExecutionContext context)
throws TypeMismatchException, AmbiguousNameException, NameResolutionException {
RuntimeIterator rIter = null;
// check cached in context
rIter = (RuntimeIterator) context.cacheGet(this);
if (rIter != null) {
return rIter;
}
ObjectType type = this.elementType;
if (type.equals(TypeUtils.OBJECT_TYPE)) {
// check to see if there's a typecast for this collection
ObjectType typc = getCollectionElementTypeCast();
if (typc != null) {
type = typc;
} else {
// Try to determine better type
// Now only works for CompiledPaths
// Does not determine type of nested query
if (!(this.collectionExpr instanceof CompiledSelect)) {
type = computeElementType(context);
}
}
}
rIter = new RuntimeIterator(this, type);
// generate from clause should take care of bucket region substitution if
// necessary and then set the definition.
String fromClause = genFromClause(context);
rIter.setDefinition(fromClause);
/*
* If the type of RunTimeIterator is still ObjectType & if the RuneTimeIterator is independent
* of any iterator of the scopes less than or equal to its own scope, we can evaluate the
* collection via RuntimeIterator. This will initialize the Collection of RuntimeIterator ,
* which is OK. The code in RuntimeIterator will be rectified such that the ElementType of that
* RuntimeIterator is taken from the collection
*/
if (type.equals(TypeUtils.OBJECT_TYPE)
&& !this.isDependentOnAnyIteratorOfScopeLessThanItsOwn(context)) {
// The current Iterator definition is independent , so lets evaluate
// the collection
evaluateCollectionForIndependentIterator(context, rIter);
}
// cache in context
context.cachePut(this, rIter);
return rIter;
}
protected void evaluateCollectionForIndependentIterator(ExecutionContext context,
RuntimeIterator rIter)
throws RegionNotFoundException, TypeMismatchException {
try {
rIter.evaluateCollection(context);
} catch (QueryExecutionTimeoutException qet) {
throw qet;
} catch (RegionNotFoundException re) {
throw re;
} catch (NotAuthorizedException e) {
throw e;
} catch (QueryExecutionCanceledException e) {
throw e;
} catch (CacheClosedException e) {
throw e;
} catch (Exception e) {
if (logger.isDebugEnabled()) {
logger.debug("Exception while getting runtime iterator.", e);
}
throw new TypeMismatchException(
"Exception in evaluating the Collection Expression in getRuntimeIterator() even though the Collection is independent of any RuntimeIterator",
e);
}
}
ObjectType getCollectionElementTypeCast() throws TypeMismatchException {
ObjectType typ = this.collectionExpr.getTypecast();
if (typ != null) {
if (!(typ instanceof CollectionType)) {
throw new TypeMismatchException(
String.format("An iterator definition must be a collection type, not a %s",
typ));
}
if (typ instanceof MapType) { // we iterate over map entries
return ((MapType) typ).getEntryType();
}
return ((CollectionType) typ).getElementType();
}
return null;
}
/** Evaluate just the collectionExpr */
SelectResults evaluateCollection(ExecutionContext context) throws FunctionDomainException,
TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
return evaluateCollection(context, null);
}
/**
* Evaluate just the collectionExpr
*
* @param stopAtIter the RuntimeIterator associated with this iterator defn -- don't use this or
* any subsequent runtime iterators to evaluate.
*/
SelectResults evaluateCollection(ExecutionContext context, RuntimeIterator stopAtIter)
throws FunctionDomainException, TypeMismatchException, NameResolutionException,
QueryInvocationTargetException {
Object coll;
context.currentScope().setLimit(stopAtIter);
try {
coll = this.collectionExpr.evaluate(context);
} finally {
context.currentScope().setLimit(null);
}
// if we don't have an elementType and there's a typecast, apply the
// element type here
if (TypeUtils.OBJECT_TYPE.equals(this.elementType)) {
ObjectType elmTypc = getCollectionElementTypeCast();
if (elmTypc != null) {
this.elementType = elmTypc;
}
}
// PR bucketRegion substitution should have already happened
// at the expression evaluation level
return prepareIteratorDef(coll, this.elementType, context);
}
@Override
public int getType() {
return OQLLexerTokenTypes.ITERATOR_DEF;
}
// for test purposes...
public String getName() {
return this.name;
}
public ObjectType getElementType() {
return this.elementType;
}
public CompiledValue getCollectionExpr() {
return this.collectionExpr;
}
public void setCollectionExpr(CompiledValue collectionExpr) {
this.collectionExpr = collectionExpr;
}
/**
* TODO: We need to implement the belwo method of computeDependencies Once we come to implement
* changes for partitioned region querying, as in that case if first iterator itself is a Select
* Query , then ideally we cannot call that CompiledIteratorDef independent ( which will be the
* case at present). When we use this commented function we will also need to take care of
* correctly implementing the function isDependentOnCurrentScope etc functions.
*/
@Override
public Set computeDependencies(ExecutionContext context)
throws TypeMismatchException, AmbiguousNameException, NameResolutionException {
return context.addDependencies(this, this.collectionExpr.computeDependencies(context));
}
// TODO: this method is overly complex, duplicating logic already
// in query evaluation itself. It is overly complex ==> It will not be
// necessary once we have full typing support.
// There is a limitation here that it assumes that the collectionExpr is some
// path on either a RuntimeIterator or a Region.
private ObjectType computeElementType(ExecutionContext context) throws AmbiguousNameException {
ObjectType type = PathUtils.computeElementTypeOfExpression(context, this.collectionExpr);
// if it's a Map, we want the Entry type, not the value type
if (type.isMapType()) {
return ((MapType) type).getEntryType();
}
if (type.isCollectionType()) { // includes Regions and arrays
return ((CollectionType) type).getElementType();
}
return type;
}
/**
* Convert the given object to a SelectResults. Must be a collection of some sort. The obj passed
* in must be unmodified, but the resulting SelectResults may or may not be modifiable. Return
* null if obj is null or UNDEFINED.
*/
private SelectResults prepareIteratorDef(Object obj, ObjectType elementType,
ExecutionContext context) throws TypeMismatchException {
if (obj == null) {
return null;
}
if (obj == QueryService.UNDEFINED) {
return null;
}
if (obj instanceof SelectResults) {
// probably came from nested query or is a QRegion already from region
// path
SelectResults sr = (SelectResults) obj;
// override the elementType if not Object.class (does not apply to
// StructBags)
if (!elementType.equals(TypeUtils.OBJECT_TYPE)) {
sr.setElementType(elementType);
}
return sr;
}
if (obj instanceof Region) {
// this can happen if region passed in as parameter
QRegion qRegion = new QRegion((Region) obj, false, context);
if (!elementType.equals(TypeUtils.OBJECT_TYPE)) {
// override the valueConstraint, if any
qRegion.setElementType(elementType);
}
return qRegion;
}
// if this is a domain collection, it should be unmodifiable
// if obj is a Collection but not a SelectResults, it must be from the
// domain, otherwise it would be a SelectResults.
if (obj instanceof Collection) {
// do not lose ordering and duplicate information,
ResultsCollectionWrapper res = new ResultsCollectionWrapper(elementType, (Collection) obj);
res.setModifiable(false);
return res;
}
// Object[] is wrapped and considered a domain object so unmodifiable
if (obj instanceof Object[]) {
// the element type is specified in the array itself, unless we have
// something more specific
if (elementType.equals(TypeUtils.OBJECT_TYPE)) { // if we don't have
// constraint info
elementType = TypeUtils.getObjectType(obj.getClass().getComponentType());
}
// do not lose ordering and duplicate information,
ResultsCollectionWrapper res =
new ResultsCollectionWrapper(elementType, Arrays.asList((Object[]) obj));
res.setModifiable(false);
return res;
}
// TODO: primitive arrays?
if (obj instanceof Map) {
if (elementType.equals(TypeUtils.OBJECT_TYPE)) { // if we don't have more
// specific type info,
// use Map.Entry
elementType = TypeUtils.getObjectType(Map.Entry.class);
}
ResultsCollectionWrapper res =
new ResultsCollectionWrapper(elementType, ((Map) obj).entrySet());
res.setModifiable(false);
return res;
} else {
obj = new Object[] {obj};
// the element type is specified in the array itself, unless we have
// something more specific
if (elementType.equals(TypeUtils.OBJECT_TYPE)) { // if we don't have
// constraint info
elementType = TypeUtils.getObjectType(obj.getClass().getComponentType());
}
// do not lose ordering and duplicate information,
ResultsCollectionWrapper res =
new ResultsCollectionWrapper(elementType, Arrays.asList((Object[]) obj));
res.setModifiable(false);
return res;
}
}
String genFromClause(ExecutionContext context)
throws AmbiguousNameException, TypeMismatchException, NameResolutionException {
StringBuilder sbuff = new StringBuilder();
collectionExpr.generateCanonicalizedExpression(sbuff, context);
return sbuff.toString();
}
/**
* Checks if the iterator in question is dependent on any other RuntimeIterator of its own or
* lesser scope.
*/
boolean isDependentOnAnyIteratorOfScopeLessThanItsOwn(ExecutionContext context) {
// Get the list of all iterators on which the colelction expression
// is ultimately dependent on
// If dependent on self then also assume it to be dependent
boolean isDep = false;
// Get the list of all iterators on which the colelction expression
// is dependent on
Set dependencySet = context.getDependencySet(this, true);
Iterator itr = dependencySet.iterator();
int currScope = context.currentScope().getScopeID();// context.getScopeCount();
while (itr.hasNext()) {
RuntimeIterator ritr = (RuntimeIterator) itr.next();
if (ritr.getScopeID() <= currScope) {
isDep = true;
break;
}
}
return isDep;
}
}