summaryrefslogtreecommitdiff
path: root/src/main/java/com/amazon/carbonado/repo/indexed/IndexAnalysis.java
blob: 1ec12ad43b6b21ea0fa017374416b1003f6ecfbd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
/*
 * Copyright 2007-2012 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 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;

import com.amazon.carbonado.info.ChainedProperty;
import com.amazon.carbonado.info.Direction;
import com.amazon.carbonado.info.StorableIndex;
import com.amazon.carbonado.info.StorableInfo;
import com.amazon.carbonado.info.StorableIntrospector;
import com.amazon.carbonado.info.StorableProperty;

import com.amazon.carbonado.qe.FilteringScore;
import com.amazon.carbonado.qe.StorableIndexSet;

import com.amazon.carbonado.synthetic.SyntheticStorableReferenceAccess;

/**
 * Builds various sets of indexes for a Storable type.
 *
 * @author Brian S O'Neill
 * @since 1.2
 */
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) {
                // Might be caused by coarse locks or isolation level
                // swtching. Avoid the explicit isolation level.
                storedInfos = query.fetch().toList();
            }

            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) {
                SyntheticStorableReferenceAccess<S> accessor =
                    IndexEntryGenerator.getIndexAccess(index);
                Class<? extends Storable> indexEntryClass = accessor.getReferenceClass();
                Storage<?> indexEntryStorage = repository.getIndexEntryStorageFor(indexEntryClass);
                ManagedIndex managedIndex = new ManagedIndex<S>
                    (repository, masterStorage, index, accessor, indexEntryStorage);

                allIndexInfoMap.put(index, managedIndex);
                managedIndexes[i++] = managedIndex;
            }

            if (repository.isStrictTriggers()) {
                indexesTrigger = new IndexesTrigger.Strict<S>(managedIndexes);
            } else {
                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);
        indexSet.addAlternateKeys(info);

        // If any join properties are used by indexed derived properties, make
        // sure join internal properties are indexed.

        for (StorableProperty<S> property : info.getAllProperties().values()) {
            if (!isJoinAndUsedByIndexedDerivedProperty(property)) {
                continue;
            }

            // Internal properties of join need to be indexed. Check if a
            // suitable index exists before defining a new one.

            Filter<S> filter = Filter.getOpenFilter(info.getStorableType());
            for (int i=property.getJoinElementCount(); --i>=0; ) {
                filter = filter.and(property.getInternalJoinElement(i).getName(), RelOp.EQ);
            }

            for (int i=info.getIndexCount(); --i>=0; ) {
                FilteringScore<S> score = FilteringScore.evaluate(info.getIndex(i), filter);
                if (score.getIdentityCount() == property.getJoinElementCount()) {
                    // Suitable index already exists.
                    continue;
                }
            }

            Direction[] directions = new Direction[property.getJoinElementCount()];
            Arrays.fill(directions, Direction.UNSPECIFIED);

            StorableIndex<S> index =
                new StorableIndex<S>(property.getInternalJoinElements(), directions);

            indexSet.add(index);
        }

        return indexSet;
    }

    private static boolean isUsedByIndex(StorableProperty<?> property) {
        StorableInfo<?> info = StorableIntrospector.examine(property.getEnclosingType());
        for (int i=info.getIndexCount(); --i>=0; ) {
            StorableIndex<?> index = info.getIndex(i);
            int propertyCount = index.getPropertyCount();
            for (int j=0; j<propertyCount; j++) {
                if (index.getProperty(j).equals(property)) {
                    return true;
                }
            }
        }
        return false;
    }

    private static boolean isJoinAndUsedByIndexedDerivedProperty(StorableProperty<?> property) {
        if (property.isJoin()) {
            for (ChainedProperty<?> derivedTo : property.getDerivedToProperties()) {
                if (isUsedByIndex(derivedTo.getPrimeProperty())) {
                    return true;
                }
            }
        }
        return false;
    }

    private static Set<ChainedProperty<?>> gatherDerivedToDependencies(StorableInfo<?> info) {
        Set<ChainedProperty<?>> set = null;
        for (StorableProperty<?> property : info.getAllProperties().values()) {
            for (ChainedProperty<?> derivedTo : property.getDerivedToProperties()) {
                if (derivedTo.getChainCount() > 0 && isUsedByIndex(derivedTo.getPrimeProperty())) {
                    if (set == null) {
                        set = new HashSet<ChainedProperty<?>>();
                    }
                    set.add(derivedTo);
                }
            }
        }
        return set;
    }
}