blob: 9a83c1ce6adbf5c79546c77b7a00bfbc7dadbf3c [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.index;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import org.apache.logging.log4j.Logger;
import org.apache.geode.annotations.internal.MutableForTesting;
import org.apache.geode.cache.CacheException;
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.IndexStatistics;
import org.apache.geode.cache.query.IndexType;
import org.apache.geode.cache.query.NameResolutionException;
import org.apache.geode.cache.query.QueryInvocationTargetException;
import org.apache.geode.cache.query.QueryService;
import org.apache.geode.cache.query.SelectResults;
import org.apache.geode.cache.query.TypeMismatchException;
import org.apache.geode.cache.query.internal.CompiledBindArgument;
import org.apache.geode.cache.query.internal.CompiledIteratorDef;
import org.apache.geode.cache.query.internal.CompiledLiteral;
import org.apache.geode.cache.query.internal.CompiledPath;
import org.apache.geode.cache.query.internal.CompiledSortCriterion;
import org.apache.geode.cache.query.internal.CompiledValue;
import org.apache.geode.cache.query.internal.CqEntry;
import org.apache.geode.cache.query.internal.ExecutionContext;
import org.apache.geode.cache.query.internal.IndexInfo;
import org.apache.geode.cache.query.internal.QRegion;
import org.apache.geode.cache.query.internal.QueryMonitor;
import org.apache.geode.cache.query.internal.QueryObserver;
import org.apache.geode.cache.query.internal.QueryObserverHolder;
import org.apache.geode.cache.query.internal.QueryUtils;
import org.apache.geode.cache.query.internal.RuntimeIterator;
import org.apache.geode.cache.query.internal.StructImpl;
import org.apache.geode.cache.query.internal.Support;
import org.apache.geode.cache.query.internal.index.IndexManager.TestHook;
import org.apache.geode.cache.query.internal.index.IndexStore.IndexStoreEntry;
import org.apache.geode.cache.query.internal.index.MemoryIndexStore.MemoryIndexStoreEntry;
import org.apache.geode.cache.query.internal.parse.OQLLexerTokenTypes;
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.ObjectType;
import org.apache.geode.cache.query.types.StructType;
import org.apache.geode.internal.cache.InternalCache;
import org.apache.geode.internal.cache.LocalRegion;
import org.apache.geode.internal.cache.NonTXEntry;
import org.apache.geode.internal.cache.RegionEntry;
import org.apache.geode.internal.cache.RegionEntryContext;
import org.apache.geode.internal.cache.entries.VMThinRegionEntryHeap;
import org.apache.geode.internal.cache.persistence.query.CloseableIterator;
import org.apache.geode.internal.logging.LogService;
import org.apache.geode.pdx.internal.PdxString;
/**
* A CompactRangeIndex is a range index that has simple data structures to minimize its footprint,
* at the expense of doing extra work at index maintenance. It is selected as the index
* implementation when the indexed expression is a path expression and the from clause has only one
* iterator. This implies there is only one value in the index for each region entry.
*
* This index does not support the storage of projection attributes.
*
* Currently this implementation only supports an index on a region path.
*
* @since GemFire 6.0
*/
public class CompactRangeIndex extends AbstractIndex {
private static final Logger logger = LogService.getLogger();
@MutableForTesting
private static TestHook testHook;
protected ThreadLocal<OldKeyValuePair> oldKeyValue;
private IndexStore indexStore;
@MutableForTesting
static boolean TEST_ALWAYS_UPDATE_IN_PROGRESS = false;
public CompactRangeIndex(InternalCache cache, String indexName, Region region, String fromClause,
String indexedExpression, String projectionAttributes, String origFromClause,
String origIndexExpr, String[] definitions, IndexStatistics stats) {
super(cache, indexName, region, fromClause, indexedExpression, projectionAttributes,
origFromClause, origIndexExpr, definitions, stats);
if (IndexManager.IS_TEST_LDM) {
indexStore = new MapIndexStore(
((LocalRegion) region).getIndexMap(indexName, indexedExpression, origFromClause), region);
} else {
indexStore =
new MemoryIndexStore(region, internalIndexStats, (InternalCache) region.getCache());
}
}
public IndexStore getIndexStorage() {
return indexStore;
}
/**
* Get the index type
*
* @return the type of index
*/
@Override
public IndexType getType() {
return IndexType.FUNCTIONAL;
}
@Override
protected boolean isCompactRangeIndex() {
return true;
}
@Override
public void initializeIndex(boolean loadEntries) throws IMQException {
long startTime = System.nanoTime();
this.evaluator.initializeIndex(loadEntries);
this.internalIndexStats.incNumUpdates(((IMQEvaluator) this.evaluator).getTotalEntriesUpdated());
long endTime = System.nanoTime();
this.internalIndexStats.incUpdateTime(endTime - startTime);
}
@Override
void addMapping(RegionEntry entry) throws IMQException {
this.evaluator.evaluate(entry, true);
this.internalIndexStats.incNumUpdates();
}
/**
* @param opCode one of OTHER_OP, BEFORE_UPDATE_OP, AFTER_UPDATE_OP.
*/
@Override
void removeMapping(RegionEntry entry, int opCode) throws IMQException {
if (opCode == BEFORE_UPDATE_OP) {
// Either take key from reverse map OR evaluate it using IMQEvaluator.
if (!IndexManager.isObjectModificationInplace()) {
// It will always contain 1 element only, for this thread.
if (oldKeyValue == null) {
oldKeyValue = new ThreadLocal<OldKeyValuePair>();
}
oldKeyValue.set(new OldKeyValuePair());
this.evaluator.evaluate(entry, false);
}
} else if (opCode == REMOVE_DUE_TO_GII_TOMBSTONE_CLEANUP) {
// we know in this specific case, that a before op was called and stored oldKey/value
// we also know that a regular remove won't work due to the entry no longer being present
// We know the old key so let's just remove mapping from the old key
if (oldKeyValue != null) {
if (oldKeyValue.get() == null) {
return;
}
indexStore.removeMapping(oldKeyValue.get().getOldKey(), entry);
} else {
// rely on reverse map in the index store to figure out the real key
indexStore.removeMapping(IndexManager.NULL, entry);
}
} else if (opCode == CLEAN_UP_THREAD_LOCALS) {
if (oldKeyValue != null) {
oldKeyValue.remove();
}
} else {
// Need to reset the thread-local map as many puts and destroys might
// happen in same thread.
if (oldKeyValue != null) {
oldKeyValue.remove();
}
this.evaluator.evaluate(entry, false);
this.internalIndexStats.incNumUpdates();
}
}
void removeMapping(Object key, RegionEntry entry) throws IMQException {
indexStore.removeMapping(key, entry);
}
@Override
public boolean clear() {
return indexStore.clear();
}
@Override
public List queryEquijoinCondition(IndexProtocol indx, ExecutionContext context)
throws TypeMismatchException, FunctionDomainException, NameResolutionException,
QueryInvocationTargetException {
// get a read lock when doing a lookup
long start = updateIndexUseStats();
((AbstractIndex) indx).updateIndexUseStats();
List data = new ArrayList();
Iterator<IndexStoreEntry> outer = null;
Iterator inner = null;
try {
// We will iterate over each of the index Map to obtain the keys
outer = ((MemoryIndexStore) indexStore).getKeysIterator();
if (indx instanceof CompactRangeIndex) {
IndexStore indexStore = ((CompactRangeIndex) indx).getIndexStorage();
inner = ((MemoryIndexStore) indexStore).getKeysIterator();
} else {
inner = ((RangeIndex) indx).getValueToEntriesMap().entrySet().iterator();
}
IndexStoreEntry outerEntry = null;
Object innerEntry = null;
Object outerKey = null;
Object innerKey = null;
boolean incrementInner = true;
outer: while (outer.hasNext()) {
outerEntry = outer.next();
outerKey = outerEntry.getDeserializedKey();
// TODO: eliminate all labels
inner: while (!incrementInner || inner.hasNext()) {
if (incrementInner) {
innerEntry = inner.next();
if (innerEntry instanceof IndexStoreEntry) {
innerKey = ((IndexStoreEntry) innerEntry).getDeserializedKey();
} else {
innerKey = ((Map.Entry) innerEntry).getKey();
}
}
int compare = ((Comparable) outerKey).compareTo(innerKey);
if (compare == 0) {
Object innerValue = null;
CloseableIterator<IndexStoreEntry> iter = null;
try {
if (innerEntry instanceof IndexStoreEntry) {
innerValue = ((CompactRangeIndex) indx).getIndexStorage().get(outerKey);
} else {
innerValue = ((Map.Entry) innerEntry).getValue();
}
iter = indexStore.get(outerKey);
populateListForEquiJoin(data, iter, innerValue, context, innerKey);
} finally {
if (iter != null) {
iter.close();
}
if (innerValue != null && innerValue instanceof CloseableIterator) {
((CloseableIterator<IndexStoreEntry>) innerValue).close();
}
}
incrementInner = true;
continue outer;
} else if (compare < 0) {
// The outer key is smaller than the inner key. That means
// that we need
// to increment the outer loop without moving inner loop.
// incrementOuter = true;
incrementInner = false;
continue outer;
} else {
// The outer key is greater than inner key , so increment the
// inner loop without changing outer
incrementInner = true;
}
}
break;
}
return data;
} finally {
((AbstractIndex) indx).updateIndexUseEndStats(start);
updateIndexUseEndStats(start);
if (inner != null && indx instanceof CompactRangeIndex
&& inner instanceof CloseableIterator) {
((CloseableIterator<IndexStoreEntry>) inner).close();
}
}
}
/**
* This evaluates the left and right side of a EQUI-JOIN where condition for which this Index was
* used. Like, if condition is "p.ID = e.ID", {@link IndexInfo} will contain Left as p.ID, Right
* as e.ID and operator as TOK_EQ. This method will evaluate p.ID OR e.ID based on if it is inner
* or outer RegionEntry, and verify the p.ID = e.ID.
*
* This method is called only for Memory indexstore
*
* @return true if entry value and index value are consistent.
*/
protected boolean verifyInnerAndOuterEntryValues(IndexStoreEntry entry, ExecutionContext context,
IndexInfo indexInfo, Object keyVal) throws FunctionDomainException, TypeMismatchException,
NameResolutionException, QueryInvocationTargetException {
// Verify index key in value only for memory index store
CompactRangeIndex index = (CompactRangeIndex) indexInfo._getIndex();
RuntimeIterator runtimeItr = index.getRuntimeIteratorForThisIndex(context, indexInfo);
if (runtimeItr != null) {
runtimeItr.setCurrent(((MemoryIndexStoreEntry) entry).getDeserializedValue());
}
return evaluateEntry(indexInfo, context, keyVal);
}
@Override
public int getSizeEstimate(Object key, int operator, int matchLevel)
throws TypeMismatchException {
// Get approx size;
int size = 0;
if (key == null) {
key = IndexManager.NULL;
}
long start = updateIndexUseStats(false);
try {
switch (operator) {
case OQLLexerTokenTypes.TOK_EQ: {
key = TypeUtils.indexKeyFor(key);
key = getPdxStringForIndexedPdxKeys(key);
size = indexStore.size(key);
break;
}
case OQLLexerTokenTypes.TOK_NE_ALT:
case OQLLexerTokenTypes.TOK_NE:
size = this.region.size();
key = TypeUtils.indexKeyFor(key);
key = getPdxStringForIndexedPdxKeys(key);
size -= indexStore.size(key);
break;
case OQLLexerTokenTypes.TOK_LE:
case OQLLexerTokenTypes.TOK_LT:
if (matchLevel <= 0 && (key instanceof Number)) {
int totalSize = indexStore.size();
if (CompactRangeIndex.testHook != null) {
CompactRangeIndex.testHook.hook(1);
}
if (totalSize > 1) {
Number keyAsNum = (Number) key;
int x = 0;
IndexStoreEntry firstEntry = null;
CloseableIterator<IndexStoreEntry> iter1 = null;
CloseableIterator<IndexStoreEntry> iter2 = null;
try {
iter1 = indexStore.iterator(null);
if (iter1.hasNext()) {
firstEntry = iter1.next();
IndexStoreEntry lastEntry = null;
iter2 = indexStore.descendingIterator(null);
if (iter2.hasNext()) {
lastEntry = iter2.next();
}
if (firstEntry != null && lastEntry != null) {
Number first = (Number) firstEntry.getDeserializedKey();
Number last = (Number) lastEntry.getDeserializedKey();
if (first.doubleValue() != last.doubleValue()) {
// Shobhit: Now without ReadLoack on index we can end up with 0
// in denominator if the numbers are floating-point and
// truncated with conversion to long, and the first and last
// truncate to the same long, so safest calculation is to
// convert to doubles.
x = (int) (((keyAsNum.doubleValue() - first.doubleValue()) * totalSize)
/ (last.doubleValue() - first.doubleValue()));
}
}
if (x < 0) {
x = 0;
}
size = x;
}
} finally {
if (iter1 != null) {
iter1.close();
}
if (iter2 != null) {
iter1.close();
}
}
} else {
// not attempting to differentiate between LT & LE
size = indexStore.size(key) > 0 ? 1 : 0;
}
} else {
size = Integer.MAX_VALUE;
}
break;
case OQLLexerTokenTypes.TOK_GE:
case OQLLexerTokenTypes.TOK_GT:
if (matchLevel <= 0 && (key instanceof Number)) {
int totalSize = indexStore.size();
if (CompactRangeIndex.testHook != null) {
CompactRangeIndex.testHook.hook(2);
}
if (totalSize > 1) {
Number keyAsNum = (Number) key;
int x = 0;
IndexStoreEntry firstEntry = null;
CloseableIterator<IndexStoreEntry> iter1 = null;
CloseableIterator<IndexStoreEntry> iter2 = null;
try {
iter1 = indexStore.iterator(null);
if (iter1.hasNext()) {
firstEntry = iter1.next();
}
IndexStoreEntry lastEntry = null;
iter2 = indexStore.descendingIterator(null);
if (iter2.hasNext()) {
lastEntry = iter2.next();
}
if (firstEntry != null && lastEntry != null) {
Number first = (Number) firstEntry.getDeserializedKey();
Number last = (Number) lastEntry.getDeserializedKey();
if (first.doubleValue() != last.doubleValue()) {
// Shobhit: Now without ReadLoack on index we can end up with 0
// in denominator if the numbers are floating-point and
// truncated with conversion to long, and the first and last
// truncate to the same long, so safest calculation is to
// convert to doubles.
x = (int) (((last.doubleValue() - keyAsNum.doubleValue()) * totalSize)
/ (last.doubleValue() - first.doubleValue()));
}
}
if (x < 0) {
x = 0;
}
size = x;
} finally {
if (iter1 != null) {
iter1.close();
}
}
} else {
// not attempting to differentiate between GT & GE
size = indexStore.size(key) > 0 ? 1 : 0;
}
} else {
size = Integer.MAX_VALUE;
}
break;
}
} catch (ClassCastException e) {
// no values will match in this index because the key types are not the same
// This means that there will be 0 results and it will be fast to use this index
// because it has filtered everything out
return 0;
} catch (EntryDestroyedException ignore) {
return Integer.MAX_VALUE;
} finally {
updateIndexUseEndStats(start, false);
}
return size;
}
/** Method called while appropriate lock held on index */
private void lockedQueryPrivate(Object key, int operator, Collection results,
CompiledValue iterOps, RuntimeIterator runtimeItr, ExecutionContext context, Set keysToRemove,
List projAttrib, SelectResults intermediateResults, boolean isIntersection)
throws TypeMismatchException, FunctionDomainException, NameResolutionException,
QueryInvocationTargetException {
if (keysToRemove == null) {
keysToRemove = new HashSet(0);
}
int limit = -1;
Boolean applyLimit = (Boolean) context.cacheGet(CompiledValue.CAN_APPLY_LIMIT_AT_INDEX);
if (applyLimit != null && applyLimit) {
limit = (Integer) context.cacheGet(CompiledValue.RESULT_LIMIT);
}
Boolean orderByClause = (Boolean) context.cacheGet(CompiledValue.CAN_APPLY_ORDER_BY_AT_INDEX);
boolean applyOrderBy = false;
List orderByAttrs = null;
if (orderByClause != null && orderByClause) {
orderByAttrs = (List) context.cacheGet(CompiledValue.ORDERBY_ATTRIB);
CompiledSortCriterion csc = (CompiledSortCriterion) orderByAttrs.get(0);
applyOrderBy = true;
}
if (isEmpty()) {
return;
}
key = getPdxStringForIndexedPdxKeys(key);
evaluate(key, operator, results, iterOps, runtimeItr, context, keysToRemove, projAttrib,
intermediateResults, isIntersection, limit, applyOrderBy, orderByAttrs);
}
/** Method called while appropriate lock held on index */
@Override
void lockedQuery(Object lowerBoundKey, int lowerBoundOperator, Object upperBoundKey,
int upperBoundOperator, Collection results, Set keysToRemove, ExecutionContext context)
throws TypeMismatchException, FunctionDomainException, NameResolutionException,
QueryInvocationTargetException {
lowerBoundKey = TypeUtils.indexKeyFor(lowerBoundKey);
upperBoundKey = TypeUtils.indexKeyFor(upperBoundKey);
boolean lowerBoundInclusive = lowerBoundOperator == OQLLexerTokenTypes.TOK_GE;
boolean upperBoundInclusive = upperBoundOperator == OQLLexerTokenTypes.TOK_LE;
// LowerBound Key inclusive , Upper bound key exclusive
int limit = -1;
Boolean applyLimit = (Boolean) context.cacheGet(CompiledValue.CAN_APPLY_LIMIT_AT_INDEX);
if (applyLimit != null && applyLimit) {
limit = (Integer) context.cacheGet(CompiledValue.RESULT_LIMIT);
}
Boolean orderByClause = (Boolean) context.cacheGet(CompiledValue.CAN_APPLY_ORDER_BY_AT_INDEX);
List orderByAttrs;
boolean asc = true;
boolean multiColOrderBy = false;
if (orderByClause != null && orderByClause) {
orderByAttrs = (List) context.cacheGet(CompiledValue.ORDERBY_ATTRIB);
CompiledSortCriterion csc = (CompiledSortCriterion) orderByAttrs.get(0);
asc = !csc.getCriterion();
multiColOrderBy = orderByAttrs.size() > 1;
}
// return if the index map is still empty at this stage
if (isEmpty()) {
return;
}
lowerBoundKey = getPdxStringForIndexedPdxKeys(lowerBoundKey);
upperBoundKey = getPdxStringForIndexedPdxKeys(upperBoundKey);
if (keysToRemove == null) {
keysToRemove = new HashSet();
}
CloseableIterator<IndexStoreEntry> iterator = null;
try {
if (asc) {
iterator = indexStore.iterator(lowerBoundKey, lowerBoundInclusive, upperBoundKey,
upperBoundInclusive, keysToRemove);
} else {
iterator = indexStore.descendingIterator(lowerBoundKey, lowerBoundInclusive, upperBoundKey,
upperBoundInclusive, keysToRemove);
}
addToResultsFromEntries(lowerBoundKey, upperBoundKey, lowerBoundOperator, upperBoundOperator,
iterator, results, null, null, context, null, null, true, multiColOrderBy ? -1 : limit);
} finally {
if (iterator != null) {
iterator.close();
}
}
}
private void evaluate(Object key, int operator, Collection results, CompiledValue iterOps,
RuntimeIterator runtimeItr, ExecutionContext context, Set keysToRemove, List projAttrib,
SelectResults intermediateResults, boolean isIntersection, int limit, boolean applyOrderBy,
List orderByAttribs) throws TypeMismatchException, FunctionDomainException,
NameResolutionException, QueryInvocationTargetException {
boolean multiColOrderBy = false;
if (keysToRemove == null) {
keysToRemove = new HashSet(0);
}
key = TypeUtils.indexKeyFor(key);
if (key == null) {
key = IndexManager.NULL;
}
boolean asc = true;
if (applyOrderBy) {
CompiledSortCriterion csc = (CompiledSortCriterion) orderByAttribs.get(0);
asc = !csc.getCriterion();
multiColOrderBy = orderByAttribs.size() > 1;
}
CloseableIterator<IndexStoreEntry> iterator = null;
try {
switch (operator) {
case OQLLexerTokenTypes.TOK_EQ:
assert keysToRemove.isEmpty();
iterator = indexStore.get(key);
addToResultsFromEntries(key, operator, iterator, results, iterOps, runtimeItr, context,
projAttrib, intermediateResults, isIntersection, multiColOrderBy ? -1 : limit);
break;
case OQLLexerTokenTypes.TOK_LT: {
if (asc) {
iterator = indexStore.iterator(null, true, key, false, keysToRemove);
} else {
iterator = indexStore.descendingIterator(null, true, key, false, keysToRemove);
}
addToResultsFromEntries(key, operator, iterator, results, iterOps, runtimeItr, context,
projAttrib, intermediateResults, isIntersection, multiColOrderBy ? -1 : limit);
}
break;
case OQLLexerTokenTypes.TOK_LE: {
if (asc) {
iterator = indexStore.iterator(null, true, key, true, keysToRemove);
} else {
iterator = indexStore.descendingIterator(null, true, key, true, keysToRemove);
}
addToResultsFromEntries(key, operator, iterator, results, iterOps, runtimeItr, context,
projAttrib, intermediateResults, isIntersection, multiColOrderBy ? -1 : limit);
}
break;
case OQLLexerTokenTypes.TOK_GT: {
if (asc) {
iterator = indexStore.iterator(key, false, keysToRemove);
} else {
iterator = indexStore.descendingIterator(key, false, keysToRemove);
}
addToResultsFromEntries(key, operator, iterator, results, iterOps, runtimeItr, context,
projAttrib, intermediateResults, isIntersection, multiColOrderBy ? -1 : limit);
}
break;
case OQLLexerTokenTypes.TOK_GE: {
if (asc) {
iterator = indexStore.iterator(key, true, keysToRemove);
} else {
iterator = indexStore.descendingIterator(key, true, keysToRemove);
}
addToResultsFromEntries(key, operator, iterator, results, iterOps, runtimeItr, context,
projAttrib, intermediateResults, isIntersection, multiColOrderBy ? -1 : limit);
}
break;
case OQLLexerTokenTypes.TOK_NE_ALT:
case OQLLexerTokenTypes.TOK_NE: {
keysToRemove.add(key);
if (asc) {
iterator = indexStore.iterator(keysToRemove);
} else {
iterator = indexStore.descendingIterator(keysToRemove);
}
addToResultsFromEntries(key, operator, iterator, results, iterOps, runtimeItr, context,
projAttrib, intermediateResults, isIntersection, multiColOrderBy ? -1 : limit);
// If the key is not null, then add the nulls to the results as this is a not equals query
if (!IndexManager.NULL.equals(key)) {
// we pass in the operator TOK_EQ because we want to add results where the key is equal
// to NULL
addToResultsFromEntries(IndexManager.NULL, OQLLexerTokenTypes.TOK_EQ,
indexStore.get(IndexManager.NULL), results, iterOps, runtimeItr, context,
projAttrib, intermediateResults, isIntersection, multiColOrderBy ? -1 : limit);
}
// If the key is not undefined, then add the undefineds to the results as this is a not
// equals query
if (!QueryService.UNDEFINED.equals(key)) {
// we pass in the operator TOK_EQ because we want to add results where the key is equal
// to UNDEFINED
addToResultsFromEntries(QueryService.UNDEFINED, OQLLexerTokenTypes.TOK_EQ,
indexStore.get(QueryService.UNDEFINED), results, iterOps, runtimeItr, context,
projAttrib, intermediateResults, isIntersection, multiColOrderBy ? -1 : limit);
}
}
break;
default:
throw new AssertionError("Operator = " + operator);
} // end switch
} catch (ClassCastException ex) {
if (operator == OQLLexerTokenTypes.TOK_EQ) { // result is empty set
return;
} else if (operator == OQLLexerTokenTypes.TOK_NE
|| operator == OQLLexerTokenTypes.TOK_NE_ALT) { // put all in result
keysToRemove.add(key);
try {
if (asc) {
iterator = indexStore.iterator(keysToRemove);
} else {
iterator = indexStore.descendingIterator(keysToRemove);
}
addToResultsFromEntries(key, OQLLexerTokenTypes.TOK_NE, iterator, results, iterOps,
runtimeItr, context, projAttrib, intermediateResults, isIntersection,
multiColOrderBy ? -1 : limit);
} finally {
if (iterator != null) {
iterator.close();
}
}
} else { // otherwise throw exception
throw new TypeMismatchException("", ex);
}
} finally {
if (iterator != null) {
iterator.close();
}
}
}
@Override
void instantiateEvaluator(IndexCreationHelper indexCreationHelper) {
this.evaluator = new IMQEvaluator(indexCreationHelper);
}
// Only used by CompactMapRangeIndex. This is due to the way the index initialization happens
// first we use the IMQEvaluator for CompactMapRangeIndex
// Each index in CMRI is a CRI that has the CRI.IMQ and not AbstractIndex.IMQ
// So instead we create create the evaluator
// because we are not doing index init as usual (each value is just put directly?)
// we must set the result type to match
void instantiateEvaluator(IndexCreationHelper ich, ObjectType objectType) {
instantiateEvaluator(ich);
((IMQEvaluator) this.evaluator).indexResultSetType = objectType;
}
@Override
public ObjectType getResultSetType() {
return this.evaluator.getIndexResultSetType();
}
/*
*
* @param lowerBoundKey the index key to match on
*
* @param lowerBoundOperator the operator to use to determine a match
*/
private void addToResultsFromEntries(Object lowerBoundKey, int lowerBoundOperator,
CloseableIterator<IndexStoreEntry> entriesIter, Collection result, CompiledValue iterOps,
RuntimeIterator runtimeItr, ExecutionContext context, List projAttrib,
SelectResults intermediateResults, boolean isIntersection, int limit)
throws FunctionDomainException, TypeMismatchException, NameResolutionException,
QueryInvocationTargetException {
addToResultsFromEntries(lowerBoundKey, null, lowerBoundOperator, -1, entriesIter, result,
iterOps, runtimeItr, context, projAttrib, intermediateResults, isIntersection, limit);
}
/*
*
* @param lowerBoundKey the index key to match on for a lower bound on a ranged query, otherwise
* the key to match on
*
* @param upperBoundKey the index key to match on for an upper bound on a ranged query, otherwise
* null
*
* @param lowerBoundOperator the operator to use to determine a match against the lower bound
*
* @param upperBoundOperator the operator to use to determine a match against the upper bound
*/
private void addToResultsFromEntries(Object lowerBoundKey, Object upperBoundKey,
int lowerBoundOperator, int upperBoundOperator,
CloseableIterator<IndexStoreEntry> entriesIter, Collection result, CompiledValue iterOps,
RuntimeIterator runtimeItr, ExecutionContext context, List projAttrib,
SelectResults intermediateResults, boolean isIntersection, int limit)
throws FunctionDomainException, TypeMismatchException, NameResolutionException,
QueryInvocationTargetException {
QueryObserver observer = QueryObserverHolder.getInstance();
boolean limitApplied = false;
if (entriesIter == null || (limitApplied = verifyLimit(result, limit))) {
if (limitApplied) {
if (observer != null) {
observer.limitAppliedAtIndexLevel(this, limit, result);
}
}
return;
}
Set seenKey = null;
if (IndexManager.IS_TEST_EXPANSION) {
seenKey = new HashSet();
}
while (entriesIter.hasNext()) {
try {
// Check if query execution on this thread is canceled.
QueryMonitor.throwExceptionIfQueryOnCurrentThreadIsCanceled();
if (IndexManager.testHook != null) {
if (this.region.getCache().getLogger().fineEnabled()) {
this.region.getCache().getLogger()
.fine("IndexManager TestHook is set in addToResultsFromEntries.");
}
IndexManager.testHook.hook(11);
}
IndexStoreEntry indexEntry = null;
try {
indexEntry = entriesIter.next();
} catch (NoSuchElementException ignore) {
// We are done with all the elements in array.
// Continue from while.
continue;
}
Object value = indexEntry.getDeserializedValue();
if (IndexManager.IS_TEST_EXPANSION) {
Object rk = indexEntry.getDeserializedRegionKey();
if (seenKey.contains(rk)) {
continue;
}
seenKey.add(rk);
List expandedResults = expandValue(context, lowerBoundKey, upperBoundKey,
lowerBoundOperator, upperBoundOperator, value);
Iterator iterator = ((Collection) expandedResults).iterator();
while (iterator.hasNext()) {
value = iterator.next();
if (value != null) {
boolean ok = true;
if (runtimeItr != null) {
runtimeItr.setCurrent(value);
}
if (ok && runtimeItr != null && iterOps != null) {
ok = QueryUtils.applyCondition(iterOps, context);
}
if (ok) {
applyCqOrProjection(projAttrib, context, result, value, intermediateResults,
isIntersection, indexEntry.getDeserializedRegionKey());
if (verifyLimit(result, limit)) {
observer.limitAppliedAtIndexLevel(this, limit, result);
return;
}
}
}
}
} else {
if (value != null) {
boolean ok = true;
if (indexEntry.isUpdateInProgress() || TEST_ALWAYS_UPDATE_IN_PROGRESS) {
IndexInfo indexInfo = (IndexInfo) context.cacheGet(CompiledValue.INDEX_INFO);
if (runtimeItr == null) {
runtimeItr = getRuntimeIteratorForThisIndex(context, indexInfo);
if (runtimeItr == null) {
// could not match index with iterator
throw new QueryInvocationTargetException(
"Query alias's must be used consistently");
}
}
runtimeItr.setCurrent(value);
// Verify index key in region entry value.
ok = evaluateEntry((IndexInfo) indexInfo, context, null);
}
if (runtimeItr != null) {
runtimeItr.setCurrent(value);
}
if (ok && runtimeItr != null && iterOps != null) {
ok = QueryUtils.applyCondition(iterOps, context);
}
if (ok) {
if (context != null && context.isCqQueryContext()) {
result.add(new CqEntry(indexEntry.getDeserializedRegionKey(), value));
} else {
if (IndexManager.testHook != null) {
IndexManager.testHook.hook(200);
}
applyProjection(projAttrib, context, result, value, intermediateResults,
isIntersection);
}
if (verifyLimit(result, limit)) {
observer.limitAppliedAtIndexLevel(this, limit, result);
return;
}
}
}
}
} catch (ClassCastException | EntryDestroyedException ignore) {
// ignore it
}
}
}
public List expandValue(ExecutionContext context, Object lowerBoundKey, Object upperBoundKey,
int lowerBoundOperator, int upperBoundOperator, Object value) {
try {
List expandedResults = new ArrayList();
this.evaluator.expansion(expandedResults, lowerBoundKey, upperBoundKey, lowerBoundOperator,
upperBoundOperator, value);
return expandedResults;
} catch (IMQException e) {
// TODO: never throw an anonymous inner class
throw new CacheException(e) {};
}
}
/**
* This evaluates the left and right side of a where condition for which this Index was used.
* Like, if condition is "ID > 1", {@link IndexInfo} will contain Left as ID, Right as '1' and
* operator as TOK_GT. This method will evaluate ID from region entry value and verify the ID > 1.
*
* Note: IndexInfo is created for each query separately based on the condition being evaluated
* using the Index.
*
* @return true if RegionEntry value satisfies the where condition (contained in IndexInfo).
*/
protected boolean evaluateEntry(IndexInfo indexInfo, ExecutionContext context, Object keyVal)
throws FunctionDomainException, TypeMismatchException, NameResolutionException,
QueryInvocationTargetException {
CompiledValue path = ((IndexInfo) indexInfo)._path();
Object left = path.evaluate(context);
CompiledValue key = ((IndexInfo) indexInfo)._key();
Object right = null;
// For CompiledUndefined indexInfo has null key.
if (keyVal == null && key == null) {
if (left == QueryService.UNDEFINED) {
return true;
} else {
return false;
}
}
if (key != null) {
right = key.evaluate(context);
// This next check is for map queries with In Clause, in those cases the reevaluation creates
// a tuple. In other cases it does not
if (null != right && indexInfo._getIndex() instanceof CompactMapRangeIndex
&& right instanceof Object[]) {
right = ((Object[]) right)[0];
}
} else {
right = keyVal;
}
int operator = indexInfo._operator();
if (left == null && right == null) {
return Boolean.TRUE;
} else {
if (left instanceof PdxString) {
if (right instanceof String) {
switch (key.getType()) {
case CompiledValue.LITERAL:
right = ((CompiledLiteral) key).getSavedPdxString();
break;
case OQLLexerTokenTypes.QUERY_PARAM:
right = ((CompiledBindArgument) key).getSavedPdxString(context);
break;
case CompiledValue.FUNCTION:
case CompiledValue.PATH:
right = new PdxString((String) right);
}
}
}
Object result = TypeUtils.compare(left, right, operator);
// result is Undefined if either left or right is Undefined or
// either of them is null and operator is other than == or !=
if (result == QueryService.UNDEFINED) {
// Undefined is added to results for != conditions only
if (operator != OQLLexerTokenTypes.TOK_NE || operator != OQLLexerTokenTypes.TOK_NE_ALT) {
return Boolean.TRUE;
} else {
return Boolean.FALSE;
}
} else {
return (Boolean) result;
}
}
}
@Override
void recreateIndexData() throws IMQException {
indexStore.clear();
int numKeys = (int) this.internalIndexStats.getNumberOfKeys();
if (numKeys > 0) {
this.internalIndexStats.incNumKeys(-numKeys);
}
int numValues = (int) this.internalIndexStats.getNumberOfValues();
if (numValues > 0) {
this.internalIndexStats.incNumValues(-numValues);
}
int updates = (int) this.internalIndexStats.getNumUpdates();
if (updates > 0) {
this.internalIndexStats.incNumUpdates(updates);
}
this.initializeIndex(true);
}
public String dump() {
return this.indexStore.printAll();
}
@Override
protected InternalIndexStatistics createStats(String indexName) {
return new RangeIndexStatistics(indexName);
}
class RangeIndexStatistics extends InternalIndexStatistics {
private IndexStats vsdStats;
public RangeIndexStatistics(String indexName) {
this.vsdStats = new IndexStats(getRegion().getCache().getDistributedSystem(), indexName);
}
/**
* Return the total number of times this index has been updated
*/
@Override
public long getNumUpdates() {
return this.vsdStats.getNumUpdates();
}
@Override
public void incNumValues(int delta) {
this.vsdStats.incNumValues(delta);
}
@Override
public void incNumUpdates() {
this.vsdStats.incNumUpdates();
}
@Override
public void incNumUpdates(int delta) {
this.vsdStats.incNumUpdates(delta);
}
@Override
public void updateNumKeys(long numKeys) {
this.vsdStats.updateNumKeys(numKeys);
}
@Override
public void incNumKeys(long numKeys) {
this.vsdStats.incNumKeys(numKeys);
}
@Override
public void incUpdateTime(long delta) {
this.vsdStats.incUpdateTime(delta);
}
@Override
public void incUpdatesInProgress(int delta) {
this.vsdStats.incUpdatesInProgress(delta);
}
@Override
public void incNumUses() {
this.vsdStats.incNumUses();
}
@Override
public void incUseTime(long delta) {
this.vsdStats.incUseTime(delta);
}
@Override
public void incUsesInProgress(int delta) {
this.vsdStats.incUsesInProgress(delta);
}
@Override
public void incReadLockCount(int delta) {
this.vsdStats.incReadLockCount(delta);
}
/**
* Returns the total amount of time (in nanoseconds) spent updating this index.
*/
@Override
public long getTotalUpdateTime() {
return this.vsdStats.getTotalUpdateTime();
}
/**
* Returns the total number of times this index has been accessed by a query.
*/
@Override
public long getTotalUses() {
return this.vsdStats.getTotalUses();
}
/**
* Returns the number of keys in this index.
*/
@Override
public long getNumberOfKeys() {
return this.vsdStats.getNumberOfKeys();
}
/**
* Returns the number of values in this index.
*/
@Override
public long getNumberOfValues() {
return this.vsdStats.getNumberOfValues();
}
/**
* Return the number of values for the specified key in this index.
*/
@Override
public long getNumberOfValues(Object key) {
return indexStore.size(key);
}
/**
* Return the number of read locks taken on this index
*/
@Override
public int getReadLockCount() {
return this.vsdStats.getReadLockCount();
}
@Override
public void close() {
this.vsdStats.close();
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("No Keys = ").append(getNumberOfKeys()).append("\n");
sb.append("No Values = ").append(getNumberOfValues()).append("\n");
sb.append("No Uses = ").append(getTotalUses()).append("\n");
sb.append("No Updates = ").append(getNumUpdates()).append("\n");
sb.append("Total Update time = ").append(getTotalUpdateTime()).append("\n");
return sb.toString();
}
}
class IMQEvaluator implements IndexedExpressionEvaluator {
private InternalCache cache;
private List fromIterators = null;
private CompiledValue indexedExpr = null;
private final String[] canonicalIterNames;
private ObjectType indexResultSetType = null;
private Region rgn = null;
private Map dependencyGraph = null;
/*
* The boolean if true indicates that the 0th iterator is on entries . If the 0th iterator is on
* collection of Region.Entry objects, then the RegionEntry object used in Index data objects is
* obtained directly from its corresponding Region.Entry object. However if the 0th iterator is
* not on entries then the boolean is false. In this case the additional projection attribute
* gives us the original value of the iterator while the Region.Entry object is obtained from
* 0th iterator. It is possible to have index being created on a Region Entry itself , instead
* of a Region. A Map operator( Compiled Index Operator) used with Region enables, us to create
* such indexes. In such case the 0th iterator, even if it represents a collection of Objects
* which are not Region.Entry objects, still the boolean remains true, as the Entry object can
* be easily obtained from the 0th iterator. In this case, the additional projection attribute s
* not null as it is used to evaluate the Entry object from the 0th iterator.
*/
private boolean isFirstItrOnEntry = false;
// List of modified iterators, not null only when the boolean
// isFirstItrOnEntry is false.
private List indexInitIterators = null;
// The additional Projection attribute representing the value of the
// original 0th iterator. If the isFirstItrOnEntry is false, then it is not
// null. However if the isFirstItrOnEntry is true and this attribute is not
// null, this indicates that the 0th iterator is derived using an individual
// entry thru Map operator on the Region.
private CompiledValue additionalProj = null;
// This is not null iff the boolean isFirstItrOnEntry is false.
private CompiledValue modifiedIndexExpr = null;
private ObjectType addnlProjType = null;
private int initEntriesUpdated = 0;
private boolean hasInitOccurredOnce = false;
private boolean hasIndxUpdateOccurredOnce = false;
private ExecutionContext initContext = null;
private int iteratorSize = -1;
/** Creates a new instance of IMQEvaluator */
IMQEvaluator(IndexCreationHelper helper) {
this.cache = helper.getCache();
this.fromIterators = helper.getIterators();
this.indexedExpr = helper.getCompiledIndexedExpression();
this.canonicalIterNames = ((FunctionalIndexCreationHelper) helper).canonicalizedIteratorNames;
this.rgn = helper.getRegion();
// The modified iterators for optmizing Index cxreation
isFirstItrOnEntry = ((FunctionalIndexCreationHelper) helper).isFirstIteratorRegionEntry;
additionalProj = ((FunctionalIndexCreationHelper) helper).additionalProj;
Object params1[] = {new QRegion(rgn, false)};
initContext = new ExecutionContext(params1, cache);
if (isFirstItrOnEntry) {
this.indexInitIterators = this.fromIterators;
} else {
this.indexInitIterators = ((FunctionalIndexCreationHelper) helper).indexInitIterators;
modifiedIndexExpr = ((FunctionalIndexCreationHelper) helper).modifiedIndexExpr;
addnlProjType = ((FunctionalIndexCreationHelper) helper).addnlProjType;
}
this.iteratorSize = this.indexInitIterators.size();
if (this.additionalProj instanceof CompiledPath) {
String tailId = ((CompiledPath) this.additionalProj).getTailID();
if (tailId.equals("key")) {
// index on keys
indexOnRegionKeys = true;
indexStore.setIndexOnRegionKeys(true);
} else if (!isFirstItrOnEntry) {
// its not entries, its on value.
indexOnValues = true;
indexStore.setIndexOnValues(true);
}
}
}
@Override
public String getIndexedExpression() {
return CompactRangeIndex.this.getCanonicalizedIndexedExpression();
}
@Override
public String getProjectionAttributes() {
return CompactRangeIndex.this.getCanonicalizedProjectionAttributes();
}
@Override
public String getFromClause() {
return CompactRangeIndex.this.getCanonicalizedFromClause();
}
@Override
public void expansion(List expandedResults, Object lowerBoundKey, Object upperBoundKey,
int lowerBoundOperator, int upperBoundOperator, Object value) throws IMQException {
try {
ExecutionContext expansionContext = createExecutionContext(value);
List iterators = expansionContext.getCurrentIterators();
RuntimeIterator iter = (RuntimeIterator) iterators.get(0);
iter.setCurrent(value);
// first iter level is region entries, we can ignore as we already broke it down in the
// index
doNestedExpansion(1, expansionContext, expandedResults, lowerBoundKey, upperBoundKey,
lowerBoundOperator, upperBoundOperator, value);
} catch (Exception e) {
throw new IMQException(e);
}
}
private void doNestedExpansion(int level, ExecutionContext expansionContext,
List expandedResults, Object lowerBoundKey, Object upperBoundKey, int lowerBoundOperator,
int upperBoundOperator, Object value)
throws TypeMismatchException, AmbiguousNameException, FunctionDomainException,
NameResolutionException, QueryInvocationTargetException, IMQException {
List iterList = expansionContext.getCurrentIterators();
int iteratorSize = iterList.size();
if (level == iteratorSize) {
expand(expansionContext, expandedResults, lowerBoundKey, upperBoundKey, lowerBoundOperator,
upperBoundOperator, value);
} else {
RuntimeIterator rIter = (RuntimeIterator) iterList.get(level);
Collection c = rIter.evaluateCollection(expansionContext);
if (c == null)
return;
Iterator cIter = c.iterator();
while (cIter.hasNext()) {
rIter.setCurrent(cIter.next());
doNestedExpansion(level + 1, expansionContext, expandedResults, lowerBoundKey,
upperBoundKey, lowerBoundOperator, upperBoundOperator, value);
}
}
}
/**
* @param upperBoundKey if null, we do not do an upperbound check (may need to change this if we
* ever use null in a range query)
*/
public void expand(ExecutionContext expansionContext, List expandedResults,
Object lowerBoundKey, Object upperBoundKey, int lowerBoundOperator, int upperBoundOperator,
Object value) throws IMQException {
try {
RuntimeIterator runtimeItr = getRuntimeIteratorForThisIndex(expansionContext);
if (runtimeItr != null) {
runtimeItr.setCurrent(value);
}
Object tupleIndexKey = indexedExpr.evaluate(expansionContext);
tupleIndexKey = getPdxStringForIndexedPdxKeys(tupleIndexKey);
Object compResult;
// Check upper bound
if (upperBoundKey != null) {
compResult = TypeUtils.compare(tupleIndexKey, upperBoundKey, upperBoundOperator);
if (compResult instanceof Boolean) {
Boolean ok = (Boolean) compResult;
if (!ok) {
return;
}
}
}
if (tupleIndexKey instanceof Map) {
if (lowerBoundOperator == OQLLexerTokenTypes.TOK_EQ) {
if (!((Map) tupleIndexKey).containsKey(lowerBoundKey)) {
return;
}
} else if (lowerBoundOperator == OQLLexerTokenTypes.TOK_NE) {
if (((Map) tupleIndexKey).containsKey(lowerBoundKey)) {
return;
}
}
} else {
// Check lower bound
compResult = TypeUtils.compare(tupleIndexKey, lowerBoundKey, lowerBoundOperator);
if (compResult instanceof Boolean) {
Boolean ok = (Boolean) compResult;
if (!ok) {
return;
}
}
}
List currentRuntimeIters = expansionContext.getCurrentIterators();
int iteratorSize = currentRuntimeIters.size();
Object indxResultSet = null;
// if the resultSetType is of structType, we need to create tuples
// this is due to the way the resultsSets are being created
boolean structType = (indexResultSetType instanceof StructType);
if (iteratorSize == 1 && !structType) {
RuntimeIterator iter = (RuntimeIterator) currentRuntimeIters.get(0);
iter.setCurrent(value);
indxResultSet = iter.evaluate(expansionContext);
indxResultSet = value;
} else {
Object tuple[] = new Object[iteratorSize];
tuple[0] = value;
if (iteratorSize > 1) {
for (int i = 1; i < iteratorSize; i++) {
RuntimeIterator iter = (RuntimeIterator) currentRuntimeIters.get(i);
tuple[i] = iter.evaluate(expansionContext);
}
Support.Assert(this.indexResultSetType instanceof StructTypeImpl,
"The Index ResultType should have been an instance of StructTypeImpl rather than ObjectTypeImpl. The indxeResultType is "
+ this.indexResultSetType);
}
indxResultSet = new StructImpl((StructTypeImpl) this.indexResultSetType, tuple);
}
expandedResults.add(indxResultSet);
} catch (Exception e) {
throw new IMQException(e);
}
}
private ExecutionContext createExecutionContext(Object value) {
DummyQRegion dQRegion = new DummyQRegion(rgn);
dQRegion.setEntry(
VMThinRegionEntryHeap.getEntryFactory().createEntry((RegionEntryContext) rgn, 0, value));
Object params[] = {dQRegion};
ExecutionContext context = new ExecutionContext(params, this.cache);
context.newScope(IndexCreationHelper.INDEX_QUERY_SCOPE_ID);
try {
if (this.dependencyGraph != null) {
context.setDependencyGraph(dependencyGraph);
}
for (int i = 0; i < this.iteratorSize; i++) {
CompiledIteratorDef iterDef = (CompiledIteratorDef) fromIterators.get(i);
if (this.dependencyGraph == null) {
iterDef.computeDependencies(context);
}
RuntimeIterator rIter = iterDef.getRuntimeIterator(context);
context.addToIndependentRuntimeItrMapForIndexCreation(iterDef);
context.bindIterator(rIter);
}
// Save the dependency graph for future updates.
if (dependencyGraph == null) {
dependencyGraph = context.getDependencyGraph();
}
Support.Assert(this.indexResultSetType != null,
"IMQEvaluator::evaluate:The StrcutType should have been initialized during index creation");
} catch (Exception e) {
logger.debug(e);
throw new Error("Unable to reevaluate, this should not happen");
}
return context;
}
/**
* @param add true if adding to index, false if removing
*/
@Override
public void evaluate(RegionEntry target, boolean add) throws IMQException {
assert !target.isInvalid() : "value in RegionEntry should not be INVALID";
DummyQRegion dQRegion = new DummyQRegion(rgn);
dQRegion.setEntry(target);
Object params[] = {dQRegion};
ExecutionContext context = new ExecutionContext(params, this.cache);
context.newScope(IndexCreationHelper.INDEX_QUERY_SCOPE_ID);
try {
if (this.dependencyGraph != null) {
context.setDependencyGraph(dependencyGraph);
}
for (int i = 0; i < this.iteratorSize; i++) {
CompiledIteratorDef iterDef = (CompiledIteratorDef) fromIterators.get(i);
// We are re-using the same ExecutionContext on every evaluate -- this
// is not how ExecutionContext was intended to be used.
// Compute the dependency only once. The call to methods of this
// class are thread safe as for update lock on Index is taken .
if (this.dependencyGraph == null) {
iterDef.computeDependencies(context);
}
RuntimeIterator rIter = iterDef.getRuntimeIterator(context);
context.addToIndependentRuntimeItrMapForIndexCreation(iterDef);
context.bindIterator(rIter);
}
// Save the dependency graph for future updates.
if (dependencyGraph == null) {
dependencyGraph = context.getDependencyGraph();
}
Support.Assert(this.indexResultSetType != null,
"IMQEvaluator::evaluate:The StrcutType should have been initialized during index creation");
doNestedIterations(0, add, context);
} catch (TypeMismatchException tme) {
if (tme.getRootCause() instanceof EntryDestroyedException) {
// This code relies on current implementation of remove mapping, relying on behavior that
// will force a
// crawl through the index to remove the entry if it exists, even if it is not present at
// the provided key
removeMapping(QueryService.UNDEFINED, target);
} else {
throw new IMQException(tme);
}
} catch (IMQException imqe) {
throw imqe;
} catch (Exception e) {
throw new IMQException(e);
} finally {
context.popScope();
}
}
/**
* This function is used for creating Index data at the start
*/
@Override
public void initializeIndex(boolean loadEntries) throws IMQException {
this.initEntriesUpdated = 0;
try {
// Since an index initialization can happen multiple times
// for a given region, due to clear operation, we are using harcoded
// scope ID of 1 , as otherwise if obtained from ExecutionContext
// object, it will get incremented on very index initialization
this.initContext.newScope(1);
for (int i = 0; i < this.iteratorSize; i++) {
CompiledIteratorDef iterDef = (CompiledIteratorDef) this.indexInitIterators.get(i);
RuntimeIterator rIter = null;
if (!this.hasInitOccurredOnce) {
iterDef.computeDependencies(this.initContext);
rIter = iterDef.getRuntimeIterator(this.initContext);
this.initContext.addToIndependentRuntimeItrMapForIndexCreation(iterDef);
}
if (rIter == null) {
rIter = iterDef.getRuntimeIterator(this.initContext);
}
this.initContext.bindIterator(rIter);
}
this.hasInitOccurredOnce = true;
if (this.indexResultSetType == null) {
this.indexResultSetType = createIndexResultSetType();
}
if (loadEntries) {
doNestedIterationsForIndexInit(0, this.initContext.getCurrentIterators());
}
} catch (IMQException imqe) {
throw imqe;
} catch (Exception e) {
throw new IMQException(e);
} finally {
this.initContext.popScope();
}
}
private void doNestedIterationsForIndexInit(int level, List runtimeIterators)
throws TypeMismatchException, AmbiguousNameException, FunctionDomainException,
NameResolutionException, QueryInvocationTargetException, IMQException {
if (level == 1) {
++this.initEntriesUpdated;
}
if (level == this.iteratorSize) {
applyProjectionForIndexInit(runtimeIterators);
} else {
RuntimeIterator rIter = (RuntimeIterator) runtimeIterators.get(level);
Collection c = rIter.evaluateCollection(this.initContext);
if (c == null)
return;
Iterator cIter = c.iterator();
while (cIter.hasNext()) {
rIter.setCurrent(cIter.next());
doNestedIterationsForIndexInit(level + 1, runtimeIterators);
}
}
}
/*
* This function is used to obtain Index data at the time of index creation. Each element of the
* List is an Object Array of size 3. The 0th element of Object Array stores the value of Index
* Expression. The 1st element of ObjectArray contains the RegionEntry object ( If the booelan
* isFirstItrOnEntry is false, then the 0th iterator will give us the Region.Entry object which
* can be used to obtain the underlying RegionEntry object. If the boolean is true & additional
* projection attribute is not null, then the Region.Entry object can be obtained by evaluating
* the additional projection attribute. If the boolean isFirstItrOnEntry is tru e& additional
* projection attribute is null, then the 0th iterator itself will evaluate to Region.Entry
* Object.
*
* The 2nd element of Object Array contains the Struct object ( tuple) created. If the boolean
* isFirstItrOnEntry is false, then the first attribute of the Struct object is obtained by
* evaluating the additional projection attribute.
*/
private void applyProjectionForIndexInit(List currentRuntimeIters)
throws FunctionDomainException, TypeMismatchException, NameResolutionException,
QueryInvocationTargetException, IMQException {
if (QueryMonitor.isLowMemory()) {
throw new IMQException(
"Index creation canceled due to low memory");
}
Object indexKey = this.isFirstItrOnEntry ? this.indexedExpr.evaluate(this.initContext)
: modifiedIndexExpr.evaluate(this.initContext);
if (indexKey == null) {
indexKey = IndexManager.NULL;
}
// if the first key is PdxString set the flag so that rest of the keys
// would be converted to PdxString
if (!isIndexedPdxKeysFlagSet) {
setPdxStringFlag(indexKey);
}
indexKey = getPdxStringForIndexedPdxKeys(indexKey);
NonTXEntry temp = null;
if (this.isFirstItrOnEntry && this.additionalProj != null) {
temp = (NonTXEntry) additionalProj.evaluate(this.initContext);
} else {
temp = (NonTXEntry) (((RuntimeIterator) currentRuntimeIters.get(0))
.evaluate(this.initContext));
}
RegionEntry re = temp.getRegionEntry();
indexStore.addMapping(indexKey, re);
}
/**
* @param add true if adding to index, false if removing
*/
private void doNestedIterations(int level, boolean add, ExecutionContext context)
throws TypeMismatchException, AmbiguousNameException, FunctionDomainException,
NameResolutionException, QueryInvocationTargetException, IMQException {
List iterList = context.getCurrentIterators();
if (level == this.iteratorSize) {
applyProjection(add, context);
} else {
RuntimeIterator rIter = (RuntimeIterator) iterList.get(level);
// System.out.println("Level = "+level+" Iter = "+rIter.getDef());
Collection c = rIter.evaluateCollection(context);
if (c == null)
return;
Iterator cIter = c.iterator();
while (cIter.hasNext()) {
rIter.setCurrent(cIter.next());
doNestedIterations(level + 1, add, context);
}
}
}
/**
* @param add true if adding, false if removing from index
*/
private void applyProjection(boolean add, ExecutionContext context)
throws FunctionDomainException, TypeMismatchException, NameResolutionException,
QueryInvocationTargetException, IMQException {
Object indexKey = indexedExpr.evaluate(context);
if (indexKey == null) {
indexKey = IndexManager.NULL;
}
// if the first key is PdxString set the flag so that rest of the keys
// would be converted to PdxString
if (!isIndexedPdxKeysFlagSet) {
setPdxStringFlag(indexKey);
}
indexKey = getPdxStringForIndexedPdxKeys(indexKey);
RegionEntry entry = ((DummyQRegion) context.getBindArgument(1)).getEntry();
// Get thread local reverse map if available.
OldKeyValuePair oldKeyValuePair = null;
if (oldKeyValue != null) {
oldKeyValuePair = oldKeyValue.get();
}
if (add) {
Object oldKey = null;
Object oldValue = null;
// Get Old keys to be removed.
if (oldKeyValuePair != null) {
oldKey = oldKeyValuePair.getOldKey();
oldValue = oldKeyValuePair.getOldValue();
}
// Add new index entries
// A null oldKey means this is a create
// oldKey would be a NullToken in case of update
if (oldKey == null) {
indexStore.addMapping(indexKey, entry);
} else {
// Add new key and remove old
indexStore.updateMapping(indexKey, oldKey, entry, oldValue);
// reset the thread local as the update is done
if (oldKeyValue != null) {
oldKeyValue.remove();
}
}
} else { // remove from forward and reverse maps
// We will cleanup the index entry later.
if (oldKeyValuePair != null) {
oldKeyValuePair.setOldKeyValuePair(indexKey, entry);
} else {
indexStore.removeMapping(indexKey, entry);
}
}
}
// The struct type calculation is modified if the
// 0th iterator is modified to make it dependent on Entry
private ObjectType createIndexResultSetType() {
List currentIterators = this.initContext.getCurrentIterators();
int len = currentIterators.size();
ObjectType type = null;
ObjectType fieldTypes[] = new ObjectType[len];
int start = this.isFirstItrOnEntry ? 0 : 1;
for (; start < len; start++) {
RuntimeIterator iter = (RuntimeIterator) currentIterators.get(start);
fieldTypes[start] = iter.getElementType();
}
if (!this.isFirstItrOnEntry) {
fieldTypes[0] = addnlProjType;
}
type = (len == 1) ? fieldTypes[0] : new StructTypeImpl(this.canonicalIterNames, fieldTypes);
return type;
}
int getTotalEntriesUpdated() {
return this.initEntriesUpdated;
}
@Override
public ObjectType getIndexResultSetType() {
return this.indexResultSetType;
}
@Override
public List getAllDependentIterators() {
return fromIterators;
}
}
@Override
void lockedQuery(Object key, int operator, Collection results, CompiledValue iterOps,
RuntimeIterator indpndntItr, ExecutionContext context, List projAttrib,
SelectResults intermediateResults, boolean isIntersection) throws TypeMismatchException,
FunctionDomainException, NameResolutionException, QueryInvocationTargetException {
this.lockedQueryPrivate(key, operator, results, iterOps, indpndntItr, context, null, projAttrib,
intermediateResults, isIntersection);
}
@Override
void lockedQuery(Object key, int operator, Collection results, Set keysToRemove,
ExecutionContext context) throws TypeMismatchException, FunctionDomainException,
NameResolutionException, QueryInvocationTargetException {
this.lockedQueryPrivate(key, operator, results, null, null, context, keysToRemove, null, null,
true);
}
@Override
void addMapping(Object key, Object value, RegionEntry entry) throws IMQException {
// Only called from CompactMapRangeIndex
indexStore.addMapping(key, entry);
}
public static void setTestHook(TestHook hook) {
testHook = hook;
}
@Override
void saveMapping(Object key, Object value, RegionEntry entry) throws IMQException {
// TODO Auto-generated method stub
}
@Override
public boolean isEmpty() {
return indexStore.size() == 0 ? true : false;
}
@Override
public Map getValueToEntriesMap() {
throw new UnsupportedOperationException("valuesToEntriesMap should not be accessed directly");
}
public void addSavedMappings(RegionEntry entry) {
}
private class OldKeyValuePair {
private Object oldKey;
private Object oldValue;
public void setOldKeyValuePair(Object oldKey, RegionEntry entry) {
this.oldKey = oldKey;
// We obtain the object currently in vm, we are using this old value
// only to detect if in place modifications have occurred
// if the object is not in memory, obviously an in place modification could
// not have occurred
this.oldValue = indexStore.getTargetObjectInVM(entry);
}
public Object getOldValue() {
return oldValue;
}
public Object getOldKey() {
return oldKey;
}
}
}