diff options
Diffstat (limited to 'src')
7 files changed, 386 insertions, 296 deletions
| diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/DependentStorableFetcher.java b/src/main/java/com/amazon/carbonado/repo/indexed/DependentStorableFetcher.java index 98454f1..d9e9f32 100644 --- a/src/main/java/com/amazon/carbonado/repo/indexed/DependentStorableFetcher.java +++ b/src/main/java/com/amazon/carbonado/repo/indexed/DependentStorableFetcher.java @@ -45,9 +45,11 @@ import com.amazon.carbonado.info.StorableProperty;  class DependentStorableFetcher<S extends Storable, D extends Storable> {
      private final IndexedRepository mRepository;
      private final IndexEntryAccessor<D>[] mIndexEntryAccessors;
 -    private final Query<D> mQuery;
 +    private final Filter<D> mFilter;
      private final String[] mJoinProperties;
 +    private Query<D> mQuery;
 +
      /**
       * @param derivedTo special chained property from StorableProperty.getDerivedToProperties
       */
 @@ -81,7 +83,7 @@ class DependentStorableFetcher<S extends Storable, D extends Storable> {          if (accessorList.size() == 0) {
              throw new SupportException
                  ("Unable to find index accessors for derived-to property: " + derivedTo +
 -                 ", enclosing type: " + dType);
 +                 ", enclosing type: " + dType.getName() + ", source type: " + sType.getName());
          }
          // Build a query on D joined to S.
 @@ -100,18 +102,18 @@ class DependentStorableFetcher<S extends Storable, D extends Storable> {          Filter<D> dFilter = Filter.getOpenFilter(dType);
          for (int i=0; i<joinElementCount; i++) {
 -            StorableProperty<S> element = join.getInternalJoinElement(i);
 -            joinProperties[i] = element.getName();
 +            joinProperties[i] = join.getExternalJoinElement(i).getName();
 +            StorableProperty<S> internal = join.getInternalJoinElement(i);
              if (base == null) {
 -                dFilter = dFilter.and(element.getName(), RelOp.EQ);
 +                dFilter = dFilter.and(internal.getName(), RelOp.EQ);
              } else {
 -                dFilter = dFilter.and(base.append(element).toString(), RelOp.EQ);
 +                dFilter = dFilter.and(base.append(internal).toString(), RelOp.EQ);
              }
          }
          mRepository = repository;
          mIndexEntryAccessors = accessorList.toArray(new IndexEntryAccessor[accessorList.size()]);
 -        mQuery = repository.storageFor(dType).query(dFilter);
 +        mFilter = dFilter;
          mJoinProperties = joinProperties;
      }
 @@ -120,7 +122,7 @@ class DependentStorableFetcher<S extends Storable, D extends Storable> {      }
      public Cursor<D> fetchDependenentStorables(S storable) throws FetchException {
 -        Query<D> query = mQuery;
 +        Query<D> query = query();
          for (String property : mJoinProperties) {
              query = query.with(storable.getPropertyValue(property));
          }
 @@ -144,7 +146,7 @@ class DependentStorableFetcher<S extends Storable, D extends Storable> {      @Override
      public int hashCode() {
 -        return mQuery.getFilter().hashCode();
 +        return mFilter.hashCode();
      }
      @Override
 @@ -154,7 +156,7 @@ class DependentStorableFetcher<S extends Storable, D extends Storable> {          }
          if (obj instanceof DependentStorableFetcher) {
              DependentStorableFetcher other = (DependentStorableFetcher) obj;
 -            return mQuery.getFilter().equals(other.mQuery.getFilter())
 +            return mFilter.equals(other.mFilter)
                  && Arrays.equals(mJoinProperties, other.mJoinProperties)
                  && Arrays.equals(mIndexEntryAccessors, other.mIndexEntryAccessors);
          }
 @@ -164,7 +166,21 @@ class DependentStorableFetcher<S extends Storable, D extends Storable> {      @Override
      public String toString() {
          return "DependentStorableFetcher: {indexes=" + Arrays.toString(mIndexEntryAccessors) +
 -            ", query=" + mQuery +
 +            ", filter=" + mFilter +
              ", join properties=" + Arrays.toString(mJoinProperties) + '}';
      }
 +
 +    private Query<D> query() throws FetchException {
 +        // Query is lazily created to avoid stack overflow due to cyclic
 +        // dependencies.
 +        Query<D> query = mQuery;
 +        if (query == null) {
 +            try {
 +                mQuery = query = mRepository.storageFor(mFilter.getStorableType()).query(mFilter);
 +            } catch (RepositoryException e) {
 +                throw e.toFetchException();
 +            }
 +        }
 +        return query;
 +    }
  }
 diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/DerivedIndexesTrigger.java b/src/main/java/com/amazon/carbonado/repo/indexed/DerivedIndexesTrigger.java index 9e69935..2cc3d8f 100644 --- a/src/main/java/com/amazon/carbonado/repo/indexed/DerivedIndexesTrigger.java +++ b/src/main/java/com/amazon/carbonado/repo/indexed/DerivedIndexesTrigger.java @@ -50,7 +50,7 @@ class DerivedIndexesTrigger<S extends Storable, D extends Storable> extends Trig          this(new DependentStorableFetcher(repository, sType, derivedTo));
      }
 -    DerivedIndexesTrigger(DependentStorableFetcher<S, D> fetcher) {
 +    private DerivedIndexesTrigger(DependentStorableFetcher<S, D> fetcher) {
          mFetcher = fetcher;
      }
 diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/IndexAnalysis.java b/src/main/java/com/amazon/carbonado/repo/indexed/IndexAnalysis.java index d65e32e..241f297 100644 --- a/src/main/java/com/amazon/carbonado/repo/indexed/IndexAnalysis.java +++ b/src/main/java/com/amazon/carbonado/repo/indexed/IndexAnalysis.java @@ -20,9 +20,23 @@ package com.amazon.carbonado.repo.indexed;  import java.util.Arrays;
  import java.util.HashSet;
 +import java.util.IdentityHashMap;
 +import java.util.List;
 +import java.util.Map;
  import java.util.Set;
 +import com.amazon.carbonado.FetchException;
 +import com.amazon.carbonado.FetchDeadlockException;
 +import com.amazon.carbonado.FetchTimeoutException;
 +import com.amazon.carbonado.IsolationLevel;
 +import com.amazon.carbonado.Query;
 +import com.amazon.carbonado.RepositoryException;
  import com.amazon.carbonado.Storable;
 +import com.amazon.carbonado.Storage;
 +import com.amazon.carbonado.Transaction;
 +
 +import com.amazon.carbonado.capability.IndexInfo;
 +import com.amazon.carbonado.capability.IndexInfoCapability;
  import com.amazon.carbonado.filter.Filter;
  import com.amazon.carbonado.filter.RelOp;
 @@ -38,12 +52,245 @@ import com.amazon.carbonado.qe.FilteringScore;  import com.amazon.carbonado.qe.StorableIndexSet;
  /**
 - * Collection of static methods which perform index analysis.
 + * Builds various sets of indexes for a Storable type.
   *
   * @author Brian S O'Neill
   * @since 1.2
   */
 -class IndexAnalysis {
 +class IndexAnalysis<S extends Storable> {
 +
 +    final IndexedRepository repository;
 +    final Storage<S> masterStorage;
 +
 +    // The set of indexes that can actually be used for querying. If index
 +    // repair is enabled, this set will be the same as desiredIndexSet.
 +    // Otherwise, it will be the intersection of existingIndexSet and
 +    // desiredIndexSet. In both cases, "free" indexes are added to the set too.
 +    final StorableIndexSet<S> queryableIndexSet;
 +
 +    // The set of indexes that should be removed and no longer managed. If
 +    // index repair is enabled, this set will be the existingIndexSet minus
 +    // desiredIndexSet minus freeIndexSet plus bogusIndexSet. Otherwise, it
 +    // will be empty.
 +    final StorableIndexSet<S> removeIndexSet;
 +
 +    // The set of indexes that should be freshly populated. If index repair is
 +    // enabled, this set will be the desiredIndexSet minus existingIndexSet
 +    // minus freeIndexSet. Otherwise, it will be empty.
 +    final StorableIndexSet<S> addIndexSet;
 +
 +    // Maps free and managed indexes to IndexInfo and ManagedIndex objects.
 +    final Map<StorableIndex<S>, IndexInfo> allIndexInfoMap;
 +
 +    // Trigger which must be installed to keep managed indexes up to date. Is
 +    // null if there are no managed indexes.
 +    final IndexesTrigger<S> indexesTrigger;
 +
 +    // The set of derived-to properties in external storables that are used by
 +    // indexes. Is null if none.
 +    final Set<ChainedProperty<?>> derivedToDependencies;
 +
 +    public IndexAnalysis(IndexedRepository repository, Storage<S> masterStorage)
 +        throws RepositoryException
 +    {
 +        this.repository = repository;
 +        this.masterStorage = masterStorage;
 +
 +        StorableInfo<S> info = StorableIntrospector.examine(masterStorage.getStorableType());
 +
 +        // The set of indexes that the Storable defines, reduced.
 +        final StorableIndexSet<S> desiredIndexSet;
 +        {
 +            desiredIndexSet = gatherDesiredIndexes(info);
 +            desiredIndexSet.reduce(Direction.ASCENDING);
 +            if (repository.isAllClustered()) {
 +                desiredIndexSet.markClustered(true);
 +            }            
 +        }
 +
 +        // The set of indexes that are populated and available for use. This is
 +        // determined by examining index metadata. If the Storable has not
 +        // changed, it will be the same as desiredIndexSet. If any existing
 +        // indexes use a property whose type has changed, it is added to
 +        // bogusIndexSet. Bogus indexes are removed if repair is enabled.
 +        final StorableIndexSet<S> existingIndexSet;
 +        final StorableIndexSet<S> bogusIndexSet;
 +        {
 +            existingIndexSet = new StorableIndexSet<S>();
 +            bogusIndexSet = new StorableIndexSet<S>();
 +
 +            Query<StoredIndexInfo> query = repository.getWrappedRepository()
 +                .storageFor(StoredIndexInfo.class)
 +                // Primary key of StoredIndexInfo is an index descriptor, which
 +                // starts with the storable type name. This emulates a
 +                // "wildcard at the end" search.
 +                .query("indexName >= ? & indexName < ?")
 +                .with(info.getStorableType().getName() + '~')
 +                .with(info.getStorableType().getName() + '~' + '\uffff');
 +
 +            List<StoredIndexInfo> storedInfos;
 +            try {
 +                Transaction txn = repository.getWrappedRepository()
 +                    .enterTopTransaction(IsolationLevel.READ_COMMITTED);
 +                try {
 +                    storedInfos = query.fetch().toList();
 +                } finally {
 +                    txn.exit();
 +                }
 +            } catch (FetchException e) {
 +                if (e instanceof FetchDeadlockException || e instanceof FetchTimeoutException) {
 +                    // Might be caused by coarse locks. Switch to nested
 +                    // transaction to share the locks.
 +                    Transaction txn = repository.getWrappedRepository()
 +                        .enterTransaction(IsolationLevel.READ_COMMITTED);
 +                    try {
 +                        storedInfos = query.fetch().toList();
 +                    } finally {
 +                        txn.exit();
 +                    }
 +                } else {
 +                    throw e;
 +                }
 +            }
 +
 +            for (StoredIndexInfo indexInfo : storedInfos) {
 +                String name = indexInfo.getIndexName();
 +                StorableIndex index;
 +                try {
 +                    index = StorableIndex.parseNameDescriptor(name, info);
 +                } catch (IllegalArgumentException e) {
 +                    // Skip unrecognized descriptors.
 +                    continue;
 +                }
 +                if (index.getTypeDescriptor().equals(indexInfo.getIndexTypeDescriptor())) {
 +                    existingIndexSet.add(index);
 +                } else {
 +                    bogusIndexSet.add(index);
 +                }
 +            }
 +        }
 +
 +        nonUniqueSearch: {
 +            // If any existing indexes are non-unique, then indexes are for an
 +            // older version. For compatibility, don't uniquify the
 +            // indexes. Otherwise, these indexes would need to be rebuilt.
 +            for (StorableIndex<S> index : existingIndexSet) {
 +                if (!index.isUnique()) {
 +                    break nonUniqueSearch;
 +                }
 +            }
 +
 +            // The index implementation includes all primary key properties
 +            // anyhow, so adding them here allows query analyzer to see these
 +            // properties. As a side-effect of uniquify, all indexes are
 +            // unique, and thus have 'U' in the descriptor. Each time
 +            // nonUniqueSearch is run, it will not find any non-unique indexes.
 +            desiredIndexSet.uniquify(info);
 +        }
 +
 +        // The set of free indexes, which are already provided by the underlying
 +        // storage. They can be used for querying, but we should not manage them.
 +        final StorableIndexSet<S> freeIndexSet;
 +        {
 +            freeIndexSet = new StorableIndexSet<S>();
 +            allIndexInfoMap = new IdentityHashMap<StorableIndex<S>, IndexInfo>();
 +
 +            IndexInfoCapability cap = repository.getWrappedRepository()
 +                .getCapability(IndexInfoCapability.class);
 +
 +            if (cap != null) {
 +                for (IndexInfo ii : cap.getIndexInfo(info.getStorableType())) {
 +                    StorableIndex<S> freeIndex;
 +                    try {
 +                        freeIndex = new StorableIndex<S>(info.getStorableType(), ii);
 +                    } catch (IllegalArgumentException e) {
 +                        // Assume index is malformed, so ignore it.
 +                        continue;
 +                    }
 +                    if (repository.isAllClustered()) {
 +                        freeIndex = freeIndex.clustered(true);
 +                    }
 +                    freeIndexSet.add(freeIndex);
 +                    allIndexInfoMap.put(freeIndex, ii);
 +                }
 +            }
 +        }
 +
 +        {
 +            queryableIndexSet = new StorableIndexSet<S>(desiredIndexSet);
 +
 +            if (!repository.isIndexRepairEnabled()) {
 +                // Can only query the intersection.
 +                queryableIndexSet.retainAll(existingIndexSet);
 +            }
 +
 +            // Add the indexes we get for free.
 +            queryableIndexSet.addAll(freeIndexSet);
 +        }
 +
 +        // The set of indexes that should be kept up-to-date. If index repair
 +        // is enabled, this set will be the same as desiredIndexSet. Otherwise,
 +        // it will be the union of existingIndexSet and desiredIndexSet. In
 +        // both cases, "free" indexes are removed from the set too. By doing a
 +        // union, no harm is caused by changing the index set and then
 +        // reverting.
 +        final StorableIndexSet<S> managedIndexSet;
 +        {
 +            managedIndexSet = new StorableIndexSet<S>(desiredIndexSet);
 +
 +            if (repository.isIndexRepairEnabled()) {
 +                // Must manage the union.
 +                managedIndexSet.addAll(existingIndexSet);
 +            }
 +
 +            // Remove the indexes we get for free.
 +            managedIndexSet.removeAll(freeIndexSet);
 +        }
 +
 +        {
 +            removeIndexSet = new StorableIndexSet<S>();
 +
 +            if (repository.isIndexRepairEnabled()) {
 +                removeIndexSet.addAll(existingIndexSet);
 +                removeIndexSet.removeAll(desiredIndexSet);
 +                removeIndexSet.removeAll(freeIndexSet);
 +                removeIndexSet.addAll(bogusIndexSet);
 +            }
 +        }
 +
 +        {
 +            addIndexSet = new StorableIndexSet<S>();
 +
 +            if (repository.isIndexRepairEnabled()) {
 +                addIndexSet.addAll(desiredIndexSet);
 +                addIndexSet.removeAll(existingIndexSet);
 +                addIndexSet.removeAll(freeIndexSet);
 +            }
 +        }
 +
 +        // Support for managed indexes...
 +        if (managedIndexSet.size() <= 0) {
 +            indexesTrigger = null;
 +        } else {
 +            ManagedIndex<S>[] managedIndexes = new ManagedIndex[managedIndexSet.size()];
 +            int i = 0;
 +            for (StorableIndex<S> index : managedIndexSet) {
 +                IndexEntryGenerator<S> builder = IndexEntryGenerator.getInstance(index);
 +                Class<? extends Storable> indexEntryClass = builder.getIndexEntryClass();
 +                Storage<?> indexEntryStorage = repository.getIndexEntryStorageFor(indexEntryClass);
 +                ManagedIndex managedIndex = new ManagedIndex<S>
 +                    (repository, masterStorage, index, builder, indexEntryStorage);
 +
 +                allIndexInfoMap.put(index, managedIndex);
 +                managedIndexes[i++] = managedIndex;
 +            }
 +
 +            indexesTrigger = new IndexesTrigger<S>(managedIndexes);
 +        }
 +
 +        derivedToDependencies = gatherDerivedToDependencies(info);
 +    }
 +
      static <S extends Storable> StorableIndexSet<S> gatherDesiredIndexes(StorableInfo<S> info) {
          StorableIndexSet<S> indexSet = new StorableIndexSet<S>();
          indexSet.addIndexes(info);
 @@ -85,7 +332,7 @@ class IndexAnalysis {          return indexSet;
      }
 -    static boolean isUsedByIndex(StorableProperty<?> property) {
 +    private static boolean isUsedByIndex(StorableProperty<?> property) {
          StorableInfo<?> info = StorableIntrospector.examine(property.getEnclosingType());
          for (int i=info.getIndexCount(); --i>=0; ) {
              StorableIndex<?> index = info.getIndex(i);
 @@ -99,7 +346,7 @@ class IndexAnalysis {          return false;
      }
 -    static boolean isJoinAndUsedByIndexedDerivedProperty(StorableProperty<?> property) {
 +    private static boolean isJoinAndUsedByIndexedDerivedProperty(StorableProperty<?> property) {
          if (property.isJoin()) {
              for (ChainedProperty<?> derivedTo : property.getDerivedToProperties()) {
                  if (isUsedByIndex(derivedTo.getPrimeProperty())) {
 @@ -110,12 +357,7 @@ class IndexAnalysis {          return false;
      }
 -    /**
 -     * Returns derived-to properties in external storables that are used by indexes.
 -     *
 -     * @return null if none
 -     */
 -    static Set<ChainedProperty<?>> gatherDerivedToDependencies(StorableInfo<?> info) {
 +    private static Set<ChainedProperty<?>> gatherDerivedToDependencies(StorableInfo<?> info) {
          Set<ChainedProperty<?>> set = null;
          for (StorableProperty<?> property : info.getAllProperties().values()) {
              for (ChainedProperty<?> derivedTo : property.getDerivedToProperties()) {
 diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/IndexAnalysisPool.java b/src/main/java/com/amazon/carbonado/repo/indexed/IndexAnalysisPool.java new file mode 100644 index 0000000..1380a78 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/repo/indexed/IndexAnalysisPool.java @@ -0,0 +1,41 @@ +/*
 + * Copyright 2008 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed 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 com.amazon.carbonado.repo.indexed;
 +
 +import com.amazon.carbonado.RepositoryException;
 +import com.amazon.carbonado.Storage;
 +
 +import com.amazon.carbonado.util.AbstractPool;
 +
 +/**
 + * 
 + *
 + * @author Brian S O'Neill
 + */
 +class IndexAnalysisPool extends AbstractPool<Storage, IndexAnalysis, RepositoryException> {
 +    private final IndexedRepository mRepository;
 +
 +    public IndexAnalysisPool(IndexedRepository repository) {
 +        mRepository = repository;
 +    }
 +
 +    protected IndexAnalysis create(Storage masterStorage) throws RepositoryException {
 +        return new IndexAnalysis(mRepository, masterStorage);
 +    }
 +}
 diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepository.java b/src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepository.java index 7c28ee8..44cc5d1 100644 --- a/src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepository.java +++ b/src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepository.java @@ -20,6 +20,7 @@ package com.amazon.carbonado.repo.indexed;  import java.util.ArrayList;
  import java.util.Arrays;
 +import java.util.List;
  import java.util.concurrent.atomic.AtomicReference;
 @@ -64,6 +65,7 @@ class IndexedRepository implements Repository,      private final double mIndexThrottle;
      private final boolean mAllClustered;
      private final StoragePool mStoragePool;
 +    private final IndexAnalysisPool mIndexAnalysisPool;
      IndexedRepository(AtomicReference<Repository> rootRef, String name,
                        Repository repository,
 @@ -71,12 +73,19 @@ class IndexedRepository implements Repository,                        double indexThrottle,
                        boolean allClustered)
      {
 +        if (repository.getCapability(IndexInfoCapability.class) == null) {
 +            throw new UnsupportedOperationException
 +                ("Wrapped repository doesn't support being indexed -- " +
 +                 "it must support IndexInfoCapability.");
 +        }
 +
          mRootRef = rootRef;
          mRepository = repository;
          mName = name;
          mIndexRepairEnabled = indexRepairEnabled;
          mIndexThrottle = indexThrottle;
          mAllClustered = allClustered;
 +        mIndexAnalysisPool = new IndexAnalysisPool(this);
          mStoragePool = new StoragePool() {
              @Override
 @@ -97,15 +106,11 @@ class IndexedRepository implements Repository,                      return masterStorage;
                  }
 -                return new IndexedStorage<S>(IndexedRepository.this, masterStorage);
 +                IndexAnalysis<S> analysis = mIndexAnalysisPool.get(masterStorage);
 +
 +                return new IndexedStorage<S>(analysis);
              }
          };
 -
 -        if (repository.getCapability(IndexInfoCapability.class) == null) {
 -            throw new UnsupportedOperationException
 -                ("Wrapped repository doesn't support being indexed -- " +
 -                 "it must support IndexInfoCapability.");
 -        }
      }
      public String getName() {
 @@ -143,22 +148,41 @@ class IndexedRepository implements Repository,          return mRepository.getCapability(capabilityType);
      }
 +    // Required by IndexInfoCapability.
      public <S extends Storable> IndexInfo[] getIndexInfo(Class<S> storableType)
          throws RepositoryException
      {
 -        return ((IndexedStorage) storageFor(storableType)).getIndexInfo();
 +        if (Unindexed.class.isAssignableFrom(storableType)) {
 +            return new IndexInfo[0];
 +        }
 +
 +        Storage<S> masterStorage = mRepository.storageFor(storableType);
 +        IndexAnalysis<S> analysis = mIndexAnalysisPool.get(masterStorage);
 +
 +        IndexInfo[] infos = new IndexInfo[analysis.allIndexInfoMap.size()];
 +        return analysis.allIndexInfoMap.values().toArray(infos);
      }
 +    // Required by IndexEntryAccessCapability.
      public <S extends Storable> IndexEntryAccessor<S>[]
          getIndexEntryAccessors(Class<S> storableType)
          throws RepositoryException
      {
 -        Storage<S> storage = storageFor(storableType);
 -        if (storage instanceof IndexedStorage) {
 -            return ((IndexedStorage<S>) storage).getIndexEntryAccessors();
 -        } else {
 +        if (Unindexed.class.isAssignableFrom(storableType)) {
              return new IndexEntryAccessor[0];
          }
 +
 +        Storage<S> masterStorage = mRepository.storageFor(storableType);
 +        IndexAnalysis<S> analysis = mIndexAnalysisPool.get(masterStorage);
 +
 +        List<IndexEntryAccessor<S>> accessors =
 +            new ArrayList<IndexEntryAccessor<S>>(analysis.allIndexInfoMap.size());
 +        for (IndexInfo info : analysis.allIndexInfoMap.values()) {
 +            if (info instanceof IndexEntryAccessor) {
 +                accessors.add((IndexEntryAccessor<S>) info);
 +            }
 +        }
 +        return accessors.toArray(new IndexEntryAccessor[accessors.size()]);
      }
      public String[] getUserStorableTypeNames() throws RepositoryException {
 diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/IndexedStorage.java b/src/main/java/com/amazon/carbonado/repo/indexed/IndexedStorage.java index 9ff204f..1abb064 100644 --- a/src/main/java/com/amazon/carbonado/repo/indexed/IndexedStorage.java +++ b/src/main/java/com/amazon/carbonado/repo/indexed/IndexedStorage.java @@ -18,12 +18,8 @@  package com.amazon.carbonado.repo.indexed;
 -import java.util.ArrayList;
  import java.util.Collection;
 -import java.util.IdentityHashMap;
 -import java.util.List;
  import java.util.Map;
 -import java.util.Set;
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
 @@ -43,16 +39,12 @@ import com.amazon.carbonado.Storage;  import com.amazon.carbonado.Transaction;
  import com.amazon.carbonado.Trigger;
  import com.amazon.carbonado.capability.IndexInfo;
 -import com.amazon.carbonado.capability.IndexInfoCapability;
  import com.amazon.carbonado.cursor.MergeSortBuffer;
  import com.amazon.carbonado.filter.Filter;
  import com.amazon.carbonado.info.ChainedProperty;
 -import com.amazon.carbonado.info.Direction;
 -import com.amazon.carbonado.info.StorableInfo;
 -import com.amazon.carbonado.info.StorableIntrospector;
  import com.amazon.carbonado.info.StorableIndex;
  import com.amazon.carbonado.cursor.SortBuffer;
 @@ -84,241 +76,35 @@ class IndexedStorage<S extends Storable> implements Storage<S>, StorageAccess<S>      private final QueryEngine<S> mQueryEngine;
 -    @SuppressWarnings("unchecked")
 -    IndexedStorage(IndexedRepository repository, Storage<S> masterStorage)
 -        throws RepositoryException
 -    {
 -        mRepository = repository;
 -        mMasterStorage = masterStorage;
 -        mAllIndexInfoMap = new IdentityHashMap<StorableIndex<S>, IndexInfo>();
 -
 -        StorableInfo<S> info = StorableIntrospector.examine(masterStorage.getStorableType());
 -
 -        // The set of indexes that the Storable defines, reduced.
 -        final StorableIndexSet<S> desiredIndexSet;
 -        {
 -            desiredIndexSet = IndexAnalysis.gatherDesiredIndexes(info);
 -            desiredIndexSet.reduce(Direction.ASCENDING);
 -            if (mRepository.isAllClustered()) {
 -                desiredIndexSet.markClustered(true);
 -            }            
 -        }
 -
 -        // The set of indexes that are populated and available for use. This is
 -        // determined by examining index metadata. If the Storable has not
 -        // changed, it will be the same as desiredIndexSet. If any existing
 -        // indexes use a property whose type has changed, it is added to
 -        // bogusIndexSet. Bogus indexes are removed if repair is enabled.
 -        final StorableIndexSet<S> existingIndexSet;
 -        final StorableIndexSet<S> bogusIndexSet;
 -        {
 -            existingIndexSet = new StorableIndexSet<S>();
 -            bogusIndexSet = new StorableIndexSet<S>();
 -
 -            Query<StoredIndexInfo> query = repository.getWrappedRepository()
 -                .storageFor(StoredIndexInfo.class)
 -                // Primary key of StoredIndexInfo is an index descriptor, which
 -                // starts with the storable type name. This emulates a
 -                // "wildcard at the end" search.
 -                .query("indexName >= ? & indexName < ?")
 -                .with(getStorableType().getName() + '~')
 -                .with(getStorableType().getName() + '~' + '\uffff');
 -
 -            List<StoredIndexInfo> storedInfos;
 -            try {
 -                Transaction txn = repository.getWrappedRepository()
 -                    .enterTopTransaction(IsolationLevel.READ_COMMITTED);
 -                try {
 -                    storedInfos = query.fetch().toList();
 -                } finally {
 -                    txn.exit();
 -                }
 -            } catch (FetchException e) {
 -                if (e instanceof FetchDeadlockException || e instanceof FetchTimeoutException) {
 -                    // Might be caused by coarse locks. Switch to nested
 -                    // transaction to share the locks.
 -                    Transaction txn = repository.getWrappedRepository()
 -                        .enterTransaction(IsolationLevel.READ_COMMITTED);
 -                    try {
 -                        storedInfos = query.fetch().toList();
 -                    } finally {
 -                        txn.exit();
 -                    }
 -                } else {
 -                    throw e;
 -                }
 -            }
 -
 -            for (StoredIndexInfo indexInfo : storedInfos) {
 -                String name = indexInfo.getIndexName();
 -                StorableIndex index;
 -                try {
 -                    index = StorableIndex.parseNameDescriptor(name, info);
 -                } catch (IllegalArgumentException e) {
 -                    // Remove unrecognized descriptors.
 -                    unregisterIndex(name);
 -                    continue;
 -                }
 -                if (index.getTypeDescriptor().equals(indexInfo.getIndexTypeDescriptor())) {
 -                    existingIndexSet.add(index);
 -                } else {
 -                    bogusIndexSet.add(index);
 -                }
 -            }
 -        }
 -
 -        nonUniqueSearch: {
 -            // If any existing indexes are non-unique, then indexes are for an
 -            // older version. For compatibility, don't uniquify the
 -            // indexes. Otherwise, these indexes would need to be rebuilt.
 -            for (StorableIndex<S> index : existingIndexSet) {
 -                if (!index.isUnique()) {
 -                    break nonUniqueSearch;
 -                }
 -            }
 -
 -            // The index implementation includes all primary key properties
 -            // anyhow, so adding them here allows query analyzer to see these
 -            // properties. As a side-effect of uniquify, all indexes are
 -            // unique, and thus have 'U' in the descriptor. Each time
 -            // nonUniqueSearch is run, it will not find any non-unique indexes.
 -            desiredIndexSet.uniquify(info);
 -        }
 -
 -        // Gather free indexes, which are already provided by the underlying
 -        // storage. They can be used for querying, but we should not manage them.
 -        final StorableIndexSet<S> freeIndexSet;
 -        {
 -            freeIndexSet = new StorableIndexSet<S>();
 -
 -            IndexInfoCapability cap = repository.getWrappedRepository()
 -                .getCapability(IndexInfoCapability.class);
 -
 -            if (cap != null) {
 -                for (IndexInfo ii : cap.getIndexInfo(masterStorage.getStorableType())) {
 -                    StorableIndex<S> freeIndex;
 -                    try {
 -                        freeIndex = new StorableIndex<S>(masterStorage.getStorableType(), ii);
 -                    } catch (IllegalArgumentException e) {
 -                        // Assume index is malformed, so ignore it.
 -                        continue;
 -                    }
 -                    if (mRepository.isAllClustered()) {
 -                        freeIndex = freeIndex.clustered(true);
 -                    }
 -                    mAllIndexInfoMap.put(freeIndex, ii);
 -                    freeIndexSet.add(freeIndex);
 -                }
 -            }
 -        }
 -
 -        // The set of indexes that can actually be used for querying. If index
 -        // repair is enabled, this set will be the same as
 -        // desiredIndexSet. Otherwise, it will be the intersection of
 -        // existingIndexSet and desiredIndexSet. In both cases, "free" indexes
 -        // are added to the set too.
 -        final StorableIndexSet<S> queryableIndexSet;
 -        {
 -            queryableIndexSet = new StorableIndexSet<S>(desiredIndexSet);
 -
 -            if (!mRepository.isIndexRepairEnabled()) {
 -                // Can only query the intersection.
 -                queryableIndexSet.retainAll(existingIndexSet);
 -            }
 -
 -            // Add the indexes we get for free.
 -            queryableIndexSet.addAll(freeIndexSet);
 -        }
 -
 -        // The set of indexes that should be kept up-to-date. If index repair
 -        // is enabled, this set will be the same as desiredIndexSet. Otherwise,
 -        // it will be the union of existingIndexSet and desiredIndexSet. In
 -        // both cases, "free" indexes are removed from the set too. By doing a
 -        // union, no harm is caused by changing the index set and then reverting.
 -        final StorableIndexSet<S> managedIndexSet;
 -        {
 -            managedIndexSet = new StorableIndexSet<S>(desiredIndexSet);
 -
 -            if (!mRepository.isIndexRepairEnabled()) {
 -                // Must manage the union.
 -                managedIndexSet.addAll(existingIndexSet);
 -            }
 -
 -            // Remove the indexes we get for free.
 -            managedIndexSet.removeAll(freeIndexSet);
 -        }
 -
 -        // The set of indexes that should be removed and no longer managed. If
 -        // index repair is enabled, this set will be the existingIndexSet minus
 -        // desiredIndexSet minus freeIndexSet plus bogusIndexSet. Otherwise, it
 -        // will be empty.
 -        final StorableIndexSet<S> removeIndexSet;
 -        {
 -            removeIndexSet = new StorableIndexSet<S>();
 -
 -            if (mRepository.isIndexRepairEnabled()) {
 -                removeIndexSet.addAll(existingIndexSet);
 -                removeIndexSet.removeAll(desiredIndexSet);
 -                removeIndexSet.removeAll(freeIndexSet);
 -                removeIndexSet.addAll(bogusIndexSet);
 -            }
 -        }
 -
 -        // The set of indexes that should be freshly populated. If index repair
 -        // is enabled, this set will be the desiredIndexSet minus
 -        // existingIndexSet minus freeIndexSet. Otherwise, it will be empty.
 -        final StorableIndexSet<S> addIndexSet;
 -        {
 -            addIndexSet = new StorableIndexSet<S>();
 -
 -            if (mRepository.isIndexRepairEnabled()) {
 -                addIndexSet.addAll(desiredIndexSet);
 -                addIndexSet.removeAll(existingIndexSet);
 -                addIndexSet.removeAll(freeIndexSet);
 -            }
 -        }
 -
 -        // Support for managed indexes...
 -        if (managedIndexSet.size() > 0) {
 -            ManagedIndex<S>[] managedIndexes = new ManagedIndex[managedIndexSet.size()];
 -            int i = 0;
 -            for (StorableIndex<S> index : managedIndexSet) {
 -                IndexEntryGenerator<S> builder = IndexEntryGenerator.getInstance(index);
 -                Class<? extends Storable> indexEntryClass = builder.getIndexEntryClass();
 -                Storage<?> indexEntryStorage = repository.getIndexEntryStorageFor(indexEntryClass);
 -                ManagedIndex managedIndex =
 -                    new ManagedIndex<S>(this, index, builder, indexEntryStorage);
 -
 -                mAllIndexInfoMap.put(index, managedIndex);
 -                managedIndexes[i++] = managedIndex;
 -            }
 +    IndexedStorage(IndexAnalysis<S> analysis) throws RepositoryException {
 +        mRepository = analysis.repository;
 +        mMasterStorage = analysis.masterStorage;
 +        mAllIndexInfoMap = analysis.allIndexInfoMap;
 +        mQueryableIndexSet = analysis.queryableIndexSet;
 -            if (!addTrigger(new IndexesTrigger<S>(managedIndexes))) {
 +        if (analysis.indexesTrigger != null) {
 +            if (!addTrigger(analysis.indexesTrigger)) {
 +                // This might be caused by this storage being created again recursively.
                  throw new RepositoryException("Unable to add trigger for managing indexes");
              }
          }
          // Okay, now start doing some damage. First, remove unnecessary indexes.
 -        for (StorableIndex<S> index : removeIndexSet) {
 +        for (StorableIndex<S> index : analysis.removeIndexSet) {
              removeIndex(index);
          }
          // Now add new indexes.
 -        for (StorableIndex<S> index : addIndexSet) {
 +        for (StorableIndex<S> index : analysis.addIndexSet) {
              registerIndex((ManagedIndex) mAllIndexInfoMap.get(index));
          }
 -        mQueryableIndexSet = queryableIndexSet;
 -        mQueryEngine = new QueryEngine<S>(masterStorage.getStorableType(), repository);
 +        mQueryEngine = new QueryEngine<S>(mMasterStorage.getStorableType(), mRepository);
          // Install triggers to manage derived properties in external Storables.
 -
 -        Set<ChainedProperty<?>> derivedToDependencies =
 -            IndexAnalysis.gatherDerivedToDependencies(info);
 -
 -        if (derivedToDependencies != null) {
 -            for (ChainedProperty<?> derivedTo : derivedToDependencies) {
 -                addTrigger(new DerivedIndexesTrigger(repository, getStorableType(), derivedTo));
 +        if (analysis.derivedToDependencies != null) {
 +            for (ChainedProperty<?> derivedTo : analysis.derivedToDependencies) {
 +                addTrigger(new DerivedIndexesTrigger(mRepository, getStorableType(), derivedTo));
              }
          }
      }
 @@ -381,25 +167,6 @@ class IndexedStorage<S extends Storable> implements Storage<S>, StorageAccess<S>          return mMasterStorage.removeTrigger(trigger);
      }
 -    // Required by IndexInfoCapability.
 -    public IndexInfo[] getIndexInfo() {
 -        IndexInfo[] infos = new IndexInfo[mAllIndexInfoMap.size()];
 -        return mAllIndexInfoMap.values().toArray(infos);
 -    }
 -
 -    // Required by IndexEntryAccessCapability.
 -    @SuppressWarnings("unchecked")
 -    public IndexEntryAccessor<S>[] getIndexEntryAccessors() {
 -        List<IndexEntryAccessor<S>> accessors =
 -            new ArrayList<IndexEntryAccessor<S>>(mAllIndexInfoMap.size());
 -        for (IndexInfo info : mAllIndexInfoMap.values()) {
 -            if (info instanceof IndexEntryAccessor) {
 -                accessors.add((IndexEntryAccessor<S>) info);
 -            }
 -        }
 -        return accessors.toArray(new IndexEntryAccessor[accessors.size()]);
 -    }
 -
      // Required by StorageAccess.
      public QueryExecutorFactory<S> getQueryExecutorFactory() {
          return mQueryEngine;
 diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/ManagedIndex.java b/src/main/java/com/amazon/carbonado/repo/indexed/ManagedIndex.java index a8f5788..2d0617e 100644 --- a/src/main/java/com/amazon/carbonado/repo/indexed/ManagedIndex.java +++ b/src/main/java/com/amazon/carbonado/repo/indexed/ManagedIndex.java @@ -82,20 +82,23 @@ class ManagedIndex<S extends Storable> implements IndexEntryAccessor<S> {          return naturalOrdering;
      }
 -    private final IndexedStorage<S> mIndexedStorage;
 +    private final IndexedRepository mRepository;
 +    private final Storage<S> mMasterStorage;
      private final StorableIndex mIndex;
      private final IndexEntryGenerator<S> mGenerator;
      private final Storage<?> mIndexEntryStorage;
      private Query<?> mSingleMatchQuery;
 -    ManagedIndex(IndexedStorage<S> indexedStorage,
 +    ManagedIndex(IndexedRepository repository,
 +                 Storage<S> masterStorage,
                   StorableIndex<S> index,
                   IndexEntryGenerator<S> generator,
                   Storage<?> indexEntryStorage)
          throws SupportException
      {
 -        mIndexedStorage = indexedStorage;
 +        mRepository = repository;
 +        mMasterStorage = masterStorage;
          mIndex = index;
          mGenerator = generator;
          mIndexEntryStorage = indexEntryStorage;
 @@ -236,9 +239,6 @@ class ManagedIndex<S extends Storable> implements IndexEntryAccessor<S> {       * @param repo used to enter transactions
       */
      void buildIndex(double desiredSpeed) throws RepositoryException {
 -        final Repository repo = mIndexedStorage.mRepository;
 -        final Storage<S> masterStorage = mIndexedStorage.mMasterStorage;
 -
          final MergeSortBuffer buffer;
          final Comparator c;
 @@ -249,13 +249,13 @@ class ManagedIndex<S extends Storable> implements IndexEntryAccessor<S> {              // Need to explicitly order master query by primary key in order
              // for fetchAfter to work correctly in case corrupt records are
              // encountered.
 -            masterQuery = masterStorage.query()
 -                .orderBy(naturalOrdering(masterStorage.getStorableType()));
 +            masterQuery = mMasterStorage.query()
 +                .orderBy(naturalOrdering(mMasterStorage.getStorableType()));
          }
          // Quick check to see if any records exist in master.
          {
 -            Transaction txn = repo.enterTransaction(IsolationLevel.READ_COMMITTED);
 +            Transaction txn = mRepository.enterTransaction(IsolationLevel.READ_COMMITTED);
              try {
                  Cursor<S> cursor = masterQuery.fetch();
                  if (!cursor.hasNext()) {
 @@ -269,14 +269,14 @@ class ManagedIndex<S extends Storable> implements IndexEntryAccessor<S> {          // Enter top transaction with isolation level of none to make sure
          // preload operation does not run in a long nested transaction.
 -        Transaction txn = repo.enterTopTransaction(IsolationLevel.NONE);
 +        Transaction txn = mRepository.enterTopTransaction(IsolationLevel.NONE);
          try {
              Cursor<S> cursor = masterQuery.fetch();
              try {
                  if (log.isInfoEnabled()) {
                      StringBuilder b = new StringBuilder();
 -                    b.append("Building index on ");
 -                    b.append(masterStorage.getStorableType().getName());
 +                    b.append("Preparing index on ");
 +                    b.append(mMasterStorage.getStorableType().getName());
                      b.append(": ");
                      try {
                          mIndex.appendTo(b);
 @@ -350,7 +350,7 @@ class ManagedIndex<S extends Storable> implements IndexEntryAccessor<S> {              // fail, since unique index cannot be built.
              if (log.isInfoEnabled()) {
 -                log.info("Verifying unique index");
 +                log.info("Verifying index");
              }
              Object last = null;
 @@ -384,7 +384,7 @@ class ManagedIndex<S extends Storable> implements IndexEntryAccessor<S> {          long totalDeleted = 0;
          long totalProgress = 0;
 -        txn = repo.enterTopTransaction(IsolationLevel.READ_COMMITTED);
 +        txn = mRepository.enterTopTransaction(IsolationLevel.READ_COMMITTED);
          try {
              txn.setForUpdate(true);
 @@ -463,7 +463,7 @@ class ManagedIndex<S extends Storable> implements IndexEntryAccessor<S> {                          log.info(String.format(format, percent));
                      }
 -                    txn = repo.enterTopTransaction(IsolationLevel.READ_COMMITTED);
 +                    txn = mRepository.enterTopTransaction(IsolationLevel.READ_COMMITTED);
                      if (indexEntryCursor != null) {
                          indexEntryCursor.close();
 | 
