AbstractDb.java

1
package com.renomad.minum.database;
2
3
import com.renomad.minum.logging.ILogger;
4
import com.renomad.minum.state.Context;
5
import com.renomad.minum.utils.FileUtils;
6
7
import java.io.IOException;
8
import java.nio.file.Path;
9
import java.util.*;
10
import java.util.concurrent.Callable;
11
import java.util.concurrent.ConcurrentHashMap;
12
import java.util.concurrent.atomic.AtomicLong;
13
import java.util.function.Function;
14
15
/**
16
 * The abstract database class is a representation of the essential capabilities of
17
 * a Minum database.
18
 * <p>
19
 *     There are two kinds of database provided, which only differ in how they
20
 *     store data on disk.  The "classic" kind, {@link Db}, stores each piece of
21
 *     data in its own file.  This is the simplest approach.
22
 * </p>
23
 * <p>
24
 *     However, for significant speed gains, the new {@link DbEngine2} will
25
 *     store each change as an append to a file, and will consolidate the on-disk
26
 *     data occasionally, and on start.  That way is thousands of times faster
27
 *     to write to disk and to read from disk at startup.
28
 * </p>
29
 * @param <T> This is the type of data, which is always an implementation of
30
 *           the {@link DbData} class.  See the code of {@link com.renomad.minum.security.Inmate}
31
 *           for an example of how this should look.
32
 */
33
public abstract class AbstractDb<T extends DbData<?>> {
34
35
    /**
36
     * The directory of the database on disk
37
     */
38
    protected final Path dbDirectory;
39
40
    /**
41
     * An empty instance of the type of data stored by this
42
     * database, used for better handling of generics.
43
     */
44
    protected final T emptyInstance;
45
46
    /**
47
     * Used for handling some file utilities in the database like creating directories
48
     */
49
    protected final FileUtils fileUtils;
50
51
    /**
52
     * Holds some system-wide information that is beneficial for components of the database
53
     */
54
    protected final Context context;
55
56
    /**
57
     * Used for providing logging throughout the database
58
     */
59
    protected final ILogger logger;
60
61
    /**
62
     * The internal data structure of the database that resides in memory.  The beating heart
63
     * of the database while it runs.
64
     */
65
    protected final Map<Long, T> data;
66
67
    /**
68
     * The current index, used when creating new data items.  Each item has its own
69
     * index value, this is where it is tracked.
70
     */
71
    protected AtomicLong index;
72
73
    // components for registered indexes (for faster read performance)
74
75
    /**
76
     * This data structure is a nested map used for providing indexed data search.
77
     * <br>
78
     * The outer map is between the name of the index and the inner map.
79
     * <br>
80
     * The inner map is between strings and sets of items related to that string.
81
     */
82
    protected final Map<String, Map<String, Set<T>>> registeredIndexes;
83
84
    /**
85
     * This map holds the functions that are registered to indexes, which are used
86
     * to construct the mappings between string values and items in the database.
87
     */
88
    protected final Map<String, Function<T, String>> partitioningMap;
89
90
    protected AbstractDb(Path dbDirectory, Context context, T instance) {
91
        this.dbDirectory = dbDirectory;
92
        this.context = context;
93
        this.emptyInstance = instance;
94
        this.data = new ConcurrentHashMap<>();
95
        this.logger = context.getLogger();
96
        this.registeredIndexes = new HashMap<>();
97
        this.partitioningMap = new HashMap<>();
98
        this.fileUtils = new FileUtils(logger, context.getConstants());
99
    }
100
101
    /**
102
     * Used to cleanly stop the database.
103
     * <br>
104
     * In the case of {@link Db} this will interrupt its internal queue and tell it
105
     * to finish up processing.
106
     * <br>
107
     * In the case of {@link DbEngine2} this will flush data to disk.
108
     */
109
    public abstract void stop();
110
111
    /**
112
     * Used to cleanly stop the database, with extra allowance of time
113
     * for cleanup.
114
     * <br>
115
     * Note that this method mostly applies to {@link Db}, and not as much
116
     * to {@link DbEngine2}.  Only Db uses a processing queue on a thread which
117
     * is what requires a longer shutdown time for interruption.
118
     * @param count number of loops before we are done waiting for a clean close
119
     *              and instead crash the instance closed.
120
     * @param sleepTime how long to wait, in milliseconds, for each iteration of the waiting loop.
121
     */
122
    public abstract void stop(int count, int sleepTime);
123
124
125
    /**
126
     * Write data to the database.  Use an index of 0 to store new data, and a positive
127
     * non-zero value to update data.
128
     * <p><em>
129
     * Example of adding new data to the database:
130
     * </p></em>
131
     * {@snippet :
132
     *          final var newSalt = StringUtils.generateSecureRandomString(10);
133
     *          final var hashedPassword = CryptoUtils.createPasswordHash(newPassword, newSalt);
134
     *          final var newUser = new User(0L, newUsername, hashedPassword, newSalt);
135
     *          userDb.write(newUser);
136
     * }
137
     * <p><em>
138
     * Example of updating data:
139
     * </p></em>
140
     * {@snippet :
141
     *         // write the updated salted password to the database
142
     *         final var updatedUser = new User(
143
     *                 user().getIndex(),
144
     *                 user().getUsername(),
145
     *                 hashedPassword,
146
     *                 newSalt);
147
     *         userDb.write(updatedUser);
148
     * }
149
     *
150
     * @param newData the data we are writing
151
     * @return the data with its new index assigned.
152
     */
153
    public abstract T write(T newData);
154
155
    /**
156
     * Write database data into memory
157
     * @param newData the new data may be totally new or an update
158
     * @param newElementCreated if true, this is a create.  If false, an update.
159
     */
160
    protected void writeToMemory(T newData, boolean newElementCreated) {
161
        // if we got here, we are safe to proceed with putting the data into memory and disk
162
        logger.logTrace(() -> String.format("in thread %s, writing data %s", Thread.currentThread().getName(), newData));
163
        T oldData = data.put(newData.getIndex(), newData);
164
165
        // handle the indexes differently depending on whether this is a create or delete
166 1 1. writeToMemory : negated conditional → KILLED
        if (newElementCreated) {
167 1 1. writeToMemory : removed call to com/renomad/minum/database/AbstractDb::addToIndexes → KILLED
            addToIndexes(newData);
168
        } else {
169 1 1. writeToMemory : removed call to com/renomad/minum/database/AbstractDb::removeFromIndexes → KILLED
            removeFromIndexes(oldData);
170 1 1. writeToMemory : removed call to com/renomad/minum/database/AbstractDb::addToIndexes → KILLED
            addToIndexes(newData);
171
        }
172
    }
173
174
    /**
175
     * When new data comes in, we look at its "index" value. If
176
     * it is zero, it's a create, and we assign it a new value.  If it is
177
     * positive, it is an update, and we had better find it in the database
178
     * already, or else throw an exception.
179
     * @return true if a create, false if an update
180
     */
181
    protected boolean processDataIndex(T newData) {
182
        // *** deal with the in-memory portion ***
183
        boolean newElementCreated = false;
184
        // create a new index for the data, if needed
185 1 1. processDataIndex : negated conditional → KILLED
        if (newData.getIndex() == 0) {
186 1 1. processDataIndex : removed call to com/renomad/minum/database/DbData::setIndex → KILLED
            newData.setIndex(index.getAndIncrement());
187
            newElementCreated = true;
188
        } else {
189
            // if the data does not exist, and a positive non-zero
190
            // index was provided, throw an exception.
191 2 1. lambda$processDataIndex$1 : replaced boolean return with true for com/renomad/minum/database/AbstractDb::lambda$processDataIndex$1 → TIMED_OUT
2. lambda$processDataIndex$1 : negated conditional → KILLED
            boolean dataEntryExists = data.values().stream().anyMatch(x -> x.getIndex() == newData.getIndex());
192 1 1. processDataIndex : negated conditional → KILLED
            if (!dataEntryExists) {
193
                throw new DbException(
194
                        String.format("Positive indexes are only allowed when updating existing data. Index: %d",
195
                                newData.getIndex()));
196
            }
197
        }
198 2 1. processDataIndex : replaced boolean return with false for com/renomad/minum/database/AbstractDb::processDataIndex → KILLED
2. processDataIndex : replaced boolean return with true for com/renomad/minum/database/AbstractDb::processDataIndex → KILLED
        return newElementCreated;
199
    }
200
201
    /**
202
     * Delete data
203
     * <p><em>Example:</p></em>
204
     * {@snippet :
205
     *      userDb.delete(user);
206
     * }
207
     *
208
     * @param dataToDelete the data we are serializing and writing
209
     */
210
    public abstract void delete(T dataToDelete);
211
212
213
    /**
214
     * Remove a particular item from the internal data structure in memory
215
     */
216
    protected void deleteFromMemory(T dataToDelete) {
217
        long dataIndex;
218 1 1. deleteFromMemory : negated conditional → TIMED_OUT
        if (dataToDelete == null) {
219
            throw new DbException("Invalid to be given a null value to delete");
220
        }
221
        dataIndex = dataToDelete.getIndex();
222 1 1. deleteFromMemory : negated conditional → KILLED
        if (!data.containsKey(dataIndex)) {
223
            throw new DbException("no data was found with index of " + dataIndex);
224
        }
225
        long finalDataIndex = dataIndex;
226
        logger.logTrace(() -> String.format("in thread %s, deleting data with index %d", Thread.currentThread().getName(), finalDataIndex));
227
        data.remove(dataIndex);
228 1 1. deleteFromMemory : removed call to com/renomad/minum/database/AbstractDb::removeFromIndexes → KILLED
        removeFromIndexes(dataToDelete);
229
        // if all the data was just now deleted, we need to
230
        // reset the index back to 1
231
232 1 1. deleteFromMemory : negated conditional → TIMED_OUT
        if (data.isEmpty()) {
233 1 1. deleteFromMemory : removed call to java/util/concurrent/atomic/AtomicLong::set → TIMED_OUT
            index.set(1);
234
        }
235
    }
236
237
238
    /**
239
     *  add the data to registered indexes.
240
     *  <br>
241
     *  For each of the registered indexes,
242
     *  get the stored function to obtain a string value which helps divide
243
     *  the overall data into partitions.
244
     */
245
    protected void addToIndexes(T dbData) {
246
247
        for (var entry : partitioningMap.entrySet()) {
248
            // a function provided by the user to obtain an index-key: a unique or semi-unique
249
            // value to help partition / index the data
250
            Function<T, String> indexStringFunction = entry.getValue();
251
            String propertyAsString = indexStringFunction.apply(dbData);
252
            Map<String, Set<T>> stringIndexMap = registeredIndexes.get(entry.getKey());
253
            synchronized (this) {
254 1 1. lambda$addToIndexes$3 : replaced return value with Collections.emptySet for com/renomad/minum/database/AbstractDb::lambda$addToIndexes$3 → KILLED
                stringIndexMap.computeIfAbsent(propertyAsString, k -> new HashSet<>());
255
            }
256
            // if the index-key provides a 1-to-1 mapping to items, like UUIDs, then
257
            // each value will have only one item in the collection.  In other cases,
258
            // like when partitioning the data into multiple groups, there could easily
259
            // be many items per index value.
260
            Set<T> dataSet = stringIndexMap.get(propertyAsString);
261
            dataSet.add(dbData);
262
        }
263
    }
264
265
    /**
266
     * Run when an item is deleted from the database
267
     */
268
    private void removeFromIndexes(T dbData) {
269
        for (var entry : partitioningMap.entrySet()) {
270
            // a function provided by the user to obtain an index-key: a unique or semi-unique
271
            // value to help partition / index the data
272
            Function<T, String> indexStringFunction = entry.getValue();
273
            String propertyAsString = indexStringFunction.apply(dbData);
274
            Map<String, Set<T>> stringIndexMap = registeredIndexes.get(entry.getKey());
275
            synchronized (this) {
276 2 1. lambda$removeFromIndexes$4 : replaced boolean return with true for com/renomad/minum/database/AbstractDb::lambda$removeFromIndexes$4 → KILLED
2. lambda$removeFromIndexes$4 : negated conditional → KILLED
                stringIndexMap.get(propertyAsString).removeIf(x -> x.getIndex() == dbData.getIndex());
277
278
                // in certain cases, we're removing one of the items that is indexed but
279
                // there are more left.  If there's nothing left though, we'll remove the mapping.
280 1 1. removeFromIndexes : negated conditional → KILLED
                if (stringIndexMap.get(propertyAsString).isEmpty()) {
281
                    stringIndexMap.remove(propertyAsString);
282
                }
283
            }
284
        }
285
    }
286
287
288
    /**
289
     * Grabs all the data from disk and returns it as a list.  This
290
     * method is run by various programs when the system first loads.
291
     */
292
    public abstract void loadData() throws IOException;
293
294
    /**
295
     * This method provides read capability for the values of a database.
296
     * <br>
297
     * The returned collection is a read-only view over the data, through {@link Collections#unmodifiableCollection(Collection)}
298
     *
299
     * <p><em>Example:</em></p>
300
     * {@snippet :
301
     * boolean doesUserAlreadyExist(String username) {
302
     *     return userDb.values().stream().anyMatch(x -> x.getUsername().equals(username));
303
     * }
304
     * }
305
     */
306
    public abstract Collection<T> values();
307
308
    /**
309
     * Register an index in the database for higher performance data access.
310
     * <p>
311
     *     This command should be run immediately after database declaration,
312
     *     or more specifically, before any data is loaded from disk. Otherwise,
313
     *     it would be possible to skip indexing that data.
314
     * </p>
315
     * <br>
316
     * Example:
317
     *  {@snippet :
318
     *           final var myDatabase = context.getDb("photos", Photograph.EMPTY);
319
     *           myDatabase.registerIndex("url", photo -> photo.getUrl());
320
     *  }
321
     * @param indexName a string used to distinguish this index.  This string will be used again
322
     *                  when requesting data in a method like {@link #getIndexedData} or {@link #findExactlyOne}
323
     * @param keyObtainingFunction a function which obtains data from the data in this database, used
324
     *                             to partition the data into groups (potentially up to a 1-to-1 correspondence
325
     *                             between id and object)
326
     * @return true if the registration succeeded
327
     * @throws DbException if the parameters are not entered properly, if the index has already
328
     * been registered, or if the data has already been loaded. It is necessary that
329
     * this is run immediately after declaring the database. To explain further: the data is not
330
     * actually loaded until the first time it is needed, such as running a write or delete, or
331
     * if the {@link #loadData()} ()} method is run.  Creating an index map for the data that
332
     * is read from disk only occurs once, at data load time.  Thus, it is crucial that the
333
     * registerIndex command is run before any data is loaded.
334
     */
335
    public boolean registerIndex(String indexName, Function<T, String> keyObtainingFunction) {
336 1 1. registerIndex : negated conditional → KILLED
        if (keyObtainingFunction == null) {
337
            throw new DbException("When registering an index, the partitioning algorithm must not be null");
338
        }
339 2 1. registerIndex : negated conditional → KILLED
2. registerIndex : negated conditional → KILLED
        if (indexName == null || indexName.isBlank()) {
340
            throw new DbException("When registering an index, value must be a non-empty string");
341
        }
342 1 1. registerIndex : negated conditional → KILLED
        if (registeredIndexes.containsKey(indexName)) {
343
            throw new DbException("It is forbidden to register the same index more than once.  Duplicate index: \""+indexName+"\"");
344
        }
345
        HashMap<String, Set<T>> stringCollectionHashMap = new HashMap<>();
346
        registeredIndexes.put(indexName, stringCollectionHashMap);
347
        partitioningMap.put(indexName, keyObtainingFunction);
348 1 1. registerIndex : replaced boolean return with false for com/renomad/minum/database/AbstractDb::registerIndex → KILLED
        return true;
349
    }
350
351
    /**
352
     * Given the name of a registered index (see {@link #registerIndex(String, Function)}),
353
     * use the key to find the collection of data that matches it.
354
     * @param indexName the name of an index
355
     * @param key a string value that matches a partition calculated from the partition
356
     *            function provided to {@link #registerIndex(String, Function)}
357
     * @return a collection of data, an empty collection if nothing found
358
     */
359
    public Collection<T> getIndexedData(String indexName, String key) {
360 1 1. getIndexedData : negated conditional → KILLED
        if (!registeredIndexes.containsKey(indexName)) {
361
            throw new DbException("There is no index registered on the database Db<"+this.emptyInstance.getClass().getSimpleName()+"> with a name of \""+indexName+"\"");
362
        }
363
        Set<T> values = registeredIndexes.get(indexName).get(key);
364
        // return an empty set rather than null
365 1 1. getIndexedData : replaced return value with Collections.emptyList for com/renomad/minum/database/AbstractDb::getIndexedData → KILLED
        return Objects.requireNonNullElseGet(values, Set::of);
366
    }
367
368
    /**
369
     * Get a set of the currently-registered indexes on this database, useful
370
     * for debugging.
371
     */
372
    public Set<String> getSetOfIndexes() {
373 1 1. getSetOfIndexes : replaced return value with Collections.emptySet for com/renomad/minum/database/AbstractDb::getSetOfIndexes → KILLED
        return partitioningMap.keySet();
374
    }
375
376
    /**
377
     * A utility to find exactly one item from the database.
378
     * <br>
379
     * This utility will search the indexes for a particular data by
380
     * indexName and indexKey.  If not found, it will return null. If
381
     * found, it will be returned. If more than one are found, an exception
382
     * will be thrown.  Use this tool when the data has been uniquely
383
     * indexed, like for example when setting a unique identifier into
384
     * each data.
385
     * @param indexName the name of the index, an arbitrary value set by the
386
     *                  user to help distinguish among potentially many indexes
387
     *                  set on this data
388
     * @param indexKey the key for this particular value, such as a UUID or a name
389
     *                 or any other way to partition the data
390
     * @see #findExactlyOne(String, String, Callable)
391
     */
392
    public T findExactlyOne(String indexName, String indexKey) {
393 1 1. findExactlyOne : replaced return value with null for com/renomad/minum/database/AbstractDb::findExactlyOne → KILLED
        return findExactlyOne(indexName, indexKey, () -> null);
394
    }
395
396
    /**
397
     * Find one item, with an alternate value if null
398
     * <br>
399
     * This utility will search the indexes for a particular data by
400
     * indexName and indexKey.  If not found, it will return null. If
401
     * found, it will be returned. If more than one are found, an exception
402
     * will be thrown.  Use this tool when the data has been uniquely
403
     * indexed, like for example when setting a unique identifier into
404
     * each data.
405
     * @param indexName the name of the index, an arbitrary value set by the
406
     *                  user to help distinguish among potentially many indexes
407
     *                  set on this data
408
     * @param indexKey the key for this particular value, such as a UUID or a name
409
     *                 or any other way to partition the data
410
     * @param alternate a functional interface that will be run if the result would
411
     *                  have been null, useful for situations where you don't want
412
     *                  the output to be null when nothing is found.
413
     * @see #findExactlyOne(String, String)
414
     */
415
    public T findExactlyOne(String indexName, String indexKey, Callable<T> alternate) {
416
        Collection<T> indexedData = getIndexedData(indexName, indexKey);
417 1 1. findExactlyOne : negated conditional → KILLED
        if (indexedData.isEmpty()) {
418
            try {
419 1 1. findExactlyOne : replaced return value with null for com/renomad/minum/database/AbstractDb::findExactlyOne → KILLED
                return alternate.call();
420
            } catch (Exception ex) {
421
                throw new DbException(ex);
422
            }
423 1 1. findExactlyOne : negated conditional → KILLED
        } else if (indexedData.size() == 1) {
424 1 1. findExactlyOne : replaced return value with null for com/renomad/minum/database/AbstractDb::findExactlyOne → KILLED
            return indexedData.stream().findFirst().orElseThrow();
425
        } else {
426
            throw new DbException("More than one item found when searching database Db<%s> on index \"%s\" with key %s"
427
                    .formatted(emptyInstance.getClass().getSimpleName(), indexName, indexKey));
428
        }
429
    }
430
431
}

Mutations

166

1.1
Location : writeToMemory
Killed by : com.renomad.minum.database.DbTests.testSearchUtils_SearchFindsNothing(com.renomad.minum.database.DbTests)
negated conditional → KILLED

167

1.1
Location : writeToMemory
Killed by : com.renomad.minum.database.DbTests.testIndex_NegativeCase_ExceptionThrownByPartitionAlgorithm(com.renomad.minum.database.DbTests)
removed call to com/renomad/minum/database/AbstractDb::addToIndexes → KILLED

169

1.1
Location : writeToMemory
Killed by : com.renomad.minum.database.DbEngine2Tests.testIndex_Update(com.renomad.minum.database.DbEngine2Tests)
removed call to com/renomad/minum/database/AbstractDb::removeFromIndexes → KILLED

170

1.1
Location : writeToMemory
Killed by : com.renomad.minum.database.DbEngine2Tests.testIndex_Update(com.renomad.minum.database.DbEngine2Tests)
removed call to com/renomad/minum/database/AbstractDb::addToIndexes → KILLED

185

1.1
Location : processDataIndex
Killed by : com.renomad.minum.database.DbTests.testIndex_NegativeCase_ExceptionThrownByPartitionAlgorithm(com.renomad.minum.database.DbTests)
negated conditional → KILLED

186

1.1
Location : processDataIndex
Killed by : com.renomad.minum.database.DbEngine2Tests.test_FailureDuringWrite(com.renomad.minum.database.DbEngine2Tests)
removed call to com/renomad/minum/database/DbData::setIndex → KILLED

191

1.1
Location : lambda$processDataIndex$1
Killed by : none
replaced boolean return with true for com/renomad/minum/database/AbstractDb::lambda$processDataIndex$1 → TIMED_OUT

2.2
Location : lambda$processDataIndex$1
Killed by : com.renomad.minum.database.DbEngine2Tests.testIndex_Update(com.renomad.minum.database.DbEngine2Tests)
negated conditional → KILLED

192

1.1
Location : processDataIndex
Killed by : com.renomad.minum.database.DbEngine2Tests.testIndex_Update(com.renomad.minum.database.DbEngine2Tests)
negated conditional → KILLED

198

1.1
Location : processDataIndex
Killed by : com.renomad.minum.database.DbTests.testSearchUtils_SearchFindsNothing(com.renomad.minum.database.DbTests)
replaced boolean return with false for com/renomad/minum/database/AbstractDb::processDataIndex → KILLED

2.2
Location : processDataIndex
Killed by : com.renomad.minum.database.DbEngine2Tests.testIndex_Update(com.renomad.minum.database.DbEngine2Tests)
replaced boolean return with true for com/renomad/minum/database/AbstractDb::processDataIndex → KILLED

218

1.1
Location : deleteFromMemory
Killed by : none
negated conditional → TIMED_OUT

222

1.1
Location : deleteFromMemory
Killed by : com.renomad.minum.database.DbEngine2Tests.testIndexesOnPartitionedData(com.renomad.minum.database.DbEngine2Tests)
negated conditional → KILLED

228

1.1
Location : deleteFromMemory
Killed by : com.renomad.minum.database.DbEngine2Tests.testIndexesOnPartitionedData(com.renomad.minum.database.DbEngine2Tests)
removed call to com/renomad/minum/database/AbstractDb::removeFromIndexes → KILLED

232

1.1
Location : deleteFromMemory
Killed by : none
negated conditional → TIMED_OUT

233

1.1
Location : deleteFromMemory
Killed by : none
removed call to java/util/concurrent/atomic/AtomicLong::set → TIMED_OUT

254

1.1
Location : lambda$addToIndexes$3
Killed by : com.renomad.minum.database.DbTests.testSearchUtils_SearchFindsNothing(com.renomad.minum.database.DbTests)
replaced return value with Collections.emptySet for com/renomad/minum/database/AbstractDb::lambda$addToIndexes$3 → KILLED

276

1.1
Location : lambda$removeFromIndexes$4
Killed by : com.renomad.minum.database.DbEngine2Tests.testIndexesOnPartitionedData(com.renomad.minum.database.DbEngine2Tests)
replaced boolean return with true for com/renomad/minum/database/AbstractDb::lambda$removeFromIndexes$4 → KILLED

2.2
Location : lambda$removeFromIndexes$4
Killed by : com.renomad.minum.database.DbEngine2Tests.testIndex_Update(com.renomad.minum.database.DbEngine2Tests)
negated conditional → KILLED

280

1.1
Location : removeFromIndexes
Killed by : com.renomad.minum.database.DbEngine2Tests.testIndexesOnPartitionedData(com.renomad.minum.database.DbEngine2Tests)
negated conditional → KILLED

336

1.1
Location : registerIndex
Killed by : com.renomad.minum.database.DbTests.testIndex_GetListOfIndexes(com.renomad.minum.database.DbTests)
negated conditional → KILLED

339

1.1
Location : registerIndex
Killed by : com.renomad.minum.database.DbTests.testIndex_GetListOfIndexes(com.renomad.minum.database.DbTests)
negated conditional → KILLED

2.2
Location : registerIndex
Killed by : com.renomad.minum.database.DbTests.testIndex_GetListOfIndexes(com.renomad.minum.database.DbTests)
negated conditional → KILLED

342

1.1
Location : registerIndex
Killed by : com.renomad.minum.database.DbTests.testIndex_GetListOfIndexes(com.renomad.minum.database.DbTests)
negated conditional → KILLED

348

1.1
Location : registerIndex
Killed by : com.renomad.minum.database.DbTests.testIndex_EdgeCase_MultipleIndexes(com.renomad.minum.database.DbTests)
replaced boolean return with false for com/renomad/minum/database/AbstractDb::registerIndex → KILLED

360

1.1
Location : getIndexedData
Killed by : com.renomad.minum.database.DbTests.testSearchUtility_EdgeCase_NoIndexRegistered(com.renomad.minum.database.DbTests)
negated conditional → KILLED

365

1.1
Location : getIndexedData
Killed by : com.renomad.minum.database.DbEngine2Tests.testSearchUtility(com.renomad.minum.database.DbEngine2Tests)
replaced return value with Collections.emptyList for com/renomad/minum/database/AbstractDb::getIndexedData → KILLED

373

1.1
Location : getSetOfIndexes
Killed by : com.renomad.minum.database.DbTests.testIndex_GetListOfIndexes(com.renomad.minum.database.DbTests)
replaced return value with Collections.emptySet for com/renomad/minum/database/AbstractDb::getSetOfIndexes → KILLED

393

1.1
Location : findExactlyOne
Killed by : com.renomad.minum.database.DbEngine2Tests.testSearchUtility(com.renomad.minum.database.DbEngine2Tests)
replaced return value with null for com/renomad/minum/database/AbstractDb::findExactlyOne → KILLED

417

1.1
Location : findExactlyOne
Killed by : com.renomad.minum.database.DbTests.testSearchUtils_SearchFindsNothing(com.renomad.minum.database.DbTests)
negated conditional → KILLED

419

1.1
Location : findExactlyOne
Killed by : com.renomad.minum.database.DbEngine2Tests.testSearchUtility(com.renomad.minum.database.DbEngine2Tests)
replaced return value with null for com/renomad/minum/database/AbstractDb::findExactlyOne → KILLED

423

1.1
Location : findExactlyOne
Killed by : com.renomad.minum.database.DbEngine2Tests.testSearchUtility(com.renomad.minum.database.DbEngine2Tests)
negated conditional → KILLED

424

1.1
Location : findExactlyOne
Killed by : com.renomad.minum.database.DbEngine2Tests.testSearchUtility(com.renomad.minum.database.DbEngine2Tests)
replaced return value with null for com/renomad/minum/database/AbstractDb::findExactlyOne → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0