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

Mutations

165

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

166

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

168

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

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::addToIndexes → KILLED

184

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

185

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

190

1.1
Location : lambda$processDataIndex$1
Killed by : com.renomad.minum.database.DbTests.test_Locking(com.renomad.minum.database.DbTests)
replaced boolean return with true for com/renomad/minum/database/AbstractDb::lambda$processDataIndex$1 → KILLED

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

191

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

197

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

217

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

221

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

227

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

231

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

232

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

253

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

275

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

279

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

358

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

361

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

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

364

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

370

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

382

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

387

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

395

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

415

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

439

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

441

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

445

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

446

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

Active mutators

Tests examined


Report generated by PIT 1.17.0