| /* |
| * 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.Arrays; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; |
| import java.util.concurrent.locks.ReadWriteLock; |
| import java.util.concurrent.locks.ReentrantReadWriteLock; |
| |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.logging.log4j.Logger; |
| |
| 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.IndexStatistics; |
| 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.Struct; |
| import org.apache.geode.cache.query.TypeMismatchException; |
| import org.apache.geode.cache.query.internal.CompiledID; |
| import org.apache.geode.cache.query.internal.CompiledIndexOperation; |
| import org.apache.geode.cache.query.internal.CompiledIteratorDef; |
| import org.apache.geode.cache.query.internal.CompiledOperation; |
| import org.apache.geode.cache.query.internal.CompiledPath; |
| import org.apache.geode.cache.query.internal.CompiledValue; |
| import org.apache.geode.cache.query.internal.CqEntry; |
| import org.apache.geode.cache.query.internal.DefaultQuery; |
| 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.QueryUtils; |
| import org.apache.geode.cache.query.internal.RuntimeIterator; |
| import org.apache.geode.cache.query.internal.StructFields; |
| import org.apache.geode.cache.query.internal.StructImpl; |
| import org.apache.geode.cache.query.internal.Support; |
| import org.apache.geode.cache.query.internal.index.IndexStore.IndexStoreEntry; |
| import org.apache.geode.cache.query.internal.parse.OQLLexerTokenTypes; |
| import org.apache.geode.cache.query.internal.types.StructTypeImpl; |
| import org.apache.geode.cache.query.types.ObjectType; |
| import org.apache.geode.internal.Assert; |
| import org.apache.geode.internal.cache.BucketRegion; |
| import org.apache.geode.internal.cache.CachedDeserializable; |
| 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.PartitionedRegion; |
| import org.apache.geode.internal.cache.RegionEntry; |
| import org.apache.geode.internal.cache.RegionEntryContext; |
| import org.apache.geode.internal.cache.partitioned.Bucket; |
| import org.apache.geode.internal.cache.persistence.query.CloseableIterator; |
| import org.apache.geode.internal.offheap.annotations.Retained; |
| import org.apache.geode.logging.internal.log4j.api.LogService; |
| import org.apache.geode.pdx.PdxInstance; |
| import org.apache.geode.pdx.internal.PdxString; |
| |
| /** |
| * This class implements abstract algorithms common to all indexes, such as index creation, use of a |
| * path evaluator object, etc. It serves as the factory for a path evaluator object and maintains |
| * the path evaluator object to use for index creation and index maintenance. It also maintains a |
| * reference to the root collection on which the index is created. This class also implements the |
| * abstract methods to add and remove entries to an underlying storage structure (e.g. a btree), and |
| * as part of this algorithm, maintains a map of entries that map to null at the end of the index |
| * path, and entries that cannot be traversed to the end of the index path (traversal is undefined). |
| */ |
| public abstract class AbstractIndex implements IndexProtocol { |
| private static final Logger logger = LogService.getLogger(); |
| |
| // package-private to avoid synthetic accessor |
| static final AtomicIntegerFieldUpdater<RegionEntryToValuesMap> atomicUpdater = |
| AtomicIntegerFieldUpdater.newUpdater(RegionEntryToValuesMap.class, "numValues"); |
| |
| final InternalCache cache; |
| |
| final String indexName; |
| |
| final Region region; |
| |
| final String indexedExpression; |
| |
| final String fromClause; |
| |
| final String projectionAttributes; |
| |
| final String originalIndexedExpression; |
| |
| final String originalFromClause; |
| |
| private final String originalProjectionAttributes; |
| |
| final String[] canonicalizedDefinitions; |
| |
| private boolean isValid; |
| |
| protected IndexedExpressionEvaluator evaluator; |
| |
| InternalIndexStatistics internalIndexStats; |
| |
| /** For PartitionedIndex for now */ |
| protected Index prIndex; |
| |
| /** |
| * Flag to indicate if index map has keys as PdxString All the keys in the index map should be |
| * either Strings or PdxStrings |
| */ |
| private Boolean isIndexedPdxKeys = false; |
| |
| /** Flag to indicate if the flag isIndexedPdxKeys is set */ |
| Boolean isIndexedPdxKeysFlagSet = false; |
| |
| boolean indexOnRegionKeys = false; |
| |
| boolean indexOnValues = false; |
| |
| private final ReadWriteLock removeIndexLock = new ReentrantReadWriteLock(); |
| |
| /** Flag to indicate if the index is populated with data */ |
| volatile boolean isPopulated = false; |
| |
| AbstractIndex(InternalCache cache, String indexName, Region region, String fromClause, |
| String indexedExpression, String projectionAttributes, String originalFromClause, |
| String originalIndexedExpression, String[] defintions, IndexStatistics stats) { |
| this.cache = cache; |
| this.indexName = indexName; |
| this.region = region; |
| this.indexedExpression = indexedExpression; |
| this.fromClause = fromClause; |
| this.originalIndexedExpression = originalIndexedExpression; |
| this.originalFromClause = originalFromClause; |
| this.canonicalizedDefinitions = defintions; |
| if (StringUtils.isEmpty(projectionAttributes)) { |
| projectionAttributes = "*"; |
| } |
| this.projectionAttributes = projectionAttributes; |
| this.originalProjectionAttributes = projectionAttributes; |
| if (stats != null) { |
| this.internalIndexStats = (InternalIndexStatistics) stats; |
| } else { |
| this.internalIndexStats = createStats(indexName); |
| } |
| } |
| |
| /** |
| * Must be implemented by all implementing classes iff they have any forward map for |
| * index-key->RE. |
| * |
| * @return the forward map of respective index. |
| */ |
| public Map getValueToEntriesMap() { |
| return null; |
| } |
| |
| /** |
| * Get statistics information for this index. |
| */ |
| @Override |
| public IndexStatistics getStatistics() { |
| return this.internalIndexStats; |
| } |
| |
| @Override |
| public void destroy() { |
| markValid(false); |
| if (this.internalIndexStats != null) { |
| this.internalIndexStats.updateNumKeys(0); |
| this.internalIndexStats.close(); |
| } |
| } |
| |
| long updateIndexUpdateStats() { |
| long result = System.nanoTime(); |
| this.internalIndexStats.incUpdatesInProgress(1); |
| return result; |
| } |
| |
| void updateIndexUpdateStats(long start) { |
| long end = System.nanoTime(); |
| this.internalIndexStats.incUpdatesInProgress(-1); |
| this.internalIndexStats.incUpdateTime(end - start); |
| } |
| |
| long updateIndexUseStats() { |
| return updateIndexUseStats(true); |
| } |
| |
| long updateIndexUseStats(boolean updateStats) { |
| long result = 0; |
| if (updateStats) { |
| this.internalIndexStats.incUsesInProgress(1); |
| result = System.nanoTime(); |
| } |
| return result; |
| } |
| |
| void updateIndexUseEndStats(long start) { |
| updateIndexUseEndStats(start, true); |
| } |
| |
| void updateIndexUseEndStats(long start, boolean updateStats) { |
| if (updateStats) { |
| long end = System.nanoTime(); |
| this.internalIndexStats.incUsesInProgress(-1); |
| this.internalIndexStats.incNumUses(); |
| this.internalIndexStats.incUseTime(end - start); |
| } |
| } |
| |
| public IndexedExpressionEvaluator getEvaluator() { |
| return this.evaluator; |
| } |
| |
| /** |
| * The Region this index is on |
| * |
| * @return the Region for this index |
| */ |
| @Override |
| public Region getRegion() { |
| return this.region; |
| } |
| |
| /** |
| * Returns the unique name of this index |
| */ |
| @Override |
| public String getName() { |
| return this.indexName; |
| } |
| |
| @Override |
| public void query(Object key, int operator, Collection results, ExecutionContext context) |
| throws TypeMismatchException, FunctionDomainException, NameResolutionException, |
| QueryInvocationTargetException { |
| |
| // get a read lock when doing a lookup |
| if (context.getBucketList() != null && this.region instanceof BucketRegion) { |
| PartitionedRegion pr = ((Bucket) this.region).getPartitionedRegion(); |
| long start = updateIndexUseStats(); |
| try { |
| for (Object bucketId : context.getBucketList()) { |
| AbstractIndex bucketIndex = |
| PartitionedIndex.getBucketIndex(pr, this.indexName, (Integer) bucketId); |
| if (bucketIndex == null) { |
| continue; |
| } |
| bucketIndex.lockedQuery(key, operator, results, null/* No Keys to be removed */, context); |
| |
| } |
| } finally { |
| updateIndexUseEndStats(start); |
| } |
| } else { |
| long start = updateIndexUseStats(); |
| try { |
| lockedQuery(key, operator, results, null/* No Keys to be removed */, context); |
| } finally { |
| updateIndexUseEndStats(start); |
| } |
| } |
| } |
| |
| @Override |
| public void query(Object key, int operator, Collection results, @Retained CompiledValue iterOp, |
| RuntimeIterator indpndntItr, ExecutionContext context, List projAttrib, |
| SelectResults intermediateResults, boolean isIntersection) throws TypeMismatchException, |
| FunctionDomainException, NameResolutionException, QueryInvocationTargetException { |
| |
| // get a read lock when doing a lookup |
| if (context.getBucketList() != null && this.region instanceof BucketRegion) { |
| PartitionedRegion pr = ((Bucket) region).getPartitionedRegion(); |
| long start = updateIndexUseStats(); |
| try { |
| for (Object bucketId : context.getBucketList()) { |
| AbstractIndex bucketIndex = |
| PartitionedIndex.getBucketIndex(pr, this.indexName, (Integer) bucketId); |
| if (bucketIndex == null) { |
| continue; |
| } |
| bucketIndex.lockedQuery(key, operator, results, iterOp, indpndntItr, context, projAttrib, |
| intermediateResults, isIntersection); |
| } |
| } finally { |
| updateIndexUseEndStats(start); |
| } |
| } else { |
| long start = updateIndexUseStats(); |
| try { |
| lockedQuery(key, operator, results, iterOp, indpndntItr, context, projAttrib, |
| intermediateResults, isIntersection); |
| } finally { |
| updateIndexUseEndStats(start); |
| } |
| } |
| } |
| |
| @Override |
| public void query(Object key, int operator, Collection results, Set keysToRemove, |
| ExecutionContext context) throws TypeMismatchException, FunctionDomainException, |
| NameResolutionException, QueryInvocationTargetException { |
| |
| // get a read lock when doing a lookup |
| if (context.getBucketList() != null && this.region instanceof BucketRegion) { |
| PartitionedRegion pr = ((Bucket) region).getPartitionedRegion(); |
| long start = updateIndexUseStats(); |
| try { |
| for (Object bucketId : context.getBucketList()) { |
| AbstractIndex bucketIndex = |
| PartitionedIndex.getBucketIndex(pr, this.indexName, (Integer) bucketId); |
| if (bucketIndex == null) { |
| continue; |
| } |
| bucketIndex.lockedQuery(key, operator, results, keysToRemove, context); |
| } |
| } finally { |
| updateIndexUseEndStats(start); |
| } |
| } else { |
| long start = updateIndexUseStats(); |
| try { |
| lockedQuery(key, operator, results, keysToRemove, context); |
| } finally { |
| updateIndexUseEndStats(start); |
| } |
| } |
| } |
| |
| @Override |
| public void query(Collection results, Set keysToRemove, ExecutionContext context) |
| throws TypeMismatchException, FunctionDomainException, NameResolutionException, |
| QueryInvocationTargetException { |
| |
| Iterator iterator = keysToRemove.iterator(); |
| Object temp = iterator.next(); |
| iterator.remove(); |
| if (context.getBucketList() != null && this.region instanceof BucketRegion) { |
| long start = updateIndexUseStats(); |
| try { |
| PartitionedRegion partitionedRegion = ((Bucket) this.region).getPartitionedRegion(); |
| for (Object bucketId : context.getBucketList()) { |
| AbstractIndex bucketIndex = PartitionedIndex.getBucketIndex(partitionedRegion, |
| this.indexName, (Integer) bucketId); |
| if (bucketIndex == null) { |
| continue; |
| } |
| bucketIndex.lockedQuery(temp, OQLLexerTokenTypes.TOK_NE, results, |
| iterator.hasNext() ? keysToRemove : null, context); |
| } |
| } finally { |
| updateIndexUseEndStats(start); |
| } |
| } else { |
| long start = updateIndexUseStats(); |
| try { |
| lockedQuery(temp, OQLLexerTokenTypes.TOK_NE, results, |
| iterator.hasNext() ? keysToRemove : null, context); |
| } finally { |
| updateIndexUseEndStats(start); |
| } |
| } |
| } |
| |
| @Override |
| public void query(Object lowerBoundKey, int lowerBoundOperator, Object upperBoundKey, |
| int upperBoundOperator, Collection results, Set keysToRemove, ExecutionContext context) |
| throws TypeMismatchException, FunctionDomainException, NameResolutionException, |
| QueryInvocationTargetException { |
| |
| if (context.getBucketList() != null) { |
| if (this.region instanceof BucketRegion) { |
| PartitionedRegion partitionedRegion = ((Bucket) this.region).getPartitionedRegion(); |
| long start = updateIndexUseStats(); |
| try { |
| for (Object bucketId : context.getBucketList()) { |
| AbstractIndex bucketIndex = PartitionedIndex.getBucketIndex(partitionedRegion, |
| this.indexName, (Integer) bucketId); |
| if (bucketIndex == null) { |
| continue; |
| } |
| bucketIndex.lockedQuery(lowerBoundKey, lowerBoundOperator, upperBoundKey, |
| upperBoundOperator, results, keysToRemove, context); |
| } |
| } finally { |
| updateIndexUseEndStats(start); |
| } |
| } |
| } else { |
| long start = updateIndexUseStats(); |
| try { |
| lockedQuery(lowerBoundKey, lowerBoundOperator, upperBoundKey, upperBoundOperator, results, |
| keysToRemove, context); |
| } finally { |
| updateIndexUseEndStats(start); |
| } |
| } |
| } |
| |
| @Override |
| public List queryEquijoinCondition(IndexProtocol index, ExecutionContext context) |
| throws TypeMismatchException, FunctionDomainException, NameResolutionException, |
| QueryInvocationTargetException { |
| |
| Support.assertionFailed( |
| " This function should have never got invoked as its meaningful implementation is present only in RangeIndex class"); |
| return null; |
| } |
| |
| /** |
| * Get the projectionAttributes for this expression. |
| * |
| * @return the projectionAttributes, or "*" if there were none specified at index creation. |
| */ |
| @Override |
| public String getProjectionAttributes() { |
| return this.originalProjectionAttributes; |
| } |
| |
| /** |
| * Get the projectionAttributes for this expression. |
| * |
| * @return the projectionAttributes, or "*" if there were none specified at index creation. |
| */ |
| @Override |
| public String getCanonicalizedProjectionAttributes() { |
| return this.projectionAttributes; |
| } |
| |
| /** |
| * Get the Original indexedExpression for this index. |
| */ |
| @Override |
| public String getIndexedExpression() { |
| return this.originalIndexedExpression; |
| } |
| |
| /** |
| * Get the Canonicalized indexedExpression for this index. |
| */ |
| @Override |
| public String getCanonicalizedIndexedExpression() { |
| return this.indexedExpression; |
| } |
| |
| /** |
| * Get the original fromClause for this index. |
| */ |
| @Override |
| public String getFromClause() { |
| return this.originalFromClause; |
| } |
| |
| /** |
| * Get the canonicalized fromClause for this index. |
| */ |
| @Override |
| public String getCanonicalizedFromClause() { |
| return this.fromClause; |
| } |
| |
| public boolean isMapType() { |
| return false; |
| } |
| |
| @Override |
| public boolean addIndexMapping(RegionEntry entry) throws IMQException { |
| addMapping(entry); |
| |
| // if no exception, then success |
| return true; |
| } |
| |
| @Override |
| public boolean addAllIndexMappings(Collection<RegionEntry> c) throws IMQException { |
| for (RegionEntry regionEntry : c) { |
| addMapping(regionEntry); |
| } |
| // if no exception, then success |
| return true; |
| } |
| |
| /** |
| * @param opCode one of OTHER_OP, BEFORE_UPDATE_OP, AFTER_UPDATE_OP. |
| */ |
| @Override |
| public boolean removeIndexMapping(RegionEntry entry, int opCode) throws IMQException { |
| removeMapping(entry, opCode); |
| // if no exception, then success |
| return true; |
| } |
| |
| @Override |
| public boolean removeAllIndexMappings(Collection<RegionEntry> c) throws IMQException { |
| for (RegionEntry regionEntry : c) { |
| removeMapping(regionEntry, OTHER_OP); |
| } |
| // if no exception, then success |
| return true; |
| } |
| |
| |
| @Override |
| public boolean isValid() { |
| return this.isValid; |
| } |
| |
| @Override |
| public void markValid(boolean b) { |
| this.isValid = b; |
| } |
| |
| @Override |
| public boolean isMatchingWithIndexExpression(CompiledValue condnExpr, String condnExprStr, |
| ExecutionContext context) |
| throws AmbiguousNameException, TypeMismatchException, NameResolutionException { |
| return this.indexedExpression.equals(condnExprStr); |
| } |
| |
| // package-private to avoid synthetic accessor |
| Object verifyAndGetPdxDomainObject(Object value) { |
| if (value instanceof StructImpl) { |
| // Doing hasPdx check first, since its cheaper. |
| if (((StructImpl) value).isHasPdx() |
| && !((InternalCache) this.region.getCache()).getPdxReadSerializedByAnyGemFireServices()) { |
| // Set the pdx values for the struct object. |
| StructImpl v = (StructImpl) value; |
| Object[] fieldValues = v.getPdxFieldValues(); |
| return new StructImpl((StructTypeImpl) v.getStructType(), fieldValues); |
| } |
| } else if (value instanceof PdxInstance |
| && !((InternalCache) this.region.getCache()).getPdxReadSerializedByAnyGemFireServices()) { |
| return ((PdxInstance) value).getObject(); |
| } |
| return value; |
| } |
| |
| private void addToResultsWithUnionOrIntersection(Collection results, |
| SelectResults intermediateResults, boolean isIntersection, Object value) { |
| value = verifyAndGetPdxDomainObject(value); |
| |
| if (intermediateResults == null) { |
| results.add(value); |
| } else { |
| if (isIntersection) { |
| int numOcc = intermediateResults.occurrences(value); |
| if (numOcc > 0) { |
| results.add(value); |
| intermediateResults.remove(value); |
| } |
| } else { |
| results.add(value); |
| } |
| } |
| } |
| |
| private void addToStructsWithUnionOrIntersection(Collection results, |
| SelectResults intermediateResults, boolean isIntersection, Object[] values) { |
| |
| for (int i = 0; i < values.length; i++) { |
| values[i] = verifyAndGetPdxDomainObject(values[i]); |
| } |
| |
| if (intermediateResults == null) { |
| if (results instanceof StructFields) { |
| ((StructFields) results).addFieldValues(values); |
| } else { |
| // The results could be LinkedStructSet or SortedResultsBag or StructSet |
| SelectResults selectResults = (SelectResults) results; |
| StructImpl structImpl = new StructImpl( |
| (StructTypeImpl) selectResults.getCollectionType().getElementType(), values); |
| selectResults.add(structImpl); |
| } |
| |
| } else { |
| if (isIntersection) { |
| if (results instanceof StructFields) { |
| int occurrences = intermediateResults.occurrences(values); |
| if (occurrences > 0) { |
| ((StructFields) results).addFieldValues(values); |
| ((StructFields) intermediateResults).removeFieldValues(values); |
| } |
| |
| } else { |
| // could be LinkedStructSet or SortedResultsBag |
| SelectResults selectResults = (SelectResults) results; |
| StructImpl structImpl = new StructImpl( |
| (StructTypeImpl) selectResults.getCollectionType().getElementType(), values); |
| if (intermediateResults.remove(structImpl)) { |
| selectResults.add(structImpl); |
| } |
| } |
| |
| } else { |
| if (results instanceof StructFields) { |
| ((StructFields) results).addFieldValues(values); |
| } else { |
| // could be LinkedStructSet or SortedResultsBag |
| SelectResults selectResults = (SelectResults) results; |
| StructImpl structImpl = new StructImpl( |
| (StructTypeImpl) selectResults.getCollectionType().getElementType(), values); |
| if (intermediateResults.remove(structImpl)) { |
| selectResults.add(structImpl); |
| } |
| } |
| } |
| } |
| } |
| |
| void applyCqOrProjection(List projAttrib, ExecutionContext context, Collection result, |
| Object iterValue, SelectResults intermediateResults, boolean isIntersection, Object key) |
| throws FunctionDomainException, TypeMismatchException, NameResolutionException, |
| QueryInvocationTargetException { |
| if (context != null && context.isCqQueryContext()) { |
| result.add(new CqEntry(key, iterValue)); |
| } else { |
| applyProjection(projAttrib, context, result, iterValue, intermediateResults, isIntersection); |
| } |
| } |
| |
| void applyProjection(List projAttrib, ExecutionContext context, Collection result, |
| Object iterValue, SelectResults intermediateResults, boolean isIntersection) |
| throws FunctionDomainException, TypeMismatchException, NameResolutionException, |
| QueryInvocationTargetException { |
| |
| if (projAttrib == null) { |
| iterValue = deserializePdxForLocalDistinctQuery(context, iterValue); |
| this.addToResultsWithUnionOrIntersection(result, intermediateResults, isIntersection, |
| iterValue); |
| |
| } else { |
| boolean isStruct = result instanceof SelectResults |
| && ((SelectResults) result).getCollectionType().getElementType() != null |
| && ((SelectResults) result).getCollectionType().getElementType().isStructType(); |
| |
| if (isStruct) { |
| int projCount = projAttrib.size(); |
| Object[] values = new Object[projCount]; |
| Iterator projIter = projAttrib.iterator(); |
| int i = 0; |
| while (projIter.hasNext()) { |
| Object[] projDef = (Object[]) projIter.next(); |
| values[i] = deserializePdxForLocalDistinctQuery(context, |
| ((CompiledValue) projDef[1]).evaluate(context)); |
| i++; |
| } |
| this.addToStructsWithUnionOrIntersection(result, intermediateResults, isIntersection, |
| values); |
| } else { |
| Object[] temp = (Object[]) projAttrib.get(0); |
| Object val = deserializePdxForLocalDistinctQuery(context, |
| ((CompiledValue) temp[1]).evaluate(context)); |
| this.addToResultsWithUnionOrIntersection(result, intermediateResults, isIntersection, val); |
| } |
| } |
| } |
| |
| /** |
| * 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 |
| */ |
| private Object deserializePdxForLocalDistinctQuery(ExecutionContext context, Object value) |
| throws QueryInvocationTargetException { |
| |
| if (!((DefaultQuery) context.getQuery()).isRemoteQuery()) { |
| if (context.isDistinct() && value instanceof PdxInstance |
| && !this.region.getCache().getPdxReadSerialized()) { |
| try { |
| value = ((PdxInstance) value).getObject(); |
| } catch (Exception ex) { |
| throw new QueryInvocationTargetException( |
| "Unable to retrieve domain object from PdxInstance while building the ResultSet. " |
| + ex.getMessage()); |
| } |
| } else if (value instanceof PdxString) { |
| value = value.toString(); |
| } |
| } |
| return value; |
| } |
| |
| private void removeFromResultsWithUnionOrIntersection(Collection results, |
| SelectResults intermediateResults, boolean isIntersection, Object value) { |
| |
| if (intermediateResults == null) { |
| results.remove(value); |
| } else { |
| if (isIntersection) { |
| int numOcc = ((SelectResults) results).occurrences(value); |
| if (numOcc > 0) { |
| results.remove(value); |
| intermediateResults.add(value); |
| } |
| } else { |
| results.remove(value); |
| } |
| } |
| } |
| |
| private void removeFromStructsWithUnionOrIntersection(Collection results, |
| SelectResults intermediateResults, boolean isIntersection, Object[] values) { |
| |
| if (intermediateResults == null) { |
| ((StructFields) results).removeFieldValues(values); |
| } else { |
| if (isIntersection) { |
| int numOcc = ((SelectResults) results).occurrences(values); |
| if (numOcc > 0) { |
| ((StructFields) results).removeFieldValues(values); |
| ((StructFields) intermediateResults).addFieldValues(values); |
| |
| } |
| } else { |
| ((StructFields) results).removeFieldValues(values); |
| } |
| } |
| } |
| |
| private void removeProjection(List projAttrib, ExecutionContext context, Collection result, |
| Object iterValue, SelectResults intermediateResults, boolean isIntersection) |
| throws FunctionDomainException, TypeMismatchException, NameResolutionException, |
| QueryInvocationTargetException { |
| |
| if (projAttrib == null) { |
| this.removeFromResultsWithUnionOrIntersection(result, intermediateResults, isIntersection, |
| iterValue); |
| } else { |
| if (result instanceof StructFields) { |
| int projCount = projAttrib.size(); |
| Object[] values = new Object[projCount]; |
| Iterator projIter = projAttrib.iterator(); |
| int i = 0; |
| while (projIter.hasNext()) { |
| Object projDef[] = (Object[]) projIter.next(); |
| values[i++] = ((CompiledValue) projDef[1]).evaluate(context); |
| } |
| this.removeFromStructsWithUnionOrIntersection(result, intermediateResults, isIntersection, |
| values); |
| } else { |
| Object[] temp = (Object[]) projAttrib.get(0); |
| Object val = ((CompiledValue) temp[1]).evaluate(context); |
| this.removeFromResultsWithUnionOrIntersection(result, intermediateResults, isIntersection, |
| val); |
| } |
| } |
| |
| } |
| |
| /** |
| * This function returns the canonicalized definitions of the from clauses used in Index creation |
| */ |
| @Override |
| public String[] getCanonicalizedIteratorDefinitions() { |
| return this.canonicalizedDefinitions; |
| } |
| |
| /** |
| * This implementation is for PrimaryKeyIndex. RangeIndex has its own implementation. For |
| * PrimaryKeyIndex , this method should not be used |
| * <p> |
| * TODO: check if an Exception should be thrown if the function implementation of this class gets |
| * invoked |
| */ |
| @Override |
| public boolean containsEntry(RegionEntry entry) { |
| return false; |
| } |
| |
| abstract void instantiateEvaluator(IndexCreationHelper indexCreationHelper); |
| |
| @Override |
| public void initializeIndex(boolean loadEntries) throws IMQException { |
| // implement me |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("Index ["); |
| sb.append(" Name=").append(getName()); |
| sb.append(" Type =").append(getType()); |
| sb.append(" IdxExp=").append(getIndexedExpression()); |
| sb.append(" From=").append(getFromClause()); |
| sb.append(" Proj=").append(getProjectionAttributes()); |
| sb.append(']'); |
| return sb.toString(); |
| } |
| |
| public abstract boolean isEmpty(); |
| |
| protected abstract boolean isCompactRangeIndex(); |
| |
| protected abstract InternalIndexStatistics createStats(String indexName); |
| |
| @Override |
| public abstract ObjectType getResultSetType(); |
| |
| abstract void recreateIndexData() throws IMQException; |
| |
| abstract void addMapping(RegionEntry entry) throws IMQException; |
| |
| abstract void removeMapping(RegionEntry entry, int opCode) throws IMQException; |
| |
| abstract void addMapping(Object key, Object value, RegionEntry entry) throws IMQException; |
| |
| /** |
| * This is used to buffer the index entries evaluated from a RegionEntry which is getting updated |
| * at present. These buffered index entries are replaced into the index later all together to |
| * avoid remove-add sequence. |
| */ |
| abstract void saveMapping(Object key, Object value, RegionEntry entry) throws IMQException; |
| |
| /** Lookup method used when appropriate lock is held */ |
| abstract 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; |
| |
| abstract void lockedQuery(Object lowerBoundKey, int lowerBoundOperator, Object upperBoundKey, |
| int upperBoundOperator, Collection results, Set keysToRemove, ExecutionContext context) |
| throws TypeMismatchException, FunctionDomainException, NameResolutionException, |
| QueryInvocationTargetException; |
| |
| abstract void lockedQuery(Object key, int operator, Collection results, Set keysToRemove, |
| ExecutionContext context) throws TypeMismatchException, FunctionDomainException, |
| NameResolutionException, QueryInvocationTargetException; |
| |
| public Index getPRIndex() { |
| return this.prIndex; |
| } |
| |
| void setPRIndex(Index parIndex) { |
| this.prIndex = parIndex; |
| } |
| |
| /** |
| * Dummy implementation that subclasses can override. |
| */ |
| protected abstract static class InternalIndexStatistics implements IndexStatistics { |
| @Override |
| public long getNumUpdates() { |
| return 0L; |
| } |
| |
| @Override |
| public long getTotalUpdateTime() { |
| return 0L; |
| } |
| |
| @Override |
| public long getTotalUses() { |
| return 0L; |
| } |
| |
| @Override |
| public long getNumberOfKeys() { |
| return 0L; |
| } |
| |
| @Override |
| public long getNumberOfValues() { |
| return 0L; |
| } |
| |
| @Override |
| public long getNumberOfValues(Object key) { |
| return 0L; |
| } |
| |
| @Override |
| public int getReadLockCount() { |
| return 0; |
| } |
| |
| @Override |
| public long getNumberOfMapIndexKeys() { |
| return 0; |
| } |
| |
| @Override |
| public int getNumberOfBucketIndexes() { |
| return 0; |
| } |
| |
| public void close() {} |
| |
| public void incNumValues(int delta) {} |
| |
| public void incNumUpdates() {} |
| |
| public void incNumUpdates(int delta) {} |
| |
| public void incUpdatesInProgress(int delta) {} |
| |
| public void incUsesInProgress(int delta) {} |
| |
| public void updateNumKeys(long count) {} |
| |
| public void incNumKeys(long count) {} |
| |
| public void incNumMapIndexKeys(long numKeys) {} |
| |
| public void incUpdateTime(long delta) {} |
| |
| public void incNumUses() {} |
| |
| public void incUseTime(long delta) {} |
| |
| public void incReadLockCount(int delta) {} |
| |
| public void incNumBucketIndexes(int delta) {} |
| } |
| |
| class IMQEvaluator implements IndexedExpressionEvaluator { |
| private final InternalCache cache; |
| |
| private List fromIterators = null; |
| |
| private CompiledValue indexedExpr = null; |
| |
| private final String[] canonicalIterNames; |
| |
| private ObjectType indexResultSetType = 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; |
| |
| /** The boolean if true indicates that the 0th iterator is on keys. */ |
| private boolean isFirstItrOnKey = 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 |
| * but & still 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 ExecutionContext initContext = null; |
| |
| private int iteratorSize = -1; |
| |
| private Region rgn = null; |
| |
| /** Creates a new instance of IMQEvaluator */ |
| IMQEvaluator(IndexCreationHelper helper) { |
| this.cache = helper.getCache(); |
| this.fromIterators = helper.getIterators(); |
| this.indexedExpr = helper.getCompiledIndexedExpression(); |
| this.rgn = helper.getRegion(); |
| // The modified iterators for optimizing Index creation |
| this.isFirstItrOnEntry = ((FunctionalIndexCreationHelper) helper).isFirstIteratorRegionEntry; |
| this.isFirstItrOnKey = ((FunctionalIndexCreationHelper) helper).isFirstIteratorRegionKey; |
| this.additionalProj = ((FunctionalIndexCreationHelper) helper).additionalProj; |
| Object[] params1 = {new QRegion(this.rgn, false)}; |
| this.initContext = new ExecutionContext(params1, this.cache); |
| this.canonicalIterNames = ((FunctionalIndexCreationHelper) helper).canonicalizedIteratorNames; |
| if (this.isFirstItrOnEntry) { |
| this.indexInitIterators = this.fromIterators; |
| } else { |
| this.indexInitIterators = ((FunctionalIndexCreationHelper) helper).indexInitIterators; |
| this.modifiedIndexExpr = ((FunctionalIndexCreationHelper) helper).modifiedIndexExpr; |
| this.addnlProjType = ((FunctionalIndexCreationHelper) helper).addnlProjType; |
| } |
| this.iteratorSize = this.indexInitIterators.size(); |
| } |
| |
| @Override |
| public String getIndexedExpression() { |
| return AbstractIndex.this.getCanonicalizedIndexedExpression(); |
| } |
| |
| @Override |
| public String getProjectionAttributes() { |
| return AbstractIndex.this.getCanonicalizedProjectionAttributes(); |
| } |
| |
| @Override |
| public String getFromClause() { |
| return AbstractIndex.this.getCanonicalizedFromClause(); |
| } |
| |
| @Override |
| public void expansion(List expandedResults, Object lowerBoundKey, Object upperBoundKey, |
| int lowerBoundOperator, int upperBoundOperator, Object value) throws IMQException { |
| // no-op |
| } |
| |
| @Override |
| public void evaluate(RegionEntry target, boolean add) throws IMQException { |
| assert add; // ignored, but should be true here |
| DummyQRegion dQRegion = new DummyQRegion(this.rgn); |
| dQRegion.setEntry(target); |
| Object[] params = {dQRegion}; |
| ExecutionContext context = new ExecutionContext(params, this.cache); |
| context.newScope(IndexCreationHelper.INDEX_QUERY_SCOPE_ID); |
| |
| try { |
| boolean computeDependency = true; |
| if (this.dependencyGraph != null) { |
| context.setDependencyGraph(this.dependencyGraph); |
| computeDependency = false; |
| } |
| |
| for (int i = 0; i < this.iteratorSize; i++) { |
| CompiledIteratorDef iterDef = (CompiledIteratorDef) this.fromIterators.get(i); |
| // Compute the dependency only once. The call to methods of this |
| // class are thread safe as for update lock on Index is taken . |
| if (computeDependency) { |
| iterDef.computeDependencies(context); |
| } |
| RuntimeIterator rIter = iterDef.getRuntimeIterator(context); |
| context.addToIndependentRuntimeItrMapForIndexCreation(iterDef); |
| context.bindIterator(rIter); |
| } |
| |
| // Save the dependency graph for future updates. |
| if (this.dependencyGraph == null) { |
| this.dependencyGraph = context.getDependencyGraph(); |
| } |
| |
| Support.Assert(this.indexResultSetType != null, |
| "IMQEvaluator::evaluate:The StrcutType should have been initialized during index creation"); |
| |
| doNestedIterations(0, context); |
| } 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 hardcoded 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 collection = rIter.evaluateCollection(this.initContext); |
| if (collection == null) { |
| return; |
| } |
| for (Object aCollection : collection) { |
| rIter.setCurrent(aCollection); |
| 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 boolean |
| * 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 true & additional |
| * projection attribute is null, then the 0th iterator itself will evaluate to Region.Entry |
| * Object. |
| * <p> |
| * 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 currrentRuntimeIters) |
| throws FunctionDomainException, TypeMismatchException, NameResolutionException, |
| QueryInvocationTargetException, IMQException { |
| |
| if (QueryMonitor.isLowMemory()) { |
| throw new IMQException( |
| "Index creation canceled due to low memory"); |
| } |
| |
| NonTXEntry temp; |
| |
| // Evaluate NonTXEntry for index on entries or additional projections |
| // on Entry or just entry value. |
| if (this.isFirstItrOnEntry && this.additionalProj != null) { |
| temp = (NonTXEntry) this.additionalProj.evaluate(this.initContext); |
| } else { |
| temp = (NonTXEntry) ((RuntimeIterator) currrentRuntimeIters.get(0)) |
| .evaluate(this.initContext); |
| } |
| |
| RegionEntry re = temp.getRegionEntry(); |
| Object indxResultSet; |
| |
| if (this.iteratorSize == 1) { |
| indxResultSet = this.isFirstItrOnEntry |
| ? this.additionalProj == null ? temp |
| : ((RuntimeIterator) currrentRuntimeIters.get(0)).evaluate(this.initContext) |
| : this.additionalProj.evaluate(this.initContext); |
| } else { |
| Object[] tuple = new Object[this.iteratorSize]; |
| int i = this.isFirstItrOnEntry ? 0 : 1; |
| for (; i < this.iteratorSize; i++) { |
| RuntimeIterator iter = (RuntimeIterator) currrentRuntimeIters.get(i); |
| tuple[i] = iter.evaluate(this.initContext); |
| } |
| if (!this.isFirstItrOnEntry) { |
| tuple[0] = this.additionalProj.evaluate(this.initContext); |
| } |
| 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); |
| } |
| |
| // Key must be evaluated after indexResultSet evaluation is done as Entry might be getting |
| // destroyed and so if value is UNDEFINED, key will definitely will be UNDEFINED. |
| Object indexKey = this.isFirstItrOnEntry ? this.indexedExpr.evaluate(this.initContext) |
| : this.modifiedIndexExpr.evaluate(this.initContext); |
| // based on the first key convert the rest to PdxString or String |
| if (!AbstractIndex.this.isIndexedPdxKeysFlagSet) { |
| setPdxStringFlag(indexKey); |
| } |
| indexKey = getPdxStringForIndexedPdxKeys(indexKey); |
| addMapping(indexKey, indxResultSet, re); |
| } |
| |
| private void doNestedIterations(int level, ExecutionContext context) |
| throws TypeMismatchException, AmbiguousNameException, FunctionDomainException, |
| NameResolutionException, QueryInvocationTargetException, IMQException { |
| |
| List iterList = context.getCurrentIterators(); |
| if (level == this.iteratorSize) { |
| applyProjection(context); |
| } else { |
| RuntimeIterator rIter = (RuntimeIterator) iterList.get(level); |
| Collection collection = rIter.evaluateCollection(context); |
| if (collection == null) { |
| return; |
| } |
| for (Object aCollection : collection) { |
| rIter.setCurrent(aCollection); |
| doNestedIterations(level + 1, context); |
| } |
| } |
| } |
| |
| private void applyProjection(ExecutionContext context) |
| throws FunctionDomainException, TypeMismatchException, NameResolutionException, |
| QueryInvocationTargetException, IMQException { |
| |
| List currrentRuntimeIters = context.getCurrentIterators(); |
| Object indexKey = this.indexedExpr.evaluate(context); |
| // based on the first key convert the rest to PdxString or String |
| if (!AbstractIndex.this.isIndexedPdxKeysFlagSet) { |
| setPdxStringFlag(indexKey); |
| } |
| indexKey = getPdxStringForIndexedPdxKeys(indexKey); |
| Object indxResultSet; |
| |
| if (this.iteratorSize == 1) { |
| RuntimeIterator iter = (RuntimeIterator) currrentRuntimeIters.get(0); |
| indxResultSet = iter.evaluate(context); |
| } else { |
| Object tuple[] = new Object[this.iteratorSize]; |
| for (int i = 0; i < this.iteratorSize; i++) { |
| RuntimeIterator iter = (RuntimeIterator) currrentRuntimeIters.get(i); |
| tuple[i] = iter.evaluate(context); |
| } |
| 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); |
| } |
| |
| // Keep Entry value in fly until all keys are evaluated |
| RegionEntry entry = ((DummyQRegion) context.getBindArgument(1)).getEntry(); |
| saveMapping(indexKey, indxResultSet, 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[] 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] = this.addnlProjType; |
| } |
| return len == 1 ? fieldTypes[0] : new StructTypeImpl(this.canonicalIterNames, fieldTypes); |
| } |
| |
| @Override |
| public ObjectType getIndexResultSetType() { |
| return this.indexResultSetType; |
| } |
| |
| boolean isFirstItrOnEntry() { |
| return this.isFirstItrOnEntry; |
| } |
| |
| boolean isFirstItrOnKey() { |
| return this.isFirstItrOnKey; |
| } |
| |
| @Override |
| public List getAllDependentIterators() { |
| return this.fromIterators; |
| } |
| } |
| |
| /** |
| * Checks the limit for the resultset for distinct and non-distinct queries separately. In case of |
| * non-distinct distinct elements size of result-set is matched against limit passed in as an |
| * argument. |
| * |
| * @return true if limit is satisfied. |
| */ |
| boolean verifyLimit(Collection result, int limit) { |
| return limit > 0 && result.size() == limit; |
| } |
| |
| /** |
| * This will verify the consistency between RegionEntry and IndexEntry. RangeIndex has following |
| * entry structure, |
| * |
| * IndexKey --> [RegionEntry, [Iterator1, Iterator2....., IteratorN]] |
| * |
| * Where Iterator1 to IteratorN are iterators defined in index from clause. |
| * |
| * For example: "/portfolio p, p.positions.values pos" from clause has two iterators where p is |
| * independent iterator and pos is dependent iterator. |
| * |
| * Query iterators can be a subset, superset or exact match of index iterators. But we take query |
| * iterators which are matching with index iterators to evaluate RegionEntry for new value and |
| * compare it with index value which could be a plain object or a Struct. |
| * |
| * Note: Struct evaluated from RegionEntry can NOT have more field values than Index Value Struct |
| * as we filter out iterators in query context before evaluating Struct from RegionEntry. |
| * |
| * @return True if Region and Index entries are consistent. |
| */ |
| // package-private to avoid synthetic accessor |
| boolean verifyEntryAndIndexValue(RegionEntry re, Object value, ExecutionContext context) { |
| IMQEvaluator evaluator = (IMQEvaluator) getEvaluator(); |
| List valuesInRegion = null; |
| Object valueInIndex = null; |
| |
| try { |
| // In a RegionEntry key and Entry itself can not be modified else RegionEntry itself will |
| // change. So no need to verify anything just return true. |
| if (evaluator.isFirstItrOnKey()) { |
| return true; |
| } else if (evaluator.isFirstItrOnEntry()) { |
| valuesInRegion = evaluateIndexIteratorsFromRE(re, context); |
| valueInIndex = verifyAndGetPdxDomainObject(value); |
| } else { |
| RegionEntryContext regionEntryContext = context.getPartitionedRegion() != null |
| ? context.getPartitionedRegion() : (RegionEntryContext) region; |
| Object val = re.getValueInVM(regionEntryContext); |
| if (val instanceof CachedDeserializable) { |
| val = ((CachedDeserializable) val).getDeserializedValue(getRegion(), re); |
| } |
| val = verifyAndGetPdxDomainObject(val); |
| valueInIndex = verifyAndGetPdxDomainObject(value); |
| valuesInRegion = evaluateIndexIteratorsFromRE(val, context); |
| } |
| } catch (Exception e) { |
| if (logger.isDebugEnabled()) { |
| logger.debug( |
| "Exception occurred while verifying a Region Entry value during a Query when the Region Entry is under update operation", |
| e); |
| } |
| } |
| |
| // We could have many index keys available in one Region entry or just one. |
| if (!valuesInRegion.isEmpty()) { |
| for (Object valueInRegion : valuesInRegion) { |
| if (compareStructWithNonStruct(valueInRegion, valueInIndex)) { |
| return true; |
| } |
| } |
| return false; |
| } else { |
| // Seems like value has been invalidated. |
| return false; |
| } |
| } |
| |
| /** |
| * This method compares two objects in which one could be StructType and other ObjectType. Fur |
| * conditions are possible, Object1 -> Struct Object2-> Struct Object1 -> Struct Object2-> Object |
| * Object1 -> Object Object2-> Struct Object1 -> Object Object2-> Object |
| * |
| * @return true if valueInRegion's all objects are part of valueInIndex. |
| */ |
| private boolean compareStructWithNonStruct(Object valueInRegion, Object valueInIndex) { |
| if (valueInRegion instanceof Struct && valueInIndex instanceof Struct) { |
| Object[] regFields = ((StructImpl) valueInRegion).getFieldValues(); |
| List indFields = Arrays.asList(((StructImpl) valueInIndex).getFieldValues()); |
| for (Object regField : regFields) { |
| if (!indFields.contains(regField)) { |
| return false; |
| } |
| } |
| return true; |
| |
| } else if (valueInRegion instanceof Struct) { |
| Object[] fields = ((StructImpl) valueInRegion).getFieldValues(); |
| for (Object field : fields) { |
| if (field.equals(valueInIndex)) { |
| return true; |
| } |
| } |
| |
| } else if (valueInIndex instanceof Struct) { |
| Object[] fields = ((StructImpl) valueInIndex).getFieldValues(); |
| for (Object field : fields) { |
| if (field.equals(valueInRegion)) { |
| return true; |
| } |
| } |
| } else { |
| return valueInRegion.equals(valueInIndex); |
| } |
| return false; |
| } |
| |
| /** |
| * Returns evaluated collection for dependent runtime iterator for this index from clause and |
| * given RegionEntry. |
| * |
| * @param context passed here is query context. |
| * @return Evaluated second level collection. |
| */ |
| private List evaluateIndexIteratorsFromRE(Object value, ExecutionContext context) |
| throws FunctionDomainException, TypeMismatchException, NameResolutionException, |
| QueryInvocationTargetException { |
| |
| // We need NonTxEntry to call getValue() on it. RegionEntry does |
| // NOT have public getValue() method. |
| if (value instanceof RegionEntry) { |
| value = new NonTXEntry((LocalRegion) getRegion(), (RegionEntry) value); |
| } |
| // Get all Independent and dependent iterators for this Index. |
| List itrs = getAllDependentRuntimeIterators(context); |
| |
| return evaluateLastColl(value, context, itrs, 0); |
| } |
| |
| private List evaluateLastColl(Object value, ExecutionContext context, List itrs, int level) |
| throws FunctionDomainException, TypeMismatchException, NameResolutionException, |
| QueryInvocationTargetException { |
| |
| // A tuple is a value generated from RegionEntry value which could be a StructType (Multiple |
| // Dependent Iterators) or ObjectType (Single Iterator) value. |
| List tuples = new ArrayList(1); |
| |
| RuntimeIterator currItrator = (RuntimeIterator) itrs.get(level); |
| currItrator.setCurrent(value); |
| |
| // If its last iterator then just evaluate final struct. |
| if (itrs.size() - 1 == level) { |
| if (itrs.size() > 1) { |
| Object[] tuple = new Object[itrs.size()]; |
| for (int i = 0; i < itrs.size(); i++) { |
| RuntimeIterator iter = (RuntimeIterator) itrs.get(i); |
| tuple[i] = iter.evaluate(context); |
| } |
| // Its ok to pass type as null as we are only interested in values. |
| tuples.add(new StructImpl(new StructTypeImpl(), tuple)); |
| } else { |
| tuples.add(currItrator.evaluate(context)); |
| } |
| } else { |
| // Not the last iterator. |
| RuntimeIterator nextItr = (RuntimeIterator) itrs.get(level + 1); |
| Collection nextLevelValues = nextItr.evaluateCollection(context); |
| |
| // If value is null or INVALID then the evaluated collection would be Null. |
| if (nextLevelValues != null) { |
| for (Object nextLevelValue : nextLevelValues) { |
| tuples.addAll(evaluateLastColl(nextLevelValue, context, itrs, level + 1)); |
| } |
| } |
| } |
| return tuples; |
| } |
| |
| /** |
| * Matches the Collection reference in given context for this index's from-clause in all current |
| * independent collection references associated to the context. For example, if a join Query has |
| * "/region1 p, region2 e" from clause context contains two region references for p and e and |
| * Index could be used for any of those of both of those regions. |
| * |
| * Note: This Index contains its own from clause definition which corresponds to a region |
| * collection reference in given context and must be contained at 0th index in |
| * {@link AbstractIndex#canonicalizedDefinitions}. |
| * |
| * @return {@link RuntimeIterator} this should not be null ever. |
| */ |
| RuntimeIterator getRuntimeIteratorForThisIndex(ExecutionContext context) { |
| List<RuntimeIterator> indItrs = context.getCurrentIterators(); |
| Region rgn = this.getRegion(); |
| if (rgn instanceof BucketRegion) { |
| rgn = ((Bucket) rgn).getPartitionedRegion(); |
| } |
| String regionPath = rgn.getFullPath(); |
| String definition = this.getCanonicalizedIteratorDefinitions()[0]; |
| for (RuntimeIterator itr : indItrs) { |
| if (itr.getDefinition().equals(regionPath) || itr.getDefinition().equals(definition)) { |
| return itr; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Similar to {@link #getRuntimeIteratorForThisIndex(ExecutionContext)} except that this one also |
| * matches the iterator name if present with alias used in the {@link IndexInfo} |
| * |
| * @return {@link RuntimeIterator} |
| */ |
| RuntimeIterator getRuntimeIteratorForThisIndex(ExecutionContext context, IndexInfo info) { |
| List<RuntimeIterator> indItrs = context.getCurrentIterators(); |
| Region rgn = this.getRegion(); |
| if (rgn instanceof BucketRegion) { |
| rgn = ((Bucket) rgn).getPartitionedRegion(); |
| } |
| String regionPath = rgn.getFullPath(); |
| String definition = this.getCanonicalizedIteratorDefinitions()[0]; |
| for (RuntimeIterator itr : indItrs) { |
| if (itr.getDefinition().equals(regionPath) || itr.getDefinition().equals(definition)) { |
| // if iterator has name alias must be used in the query |
| if (itr.getName() != null) { |
| CompiledValue path = info._path(); |
| // match the iterator name with alias |
| String pathName = getReceiverNameFromPath(path); |
| if (path.getType() == OQLLexerTokenTypes.Identifier || itr.getName().equals(pathName)) { |
| return itr; |
| } |
| } else { |
| return itr; |
| } |
| } |
| } |
| return null; |
| } |
| |
| private String getReceiverNameFromPath(CompiledValue path) { |
| if (path instanceof CompiledID) { |
| return ((CompiledID) path).getId(); |
| } else if (path instanceof CompiledPath) { |
| return getReceiverNameFromPath(path.getReceiver()); |
| } else if (path instanceof CompiledOperation) { |
| return getReceiverNameFromPath(path.getReceiver()); |
| } else if (path instanceof CompiledIndexOperation) { |
| return getReceiverNameFromPath(path.getReceiver()); |
| } |
| return ""; |
| } |
| |
| /** |
| * Take all independent iterators from context and remove the one which matches for this Index's |
| * independent iterator. Then get all Dependent iterators from given context for this Index's |
| * independent iterator. |
| * |
| * @param context from executing query. |
| * @return List of all iterators pertaining to this Index. |
| */ |
| private List getAllDependentRuntimeIterators(ExecutionContext context) { |
| List<RuntimeIterator> indItrs = context |
| .getCurrScopeDpndntItrsBasedOnSingleIndpndntItr(getRuntimeIteratorForThisIndex(context)); |
| |
| List<String> definitions = Arrays.asList(this.getCanonicalizedIteratorDefinitions()); |
| // These are the common iterators between query from clause and index from clause. |
| List itrs = new ArrayList(); |
| |
| for (RuntimeIterator itr : indItrs) { |
| if (definitions.contains(itr.getDefinition())) { |
| itrs.add(itr); |
| } |
| } |
| return itrs; |
| } |
| |
| /** |
| * This map is not thread-safe. We rely on the fact that every thread which is trying to update |
| * this kind of map (In Indexes), must have RegionEntry lock before adding OR removing elements. |
| * |
| * This map does NOT provide an iterator. To iterate over its element caller has to get inside the |
| * map itself through addValuesToCollection() calls. |
| */ |
| class RegionEntryToValuesMap { |
| protected Map map; |
| private final boolean useList; |
| volatile int numValues; |
| |
| RegionEntryToValuesMap(boolean useList) { |
| this.map = new ConcurrentHashMap(2, 0.75f, 1); |
| this.useList = useList; |
| } |
| |
| RegionEntryToValuesMap(Map map, boolean useList) { |
| this.map = map; |
| this.useList = useList; |
| } |
| |
| /** |
| * We do NOT use any locks here as every add is for a RegionEntry which is locked before coming |
| * here. No two threads can be entering in this method together for a RegionEntry. |
| */ |
| public void add(RegionEntry entry, Object value) { |
| assert value != null; |
| // Values must NOT be null and ConcurrentHashMap does not support null values. |
| if (value == null) { |
| return; |
| } |
| Object object = this.map.get(entry); |
| if (object == null) { |
| this.map.put(entry, value); |
| } else if (object instanceof Collection) { |
| Collection coll = (Collection) object; |
| // If its a list query might get ConcurrentModificationException. |
| // This can only happen for Null mapped or Undefined entries in a |
| // RangeIndex. So we are synchronizing on ArrayList. |
| if (this.useList) { |
| synchronized (coll) { |
| coll.add(value); |
| } |
| } else { |
| coll.add(value); |
| } |
| } else { |
| Collection coll = this.useList ? new ArrayList(2) : new IndexConcurrentHashSet(2, 0.75f, 1); |
| coll.add(object); |
| coll.add(value); |
| this.map.put(entry, coll); |
| } |
| atomicUpdater.incrementAndGet(this); |
| } |
| |
| public void addAll(RegionEntry entry, Collection values) { |
| Object object = this.map.get(entry); |
| if (object == null) { |
| Collection coll = this.useList ? new ArrayList(values.size()) |
| : new IndexConcurrentHashSet(values.size(), 0.75f, 1); |
| coll.addAll(values); |
| this.map.put(entry, coll); |
| atomicUpdater.addAndGet(this, values.size()); |
| } else if (object instanceof Collection) { |
| Collection coll = (Collection) object; |
| // If its a list query might get ConcurrentModificationException. |
| // This can only happen for Null mapped or Undefined entries in a |
| // RangeIndex. So we are synchronizing on ArrayList. |
| if (this.useList) { |
| synchronized (coll) { |
| coll.addAll(values); |
| } |
| } else { |
| coll.addAll(values); |
| } |
| } else { |
| Collection coll = this.useList ? new ArrayList(values.size() + 1) |
| : new IndexConcurrentHashSet(values.size() + 1, 0.75f, 1); |
| coll.addAll(values); |
| coll.add(object); |
| this.map.put(entry, coll); |
| } |
| atomicUpdater.addAndGet(this, values.size()); |
| } |
| |
| public Object get(RegionEntry entry) { |
| return this.map.get(entry); |
| } |
| |
| /** |
| * We do NOT use any locks here as every remove is for a RegionEntry which is locked before |
| * coming here. No two threads can be entering in this method together for a RegionEntry. |
| */ |
| public void remove(RegionEntry entry, Object value) { |
| Object object = this.map.get(entry); |
| if (object == null) |
| return; |
| if (object instanceof Collection) { |
| Collection coll = (Collection) object; |
| boolean removed; |
| // If its a list query might get ConcurrentModificationException. |
| // This can only happen for Null mapped or Undefined entries in a |
| // RangeIndex. So we are synchronizing on ArrayList. |
| if (this.useList) { |
| synchronized (coll) { |
| removed = coll.remove(value); |
| } |
| } else { |
| removed = coll.remove(value); |
| } |
| if (removed) { |
| if (coll.size() == 0) { |
| this.map.remove(entry); |
| } |
| atomicUpdater.decrementAndGet(this); |
| } |
| } else { |
| if (object.equals(value)) { |
| this.map.remove(entry); |
| } |
| atomicUpdater.decrementAndGet(this); |
| } |
| } |
| |
| public Object remove(RegionEntry entry) { |
| Object retVal = this.map.remove(entry); |
| if (retVal != null) { |
| atomicUpdater.addAndGet(this, |
| retVal instanceof Collection ? -((Collection) retVal).size() : -1); |
| } |
| return retVal; |
| } |
| |
| int getNumValues(RegionEntry entry) { |
| Object object = this.map.get(entry); |
| if (object == null) |
| return 0; |
| if (object instanceof Collection) { |
| Collection coll = (Collection) object; |
| return coll.size(); |
| } else { |
| return 1; |
| } |
| } |
| |
| public int getNumValues() { |
| return atomicUpdater.get(this); |
| } |
| |
| public int getNumEntries() { |
| return this.map.keySet().size(); |
| } |
| |
| void addValuesToCollection(Collection result, int limit, ExecutionContext context) { |
| for (final Object o : this.map.entrySet()) { |
| // Check if query execution on this thread is canceled. |
| QueryMonitor.throwExceptionIfQueryOnCurrentThreadIsCanceled(); |
| if (this.verifyLimit(result, limit, context)) { |
| return; |
| } |
| Entry e = (Entry) o; |
| Object value = e.getValue(); |
| assert value != null; |
| |
| RegionEntry re = (RegionEntry) e.getKey(); |
| boolean reUpdateInProgress = re.isUpdateInProgress(); |
| if (value instanceof Collection) { |
| // If its a list query might get ConcurrentModificationException. |
| // This can only happen for Null mapped or Undefined entries in a |
| // RangeIndex. So we are synchronizing on ArrayList. |
| if (this.useList) { |
| synchronized (value) { |
| for (Object val : (Iterable) value) { |
| // Compare the value in index with in RegionEntry. |
| if (!reUpdateInProgress || verifyEntryAndIndexValue(re, val, context)) { |
| result.add(val); |
| } |
| if (limit != -1) { |
| if (result.size() == limit) { |
| return; |
| } |
| } |
| } |
| } |
| } else { |
| for (Object val : (Iterable) value) { |
| // Compare the value in index with in RegionEntry. |
| if (!reUpdateInProgress || verifyEntryAndIndexValue(re, val, context)) { |
| result.add(val); |
| } |
| if (limit != -1) { |
| if (this.verifyLimit(result, limit, context)) { |
| return; |
| } |
| } |
| } |
| } |
| } else { |
| if (!reUpdateInProgress || verifyEntryAndIndexValue(re, value, context)) { |
| if (context.isCqQueryContext()) { |
| result.add(new CqEntry(((RegionEntry) e.getKey()).getKey(), value)); |
| } else { |
| result.add(verifyAndGetPdxDomainObject(value)); |
| } |
| } |
| } |
| } |
| } |
| |
| void addValuesToCollection(Collection result, CompiledValue iterOp, RuntimeIterator runtimeItr, |
| ExecutionContext context, List projAttrib, SelectResults intermediateResults, |
| boolean isIntersection, int limit) throws FunctionDomainException, TypeMismatchException, |
| NameResolutionException, QueryInvocationTargetException { |
| |
| if (this.verifyLimit(result, limit, context)) { |
| return; |
| } |
| |
| for (Object o : this.map.entrySet()) { |
| // Check if query execution on this thread is canceled. |
| QueryMonitor.throwExceptionIfQueryOnCurrentThreadIsCanceled(); |
| Entry e = (Entry) o; |
| Object value = e.getValue(); |
| // Key is a RegionEntry here. |
| RegionEntry entry = (RegionEntry) e.getKey(); |
| if (value != null) { |
| boolean reUpdateInProgress = false; |
| if (entry.isUpdateInProgress()) { |
| reUpdateInProgress = true; |
| } |
| if (value instanceof Collection) { |
| // If its a list query might get ConcurrentModificationException. |
| // This can only happen for Null mapped or Undefined entries in a |
| // RangeIndex. So we are synchronizing on ArrayList. |
| if (this.useList) { |
| synchronized (value) { |
| for (Object o1 : ((Iterable) value)) { |
| boolean ok = true; |
| if (reUpdateInProgress) { |
| // Compare the value in index with value in RegionEntry. |
| ok = verifyEntryAndIndexValue(entry, o1, context); |
| } |
| if (ok && runtimeItr != null) { |
| runtimeItr.setCurrent(o1); |
| ok = QueryUtils.applyCondition(iterOp, context); |
| } |
| if (ok) { |
| applyProjection(projAttrib, context, result, o1, intermediateResults, |
| isIntersection); |
| if (limit != -1 && result.size() == limit) { |
| return; |
| } |
| } |
| } |
| } |
| } else { |
| for (Object o1 : ((Iterable) value)) { |
| boolean ok = true; |
| if (reUpdateInProgress) { |
| // Compare the value in index with value in RegionEntry. |
| ok = verifyEntryAndIndexValue(entry, o1, context); |
| } |
| if (ok && runtimeItr != null) { |
| runtimeItr.setCurrent(o1); |
| ok = QueryUtils.applyCondition(iterOp, context); |
| } |
| if (ok) { |
| applyProjection(projAttrib, context, result, o1, intermediateResults, |
| isIntersection); |
| if (this.verifyLimit(result, limit, context)) { |
| return; |
| } |
| } |
| } |
| } |
| } else { |
| boolean ok = true; |
| if (reUpdateInProgress) { |
| // Compare the value in index with in RegionEntry. |
| ok = verifyEntryAndIndexValue(entry, value, context); |
| } |
| if (ok && runtimeItr != null) { |
| runtimeItr.setCurrent(value); |
| ok = QueryUtils.applyCondition(iterOp, context); |
| } |
| if (ok) { |
| if (context.isCqQueryContext()) { |
| result.add(new CqEntry(((RegionEntry) e.getKey()).getKey(), value)); |
| } else { |
| applyProjection(projAttrib, context, result, value, intermediateResults, |
| isIntersection); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private boolean verifyLimit(Collection result, int limit, ExecutionContext context) { |
| if (limit > 0) { |
| if (!context.isDistinct()) { |
| return result.size() == limit; |
| } else if (result.size() == limit) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public boolean containsEntry(RegionEntry entry) { |
| return this.map.containsKey(entry); |
| } |
| |
| public boolean containsValue(Object value) { |
| throw new RuntimeException( |
| "Not yet implemented"); |
| } |
| |
| public void clear() { |
| this.map.clear(); |
| atomicUpdater.set(this, 0); |
| } |
| |
| public Set entrySet() { |
| return this.map.entrySet(); |
| } |
| |
| /** |
| * This replaces a key's value along with updating the numValues correctly. |
| */ |
| public void replace(RegionEntry entry, Object values) { |
| int numOldValues = getNumValues(entry); |
| this.map.put(entry, values); |
| atomicUpdater.addAndGet(this, |
| (values instanceof Collection ? ((Collection) values).size() : 1) - numOldValues); |
| } |
| } |
| |
| /** |
| * This will populate resultSet from both type of indexes, {@link CompactRangeIndex} and |
| * {@link RangeIndex}. |
| */ |
| void populateListForEquiJoin(List list, Object outerEntries, Object innerEntries, |
| ExecutionContext context, Object key) throws FunctionDomainException, TypeMismatchException, |
| NameResolutionException, QueryInvocationTargetException { |
| |
| Assert.assertTrue(outerEntries != null && innerEntries != null, |
| "OuterEntries or InnerEntries must not be null"); |
| |
| Object[][] values = new Object[2][]; |
| Iterator itr = null; |
| int j = 0; |
| |
| while (j < 2) { |
| boolean isRangeIndex = false; |
| if (j == 0) { |
| if (outerEntries instanceof RegionEntryToValuesMap) { |
| itr = ((RegionEntryToValuesMap) outerEntries).map.entrySet().iterator(); |
| isRangeIndex = true; |
| } else if (outerEntries instanceof CloseableIterator) { |
| itr = (Iterator) outerEntries; |
| } |
| } else { |
| if (innerEntries instanceof RegionEntryToValuesMap) { |
| itr = ((RegionEntryToValuesMap) innerEntries).map.entrySet().iterator(); |
| isRangeIndex = true; |
| } else if (innerEntries instanceof CloseableIterator) { |
| itr = (Iterator) innerEntries; |
| } |
| } |
| |
| // extract the values from the RegionEntries |
| List dummy = new ArrayList(); |
| RegionEntry re = null; |
| IndexStoreEntry ie = null; |
| Object val = null; |
| Object entryVal = null; |
| |
| IndexInfo[] indexInfo = (IndexInfo[]) context.cacheGet(CompiledValue.INDEX_INFO); |
| IndexInfo indInfo = indexInfo[j]; |
| |
| while (itr.hasNext()) { |
| if (isRangeIndex) { |
| Map.Entry entry = (Map.Entry) itr.next(); |
| val = entry.getValue(); |
| if (val instanceof Collection) { |
| entryVal = ((Iterable) val).iterator().next(); |
| } else { |
| entryVal = val; |
| } |
| re = (RegionEntry) entry.getKey(); |
| } else { |
| ie = (IndexStoreEntry) itr.next(); |
| } |
| |
| // Bug#41010: We need to verify if Inner and Outer Entries |
| // are consistent with index key values. |
| boolean ok = true; |
| if (isRangeIndex) { |
| if (re.isUpdateInProgress()) { |
| ok = ((RangeIndex) indInfo._getIndex()).verifyEntryAndIndexValue(re, entryVal, context); |
| } |
| } else if (ie.isUpdateInProgress()) { |
| ok = ((CompactRangeIndex) indInfo._getIndex()).verifyInnerAndOuterEntryValues(ie, context, |
| indInfo, key); |
| } |
| if (ok) { |
| if (isRangeIndex) { |
| if (val instanceof Collection) { |
| dummy.addAll((Collection) val); |
| } else { |
| dummy.add(val); |
| } |
| } else { |
| if (IndexManager.IS_TEST_EXPANSION) { |
| dummy.addAll(((CompactRangeIndex) indInfo._getIndex()).expandValue(context, key, null, |
| OQLLexerTokenTypes.TOK_EQ, -1, ie.getDeserializedValue())); |
| } else { |
| dummy.add(ie.getDeserializedValue()); |
| } |
| } |
| } |
| } |
| Object[] newValues = new Object[dummy.size()]; |
| dummy.toArray(newValues); |
| values[j++] = newValues; |
| } |
| list.add(values); |
| } |
| |
| /** |
| * Sets the isIndexedPdxKeys flag indicating if all the keys in the index are Strings or |
| * PdxStrings. Also sets another flag isIndexedPdxKeysFlagSet that indicates isIndexedPdxKeys has |
| * been set/reset to avoid frequent calculation of map size |
| */ |
| synchronized void setPdxStringFlag(Object key) { |
| // For Null and Undefined keys do not set the isIndexedPdxKeysFlagSet flag |
| if (isIndexedPdxKeysFlagSet || key == null || key == IndexManager.NULL |
| || key == QueryService.UNDEFINED) { |
| return; |
| } |
| if (!this.isIndexedPdxKeys) { |
| if (key instanceof PdxString && this.region.getAttributes().getCompressor() == null) { |
| this.isIndexedPdxKeys = true; |
| } |
| } |
| this.isIndexedPdxKeysFlagSet = true; |
| } |
| |
| /** |
| * Converts Strings to PdxStrings and vice-versa based on the isIndexedPdxKeys flag |
| * |
| * @return PdxString or String based on isIndexedPdxKeys flag |
| */ |
| Object getPdxStringForIndexedPdxKeys(Object key) { |
| if (this.isIndexedPdxKeys) { |
| if (key instanceof String) { |
| return new PdxString((String) key); |
| } |
| } else if (key instanceof PdxString) { |
| return key.toString(); |
| } |
| return key; |
| } |
| |
| boolean acquireIndexReadLockForRemove() { |
| boolean success = this.removeIndexLock.readLock().tryLock(); |
| if (success) { |
| this.internalIndexStats.incReadLockCount(1); |
| if (logger.isDebugEnabled()) { |
| logger.debug("Acquired read lock on index {}", this.getName()); |
| } |
| } |
| return success; |
| } |
| |
| public void releaseIndexReadLockForRemove() { |
| this.removeIndexLock.readLock().unlock(); |
| this.internalIndexStats.incReadLockCount(-1); |
| if (logger.isDebugEnabled()) { |
| logger.debug("Released read lock on index {}", this.getName()); |
| } |
| } |
| |
| /** |
| * This makes current thread wait until all query threads are done using it. |
| */ |
| public void acquireIndexWriteLockForRemove() { |
| final boolean isDebugEnabled = logger.isDebugEnabled(); |
| if (isDebugEnabled) { |
| logger.debug("Acquiring write lock on Index {}", this.getName()); |
| } |
| this.removeIndexLock.writeLock().lock(); |
| if (isDebugEnabled) { |
| logger.debug("Acquired write lock on index {}", this.getName()); |
| } |
| } |
| |
| public void releaseIndexWriteLockForRemove() { |
| final boolean isDebugEnabled = logger.isDebugEnabled(); |
| if (isDebugEnabled) { |
| logger.debug("Releasing write lock on Index {}", this.getName()); |
| } |
| this.removeIndexLock.writeLock().unlock(); |
| if (isDebugEnabled) { |
| logger.debug("Released write lock on Index {}", this.getName()); |
| } |
| } |
| |
| public boolean isPopulated() { |
| return this.isPopulated; |
| } |
| |
| public void setPopulated(boolean isPopulated) { |
| this.isPopulated = isPopulated; |
| } |
| |
| boolean isIndexOnPdxKeys() { |
| return isIndexedPdxKeys; |
| } |
| } |