summaryrefslogtreecommitdiff
path: root/src/main/java/com/amazon
diff options
context:
space:
mode:
authorBrian S. O'Neill <bronee@gmail.com>2008-05-25 19:02:37 +0000
committerBrian S. O'Neill <bronee@gmail.com>2008-05-25 19:02:37 +0000
commit6cee40de307fa88391573b94af60fc0942a99faa (patch)
tree1565e081c7e898ea2016219c01bb4474d47d204f /src/main/java/com/amazon
parent7ac92c3956196faafc8e613b62b7278334137928 (diff)
Fixed cyclic dependency with indexes against complex derived properties.
Diffstat (limited to 'src/main/java/com/amazon')
-rw-r--r--src/main/java/com/amazon/carbonado/repo/indexed/DependentStorableFetcher.java38
-rw-r--r--src/main/java/com/amazon/carbonado/repo/indexed/DerivedIndexesTrigger.java2
-rw-r--r--src/main/java/com/amazon/carbonado/repo/indexed/IndexAnalysis.java262
-rw-r--r--src/main/java/com/amazon/carbonado/repo/indexed/IndexAnalysisPool.java41
-rw-r--r--src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepository.java48
-rw-r--r--src/main/java/com/amazon/carbonado/repo/indexed/IndexedStorage.java261
-rw-r--r--src/main/java/com/amazon/carbonado/repo/indexed/ManagedIndex.java30
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();