/* * 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.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apache.geode.cache.Region; import org.apache.geode.cache.RegionAttributes; 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.QueryException; 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.CompiledValue; import org.apache.geode.cache.query.internal.ExecutionContext; import org.apache.geode.cache.query.internal.MapIndexable; import org.apache.geode.cache.query.internal.RuntimeIterator; import org.apache.geode.cache.query.types.ObjectType; import org.apache.geode.internal.cache.BucketRegion; import org.apache.geode.internal.cache.RegionEntry; public abstract class AbstractMapIndex extends AbstractIndex { final protected boolean isAllKeys; final String[] patternStr; protected final Map<Object, AbstractIndex> mapKeyToValueIndex; protected final Object[] mapKeys; AbstractMapIndex(String indexName, Region region, String fromClause, String indexedExpression, String projectionAttributes, String origFromClause, String origIndxExpr, String[] defintions, boolean isAllKeys, String[] multiIndexingKeysPattern, Object[] mapKeys, IndexStatistics stats) { super(indexName, region, fromClause, indexedExpression, projectionAttributes, origFromClause, origIndxExpr, defintions, stats); this.mapKeyToValueIndex = new ConcurrentHashMap<Object, AbstractIndex>(2, 0.75f, 1); RegionAttributes ra = region.getAttributes(); this.isAllKeys = isAllKeys; this.mapKeys = mapKeys; if (this.isAllKeys) { this.patternStr = new String[] {new StringBuilder(indexedExpression) .deleteCharAt(indexedExpression.length() - 2).toString()}; } else { this.patternStr = multiIndexingKeysPattern; } } @Override void addMapping(RegionEntry entry) throws IMQException { this.evaluator.evaluate(entry, true); } protected InternalIndexStatistics createStats(String indexName) { // PartitionedIndexStatistics are used for PR if (!(this.region instanceof BucketRegion)) { return new MapIndexStatistics(indexName); } else { return new InternalIndexStatistics() {}; } } class MapIndexStatistics extends InternalIndexStatistics { private IndexStats vsdStats; public MapIndexStatistics(String indexName) { this.vsdStats = new IndexStats(getRegion().getCache().getDistributedSystem(), indexName); } /** * Return the total number of times this index has been updated */ public long getNumUpdates() { return this.vsdStats.getNumUpdates(); } public void incNumValues(int delta) { this.vsdStats.incNumValues(delta); } public void incNumUpdates() { this.vsdStats.incNumUpdates(); } public void incNumUpdates(int delta) { this.vsdStats.incNumUpdates(delta); } public void updateNumKeys(long numKeys) { this.vsdStats.updateNumKeys(numKeys); } public void incNumMapIndexKeys(long numKeys) { this.vsdStats.incNumMapIndexKeys(numKeys); } public void incNumKeys(long numKeys) { this.vsdStats.incNumKeys(numKeys); } public void incUpdateTime(long delta) { this.vsdStats.incUpdateTime(delta); } public void incUpdatesInProgress(int delta) { this.vsdStats.incUpdatesInProgress(delta); } public void incNumUses() { this.vsdStats.incNumUses(); } public void incUseTime(long delta) { this.vsdStats.incUseTime(delta); } public void incUsesInProgress(int delta) { this.vsdStats.incUsesInProgress(delta); } public void incReadLockCount(int delta) { this.vsdStats.incReadLockCount(delta); } /** * Returns the total amount of time (in nanoseconds) spent updating this index. */ public long getTotalUpdateTime() { return this.vsdStats.getTotalUpdateTime(); } /** * Returns the total number of times this index has been accessed by a query. */ public long getTotalUses() { return this.vsdStats.getTotalUses(); } /** * Returns the number of keys in this index at the highest level */ public long getNumberOfMapIndexKeys() { return this.vsdStats.getNumberOfMapIndexKeys(); } /** * Returns the number of keys in this index. */ public long getNumberOfKeys() { return this.vsdStats.getNumberOfKeys(); } /** * Returns the number of values in this index. */ public long getNumberOfValues() { return this.vsdStats.getNumberOfValues(); } /** * Return the number of values for the specified key in this index. */ public long getNumberOfValues(Object key) { long numValues = 0; for (Object ind : mapKeyToValueIndex.values()) { numValues += ((AbstractIndex) ind).getStatistics().getNumberOfValues(key); } return numValues; } /** * Return the number of read locks taken on this index */ public int getReadLockCount() { return this.vsdStats.getReadLockCount(); } public void close() { this.vsdStats.close(); } public String toString() { StringBuffer sb = new StringBuffer(); sb.append("No Keys = ").append(getNumberOfKeys()).append("\n"); sb.append("No Map Index Keys = ").append(getNumberOfMapIndexKeys()).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(); } } @Override public ObjectType getResultSetType() { return this.evaluator.getIndexResultSetType(); } @Override void instantiateEvaluator(IndexCreationHelper ich) { this.evaluator = new IMQEvaluator(ich); } @Override public void initializeIndex(boolean loadEntries) throws IMQException { evaluator.initializeIndex(loadEntries); } @Override void lockedQuery(Object key, int operator, Collection results, CompiledValue iterOps, RuntimeIterator runtimeItr, ExecutionContext context, List projAttrib, SelectResults intermediateResults, boolean isIntersection) throws TypeMismatchException, FunctionDomainException, NameResolutionException, QueryInvocationTargetException { Object[] mapKeyAndVal = (Object[]) key; AbstractIndex ri = this.mapKeyToValueIndex.get(mapKeyAndVal[1]); if (ri != null) { ri.lockedQuery(mapKeyAndVal[0], operator, results, iterOps, runtimeItr, context, projAttrib, intermediateResults, isIntersection); } } @Override void lockedQuery(Object lowerBoundKey, int lowerBoundOperator, Object upperBoundKey, int upperBoundOperator, Collection results, Set keysToRemove, ExecutionContext context) throws TypeMismatchException, FunctionDomainException, NameResolutionException, QueryInvocationTargetException { throw new UnsupportedOperationException( "Range grouping for MapIndex condition is not supported"); } @Override void lockedQuery(Object key, int operator, Collection results, Set keysToRemove, ExecutionContext context) throws TypeMismatchException, FunctionDomainException, NameResolutionException, QueryInvocationTargetException { Object[] mapKeyAndVal = (Object[]) key; AbstractIndex ri = this.mapKeyToValueIndex.get(mapKeyAndVal[1]); if (ri != null) { ri.lockedQuery(mapKeyAndVal[0], operator, results, keysToRemove, context); } } abstract void recreateIndexData() throws IMQException; protected abstract void removeMapping(RegionEntry entry, int opCode) throws IMQException; public boolean clear() throws QueryException { throw new UnsupportedOperationException("MapType Index method not supported"); } public int getSizeEstimate(Object key, int op, int matchLevel) throws TypeMismatchException { Object[] mapKeyAndVal = (Object[]) key; Object mapKey = mapKeyAndVal[1]; AbstractIndex ri = this.mapKeyToValueIndex.get(mapKey); if (ri != null) { return ri.getSizeEstimate(mapKeyAndVal[0], op, matchLevel); } else { return 0; } } @Override protected boolean isCompactRangeIndex() { return false; } public IndexType getType() { return IndexType.FUNCTIONAL; } @Override public boolean isMapType() { return true; } @Override void addMapping(Object key, Object value, RegionEntry entry) throws IMQException { if (key == QueryService.UNDEFINED || !(key instanceof Map)) { return; } if (this.isAllKeys) { Iterator<Map.Entry<?, ?>> entries = ((Map) key).entrySet().iterator(); while (entries.hasNext()) { Map.Entry<?, ?> mapEntry = entries.next(); Object mapKey = mapEntry.getKey(); Object indexKey = mapEntry.getValue(); this.doIndexAddition(mapKey, indexKey, value, entry); } } else { for (Object mapKey : mapKeys) { Object indexKey = ((Map) key).get(mapKey); if (indexKey != null) { this.doIndexAddition(mapKey, indexKey, value, entry); } } } } @Override void saveMapping(Object key, Object value, RegionEntry entry) throws IMQException { if (key == QueryService.UNDEFINED || !(key instanceof Map)) { return; } if (this.isAllKeys) { Iterator<Map.Entry<?, ?>> entries = ((Map) key).entrySet().iterator(); while (entries.hasNext()) { Map.Entry<?, ?> mapEntry = entries.next(); Object mapKey = mapEntry.getKey(); Object indexKey = mapEntry.getValue(); this.saveIndexAddition(mapKey, indexKey, value, entry); } } else { for (Object mapKey : mapKeys) { Object indexKey = ((Map) key).get(mapKey); if (indexKey != null) { this.saveIndexAddition(mapKey, indexKey, value, entry); } } } } protected abstract void doIndexAddition(Object mapKey, Object indexKey, Object value, RegionEntry entry) throws IMQException; protected abstract void saveIndexAddition(Object mapKey, Object indexKey, Object value, RegionEntry entry) throws IMQException; public Map<Object, AbstractIndex> getRangeIndexHolderForTesting() { return Collections.unmodifiableMap(this.mapKeyToValueIndex); } public String[] getPatternsForTesting() { return this.patternStr; } public Object[] getMapKeysForTesting() { return this.mapKeys; } public abstract boolean containsEntry(RegionEntry entry); @Override public boolean isMatchingWithIndexExpression(CompiledValue condnExpr, String conditionExprStr, ExecutionContext context) throws AmbiguousNameException, TypeMismatchException, NameResolutionException { if (this.isAllKeys) { // check if the conditionExps is of type MapIndexable.If yes then check // the canonicalized string // stripped of the index arg & see if it matches. if (condnExpr instanceof MapIndexable) { MapIndexable mi = (MapIndexable) condnExpr; CompiledValue recvr = mi.getRecieverSansIndexArgs(); StringBuffer sb = new StringBuffer(); recvr.generateCanonicalizedExpression(sb, context); sb.append('[').append(']'); return sb.toString().equals(this.patternStr[0]); } else { return false; } } else { for (String expr : this.patternStr) { if (expr.equals(conditionExprStr)) { return true; } } return false; } } @Override public boolean isEmpty() { return mapKeyToValueIndex.size() == 0 ? true : false; } }