1 /**
2 Copyright: Copyright (c) 2018-2021, Joakim Brännström. All rights reserved.
3 License: MPL-2
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 This Source Code Form is subject to the terms of the Mozilla Public License,
7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain
8 one at http://mozilla.org/MPL/2.0/.
9 
10 This module contains the a basic database interface that have minimal
11 dependencies on internal modules.  It is intended to be reusable from the test
12 suite.
13 
14 The only acceptable dependency are:
15  * ../type.d
16  * ..backend/type.d
17  * ../database/type.d
18  * ../database/schema.d
19 */
20 module dextool.plugin.mutate.backend.database.standalone;
21 
22 import core.time : Duration, dur;
23 import logger = std.experimental.logger;
24 import std.algorithm : copy, map, joiner, filter;
25 import std.array : Appender, appender, array, empty;
26 import std.conv : to;
27 import std.datetime : SysTime, Clock;
28 import std.exception : collectException;
29 import std.format : format;
30 import std.path : relativePath;
31 import std.range : enumerate;
32 import std.regex : Regex, matchFirst;
33 import std.typecons : Nullable, Flag, No;
34 
35 import d2sqlite3 : SqlDatabase = Database;
36 import miniorm : Miniorm, select, insert, insertOrReplace, delete_,
37     insertOrIgnore, toSqliteDateTime, fromSqLiteDateTime, Bind;
38 import my.named_type;
39 import my.optional;
40 import my.term_color;
41 import my.set;
42 
43 import dextool.type : AbsolutePath, Path, ExitStatusType;
44 
45 import dextool.plugin.mutate.backend.database.schema;
46 import dextool.plugin.mutate.backend.database.type;
47 import dextool.plugin.mutate.backend.type : MutationPoint, Mutation, Checksum,
48     Language, Offset, TestCase, SourceLoc, SchemataChecksum;
49 import dextool.plugin.mutate.type : MutationOrder;
50 
51 /** Database wrapper with minimal dependencies.
52  */
53 struct Database {
54     package Miniorm db;
55 
56     /** Create a database by either opening an existing or initializing a new.
57      *
58      * Params:
59      *  db = path to the database
60      */
61     static auto make(string db) @safe {
62         return Database(initializeDB(db));
63     }
64 
65     scope auto transaction() @trusted {
66         return db.transaction;
67     }
68 
69     void run(string sql) {
70         db.run(sql);
71     }
72 
73     bool isToolVersionDifferent(ToolVersion compareTo) @trusted {
74         foreach (a; db.run(select!DextoolVersionTable)) {
75             return a.checksum != compareTo.get;
76         }
77         // if there is no tool version recorded then assume it is different.
78         return true;
79     }
80 
81     /// Update the version of the tool.
82     void updateToolVersion(const ToolVersion tv) @trusted {
83         db.run(delete_!DextoolVersionTable);
84         db.run(insert!DextoolVersionTable, DextoolVersionTable(tv.get));
85     }
86 
87     /// If the file has already been analyzed.
88     bool isAnalyzed(const Path p) @trusted {
89         auto stmt = db.prepare("SELECT count(*) FROM files WHERE path=:path LIMIT 1");
90         stmt.get.bind(":path", cast(string) p);
91         auto res = stmt.get.execute;
92         return res.oneValue!long != 0;
93     }
94 
95     /// If the file has already been analyzed.
96     bool isAnalyzed(const Path p, const Checksum cs) @trusted {
97         auto stmt = db.prepare(
98                 "SELECT count(*) FROM files WHERE path=:path AND checksum0=:cs0 AND checksum1=:cs1 LIMIT 1");
99         stmt.get.bind(":path", cast(string) p);
100         stmt.get.bind(":cs0", cast(long) cs.c0);
101         stmt.get.bind(":cs1", cast(long) cs.c1);
102         auto res = stmt.get.execute;
103         return res.oneValue!long != 0;
104     }
105 
106     Nullable!FileId getFileId(const Path p) @trusted {
107         static immutable sql = format("SELECT id FROM %s WHERE path=:path", filesTable);
108         auto stmt = db.prepare(sql);
109         stmt.get.bind(":path", p.toString);
110         auto res = stmt.get.execute;
111 
112         typeof(return) rval;
113         if (!res.empty)
114             rval = FileId(res.oneValue!long);
115         return rval;
116     }
117 
118     /// Returns: the path ID for the mutant.
119     Nullable!FileId getFileId(const MutationId id) @trusted {
120         static immutable sql = format("SELECT t1.file_id
121             FROM %s t0, %s t1
122             WHERE t0.id = :id AND t0.mp_id = t1.id",
123                 mutationTable, mutationPointTable);
124         auto stmt = db.prepare(sql);
125         stmt.get.bind(":id", cast(long) id);
126 
127         typeof(return) rval;
128         foreach (ref r; stmt.get.execute)
129             rval = FileId(r.peek!long(0));
130         return rval;
131     }
132 
133     /// Returns: the file path that the id correspond to.
134     Nullable!Path getFile(const FileId id) @trusted {
135         static immutable sql = format("SELECT path FROM %s WHERE id = :id", filesTable);
136         auto stmt = db.prepare(sql);
137         stmt.get.bind(":id", id.get);
138 
139         typeof(return) rval;
140         foreach (ref r; stmt.get.execute)
141             rval = Path(r.peek!string(0));
142         return rval;
143     }
144 
145     Optional!Language getFileIdLanguage(const FileId id) @trusted {
146         static immutable sql = format!"SELECT lang FROM %s WHERE id = :id"(filesTable);
147         auto stmt = db.prepare(sql);
148         stmt.get.bind(":id", id.get);
149 
150         foreach (ref r; stmt.get.execute)
151             return some(r.peek!ubyte(0).to!Language);
152         return none!Language;
153     }
154 
155     /// Returns: all files tagged as a root.
156     FileId[] getRootFiles() @trusted {
157         static immutable sql = format!"SELECT id FROM %s WHERE root=1"(filesTable);
158 
159         auto app = appender!(FileId[])();
160         auto stmt = db.prepare(sql);
161         foreach (ref r; stmt.get.execute) {
162             app.put(r.peek!long(0).FileId);
163         }
164         return app.data;
165     }
166 
167     /// Remove the file with all mutations that are coupled to it.
168     void removeFile(const Path p) @trusted {
169         auto stmt = db.prepare(format!"DELETE FROM %s WHERE path=:path"(filesTable));
170         stmt.get.bind(":path", p.toString);
171         stmt.get.execute;
172     }
173 
174     /// Returns: All files in the database as relative paths.
175     Path[] getFiles() @trusted {
176         auto stmt = db.prepare(format!"SELECT path FROM %s"(filesTable));
177         auto res = stmt.get.execute;
178 
179         auto app = appender!(Path[]);
180         foreach (ref r; res) {
181             app.put(Path(r.peek!string(0)));
182         }
183 
184         return app.data;
185     }
186 
187     Nullable!Checksum getFileChecksum(const Path p) @trusted {
188         static immutable sql = format!"SELECT checksum0,checksum1 FROM %s WHERE path=:path"(
189                 filesTable);
190         auto stmt = db.prepare(sql);
191         stmt.get.bind(":path", p.toString);
192         auto res = stmt.get.execute;
193 
194         typeof(return) rval;
195         if (!res.empty) {
196             rval = Checksum(res.front.peek!long(0), res.front.peek!long(1));
197         }
198 
199         return rval;
200     }
201 
202     /// Returns: the timestamp of the newest file that was added.
203     Optional!SysTime getNewestFile() @trusted {
204         auto stmt = db.prepare(format!"SELECT timestamp
205             FROM %s ORDER BY datetime(timestamp) DESC LIMIT 1"(
206                 filesTable));
207         auto res = stmt.get.execute;
208 
209         foreach (ref r; res) {
210             return some(r.peek!string(0).fromSqLiteDateTime);
211         }
212 
213         return none!SysTime;
214     }
215 
216     void put(const Path p, Checksum cs, const Language lang, bool isRoot) @trusted {
217         static immutable sql = format!"INSERT OR IGNORE INTO %s (path, checksum0, checksum1, lang, timestamp, root)
218             VALUES (:path, :checksum0, :checksum1, :lang, :time, :root)"(
219                 filesTable);
220         auto stmt = db.prepare(sql);
221         stmt.get.bind(":path", p.toString);
222         stmt.get.bind(":checksum0", cast(long) cs.c0);
223         stmt.get.bind(":checksum1", cast(long) cs.c1);
224         stmt.get.bind(":lang", cast(long) lang);
225         stmt.get.bind(":time", Clock.currTime.toSqliteDateTime);
226         stmt.get.bind(":root", isRoot);
227         stmt.get.execute;
228     }
229 
230     /** Remove all mutants points from the database.
231      *
232      * This removes all the mutants because of the cascade delete of the
233      * tables. But it will keep the mutation statuses and thus the checksums
234      * and the status of the code changes.
235      *
236      * This then mean that when mutations+mutation points are added back they
237      * may reconnect with a mutation status.
238      */
239     void removeAllMutationPoints() @trusted {
240         static immutable sql = format!"DELETE FROM %s"(mutationPointTable);
241         db.run(sql);
242     }
243 
244     /// ditto
245     void removeAllFiles() @trusted {
246         static immutable sql = format!"DELETE FROM %s"(filesTable);
247         db.run(sql);
248     }
249 
250     /// Compact the database by running a VACUUM operation
251     void vacuum() @trusted {
252         db.run("VACUUM");
253     }
254 
255     /// Returns: the stored scores in ascending order by their `time`.
256     MutationScore[] getMutationScoreHistory() @trusted {
257         import std.algorithm : sort;
258 
259         auto app = appender!(MutationScore[])();
260         foreach (r; db.run(select!MutationScoreHistoryTable)) {
261             app.put(MutationScore(r.timeStamp, typeof(MutationScore.score)(r.score)));
262         }
263 
264         return app.data.sort!((a, b) => a.timeStamp < b.timeStamp).array;
265     }
266 
267     /// Add a mutation score to the history table.
268     void putMutationScore(const MutationScore score) @trusted {
269         db.run(insert!MutationScoreHistoryTable, MutationScoreHistoryTable(0,
270                 score.timeStamp, score.score.get));
271     }
272 
273     /// Trim the mutation score history table to only contain the last `keep` scores.
274     void trimMutationScore(const long keep) @trusted {
275         auto stmt = db.prepare(format!"SELECT count(*) FROM %s"(mutationScoreHistoryTable));
276         const sz = stmt.get.execute.oneValue!long;
277 
278         if (sz < keep)
279             return;
280 
281         auto ids = appender!(long[])();
282         stmt = db.prepare(format!"SELECT t0.id FROM t0 %s ORDER BY t0.time ASC LIMIT :limit"(
283                 mutationScoreHistoryTable));
284         stmt.get.bind(":limit", sz - keep);
285         foreach (a; stmt.get.execute)
286             ids.put(a.peek!long(0));
287 
288         stmt = db.prepare(format!"DELETE FROM %s WHERE id = :id"(mutationScoreHistoryTable));
289         foreach (a; ids.data) {
290             stmt.get.bind(":id", a);
291             stmt.get.execute;
292             stmt.get.reset;
293         }
294     }
295 
296     /// Returns: the latest/newest timestamp of the tracked SUT or test files.
297     Optional!SysTime getLatestTimeStampOfTestOrSut() @trusted {
298         import std.algorithm : max;
299 
300         auto test = testFileApi.getNewestTestFile;
301         auto sut = getNewestFile;
302 
303         if (!(test.hasValue || sut.hasValue))
304             return none!SysTime;
305 
306         return some(max(test.orElse(TestFile.init).timeStamp, sut.orElse(SysTime.init)));
307     }
308 
309     DbDependency dependencyApi() return @safe {
310         return typeof(return)(&db, &this);
311     }
312 
313     DbTestCmd testCmdApi() return @safe {
314         return typeof(return)(&db, &this);
315     }
316 
317     DbTestCase testCaseApi() return @safe {
318         return typeof(return)(&db, &this);
319     }
320 
321     DbMutant mutantApi() return @safe {
322         return typeof(return)(&db, &this);
323     }
324 
325     DbWorklist worklistApi() return @safe {
326         return typeof(return)(&db, &this);
327     }
328 
329     DbMemOverload memOverloadApi() return @safe {
330         return typeof(return)(&db);
331     }
332 
333     DbMarkMutant markMutantApi() return @safe {
334         return typeof(return)(&db, &this);
335     }
336 
337     DbTimeout timeoutApi() return @safe {
338         return typeof(return)(&db, &this);
339     }
340 
341     DbCoverage coverageApi() return @safe {
342         return typeof(return)(&db, &this);
343     }
344 
345     DbSchema schemaApi() return @safe {
346         return typeof(return)(&db, &this);
347     }
348 
349     DbTestFile testFileApi() return @safe {
350         return typeof(return)(&db, &this);
351     }
352 
353     DbMetaData metaDataApi() return @safe {
354         return typeof(return)(&db, &this);
355     }
356 }
357 
358 /** Dependencies between root and those files that should trigger a re-analyze
359  * of the root if they are changed.
360  */
361 struct DbDependency {
362     private Miniorm* db;
363     private Database* wrapperDb;
364 
365     /// The root must already exist or the whole operation will fail with an sql error.
366     void set(const Path path, const DepFile[] deps) @trusted {
367         static immutable insertDepSql = format!"INSERT OR IGNORE INTO %1$s (file,checksum0,checksum1)
368             VALUES(:file,:cs0,:cs1)
369             ON CONFLICT (file) DO UPDATE SET checksum0=:cs0,checksum1=:cs1 WHERE file=:file"(
370                 depFileTable);
371 
372         auto stmt = db.prepare(insertDepSql);
373         auto ids = appender!(long[])();
374         foreach (a; deps) {
375             stmt.get.bind(":file", a.file.toString);
376             stmt.get.bind(":cs0", cast(long) a.checksum.c0);
377             stmt.get.bind(":cs1", cast(long) a.checksum.c1);
378             stmt.get.execute;
379             stmt.get.reset;
380 
381             // can't use lastInsertRowid because a conflict would not update
382             // the ID.
383             auto id = getId(a.file);
384             if (id.hasValue)
385                 ids.put(id.orElse(0L));
386         }
387 
388         static immutable addRelSql = format!"INSERT OR IGNORE INTO %1$s (dep_id,file_id) VALUES(:did, :fid)"(
389                 depRootTable);
390         stmt = db.prepare(addRelSql);
391         const fid = () {
392             auto a = wrapperDb.getFileId(path);
393             if (a.isNull) {
394                 throw new Exception(
395                         "File is not tracked (is missing from the files table in the database) "
396                         ~ path);
397             }
398             return a.get;
399         }();
400 
401         foreach (id; ids.data) {
402             stmt.get.bind(":did", id);
403             stmt.get.bind(":fid", fid.get);
404             stmt.get.execute;
405             stmt.get.reset;
406         }
407     }
408 
409     private Optional!long getId(const Path file) {
410         foreach (a; db.run(select!DependencyFileTable.where("file = :file",
411                 Bind("file")), file.toString)) {
412             return some(a.id);
413         }
414         return none!long;
415     }
416 
417     /// Returns: all dependencies.
418     DepFile[] getAll() @trusted {
419         return db.run(select!DependencyFileTable)
420             .map!(a => DepFile(Path(a.file), Checksum(a.checksum0, a.checksum1))).array;
421     }
422 
423     /// Returns: all files that a root is dependent on.
424     Path[] get(const Path root) @trusted {
425         static immutable sql = format!"SELECT t0.file
426             FROM %1$s t0, %2$s t1, %3$s t2
427             WHERE
428             t0.id = t1.dep_id AND
429             t1.file_id = t2.id AND
430             t2.path = :file"(depFileTable,
431                 depRootTable, filesTable);
432 
433         auto stmt = db.prepare(sql);
434         stmt.get.bind(":file", root.toString);
435         auto app = appender!(Path[])();
436         foreach (ref a; stmt.get.execute) {
437             app.put(Path(a.peek!string(0)));
438         }
439 
440         return app.data;
441     }
442 
443     /// Remove all dependencies that have no relation to a root.
444     void cleanup() @trusted {
445         db.run(format!"DELETE FROM %1$s
446                WHERE id NOT IN (SELECT dep_id FROM %2$s)"(depFileTable,
447                 depRootTable));
448     }
449 
450 }
451 
452 struct DbTestCmd {
453     import my.hash : Checksum64;
454 
455     private Miniorm* db;
456     private Database* wrapperDb;
457 
458     void set(string testCmd, ChecksumTestCmdOriginal cs) @trusted {
459         static immutable sql = format!"INSERT OR IGNORE INTO %1$s (checksum, cmd) VALUES(:cs, :cmd)"(
460                 testCmdOriginalTable);
461 
462         auto stmt = db.prepare(sql);
463         stmt.get.bind(":cs", cast(long) cs.get.c0);
464         stmt.get.bind(":cmd", testCmd);
465         stmt.get.execute;
466     }
467 
468     void removeOriginal(string testCmd) @trusted {
469         static immutable sql = "DELETE FROM " ~ testCmdOriginalTable ~ " WHERE cmd = :cmd";
470         auto stmt = db.prepare(sql);
471         stmt.get.bind(":cmd", testCmd);
472         stmt.get.execute;
473     }
474 
475     void remove(ChecksumTestCmdOriginal cs) @trusted {
476         static immutable sql = "DELETE FROM " ~ testCmdOriginalTable ~ " WHERE checksum = :cs";
477         auto stmt = db.prepare(sql);
478         stmt.get.bind(":cs", cast(long) cs.get.c0);
479         stmt.get.execute;
480     }
481 
482     Set!Checksum64 original() @trusted {
483         static immutable sql = "SELECT checksum FROM " ~ testCmdOriginalTable;
484 
485         auto stmt = db.prepare(sql);
486         typeof(return) rval;
487         foreach (ref r; stmt.get.execute)
488             rval.add(Checksum64(cast(ulong) r.peek!long(0)));
489         return rval;
490     }
491 
492     void add(ChecksumTestCmdMutated cs, Mutation.Status status) @trusted {
493         static immutable sql = format!"INSERT OR REPLACE INTO %1$s (checksum,status,timestamp) VALUES(:cs,:status,:ts)"(
494                 testCmdMutatedTable);
495 
496         auto stmt = db.prepare(sql);
497         stmt.get.bind(":cs", cast(long) cs.get.c0);
498         stmt.get.bind(":status", cast(long) status);
499         stmt.get.bind(":ts", Clock.currTime.toSqliteDateTime);
500         stmt.get.execute;
501     }
502 
503     /// Trim the saved checksums to only the latest `keep`.
504     void trimMutated(const long keep) @trusted {
505         auto stmt = db.prepare(format!"SELECT count(*) FROM %s"(testCmdMutatedTable));
506         const sz = stmt.get.execute.oneValue!long;
507         if (sz < keep)
508             return;
509 
510         auto ids = appender!(long[])();
511         stmt = db.prepare(format!"SELECT checksum FROM %s ORDER BY timestamp ASC LIMIT :limit"(
512                 testCmdMutatedTable));
513         stmt.get.bind(":limit", sz - keep);
514         foreach (a; stmt.get.execute)
515             ids.put(a.peek!long(0));
516 
517         stmt = db.prepare(format!"DELETE FROM %s WHERE checksum = :cs"(testCmdMutatedTable));
518         foreach (a; ids.data) {
519             stmt.get.bind(":cs", a);
520             stmt.get.execute;
521             stmt.get.reset;
522         }
523     }
524 
525     Mutation.Status[Checksum64] mutated() @trusted {
526         static immutable sql = "SELECT checksum,status FROM " ~ testCmdMutatedTable;
527 
528         auto stmt = db.prepare(sql);
529         typeof(return) rval;
530         foreach (ref r; stmt.get.execute)
531             rval[Checksum64(cast(ulong) r.peek!long(0))] = r.peek!long(1).to!(Mutation.Status);
532         return rval;
533     }
534 
535     /// Returns: the stored runtimes in ascending order by their `timeStamp`.
536     TestCmdRuntime[] getTestCmdRuntimes() @trusted {
537         import std.algorithm : sort;
538 
539         auto app = appender!(TestCmdRuntime[])();
540         foreach (r; db.run(select!RuntimeHistoryTable)) {
541             app.put(TestCmdRuntime(r.timeStamp, r.timeMs.dur!"msecs"));
542         }
543 
544         return app.data.sort!((a, b) => a.timeStamp < b.timeStamp).array;
545     }
546 
547     /// Drop all currently stored runtimes and replaces with `runtime`.
548     void setTestCmdRuntimes(const TestCmdRuntime[] runtimes) @trusted {
549         db.run(format!"DELETE FROM %s"(runtimeHistoryTable));
550         db.run(insertOrReplace!RuntimeHistoryTable,
551                 runtimes.enumerate.map!(a => RuntimeHistoryTable(a.index,
552                     a.value.timeStamp, a.value.runtime.total!"msecs")));
553     }
554 }
555 
556 struct DbTestCase {
557     private Miniorm* db;
558     private Database* wrapperDb;
559 
560     /** Add a link between the mutation and what test case killed it.
561      *
562      * Params:
563      *  id = ?
564      *  tcs = test cases to add
565      */
566     void updateMutationTestCases(const MutationId id, const(TestCase)[] tcs) @trusted {
567         if (tcs.length == 0)
568             return;
569 
570         immutable statusId = () {
571             static immutable st_id_for_mutation_q = format!"SELECT st_id FROM %s WHERE id=:id"(
572                     mutationTable);
573             auto stmt = db.prepare(st_id_for_mutation_q);
574             stmt.get.bind(":id", cast(long) id);
575             return stmt.get.execute.oneValue!long;
576         }();
577         updateMutationTestCases(MutationStatusId(statusId), tcs);
578     }
579 
580     /** Add a link between the mutation and what test case killed it.
581      *
582      * Params:
583      *  id = ?
584      *  tcs = test cases to add
585      */
586     void updateMutationTestCases(const MutationStatusId statusId, const(TestCase)[] tcs) @trusted {
587         if (tcs.length == 0)
588             return;
589 
590         try {
591             static immutable remove_old_sql = format!"DELETE FROM %s WHERE st_id=:id"(
592                     killedTestCaseTable);
593             auto stmt = db.prepare(remove_old_sql);
594             stmt.get.bind(":id", statusId.get);
595             stmt.get.execute;
596         } catch (Exception e) {
597         }
598 
599         static immutable add_if_non_exist_tc_sql = format!"INSERT OR IGNORE INTO %s (name,is_new) SELECT :name1,1 WHERE NOT EXISTS (SELECT * FROM %s WHERE name = :name2)"(
600                 allTestCaseTable, allTestCaseTable);
601         auto stmt_insert_tc = db.prepare(add_if_non_exist_tc_sql);
602 
603         static immutable add_new_sql = format!"INSERT OR IGNORE INTO %s (st_id, tc_id, location) SELECT :st_id,t1.id,:loc FROM %s t1 WHERE t1.name = :tc"(
604                 killedTestCaseTable, allTestCaseTable);
605         auto stmt_insert = db.prepare(add_new_sql);
606         foreach (const tc; tcs) {
607             try {
608                 stmt_insert_tc.get.reset;
609                 stmt_insert_tc.get.bind(":name1", tc.name);
610                 stmt_insert_tc.get.bind(":name2", tc.name);
611                 stmt_insert_tc.get.execute;
612 
613                 stmt_insert.get.reset;
614                 stmt_insert.get.bind(":st_id", statusId.get);
615                 stmt_insert.get.bind(":loc", tc.location);
616                 stmt_insert.get.bind(":tc", tc.name);
617                 stmt_insert.get.execute;
618             } catch (Exception e) {
619                 logger.warning(e.msg);
620             }
621         }
622     }
623 
624     /** Set detected test cases.
625      *
626      * This will replace those that where previously stored.
627      *
628      * Returns: ID of affected mutation statuses.
629      */
630     MutationStatusId[] setDetectedTestCases(const(TestCase)[] tcs) @trusted {
631         if (tcs.length == 0)
632             return null;
633 
634         auto ids = appender!(MutationStatusId[])();
635 
636         static immutable tmp_name = "tmp_new_tc_" ~ __LINE__.to!string;
637         internalAddDetectedTestCases(tcs, tmp_name);
638 
639         static immutable mut_st_id = format!"SELECT DISTINCT t1.st_id
640             FROM %s t0, %s t1
641             WHERE
642             t0.name NOT IN (SELECT name FROM %s) AND
643             t0.id = t1.tc_id"(allTestCaseTable,
644                 killedTestCaseTable, tmp_name);
645         auto stmt = db.prepare(mut_st_id);
646         foreach (res; stmt.get.execute) {
647             ids.put(res.peek!long(0).MutationStatusId);
648         }
649 
650         static immutable remove_old_sql = format!"DELETE FROM %s WHERE name NOT IN (SELECT name FROM %s)"(
651                 allTestCaseTable, tmp_name);
652         db.run(remove_old_sql);
653 
654         db.run(format!"DROP TABLE %s"(tmp_name));
655 
656         return ids.data;
657     }
658 
659     /** Add test cases to those that have been detected.
660      *
661      * They will be added if they are unique.
662      */
663     void addDetectedTestCases(const(TestCase)[] tcs) @trusted {
664         if (tcs.length == 0)
665             return;
666 
667         static immutable tmp_name = "tmp_new_tc_" ~ __LINE__.to!string;
668         internalAddDetectedTestCases(tcs, tmp_name);
669         db.run(format!"DROP TABLE %s"(tmp_name));
670     }
671 
672     /// ditto.
673     private void internalAddDetectedTestCases(const(TestCase)[] tcs, string tmp_tbl) @trusted {
674         db.run(format!"CREATE TEMP TABLE %s (id INTEGER PRIMARY KEY, name TEXT NOT NULL)"(
675                 tmp_tbl));
676 
677         const add_tc_sql = format!"INSERT OR IGNORE INTO %s (name) VALUES(:name)"(tmp_tbl);
678         auto insert_s = db.prepare(add_tc_sql);
679         foreach (tc; tcs.filter!(a => !a.name.empty)) {
680             insert_s.get.bind(":name", tc.name);
681             insert_s.get.execute;
682             insert_s.get.reset;
683         }
684 
685         // https://stackoverflow.com/questions/2686254/how-to-select-all-records-from-one-table-that-do-not-exist-in-another-table
686         //Q: What is happening here?
687         //
688         //A: Conceptually, we select all rows from table1 and for each row we
689         //attempt to find a row in table2 with the same value for the name
690         //column.  If there is no such row, we just leave the table2 portion of
691         //our result empty for that row. Then we constrain our selection by
692         //picking only those rows in the result where the matching row does not
693         //exist. Finally, We ignore all fields from our result except for the
694         //name column (the one we are sure that exists, from table1).
695         //
696         //While it may not be the most performant method possible in all cases,
697         //it should work in basically every database engine ever that attempts
698         //to implement ANSI 92 SQL
699         const add_missing_sql = format!"INSERT OR IGNORE INTO %s (name,is_new) SELECT t1.name,1 FROM %s t1 LEFT JOIN %s t2 ON t2.name = t1.name WHERE t2.name IS NULL"(
700                 allTestCaseTable, tmp_tbl, allTestCaseTable);
701         db.run(add_missing_sql);
702     }
703 
704     /// Returns: detected test cases.
705     TestCase[] getDetectedTestCases() @trusted {
706         auto rval = appender!(TestCase[])();
707         db.run(select!AllTestCaseTbl).map!(a => TestCase(a.name)).copy(rval);
708         return rval.data;
709     }
710 
711     /// Returns: detected test cases.
712     TestCaseId[] getDetectedTestCaseIds() @trusted {
713         auto rval = appender!(TestCaseId[])();
714         db.run(select!AllTestCaseTbl).map!(a => TestCaseId(a.id)).copy(rval);
715         return rval.data;
716     }
717 
718     /// Returns: test cases that has killed zero mutants.
719     TestCase[] getTestCasesWithZeroKills() @trusted {
720         static immutable sql = format("SELECT t1.name FROM %s t1 WHERE t1.id NOT IN (SELECT tc_id FROM %s)",
721                 allTestCaseTable, killedTestCaseTable);
722 
723         auto rval = appender!(TestCase[])();
724         auto stmt = db.prepare(sql);
725         foreach (a; stmt.get.execute)
726             rval.put(TestCase(a.peek!string(0)));
727 
728         return rval.data;
729     }
730 
731     /** Guarantees that the there are no duplications of `TestCaseId`.
732      *
733      * Returns: test cases that has killed at least one mutant.
734      */
735     TestCaseId[] getTestCasesWithAtLeastOneKill(const Mutation.Kind[] kinds) @trusted {
736         const sql = format!"SELECT DISTINCT t1.id
737             FROM %s t1, %s t2, %s t3
738             WHERE
739             t1.id = t2.tc_id AND
740             t2.st_id == t3.st_id AND
741             t3.kind IN (%(%s,%))"(allTestCaseTable,
742                 killedTestCaseTable, mutationTable, kinds.map!(a => cast(int) a));
743 
744         auto rval = appender!(TestCaseId[])();
745         auto stmt = db.prepare(sql);
746         foreach (a; stmt.get.execute)
747             rval.put(TestCaseId(a.peek!long(0)));
748 
749         return rval.data;
750     }
751 
752     /// Returns: the name of the test case.
753     string getTestCaseName(const TestCaseId id) @trusted {
754         static immutable sql = format!"SELECT name FROM %s WHERE id = :id"(allTestCaseTable);
755         auto stmt = db.prepare(sql);
756         stmt.get.bind(":id", cast(long) id);
757         auto res = stmt.get.execute;
758         return res.oneValue!string;
759     }
760 
761     /// Returns: stats about the test case.
762     TestCaseInfo getTestCaseInfo(const TestCaseId tcId, const Mutation.Kind[] kinds) @trusted {
763         const sql = format("SELECT sum(ctime),sum(ttime),count(*)
764             FROM (
765             SELECT sum(t2.compile_time_ms) ctime,sum(t2.test_time_ms) ttime
766             FROM %s t1, %s t2, %s t3
767             WHERE
768             :id = t1.tc_id AND
769             t1.st_id = t2.id AND
770             t1.st_id = t3.st_id AND
771             t3.kind IN (%(%s,%))
772             GROUP BY t1.st_id)", killedTestCaseTable,
773                 mutationStatusTable, mutationTable, kinds.map!(a => cast(int) a));
774         auto stmt = db.prepare(sql);
775         stmt.get.bind(":id", tcId.get);
776 
777         typeof(return) rval;
778         foreach (a; stmt.get.execute) {
779             rval = TestCaseInfo(MutantTimeProfile(a.peek!long(0).dur!"msecs",
780                     a.peek!long(1).dur!"msecs"), a.peek!long(2));
781         }
782         return rval;
783     }
784 
785     Nullable!TestCaseInfo getTestCaseInfo(const TestCase tc, const Mutation.Kind[] kinds) @safe {
786         typeof(return) rval;
787 
788         auto id = getTestCaseId(tc);
789         if (!id.isNull)
790             rval = getTestCaseInfo(id.get, kinds);
791 
792         return rval;
793     }
794 
795     /// Returns: all test cases for the file and the mutants they killed.
796     TestCaseInfo2[] getAllTestCaseInfo2(const FileId file, const Mutation.Kind[] kinds) @trusted {
797         // row of test case name and mutation id.
798         const sql = format("SELECT t0.name,t3.id
799             FROM %s t0, %s t1, %s t2, %s t3, %s t4
800             WHERE
801             t0.id = t1.tc_id AND
802             t1.st_id = t2.id AND
803             t2.id = t3.st_id AND
804             t4.id = :file_id AND
805             t3.kind IN (%(%s,%))", allTestCaseTable, killedTestCaseTable,
806                 mutationStatusTable, mutationTable, filesTable, kinds.map!(a => cast(int) a));
807         auto stmt = db.prepare(sql);
808         stmt.get.bind(":file_id", cast(long) file);
809 
810         MutationId[][string] data;
811         foreach (row; stmt.get.execute) {
812             const name = row.peek!string(0);
813             auto id = MutationId(row.peek!long(1));
814             data.update(name, () => [id], (ref MutationId[] a) { a ~= id; });
815         }
816 
817         auto app = appender!(TestCaseInfo2[])();
818         data.byKeyValue.map!(a => TestCaseInfo2(TestCase(a.key), a.value)).copy(app);
819         return app.data;
820     }
821 
822     /// Returns: the test case.
823     Nullable!TestCase getTestCase(const TestCaseId id) @trusted {
824         static immutable sql = format!"SELECT name FROM %s WHERE id = :id"(allTestCaseTable);
825         auto stmt = db.prepare(sql);
826         stmt.get.bind(":id", cast(long) id);
827 
828         typeof(return) rval;
829         foreach (res; stmt.get.execute) {
830             rval = TestCase(res.peek!string(0));
831         }
832         return rval;
833     }
834 
835     /// Returns: the test case id.
836     Nullable!TestCaseId getTestCaseId(const TestCase tc) @trusted {
837         static immutable sql = format!"SELECT id FROM %s WHERE name = :name"(allTestCaseTable);
838         auto stmt = db.prepare(sql);
839         stmt.get.bind(":name", tc.name);
840 
841         typeof(return) rval;
842         foreach (res; stmt.get.execute) {
843             rval = TestCaseId(res.peek!long(0));
844         }
845         return rval;
846     }
847 
848     /// The mutation ids are guaranteed to be sorted.
849     /// Returns: the mutants the test case killed.
850     MutationId[] getTestCaseMutantKills(const TestCaseId id, const Mutation.Kind[] kinds) @trusted {
851         immutable sql = format!"SELECT t2.id
852             FROM %s t1, %s t2
853             WHERE
854             t1.tc_id = :tid AND
855             t1.st_id = t2.st_id AND
856             t2.kind IN (%(%s,%))
857             GROUP BY t2.st_id
858             ORDER BY t2.id"(killedTestCaseTable,
859                 mutationTable, kinds.map!(a => cast(int) a));
860 
861         auto rval = appender!(MutationId[])();
862         auto stmt = db.prepare(sql);
863         stmt.get.bind(":tid", cast(long) id);
864         foreach (a; stmt.get.execute)
865             rval.put(MutationId(a.peek!long(0)));
866 
867         return rval.data;
868     }
869 
870     /// Returns: test cases that killed the mutant.
871     TestCase[] getTestCases(const MutationId id) @trusted {
872         Appender!(TestCase[]) rval;
873 
874         static immutable get_test_cases_sql = format!"SELECT t1.name,t2.location
875             FROM %s t1, %s t2, %s t3
876             WHERE
877             t3.id = :id AND
878             t3.st_id = t2.st_id AND
879             t2.tc_id = t1.id"(
880                 allTestCaseTable, killedTestCaseTable, mutationTable);
881         auto stmt = db.prepare(get_test_cases_sql);
882         stmt.get.bind(":id", cast(long) id);
883         foreach (a; stmt.get.execute)
884             rval.put(TestCase(a.peek!string(0), a.peek!string(1)));
885 
886         return rval.data;
887     }
888 
889     /// Returns: if the mutant have any test cases recorded that killed it
890     bool hasTestCases(const MutationStatusId id) @trusted {
891         static immutable sql = format!"SELECT count(*) FROM %s t0 WHERE t0.st_id = :id"(
892                 killedTestCaseTable);
893         auto stmt = db.prepare(sql);
894         stmt.get.bind(":id", id.get);
895         foreach (a; stmt.get.execute) {
896             return a.peek!long(0) != 0;
897         }
898         return false;
899     }
900 
901     /** Returns: number of test cases
902      */
903     long getNumOfTestCases() @trusted {
904         static immutable num_test_cases_sql = format!"SELECT count(*) FROM %s"(allTestCaseTable);
905         return db.execute(num_test_cases_sql).oneValue!long;
906     }
907 
908     void removeTestCase(const TestCaseId id) @trusted {
909         auto stmt = db.prepare(format!"DELETE FROM %s WHERE id=:id"(allTestCaseTable));
910         stmt.get.bind(":id", cast(long) id);
911         stmt.get.execute;
912     }
913 
914     /// Change the status of all mutants that the test case has killed to unknown.
915     void resetTestCaseId(const TestCaseId id) @trusted {
916         {
917             static immutable sql = format!"UPDATE %1$s SET status=0 WHERE id IN (SELECT t1.id FROM %2$s t0, %1$s t1 WHERE t0.tc_id = :id AND t0.st_id = t1.id)"(
918                     mutationStatusTable, killedTestCaseTable);
919             auto stmt = db.prepare(sql);
920             stmt.get.bind(":id", cast(long) id);
921             stmt.get.execute;
922         }
923         {
924             static immutable sql2 = format!"DELETE FROM %1$s WHERE tc_id = :id"(killedTestCaseTable);
925             auto stmt = db.prepare(sql2);
926             stmt.get.bind(":id", cast(long) id);
927             stmt.get.execute;
928         }
929     }
930 
931     /// Returns: mutants killed by the test case.
932     MutationStatusId[] testCaseKilledSrcMutants(const Mutation.Kind[] kinds, const TestCaseId id) @trusted {
933         const sql = format("SELECT t1.id
934             FROM %s t0, %s t1, %s t3
935             WHERE
936             t0.st_id = t1.id AND
937             t1.status = :st AND
938             t0.kind IN (%(%s,%)) AND
939             t3.tc_id = :id AND
940             t3.st_id = t1.id
941             GROUP BY t1.id", mutationTable, mutationStatusTable,
942                 killedTestCaseTable, kinds.map!(a => cast(int) a));
943 
944         auto stmt = db.prepare(sql);
945         stmt.get.bind(":st", cast(long) Mutation.Status.killed);
946         stmt.get.bind(":id", id.get);
947 
948         auto app = appender!(MutationStatusId[])();
949         foreach (res; stmt.get.execute)
950             app.put(MutationStatusId(res.peek!long(0)));
951 
952         return app.data;
953     }
954 
955     MutationStatusId[] testCaseKilledSrcMutants(const Mutation.Kind[] kinds, const TestCase tc) @safe {
956         auto id = getTestCaseId(tc);
957         if (id.isNull)
958             return null;
959         return testCaseKilledSrcMutants(kinds, id.get);
960     }
961 
962     /// Returns: mutants at mutations points that the test case has killed mutants at.
963     alias testCaseMutationPointAliveSrcMutants = testCaseCountSrcMutants!([
964             Mutation.Status.alive
965             ]);
966     /// ditto
967     alias testCaseMutationPointTimeoutSrcMutants = testCaseCountSrcMutants!(
968             [Mutation.Status.timeout]);
969     /// ditto
970     alias testCaseMutationPointKilledSrcMutants = testCaseCountSrcMutants!([
971             Mutation.Status.killed
972             ]);
973     /// ditto
974     alias testCaseMutationPointUnknownSrcMutants = testCaseCountSrcMutants!(
975             [Mutation.Status.unknown]);
976     /// ditto
977     alias testCaseMutationPointKilledByCompilerSrcMutants = testCaseCountSrcMutants!(
978             [Mutation.Status.killedByCompiler]);
979     /// ditto
980     alias testCaseMutationPointTotalSrcMutants = testCaseCountSrcMutants!(
981             [
982             Mutation.Status.alive, Mutation.Status.killed, Mutation.Status.timeout
983             ]);
984 
985     private MutationStatusId[] testCaseCountSrcMutants(int[] status)(
986             const Mutation.Kind[] kinds, TestCase tc) @trusted {
987         const query = format("
988             SELECT t1.id
989             FROM %s t0, %s t1
990             WHERE
991             t0.mp_id IN (SELECT t1.id
992                       FROM %s t0,%s t1, %s t2, %s t3
993                       WHERE
994                       t0.mp_id = t1.id AND
995                       t2.name = :name AND
996                       t2.id = t3.tc_id AND
997                       t3.st_id = t0.st_id
998                       )
999             AND
1000             t0.st_id = t1.id AND
1001             t1.status IN (%(%s,%)) AND
1002             t0.kind IN (%(%s,%))
1003             GROUP BY t1.id", mutationTable, mutationStatusTable, mutationTable, mutationPointTable,
1004                 allTestCaseTable, killedTestCaseTable, status, kinds.map!(a => cast(int) a));
1005 
1006         auto stmt = db.prepare(query);
1007         stmt.get.bind(":name", tc.name);
1008 
1009         auto app = appender!(MutationStatusId[])();
1010         foreach (res; stmt.get.execute)
1011             app.put(MutationStatusId(res.peek!long(0)));
1012 
1013         return app.data;
1014     }
1015 
1016     TestCaseId[] getNewTestCases() @trusted {
1017         auto rval = appender!(TestCaseId[])();
1018         db.run(select!AllTestCaseTbl).filter!(a => a.isNew)
1019             .map!(a => TestCaseId(a.id))
1020             .copy(rval);
1021         return rval.data;
1022     }
1023 
1024     void removeNewTestCaseTag() @trusted {
1025         immutable sql = "UPDATE " ~ allTestCaseTable ~ " SET is_new=0";
1026         auto stmt = db.prepare(sql);
1027         stmt.get.execute;
1028     }
1029 }
1030 
1031 struct DbMutant {
1032     private Miniorm* db;
1033     private Database* wrapperDb;
1034 
1035     bool exists(MutationStatusId id) {
1036         static immutable s = format!"SELECT COUNT(*) FROM %s WHERE id=:id LIMIT 1"(
1037                 mutationStatusTable);
1038         auto stmt = db.prepare(s);
1039         stmt.get.bind(":id", cast(long) id);
1040         auto res = stmt.get.execute;
1041         return res.oneValue!long == 0;
1042     }
1043 
1044     /** Update the status of a mutant.
1045      *
1046      * Params:
1047      *  id = ID of the mutant
1048      *  st = status to broadcast
1049      *  d = time spent on veryfing the mutant
1050      *  tcs = test cases that killed the mutant
1051      *  counter = how to act with the counter
1052      */
1053     void updateMutation(const MutationId id, const Mutation.Status st,
1054             const ExitStatus ecode, const MutantTimeProfile p, const(TestCase)[] tcs) @trusted {
1055         static immutable sql = "UPDATE %s SET
1056             status=:st,compile_time_ms=:compile,test_time_ms=:test,update_ts=:update_ts
1057             WHERE id IN (SELECT st_id FROM %s WHERE id = :id)";
1058 
1059         auto stmt = db.prepare(format!sql(mutationStatusTable, mutationTable));
1060         stmt.get.bind(":st", cast(long) st);
1061         stmt.get.bind(":id", id.get);
1062         stmt.get.bind(":compile", p.compile.total!"msecs");
1063         stmt.get.bind(":test", p.test.total!"msecs");
1064         stmt.get.bind(":update_ts", Clock.currTime.toSqliteDateTime);
1065         stmt.get.execute;
1066 
1067         wrapperDb.testCaseApi.updateMutationTestCases(id, tcs);
1068     }
1069 
1070     /** Update the status of a mutant.
1071      *
1072      * Params:
1073      *  id = ID of the mutant
1074      *  st = status to broadcast
1075      *  d = time spent on veryfing the mutant
1076      *  tcs = test cases that killed the mutant
1077      *  counter = how to act with the counter
1078      */
1079     void updateMutation(const MutationStatusId id, const Mutation.Status st,
1080             const ExitStatus ecode, const MutantTimeProfile p) @trusted {
1081         static immutable sql = "UPDATE %s SET
1082             status=:st,compile_time_ms=:compile,test_time_ms=:test,update_ts=:update_ts
1083             WHERE id = :id";
1084 
1085         auto stmt = db.prepare(format!sql(mutationStatusTable));
1086         stmt.get.bind(":id", id.get);
1087         stmt.get.bind(":st", cast(long) st);
1088         stmt.get.bind(":compile", p.compile.total!"msecs");
1089         stmt.get.bind(":test", p.test.total!"msecs");
1090         stmt.get.bind(":update_ts", Clock.currTime.toSqliteDateTime);
1091         stmt.get.execute;
1092     }
1093 
1094     /// Update the time used to test the mutant.
1095     void updateMutation(const MutationStatusId id, const MutantTimeProfile p) @trusted {
1096         static immutable sql = format!"UPDATE %s SET compile_time_ms=:compile,test_time_ms=:test WHERE id = :id"(
1097                 mutationStatusTable);
1098         auto stmt = db.prepare(sql);
1099         stmt.get.bind(":id", id.get);
1100         stmt.get.bind(":compile", p.compile.total!"msecs");
1101         stmt.get.bind(":test", p.test.total!"msecs");
1102         stmt.get.execute;
1103     }
1104 
1105     /** Update the status of a mutant.
1106      *
1107      * Params:
1108      *  id = mutation status ID
1109      *  st = new status
1110      *  update_ts = if the update timestamp should be updated.
1111      */
1112     void updateMutationStatus(const MutationStatusId id, const Mutation.Status st,
1113             const ExitStatus ecode, Flag!"updateTs" update_ts = No.updateTs) @trusted {
1114 
1115         auto stmt = () {
1116             if (update_ts) {
1117                 const ts = Clock.currTime.toSqliteDateTime;
1118                 auto s = db.prepare(
1119                         format!"UPDATE %s SET status=:st,exit_code=:ecode,update_ts=:update_ts WHERE id=:id"(
1120                         mutationStatusTable));
1121                 s.get.bind(":update_ts", ts);
1122                 return s;
1123             } else
1124                 return db.prepare(format!"UPDATE %s SET status=:st,exit_code=:ecode WHERE id=:id"(
1125                         mutationStatusTable));
1126         }();
1127         stmt.get.bind(":st", st.to!long);
1128         stmt.get.bind(":id", id.to!long);
1129         stmt.get.bind(":ecode", ecode.get);
1130         stmt.get.execute;
1131     }
1132 
1133     /// Returns: all mutants and how many test cases that have killed them.
1134     long[] getAllTestCaseKills() @trusted {
1135         static immutable sql = format!"SELECT (SELECT count(*) FROM %s WHERE t0.id=st_id) as vc_cnt FROM %s t0"(
1136                 killedTestCaseTable, mutationStatusTable);
1137         auto stmt = db.prepare(sql);
1138 
1139         auto app = appender!(long[])();
1140         foreach (res; stmt.get.execute)
1141             app.put(res.peek!long(0));
1142 
1143         return app.data;
1144     }
1145 
1146     /// Returns: all mutation status IDs.
1147     MutationStatusId[] getAllMutationStatus() @trusted {
1148         static immutable sql = format!"SELECT id FROM %s"(mutationStatusTable);
1149 
1150         auto app = appender!(MutationStatusId[])();
1151         auto stmt = db.prepare(sql);
1152         foreach (r; stmt.get.execute)
1153             app.put(MutationStatusId(r.peek!long(0)));
1154         return app.data;
1155     }
1156 
1157     // TODO: change to my.optional
1158     Nullable!(Mutation.Status) getMutationStatus(const MutationStatusId id) @trusted {
1159         static immutable sql = format!"SELECT status FROM %s WHERE id=:id"(mutationStatusTable);
1160         auto stmt = db.prepare(sql);
1161         stmt.get.bind(":id", id.get);
1162 
1163         typeof(return) rval;
1164         foreach (a; stmt.get.execute) {
1165             rval = cast(Mutation.Status) a.peek!long(0);
1166         }
1167         return rval;
1168     }
1169 
1170     Nullable!MutationEntry getMutation(const MutationId id) @trusted {
1171         import dextool.plugin.mutate.backend.type;
1172 
1173         typeof(return) rval;
1174 
1175         static immutable get_mut_sql = format("SELECT
1176             t0.id,
1177             t0.kind,
1178             t3.compile_time_ms,
1179             t3.test_time_ms,
1180             t1.offset_begin,
1181             t1.offset_end,
1182             t1.line,
1183             t1.column,
1184             t2.path,
1185             t2.lang
1186             FROM %s t0,%s t1,%s t2,%s t3
1187             WHERE
1188             t0.id = :id AND
1189             t0.mp_id = t1.id AND
1190             t1.file_id = t2.id AND
1191             t3.id = t0.st_id
1192             ", mutationTable,
1193                 mutationPointTable, filesTable, mutationStatusTable);
1194 
1195         auto stmt = db.prepare(get_mut_sql);
1196         stmt.get.bind(":id", cast(long) id);
1197         auto res = stmt.get.execute;
1198 
1199         if (res.empty)
1200             return rval;
1201 
1202         auto v = res.front;
1203 
1204         auto mp = MutationPoint(Offset(v.peek!uint(4), v.peek!uint(5)));
1205         mp.mutations = [Mutation(v.peek!long(1).to!(Mutation.Kind))];
1206         auto pkey = MutationId(v.peek!long(0));
1207         auto file = Path(v.peek!string(8));
1208         auto sloc = SourceLoc(v.peek!uint(6), v.peek!uint(7));
1209         auto lang = v.peek!long(9).to!Language;
1210 
1211         rval = MutationEntry(pkey, file, sloc, mp,
1212                 MutantTimeProfile(v.peek!long(2).dur!"msecs", v.peek!long(3).dur!"msecs"), lang);
1213 
1214         return rval;
1215     }
1216 
1217     MutantMetaData getMutantationMetaData(const MutationId id) @trusted {
1218         auto rval = MutantMetaData(id);
1219         foreach (res; db.run(select!NomutDataTbl.where("mut_id = :mutid", Bind("mutid")), id.get)) {
1220             rval.set(NoMut(res.tag, res.comment));
1221         }
1222         return rval;
1223     }
1224 
1225     // TODO: fix spelling error
1226     // TODO: this is a bit inefficient. it should use a callback iterator
1227     MutantMetaData[] getMutantationMetaData(const Mutation.Kind[] kinds, const Mutation
1228             .Status status) @trusted {
1229         const sql = format!"SELECT DISTINCT t.mut_id, t.tag, t.comment
1230         FROM %s t, %s t1, %s t2
1231         WHERE
1232         t.mut_id = t1.id AND
1233         t1.st_id = t2.id AND
1234         t2.status = :status AND
1235         t1.kind IN (%(%s,%))
1236         ORDER BY
1237         t.mut_id"(nomutDataTable, mutationTable,
1238                 mutationStatusTable, kinds.map!(a => cast(long) a));
1239         auto stmt = db.prepare(sql);
1240         stmt.get.bind(":status", cast(long) status);
1241 
1242         auto app = appender!(MutantMetaData[])();
1243         foreach (res; stmt.get.execute) {
1244             app.put(MutantMetaData(MutationId(res.peek!long(0)),
1245                     MutantAttr(NoMut(res.peek!string(1), res.peek!string(2)))));
1246         }
1247         return app.data;
1248     }
1249 
1250     Nullable!Path getPath(const MutationId id) @trusted {
1251         static immutable get_path_sql = format("SELECT t2.path
1252             FROM
1253             %s t0, %s t1, %s t2
1254             WHERE
1255             t0.id = :id AND
1256             t0.mp_id = t1.id AND
1257             t1.file_id = t2.id
1258             ", mutationTable,
1259                 mutationPointTable, filesTable);
1260 
1261         auto stmt = db.prepare(get_path_sql);
1262         stmt.get.bind(":id", cast(long) id);
1263         auto res = stmt.get.execute;
1264 
1265         typeof(return) rval;
1266         if (!res.empty)
1267             rval = Path(res.front.peek!string(0));
1268         return rval;
1269     }
1270 
1271     Nullable!Path getPath(const MutationStatusId id) @trusted {
1272         static immutable get_path_sql = format("SELECT t2.path
1273             FROM
1274             %s t0, %s t1, %s t2
1275             WHERE
1276             t0.st_id = :id AND t0.mp_id = t1.id AND t1.file_id = t2.id
1277             ", mutationTable,
1278                 mutationPointTable, filesTable);
1279 
1280         auto stmt = db.prepare(get_path_sql);
1281         stmt.get.bind(":id", id.get);
1282         auto res = stmt.get.execute;
1283 
1284         typeof(return) rval;
1285         if (!res.empty)
1286             rval = Path(res.front.peek!string(0));
1287         return rval;
1288     }
1289 
1290     /// Returns: the mutants that are connected to the mutation statuses.
1291     MutantInfo[] getMutantsInfo(const Mutation.Kind[] kinds, const(MutationStatusId)[] id) @trusted {
1292         const get_mutid_sql = format("SELECT t0.id,t2.status,t2.exit_code,t0.kind,t1.line,t1.column
1293             FROM %s t0,%s t1, %s t2
1294             WHERE
1295             t0.st_id IN (%(%s,%)) AND
1296             t0.st_id = t2.id AND
1297             t0.kind IN (%(%s,%)) AND
1298             t0.mp_id = t1.id", mutationTable,
1299                 mutationPointTable, mutationStatusTable, id.map!(a => a.get),
1300                 kinds.map!(a => cast(int) a));
1301         auto stmt = db.prepare(get_mutid_sql);
1302 
1303         auto app = appender!(MutantInfo[])();
1304         foreach (res; stmt.get.execute) {
1305             app.put(MutantInfo(MutationId(res.peek!long(0)), res.peek!long(1)
1306                     .to!(Mutation.Status), res.peek!int(2).ExitStatus,
1307                     res.peek!long(3).to!(Mutation.Kind),
1308                     SourceLoc(res.peek!uint(4), res.peek!uint(5))));
1309         }
1310 
1311         return app.data;
1312     }
1313 
1314     /// Returns: the mutants that are connected to the mutation statuses.
1315     Optional!MutantInfo2 getMutantInfo(const MutationStatusId id) @trusted {
1316         static const sql = format("SELECT t0.id,t2.status,t2.exit_code,t3.path,t1.line,t1.column,t2.prio,t2.update_ts,
1317             (SELECT count(*) FROM %s WHERE st_id = :id) as vc_cnt
1318             FROM %s t0,%s t1, %s t2, %s t3
1319             WHERE
1320             t2.id = :id AND
1321             t0.st_id = :id AND
1322             t0.mp_id = t1.id AND
1323             t1.file_id = t3.id
1324             ",
1325                 killedTestCaseTable, mutationTable, mutationPointTable,
1326                 mutationStatusTable, filesTable);
1327         auto stmt = db.prepare(sql);
1328         stmt.get.bind(":id", id.get);
1329 
1330         foreach (res; stmt.get.execute) {
1331             // dfmt off
1332             return MutantInfo2(
1333                 res.peek!long(0).MutationId,
1334                 res.peek!long(1).to!(Mutation.Status),
1335                 res.peek!int(2).to!ExitStatus,
1336                 res.peek!string(3).Path,
1337                 SourceLoc(res.peek!uint(4), res.peek!uint(5)),
1338                 res.peek!long(6).MutantPrio,
1339                 res.peek!string(7).fromSqLiteDateTime,
1340                 res.peek!int(8)).some;
1341             // dfmt on
1342         }
1343 
1344         return none!MutantInfo2;
1345     }
1346 
1347     /// Returns: the mutants that are connected to the mutation statuses.
1348     MutationId[] getMutationIds(const(Mutation.Kind)[] kinds, const(MutationStatusId)[] id) @trusted {
1349         if (id.length == 0)
1350             return null;
1351 
1352         const get_mutid_sql = format!"SELECT id FROM %s t0
1353             WHERE
1354             t0.st_id IN (%(%s,%)) AND
1355             t0.kind IN (%(%s,%))"(mutationTable,
1356                 id.map!(a => cast(long) a), kinds.map!(a => cast(int) a));
1357         auto stmt = db.prepare(get_mutid_sql);
1358 
1359         auto app = appender!(MutationId[])();
1360         foreach (res; stmt.get.execute)
1361             app.put(MutationId(res.peek!long(0)));
1362         return app.data;
1363     }
1364 
1365     Nullable!MutationId getMutationId(const MutationStatusId id) @trusted {
1366         static immutable sql = format!"SELECT id FROM %s WHERE st_id=:st_id"(mutationTable);
1367         auto stmt = db.prepare(sql);
1368         stmt.get.bind(":st_id", id.get);
1369 
1370         typeof(return) rval;
1371         foreach (res; stmt.get.execute) {
1372             rval = res.peek!long(0).MutationId;
1373             break;
1374         }
1375         return rval;
1376     }
1377 
1378     MutationStatus getMutationStatus2(const MutationStatusId id) @trusted {
1379         const sql = format("SELECT t0.id,t0.status,t0.prio,t0.update_ts,t0.added_ts
1380             FROM %s t0
1381             WHERE
1382             t0.update_ts IS NOT NULL AND
1383             t0.id = :id",
1384                 mutationStatusTable);
1385         auto stmt = db.prepare(sql);
1386         stmt.get.bind(":id", id.get);
1387 
1388         foreach (res; stmt.get.execute) {
1389             auto added = () {
1390                 auto raw = res.peek!string(4);
1391                 if (raw.length == 0)
1392                     return Nullable!SysTime();
1393                 return Nullable!SysTime(raw.fromSqLiteDateTime);
1394             }();
1395 
1396             return MutationStatus(MutationStatusId(res.peek!long(0)),
1397                     res.peek!long(1).to!(Mutation.Status), res.peek!long(2)
1398                     .MutantPrio, res.peek!string(3).fromSqLiteDateTime, added,);
1399         }
1400 
1401         return MutationStatus.init;
1402     }
1403 
1404     Nullable!MutationStatusId getMutationStatusId(const MutationId id) @trusted {
1405         static immutable sql = format!"SELECT st_id FROM %s WHERE id=:id"(mutationTable);
1406         auto stmt = db.prepare(sql);
1407         stmt.get.bind(":id", cast(long) id);
1408 
1409         typeof(return) rval;
1410         foreach (res; stmt.get.execute) {
1411             rval = MutationStatusId(res.peek!long(0));
1412         }
1413         return rval;
1414     }
1415 
1416     Nullable!MutationStatusId getMutationStatusId(const Checksum cs) @trusted {
1417         static immutable sql = format!"SELECT id FROM %s WHERE checksum0=:cs0 AND checksum1=:cs1"(
1418                 mutationStatusTable);
1419         auto stmt = db.prepare(sql);
1420         stmt.get.bind(":cs0", cast(long) cs.c0);
1421         stmt.get.bind(":cs1", cast(long) cs.c1);
1422 
1423         typeof(return) rval;
1424         foreach (res; stmt.get.execute) {
1425             rval = MutationStatusId(res.peek!long(0));
1426         }
1427         return rval;
1428     }
1429 
1430     // TODO: maybe this need to return the exit code too?
1431     // Returns: the status of the mutant
1432     Nullable!(Mutation.Status) getMutationStatus(const MutationId id) @trusted {
1433         auto s = format!"SELECT status FROM %s WHERE id IN (SELECT st_id FROM %s WHERE id=:mut_id)"(
1434                 mutationStatusTable, mutationTable);
1435         auto stmt = db.prepare(s);
1436         stmt.get.bind(":mut_id", cast(long) id);
1437         typeof(return) rval;
1438         foreach (res; stmt.get.execute)
1439             rval = res.peek!long(0).to!(Mutation.Status);
1440         return rval;
1441     }
1442 
1443     /// Returns: the mutants in the file at the line.
1444     MutationStatusId[] getMutationsOnLine(const(Mutation.Kind)[] kinds, FileId fid, SourceLoc sloc) @trusted {
1445         // TODO: should it also be line_end?
1446         const sql = format("SELECT DISTINCT t0.id FROM %s t0, %s t1, %s t2
1447                     WHERE
1448                     t1.st_id = t0.id AND
1449                     t1.kind IN (%(%s,%)) AND
1450                     t1.mp_id = t2.id AND
1451                     t2.file_id = :fid AND
1452                     (:line BETWEEN t2.line AND t2.line_end)",
1453                 mutationStatusTable, mutationTable, mutationPointTable,
1454                 kinds.map!(a => cast(int) a));
1455         auto stmt = db.prepare(sql);
1456         stmt.get.bind(":fid", cast(long) fid);
1457         stmt.get.bind(":line", sloc.line);
1458 
1459         auto app = appender!(typeof(return))();
1460         foreach (res; stmt.get.execute)
1461             app.put(MutationStatusId(res.peek!long(0)));
1462         return app.data;
1463     }
1464 
1465     /// Returns: the `nr` mutants that where the longst since they where tested.
1466     MutationStatusTime[] getOldestMutants(const(Mutation.Kind)[] kinds,
1467             const long nr, const Mutation.Status[] status) @trusted {
1468         const sql = format("SELECT t0.id,t0.update_ts FROM %s t0, %s t1
1469                     WHERE
1470                     t0.update_ts IS NOT NULL AND
1471                     t1.st_id = t0.id AND
1472                     t1.kind IN (%(%s,%)) AND
1473                     t0.status IN (%(%s,%))
1474                     ORDER BY datetime(t0.update_ts) ASC LIMIT :limit", mutationStatusTable,
1475                 mutationTable, kinds.map!(a => cast(int) a), status.map!(a => cast(int) a));
1476         auto stmt = db.prepare(sql);
1477         stmt.get.bind(":limit", nr);
1478 
1479         auto app = appender!(MutationStatusTime[])();
1480         foreach (res; stmt.get.execute)
1481             app.put(MutationStatusTime(MutationStatusId(res.peek!long(0)),
1482                     res.peek!string(1).fromSqLiteDateTime));
1483         return app.data;
1484     }
1485 
1486     /// Returns: the `nr` mutants that where last tested.
1487     MutationStatusTime[] getLatestMutants(const(Mutation.Kind)[] kinds, const long nr) @trusted {
1488         const sql = format("SELECT t0.id,t0.update_ts FROM %s t0, %s t1
1489                     WHERE
1490                     t0.update_ts IS NOT NULL AND
1491                     t1.st_id = t0.id AND
1492                     t1.kind IN (%(%s,%))
1493                     ORDER BY datetime(t0.update_ts) DESC LIMIT :limit",
1494                 mutationStatusTable, mutationTable, kinds.map!(a => cast(int) a));
1495         auto stmt = db.prepare(sql);
1496         stmt.get.bind(":limit", nr);
1497 
1498         auto app = appender!(MutationStatusTime[])();
1499         foreach (res; stmt.get.execute)
1500             app.put(MutationStatusTime(MutationStatusId(res.peek!long(0)),
1501                     res.peek!string(1).fromSqLiteDateTime));
1502         return app.data;
1503     }
1504 
1505     /// Returns: the `nr` mutant with the highest count that has not been killed and existed in the system the longest.
1506     MutationStatus[] getHighestPrioMutant(const(Mutation.Kind)[] kinds,
1507             const Mutation.Status status, const long nr) @trusted {
1508         const sql = format("SELECT t0.id,t0.status,t0.prio,t0.update_ts,t0.added_ts
1509             FROM %s t0, %s t1
1510             WHERE
1511             t0.update_ts IS NOT NULL AND
1512             t0.status = :status AND
1513             t1.st_id = t0.id AND
1514             t1.kind IN (%(%s,%)) AND
1515             t1.st_id NOT IN (SELECT st_id FROM %s WHERE nomut != 0)
1516             ORDER BY t0.prio DESC LIMIT :limit",
1517                 mutationStatusTable, mutationTable, kinds.map!(a => cast(int) a), srcMetadataTable);
1518         auto stmt = db.prepare(sql);
1519         stmt.get.bind(":status", cast(long) status);
1520         stmt.get.bind(":limit", nr);
1521 
1522         auto app = appender!(MutationStatus[])();
1523         foreach (res; stmt.get.execute) {
1524             auto added = () {
1525                 auto raw = res.peek!string(4);
1526                 if (raw.length == 0)
1527                     return Nullable!SysTime();
1528                 return Nullable!SysTime(raw.fromSqLiteDateTime);
1529             }();
1530 
1531             // dfmt off
1532             app.put(MutationStatus(
1533                 MutationStatusId(res.peek!long(0)),
1534                 res.peek!long(1).to!(Mutation.Status),
1535                 res.peek!long(2).MutantPrio,
1536                 res.peek!string(3).fromSqLiteDateTime,
1537                 added,
1538             ));
1539             // dfmt on
1540         }
1541 
1542         return app.data;
1543     }
1544 
1545     /** Get SourceLoc for a specific mutation id.
1546      */
1547     Nullable!SourceLoc getSourceLocation(MutationId id) @trusted {
1548         auto s = format!"SELECT line, column FROM %s WHERE id IN (SELECT mp_id FROM %s WHERE id=:mut_id)"(
1549                 mutationPointTable, mutationTable);
1550         auto stmt = db.prepare(s);
1551         stmt.get.bind(":mut_id", cast(long) id);
1552         typeof(return) rval;
1553         foreach (res; stmt.get.execute)
1554             rval = SourceLoc(res.peek!uint(0), res.peek!uint(1));
1555         return rval;
1556     }
1557 
1558     /** Remove all mutations of kinds.
1559      */
1560     void removeMutant(const Mutation.Kind[] kinds) @trusted {
1561         const s = format!"DELETE FROM %s WHERE id IN (SELECT mp_id FROM %s WHERE kind IN (%(%s,%)))"(
1562                 mutationPointTable, mutationTable, kinds.map!(a => cast(int) a));
1563         auto stmt = db.prepare(s);
1564         stmt.get.execute;
1565     }
1566 
1567     /** Reset all mutations of kinds with the status `st` to unknown.
1568      */
1569     void resetMutant(const Mutation.Kind[] kinds, Mutation.Status st, Mutation.Status to_st) @trusted {
1570         const s = format!"UPDATE %s SET status=%s WHERE status = %s AND id IN(SELECT st_id FROM %s WHERE kind IN (%(%s,%)))"(
1571                 mutationStatusTable, to_st.to!long, st.to!long,
1572                 mutationTable, kinds.map!(a => cast(int) a));
1573         auto stmt = db.prepare(s);
1574         stmt.get.execute;
1575     }
1576 
1577     Mutation.Kind getKind(MutationId id) @trusted {
1578         static immutable sql = format!"SELECT kind FROM %s WHERE id=:id"(mutationTable);
1579         auto stmt = db.prepare(sql);
1580         stmt.get.bind(":id", cast(long) id);
1581 
1582         typeof(return) rval;
1583         foreach (res; stmt.get.execute) {
1584             rval = res.peek!long(0).to!(Mutation.Kind);
1585         }
1586         return rval;
1587     }
1588 
1589     /// Returns: the `nr` mutants that where last tested.
1590     MutantTestTime[] getLatestMutantTimes(const(Mutation.Kind)[] kinds, const long nr) @trusted {
1591         const sql = format("SELECT t0.id,t0.status,t0.compile_time_ms,t0.test_time_ms FROM %s t0, %s t1
1592                     WHERE
1593                     t0.update_ts IS NOT NULL AND
1594                     t1.st_id = t0.id AND
1595                     t1.kind IN (%(%s,%)) AND
1596                     (t0.compile_time_ms + t0.test_time_ms) > 0
1597                     ORDER BY datetime(t0.update_ts) DESC LIMIT :limit",
1598                 mutationStatusTable, mutationTable, kinds.map!(a => cast(int) a));
1599         auto stmt = db.prepare(sql);
1600         stmt.get.bind(":limit", nr);
1601 
1602         auto app = appender!(MutantTestTime[])();
1603         foreach (res; stmt.get.execute)
1604             app.put(MutantTestTime(MutationStatusId(res.peek!long(0)),
1605                     res.peek!long(1).to!(Mutation.Status), res.peek!long(2)
1606                     .dur!"msecs", res.peek!long(3).dur!"msecs"));
1607         return app.data;
1608     }
1609 
1610     import dextool.plugin.mutate.backend.type;
1611 
1612     alias aliveSrcMutants = countMutants!([
1613             Mutation.Status.alive, Mutation.Status.noCoverage
1614             ]);
1615     alias killedSrcMutants = countMutants!([Mutation.Status.killed]);
1616     alias timeoutSrcMutants = countMutants!([Mutation.Status.timeout]);
1617     alias noCovSrcMutants = countMutants!([Mutation.Status.noCoverage]);
1618     alias equivalentMutants = countMutants!([Mutation.Status.equivalent]);
1619     alias skippedMutants = countMutants!([Mutation.Status.skipped]);
1620     alias memOverloadMutants = countMutants!([Mutation.Status.memOverload]);
1621 
1622     /// Returns: Total that should be counted when calculating the mutation score.
1623     alias totalSrcMutants = countMutants!([
1624             Mutation.Status.alive, Mutation.Status.killed, Mutation.Status.timeout,
1625             Mutation.Status.noCoverage, Mutation.Status.memOverload,
1626             ]);
1627 
1628     alias unknownSrcMutants = countMutants!([Mutation.Status.unknown]);
1629     alias killedByCompilerSrcMutants = countMutants!([
1630             Mutation.Status.killedByCompiler
1631             ]);
1632 
1633     /** Count the mutants with the nomut metadata.
1634      *
1635      * Params:
1636      *  status = status the mutants must be in to be counted.
1637      *  distinc = count based on unique source code changes.
1638      *  kinds = the kind of mutants to count.
1639      *  file = file to count mutants in.
1640      */
1641     private MutationReportEntry countMutants(int[] status)(const Mutation.Kind[] kinds,
1642             string file = null) @trusted {
1643         const qq = "
1644             SELECT count(*),sum(compile_time_ms),sum(test_time_ms)
1645             FROM (
1646             SELECT sum(t1.compile_time_ms) compile_time_ms,sum(t1.test_time_ms) test_time_ms
1647             FROM %s t0, %s t1%s
1648             WHERE
1649             %s
1650             t0.st_id = t1.id AND
1651             t1.status IN (%(%s,%)) AND
1652             t0.kind IN (%(%s,%))
1653             GROUP BY t1.id)";
1654         const query = () {
1655             auto fq = file.length == 0
1656                 ? null : "t0.mp_id = t2.id AND t2.file_id = t3.id AND t3.path = :path AND";
1657             auto fq_from = file.length == 0 ? null : format(", %s t2, %s t3",
1658                     mutationPointTable, filesTable);
1659             return format(qq, mutationTable, mutationStatusTable, fq_from, fq,
1660                     status, kinds.map!(a => cast(int) a));
1661         }();
1662 
1663         typeof(return) rval;
1664         auto stmt = db.prepare(query);
1665         if (file.length != 0)
1666             stmt.get.bind(":path", file);
1667         auto res = stmt.get.execute;
1668         if (!res.empty) {
1669             rval = MutationReportEntry(res.front.peek!long(0),
1670                     MutantTimeProfile(res.front.peek!long(1).dur!"msecs",
1671                         res.front.peek!long(2).dur!"msecs"));
1672         }
1673         return rval;
1674     }
1675 
1676     /** Count the mutants with the nomut metadata.
1677      *
1678      * Params:
1679      *  status = status the mutants must be in to be counted.
1680      *  distinc = count based on unique source code changes.
1681      *  kinds = the kind of mutants to count.
1682      *  file = file to count mutants in.
1683      */
1684     private MetadataNoMutEntry countNoMutMutants(int[] status, bool distinct)(
1685             const Mutation.Kind[] kinds, string file = null) @trusted {
1686         static if (distinct) {
1687             auto sql_base = "
1688                 SELECT count(*)
1689                 FROM (
1690                 SELECT count(*)
1691                 FROM %s t0, %s t1,%s t4%s
1692                 WHERE
1693                 %s
1694                 t0.st_id = t1.id AND
1695                 t0.st_id = t4.st_id AND
1696                 t4.nomut != 0 AND
1697                 t1.status IN (%(%s,%)) AND
1698                 t0.kind IN (%(%s,%))
1699                 GROUP BY t1.id)";
1700         } else {
1701             auto sql_base = "
1702                 SELECT count(*)
1703                 FROM %s t0, %s t1,%s t4%s
1704                 WHERE
1705                 %s
1706                 t0.st_id = t1.id AND
1707                 t0.st_id = t4.st_id AND
1708                 t4.nomut != 0 AND
1709                 t1.status IN (%(%s,%)) AND
1710                 t0.kind IN (%(%s,%))";
1711         }
1712         const query = () {
1713             auto fq = file.length == 0
1714                 ? null : "t0.mp_id = t2.id AND t2.file_id = t3.id AND t3.path = :path AND";
1715             auto fq_from = file.length == 0 ? null : format(", %s t2, %s t3",
1716                     mutationPointTable, filesTable);
1717             return format(sql_base, mutationTable, mutationStatusTable,
1718                     srcMetadataTable, fq_from, fq, status, kinds.map!(a => cast(int) a));
1719         }();
1720 
1721         typeof(return) rval;
1722         auto stmt = db.prepare(query);
1723         if (file.length != 0)
1724             stmt.get.bind(":path", file);
1725         auto res = stmt.get.execute;
1726         if (!res.empty)
1727             rval = MetadataNoMutEntry(res.front.peek!long(0));
1728         return rval;
1729     }
1730 
1731     /// ditto.
1732     alias aliveNoMutSrcMutants = countNoMutMutants!([
1733             Mutation.Status.alive, Mutation.Status.noCoverage
1734             ], true);
1735 
1736     Nullable!Checksum getChecksum(MutationStatusId id) @trusted {
1737         static immutable sql = format!"SELECT checksum0, checksum1 FROM %s WHERE id=:id"(
1738                 mutationStatusTable);
1739         auto stmt = db.prepare(sql);
1740         stmt.get.bind(":id", id.get);
1741 
1742         typeof(return) rval;
1743         foreach (res; stmt.get.execute) {
1744             rval = Checksum(res.peek!long(0), res.peek!long(1));
1745             break;
1746         }
1747         return rval;
1748     }
1749 
1750     /// Store all found mutants.
1751     void put(MutationPointEntry2[] mps, AbsolutePath root) @trusted {
1752         if (mps.empty)
1753             return;
1754 
1755         static immutable insert_mp_sql = format("INSERT OR IGNORE INTO %s
1756             (file_id, offset_begin, offset_end, line, column, line_end, column_end)
1757             SELECT id,:begin,:end,:line,:column,:line_end,:column_end
1758             FROM %s
1759             WHERE
1760             path = :path",
1761                 mutationPointTable, filesTable);
1762         auto mp_stmt = db.prepare(insert_mp_sql);
1763 
1764         foreach (mp; mps) {
1765             auto rel_file = relativePath(mp.file, root).Path;
1766             mp_stmt.get.bind(":begin", mp.offset.begin);
1767             mp_stmt.get.bind(":end", mp.offset.end);
1768             mp_stmt.get.bind(":line", mp.sloc.line);
1769             mp_stmt.get.bind(":column", mp.sloc.column);
1770             mp_stmt.get.bind(":line_end", mp.slocEnd.line);
1771             mp_stmt.get.bind(":column_end", mp.slocEnd.column);
1772             mp_stmt.get.bind(":path", cast(string) rel_file);
1773             mp_stmt.get.execute;
1774             mp_stmt.get.reset;
1775         }
1776 
1777         static immutable insert_cmut_sql = format("INSERT OR IGNORE INTO %s
1778             (status,exit_code,compile_time_ms,test_time_ms,update_ts,added_ts,checksum0,checksum1,prio)
1779             VALUES(:st,0,0,0,:update_ts,:added_ts,:c0,:c1,:prio)",
1780                 mutationStatusTable);
1781         auto cmut_stmt = db.prepare(insert_cmut_sql);
1782         const ts = Clock.currTime.toSqliteDateTime;
1783         cmut_stmt.get.bind(":st", Mutation.Status.unknown);
1784         cmut_stmt.get.bind(":update_ts", ts);
1785         cmut_stmt.get.bind(":added_ts", ts);
1786         foreach (mp; mps) {
1787             const prio = (mp.offset.begin < mp.offset.end) ? mp.offset.end - mp.offset.begin : 0;
1788             foreach (cm; mp.cms) {
1789                 cmut_stmt.get.bind(":c0", cast(long) cm.id.c0);
1790                 cmut_stmt.get.bind(":c1", cast(long) cm.id.c1);
1791                 cmut_stmt.get.bind(":prio", prio);
1792                 cmut_stmt.get.execute;
1793                 cmut_stmt.get.reset;
1794             }
1795         }
1796 
1797         static immutable insert_m_sql = format("INSERT OR IGNORE INTO %s
1798             (mp_id, st_id, kind)
1799             SELECT t0.id,t1.id,:kind FROM %s t0, %s t1, %s t2 WHERE
1800             t2.path = :path AND
1801             t0.file_id = t2.id AND
1802             t0.offset_begin = :off_begin AND
1803             t0.offset_end = :off_end AND
1804             t1.checksum0 = :c0 AND
1805             t1.checksum1 = :c1",
1806                 mutationTable, mutationPointTable, mutationStatusTable, filesTable);
1807         auto insert_m = db.prepare(insert_m_sql);
1808 
1809         foreach (mp; mps) {
1810             foreach (m; mp.cms) {
1811                 auto rel_file = relativePath(mp.file, root).Path;
1812                 insert_m.get.bind(":path", cast(string) rel_file);
1813                 insert_m.get.bind(":off_begin", mp.offset.begin);
1814                 insert_m.get.bind(":off_end", mp.offset.end);
1815                 insert_m.get.bind(":c0", cast(long) m.id.c0);
1816                 insert_m.get.bind(":c1", cast(long) m.id.c1);
1817                 insert_m.get.bind(":kind", m.mut.kind);
1818                 insert_m.get.execute;
1819                 insert_m.get.reset;
1820             }
1821         }
1822     }
1823 
1824     /// Remove mutants that have no connection to a mutation point, orphaned mutants.
1825     void removeOrphanedMutants() @trusted {
1826         import std.datetime.stopwatch : StopWatch, AutoStart;
1827 
1828         const removeIds = () {
1829             static immutable sql = format!"SELECT id FROM %1$s WHERE id NOT IN (SELECT st_id FROM %2$s)"(
1830                     mutationStatusTable, mutationTable);
1831             auto stmt = db.prepare(sql);
1832             auto removeIds = appender!(long[])();
1833             foreach (res; stmt.get.execute)
1834                 removeIds.put(res.peek!long(0));
1835             return removeIds.data;
1836         }();
1837 
1838         immutable batchNr = 1000;
1839         static immutable sql = format!"DELETE FROM %1$s WHERE id=:id"(mutationStatusTable);
1840         auto stmt = db.prepare(sql);
1841         auto sw = StopWatch(AutoStart.yes);
1842         foreach (const i, const id; removeIds) {
1843             stmt.get.bind(":id", id);
1844             stmt.get.execute;
1845             stmt.get.reset;
1846 
1847             // continuously print to inform the user of the progress and avoid
1848             // e.g. timeout on jenkins.
1849             if (i > 0 && i % batchNr == 0) {
1850                 const avg = cast(long)(cast(double) sw.peek.total!"msecs" / cast(double) batchNr);
1851                 const t = dur!"msecs"(avg * (removeIds.length - i));
1852                 logger.infof("%s/%s removed (average %sms) (%s) (%s)", i,
1853                         removeIds.length, avg, t, (Clock.currTime + t).toSimpleString);
1854                 sw.reset;
1855             }
1856         }
1857 
1858         logger.infof(!removeIds.empty, "%1$s/%1$s removed", removeIds.length);
1859     }
1860 
1861     /// Returns: all alive mutants on the same mutation point as `id`.
1862     MutationStatusId[] getSurroundingAliveMutants(const MutationStatusId id) @trusted {
1863         long mp_id;
1864         {
1865             auto stmt = db.prepare(format!"SELECT mp_id FROM %s WHERE st_id=:id"(mutationTable));
1866             stmt.get.bind(":id", id.get);
1867             auto res = stmt.get.execute;
1868             if (res.empty)
1869                 return null;
1870             mp_id = res.oneValue!long;
1871         }
1872 
1873         static immutable sql = format!"SELECT DISTINCT t0.st_id FROM %s t0, %s t1 WHERE
1874             t0.mp_id = :id AND
1875             t0.st_id = t1.id AND
1876             t1.status = %s"(mutationTable,
1877                 mutationStatusTable, cast(int) Mutation.Status.alive);
1878 
1879         auto stmt = db.prepare(sql);
1880         stmt.get.bind(":id", mp_id);
1881 
1882         auto rval = appender!(MutationStatusId[])();
1883         foreach (a; stmt.get.execute)
1884             rval.put(a.peek!long(0).MutationStatusId);
1885         return rval.data;
1886     }
1887 
1888     MutationStatusId[] mutantsInRegion(const FileId id, const Offset region,
1889             const Mutation.Status status, const Mutation.Kind[] kinds) @trusted {
1890         const sql = format!"SELECT DISTINCT t1.st_id
1891             FROM %s t0, %s t1, %s t2
1892             WHERE t0.file_id = :file_id AND
1893             t0.id = t1.mp_id AND
1894             (t0.offset_begin BETWEEN :begin AND :end) AND
1895             (t0.offset_end BETWEEN :begin AND :end) AND
1896             t1.st_id = t2.id AND
1897             t2.status = :status AND
1898             t1.kind IN (%(%s,%))
1899             "(mutationPointTable, mutationTable,
1900                 mutationStatusTable, kinds.map!(a => cast(int) a));
1901 
1902         auto stmt = db.prepare(sql);
1903         stmt.get.bind(":file_id", id.get);
1904         stmt.get.bind(":begin", region.begin);
1905         stmt.get.bind(":end", region.end);
1906         stmt.get.bind(":status", cast(int) status);
1907         auto app = appender!(MutationStatusId[])();
1908         foreach (ref r; stmt.get.execute)
1909             app.put(MutationStatusId(r.peek!long(0)));
1910         return app.data;
1911     }
1912 }
1913 
1914 struct DbWorklist {
1915     private Miniorm* db;
1916     private Database* wrapperDb;
1917 
1918     /** Add all mutants with the specific status to the worklist.
1919      */
1920     void update(const Mutation.Kind[] kinds, const Mutation.Status[] status,
1921             const long basePrio = 100, const MutationOrder userOrder = MutationOrder.random) @trusted {
1922         const order = fromOrder(userOrder);
1923 
1924         const sql = format!"INSERT OR IGNORE INTO %s (id,prio)
1925             SELECT t1.id,%s FROM %s t0, %s t1 WHERE t0.kind IN (%(%s,%)) AND
1926             t0.st_id = t1.id AND
1927             t1.status IN (%(%s,%))
1928             "(mutantWorklistTable, order, mutationTable,
1929                 mutationStatusTable, kinds.map!(a => cast(int) a), status.map!(a => cast(int) a));
1930         auto stmt = db.prepare(sql);
1931         stmt.get.bind(":base_prio", basePrio);
1932         stmt.get.execute;
1933     }
1934 
1935     /// Add a mutant to the worklist.
1936     void add(const MutationStatusId id, const long basePrio = 0,
1937             const MutationOrder userOrder = MutationOrder.consecutive) @trusted {
1938         const order = fromOrder(userOrder);
1939         const sql = format!"INSERT OR REPLACE INTO %s (id,prio)
1940             SELECT t1.id,%s FROM %s t1 WHERE t1.id = :id
1941             "(mutantWorklistTable, order, mutationStatusTable);
1942         auto stmt = db.prepare(sql);
1943         stmt.get.bind(":id", id.get);
1944         stmt.get.bind(":base_prio", basePrio);
1945         stmt.get.execute;
1946     }
1947 
1948     /// Remove a mutant from the worklist.
1949     void remove(const MutationStatusId id) @trusted {
1950         static immutable sql = "DELETE FROM " ~ mutantWorklistTable ~ " WHERE id = :id";
1951         auto stmt = db.prepare(sql);
1952         stmt.get.bind(":id", id.get);
1953         stmt.get.execute;
1954     }
1955 
1956     /// Remove all mutants with `status` from the worklist.
1957     void remove(const Mutation.Status status) @trusted {
1958         static immutable sql = "DELETE FROM " ~ mutantWorklistTable ~ " WHERE "
1959             ~ " id IN (SELECT id FROM " ~ mutationStatusTable ~ " WHERE status=:status)";
1960         auto stmt = db.prepare(sql);
1961         stmt.get.bind(":status", cast(ubyte) status);
1962         stmt.get.execute;
1963     }
1964 
1965     void clear() @trusted {
1966         static immutable sql = format!"DELETE FROM %1$s"(mutantWorklistTable);
1967         auto stmt = db.prepare(sql);
1968         stmt.get.execute;
1969     }
1970 
1971     long getCount() @trusted {
1972         static immutable sql = format!"SELECT count(*) FROM %1$s"(mutantWorklistTable);
1973         auto stmt = db.prepare(sql);
1974         auto res = stmt.get.execute;
1975         return res.oneValue!long;
1976     }
1977 
1978     long getCount(Mutation.Status[] status) @trusted {
1979         const sql = format("SELECT count(*) FROM " ~ mutantWorklistTable ~ " t0, "
1980                 ~ mutationStatusTable ~ " t1 WHERE t0.id=t1.id AND t1.status IN (%(%s,%))",
1981                 status.map!(a => cast(int) a));
1982         auto stmt = db.prepare(sql);
1983         auto res = stmt.get.execute;
1984         return res.oneValue!long;
1985     }
1986 
1987     /// All mutants in the worklist ordred by their priority
1988     WorklistItem[] getAll() @trusted {
1989         static immutable sql = "SELECT id,prio FROM " ~ mutantWorklistTable ~ " ORDER BY prio DESC";
1990         auto stmt = db.prepare(sql);
1991         auto res = stmt.get.execute;
1992         return res.map!(a => WorklistItem(a.peek!long(0).MutationStatusId,
1993                 a.peek!long(1).MutantPrio)).array;
1994     }
1995 
1996     /// Add all mutants with `status` to worklist
1997     void statusToWorklist(const Mutation.Status status, const long prio = 100) @trusted {
1998         immutable sql = format!"INSERT OR IGNORE INTO %1$s (id,prio)
1999             SELECT id,%3$s+prio FROM %2$s WHERE status=:status"(
2000                 mutantWorklistTable, mutationStatusTable, prio);
2001         auto stmt = db.prepare(sql);
2002         stmt.get.bind(":status", cast(int) status);
2003         stmt.get.execute;
2004     }
2005 }
2006 
2007 struct DbMemOverload {
2008     private Miniorm* db;
2009 
2010     void put(const MutationStatusId id) @trusted {
2011         immutable sql = "INSERT OR IGNORE INTO "
2012             ~ mutantMemOverloadWorklistTable ~ " (id) VALUES (:id)";
2013         auto stmt = db.prepare(sql);
2014         stmt.get.bind(":id", id.get);
2015         stmt.get.execute;
2016     }
2017 
2018     /// Copy the memory overload mutants to the worklist of mutants to test.
2019     void toWorklist(const long prio = 100) @trusted {
2020         immutable sql = format!"INSERT OR IGNORE INTO %1$s (id,prio)
2021             SELECT id,%3$s FROM %2$s"(mutantWorklistTable,
2022                 mutantMemOverloadWorklistTable, prio);
2023         auto stmt = db.prepare(sql);
2024         stmt.get.execute;
2025 
2026     }
2027 
2028     void clear() @trusted {
2029         immutable sql = "DELETE FROM " ~ mutantMemOverloadWorklistTable;
2030         db.run(sql);
2031     }
2032 }
2033 
2034 struct DbMarkMutant {
2035     private Miniorm* db;
2036     private Database* wrapperDb;
2037 
2038     bool isMarked(MutationId id) @trusted {
2039         static immutable s = format!"SELECT COUNT(*) FROM %s WHERE st_id IN
2040             (SELECT st_id FROM %s WHERE id=:id)"(
2041                 markedMutantTable, mutationTable);
2042         auto stmt = db.prepare(s);
2043         stmt.get.bind(":id", cast(long) id);
2044         auto res = stmt.get.execute;
2045         return res.oneValue!long != 0;
2046     }
2047 
2048     /// All marked mutants whom have a mutation status checksum that has been removed from the database.
2049     MarkedMutant[] getLostMarkings() @trusted {
2050         static immutable sql = format!"SELECT checksum0 FROM %s
2051             WHERE
2052             checksum0 NOT IN (SELECT checksum0 FROM %s)"(
2053                 markedMutantTable, mutationStatusTable);
2054 
2055         auto stmt = db.prepare(sql);
2056         auto app = appender!(MarkedMutant[])();
2057         foreach (res; stmt.get.execute) {
2058             foreach (m; db.run(select!MarkedMutantTbl.where("checksum0 = :cs0",
2059                     Bind("cs0")), res.peek!long(0))) {
2060                 app.put(.make(m));
2061             }
2062         }
2063 
2064         return app.data;
2065     }
2066 
2067     /** Mark a mutant with status and rationale (also adds metadata).
2068      */
2069     void markMutant(const MutationId id, const Path file, const SourceLoc sloc,
2070             const MutationStatusId statusId, const Checksum cs,
2071             const Mutation.Status s, const Rationale r, string mutationTxt) @trusted {
2072         db.run(insertOrReplace!MarkedMutantTbl, MarkedMutantTbl(cs.c0, cs.c1,
2073                 statusId.get, id.get, sloc.line, sloc.column, file, s,
2074                 Clock.currTime.toUTC, r.get, mutationTxt));
2075     }
2076 
2077     void removeMarkedMutant(const Checksum cs) @trusted {
2078         db.run(delete_!MarkedMutantTbl.where("checksum0 = :cs0", Bind("cs0")), cast(long) cs.c0);
2079     }
2080 
2081     void removeMarkedMutant(const MutationStatusId id) @trusted {
2082         db.run(delete_!MarkedMutantTbl.where("st_id = :st_id", Bind("st_id")), id.get);
2083     }
2084 
2085     /// Returns: All mutants with that are marked orderd by their path
2086     MarkedMutant[] getMarkedMutants() @trusted {
2087         import miniorm : OrderingTermSort;
2088 
2089         auto app = appender!(MarkedMutant[])();
2090         foreach (m; db.run(select!MarkedMutantTbl.orderBy(OrderingTermSort.ASC, [
2091                     "path"
2092                 ]))) {
2093             app.put(.make(m));
2094         }
2095 
2096         return app.data;
2097     }
2098 }
2099 
2100 struct DbTimeout {
2101     private Miniorm* db;
2102     private Database* wrapperDb;
2103 
2104     /// Returns: the context for the timeout algorithm.
2105     MutantTimeoutCtx getMutantTimeoutCtx() @trusted {
2106         foreach (res; db.run(select!MutantTimeoutCtx))
2107             return res;
2108         return MutantTimeoutCtx.init;
2109     }
2110 
2111     void put(const MutantTimeoutCtx ctx) @trusted {
2112         db.run(delete_!MutantTimeoutCtx);
2113         db.run(insert!MutantTimeoutCtx.insert, ctx);
2114     }
2115 
2116     void put(const MutationStatusId id, const long iter) @trusted {
2117         static immutable sql = "INSERT OR REPLACE INTO "
2118             ~ mutantTimeoutWorklistTable ~ " (id,iter) VALUES (:id,:iter)";
2119         auto stmt = db.prepare(sql);
2120         stmt.get.bind(":id", id.get);
2121         stmt.get.bind(":iter", iter);
2122         stmt.get.execute;
2123     }
2124 
2125     void update(const MutationStatusId id, const long iter) @trusted {
2126         static immutable sql = "UPDATE " ~ mutantTimeoutWorklistTable
2127             ~ " SET iter=:iter WHERE id=:id";
2128         auto stmt = db.prepare(sql);
2129         stmt.get.bind(":id", id.get);
2130         stmt.get.bind(":iter", iter);
2131         stmt.get.execute;
2132     }
2133 
2134     /** Remove all mutants that are in the worklist of the iteration run `iter`
2135      * that do NOT have the mutation status timeout.
2136      */
2137     void reduceMutantTimeoutWorklist(const long iter) @trusted {
2138         static immutable sql = "DELETE FROM " ~ mutantTimeoutWorklistTable ~ " WHERE id IN (SELECT id FROM "
2139             ~ mutationStatusTable ~ " WHERE status != :status)" ~ " AND iter=:iter";
2140         auto stmt = db.prepare(sql);
2141         stmt.get.bind(":status", cast(ubyte) Mutation.Status.timeout);
2142         stmt.get.bind(":iter", iter);
2143         stmt.get.execute;
2144     }
2145 
2146     /// Remove all mutants from the worklist.
2147     void clearMutantTimeoutWorklist() @trusted {
2148         static immutable sql = "DELETE FROM " ~ mutantTimeoutWorklistTable;
2149         db.run(sql);
2150     }
2151 
2152     /// Returns: the number of mutants in the worklist.
2153     long countMutantTimeoutWorklist() @trusted {
2154         static immutable sql = "SELECT count(*) FROM " ~ mutantTimeoutWorklistTable;
2155         auto stmt = db.prepare(sql);
2156         auto res = stmt.get.execute();
2157         return res.oneValue!long;
2158     }
2159 
2160     /// Changes the status of mutants in the timeout worklist to unknown.
2161     void resetMutantTimeoutWorklist(Mutation.Status toStatus) @trusted {
2162         static immutable sql = format!"UPDATE %1$s SET status=:st WHERE id IN (SELECT id FROM %2$s)"(
2163                 mutationStatusTable, mutantTimeoutWorklistTable);
2164         auto stmt = db.prepare(sql);
2165         stmt.get.bind(":st", cast(ubyte) toStatus);
2166         stmt.get.execute;
2167     }
2168 
2169     /// Copy the timeout mutants to the worklist of mutants to test.
2170     void copyMutantTimeoutWorklist(const long iter, const long prio = 100) @trusted {
2171         immutable sql = format!"INSERT OR IGNORE INTO %1$s (id,prio)
2172             SELECT id,%3$s FROM %2$s WHERE iter=:iter"(
2173                 mutantWorklistTable, mutantTimeoutWorklistTable, prio);
2174         auto stmt = db.prepare(sql);
2175         stmt.get.bind(":iter", iter);
2176         stmt.get.execute;
2177     }
2178 }
2179 
2180 struct DbCoverage {
2181     private Miniorm* db;
2182     private Database* wrapperDb;
2183 
2184     /// Add coverage regions.
2185     void putCoverageMap(const FileId id, const Offset[] region) @trusted {
2186         static immutable sql = format!"INSERT OR IGNORE INTO %1$s (file_id, begin, end)
2187             VALUES(:fid, :begin, :end)"(srcCovTable);
2188         auto stmt = db.prepare(sql);
2189 
2190         foreach (a; region) {
2191             stmt.get.bind(":fid", id.get);
2192             stmt.get.bind(":begin", a.begin);
2193             stmt.get.bind(":end", a.end);
2194             stmt.get.execute;
2195             stmt.get.reset;
2196         }
2197     }
2198 
2199     CovRegion[][FileId] getCoverageMap() @trusted {
2200         static immutable sql = format!"SELECT file_id,begin,end,id FROM %s"(srcCovTable);
2201         auto stmt = db.prepare(sql);
2202 
2203         typeof(return) rval;
2204         foreach (ref r; stmt.get.execute) {
2205             auto region = CovRegion(r.peek!long(3).CoverageRegionId,
2206                     Offset(r.peek!uint(1), r.peek!uint(2)));
2207             if (auto v = FileId(r.peek!long(0)) in rval) {
2208                 *v ~= region;
2209             } else {
2210                 rval[FileId(r.peek!long(0))] = [region];
2211             }
2212         }
2213 
2214         return rval;
2215     }
2216 
2217     long getCoverageMapCount() @trusted {
2218         static immutable sql = format!"SELECT count(*) FROM %s"(srcCovTable);
2219         auto stmt = db.prepare(sql);
2220         foreach (ref r; stmt.get.execute)
2221             return r.peek!long(0);
2222         return 0;
2223     }
2224 
2225     void clearCoverageMap(const FileId id) @trusted {
2226         static immutable sql = format!"DELETE FROM %1$s WHERE file_id = :id"(srcCovTable);
2227         auto stmt = db.prepare(sql);
2228         stmt.get.bind(":id", id.get);
2229         stmt.get.execute;
2230     }
2231 
2232     void putCoverageInfo(const CoverageRegionId regionId, bool status) {
2233         static immutable sql = format!"INSERT OR REPLACE INTO %1$s (id, status) VALUES(:id, :status)"(
2234                 srcCovInfoTable);
2235         auto stmt = db.prepare(sql);
2236         stmt.get.bind(":id", regionId.get);
2237         stmt.get.bind(":status", status);
2238         stmt.get.execute;
2239     }
2240 
2241     Optional!SysTime getCoverageTimeStamp() @trusted {
2242         static immutable sql = format!"SELECT timeStamp FROM %s"(srcCovTimeStampTable);
2243         auto stmt = db.prepare(sql);
2244 
2245         foreach (ref r; stmt.get.execute) {
2246             return some(r.peek!string(0).fromSqLiteDateTime);
2247         }
2248         return none!SysTime;
2249     }
2250 
2251     /// Set the timestamp to the current UTC time.
2252     void updateCoverageTimeStamp() @trusted {
2253         static immutable sql = format!"INSERT OR REPLACE INTO %s (id, timestamp) VALUES(0, :time)"(
2254                 srcCovTimeStampTable);
2255         auto stmt = db.prepare(sql);
2256         stmt.get.bind(":time", Clock.currTime.toSqliteDateTime);
2257         stmt.get.execute;
2258     }
2259 
2260     MutationStatusId[] getNotCoveredMutants() @trusted {
2261         static immutable sql = format!"SELECT DISTINCT t3.st_id FROM %1$s t0, %2$s t1, %3$s t2, %4$s t3
2262             WHERE t0.status = 0 AND
2263             t0.id = t1.id AND
2264             t1.file_id = t2.file_id AND
2265             (t2.offset_begin BETWEEN t1.begin AND t1.end) AND
2266             (t2.offset_end BETWEEN t1.begin AND t1.end) AND
2267             t2.id = t3.mp_id"(srcCovInfoTable,
2268                 srcCovTable, mutationPointTable, mutationTable);
2269 
2270         auto app = appender!(MutationStatusId[])();
2271         auto stmt = db.prepare(sql);
2272         foreach (ref r; stmt.get.execute) {
2273             app.put(MutationStatusId(r.peek!long(0)));
2274         }
2275 
2276         return app.data;
2277     }
2278 }
2279 
2280 struct DbSchema {
2281     import my.hash : Checksum64;
2282 
2283     private Miniorm* db;
2284     private Database* wrapperDb;
2285 
2286     /// Returns: all schematas excluding those that are known to not be
2287     /// possible to compile.
2288     SchemataId[] getSchematas(const SchemaStatus exclude) @trusted {
2289         static immutable sql = format!"SELECT t0.id
2290             FROM %1$s t0
2291             WHERE
2292             t0.id NOT IN (SELECT id FROM %2$s WHERE status = :status)"(
2293                 schemataTable, schemataUsedTable);
2294         auto stmt = db.prepare(sql);
2295         stmt.get.bind(":status", cast(long) exclude);
2296         auto app = appender!(SchemataId[])();
2297         foreach (a; stmt.get.execute) {
2298             app.put(SchemataId(a.peek!long(0)));
2299         }
2300         return app.data;
2301     }
2302 
2303     Nullable!Schemata getSchemata(SchemataId id) @trusted {
2304         import std.zlib : uncompress;
2305 
2306         static immutable sql = format!"SELECT
2307             t1.path, t0.text, t0.offset_begin, t0.offset_end
2308             FROM %1$s t0, %2$s t1
2309             WHERE
2310             t0.schem_id = :id AND
2311             t0.file_id = t1.id
2312             ORDER BY t0.order_ ASC
2313             "(schemataFragmentTable, filesTable);
2314 
2315         typeof(return) rval;
2316         auto stmt = db.prepare(sql);
2317         stmt.get.bind(":id", cast(long) id);
2318 
2319         auto app = appender!(SchemataFragment[])();
2320         foreach (a; stmt.get.execute) {
2321             auto raw = a.peek!(ubyte[])(1);
2322             auto offset = Offset(a.peek!uint(2), a.peek!uint(3));
2323             app.put(SchemataFragment(a.peek!string(0).Path, offset,
2324                     cast(const(ubyte)[]) uncompress(raw, offset.end - offset.begin)));
2325         }
2326 
2327         if (!app.data.empty) {
2328             rval = Schemata(SchemataId(id), app.data);
2329         }
2330 
2331         return rval;
2332     }
2333 
2334     /// Returns: number of mutants in a schema with `status`.
2335     long countMutants(const SchemataId id, const Mutation.Kind[] kinds,
2336             const Mutation.Status[] status) @trusted {
2337         const sql = format!"SELECT count(*)
2338         FROM %s t1, %s t2, %s t3
2339         WHERE
2340         t1.schem_id = :id AND
2341         t1.st_id = t2.id AND
2342         t3.st_id = t1.st_id AND
2343         t3.kind IN (%(%s,%)) AND
2344         t2.status IN (%(%s,%))
2345         "(schemataMutantTable, mutationStatusTable,
2346                 mutationTable, kinds.map!(a => cast(int) a), status.map!(a => cast(int) a));
2347 
2348         auto stmt = db.prepare(sql);
2349         stmt.get.bind(":id", id.get);
2350         return stmt.get.execute.oneValue!long;
2351     }
2352 
2353     /// Returns: number of mutants in a schema that are marked for testing and in the worklist.
2354     long countMutantsInWorklist(const SchemataId id, const Mutation.Kind[] kinds) @trusted {
2355         const sql = format!"SELECT count(*)
2356         FROM %s t1, %s t2, %s t3, %s t4
2357         WHERE
2358         t1.schem_id = :id AND
2359         t1.st_id = t2.id AND
2360         t3.st_id = t1.st_id AND
2361         t2.id = t4.id AND
2362         t3.kind IN (%(%s,%))
2363         "(schemataMutantTable, mutationStatusTable,
2364                 mutationTable, mutantWorklistTable, kinds.map!(a => cast(int) a));
2365 
2366         auto stmt = db.prepare(sql);
2367         stmt.get.bind(":id", id.get);
2368         return stmt.get.execute.oneValue!long;
2369     }
2370 
2371     MutationStatusId[] getSchemataMutants(const SchemataId id, const Mutation.Kind[] kinds) @trusted {
2372         // TODO: DISTINCT should not be needed. Instead use e.g. a constraint on the table or something
2373         immutable sql = format!"SELECT DISTINCT t1.st_id
2374             FROM %s t1, %s t2, %s t3, %s t4
2375             WHERE
2376             t1.schem_id = :id AND
2377             t1.st_id = t2.id AND
2378             t3.st_id = t1.st_id AND
2379             t2.id = t4.id AND
2380             t3.kind IN (%(%s,%))
2381             "(schemataMutantTable, mutationStatusTable,
2382                 mutationTable, mutantWorklistTable, kinds.map!(a => cast(int) a));
2383         auto stmt = db.prepare(sql);
2384         stmt.get.bind(":id", id.get);
2385 
2386         auto app = appender!(MutationStatusId[])();
2387         foreach (a; stmt.get.execute) {
2388             app.put(a.peek!long(0).MutationStatusId);
2389         }
2390 
2391         return app.data;
2392     }
2393 
2394     /// Returns: the kind of mutants a schemata contains.
2395     Mutation.Kind[] getSchemataKinds(const SchemataId id) @trusted {
2396         static immutable sql = format!"SELECT DISTINCT t1.kind
2397             FROM %1$s t0, %2$s t1
2398             WHERE
2399             t0.schem_id = :id AND
2400             t0.st_id = t1.st_id
2401             "(schemataMutantTable, mutationTable);
2402         auto stmt = db.prepare(sql);
2403         stmt.get.bind(":id", cast(long) id);
2404 
2405         auto app = appender!(Mutation.Kind[])();
2406         foreach (a; stmt.get.execute) {
2407             app.put(a.peek!long(0).to!(Mutation.Kind));
2408         }
2409 
2410         return app.data;
2411     }
2412 
2413     /// Mark a schemata as used.
2414     void markUsed(const SchemataId id, const SchemaStatus status) @trusted {
2415         static immutable sql = format!"INSERT OR IGNORE INTO %1$s VALUES(:id, :status)"(
2416                 schemataUsedTable);
2417         auto stmt = db.prepare(sql);
2418         stmt.get.bind(":id", cast(long) id);
2419         stmt.get.bind(":status", status);
2420         stmt.get.execute;
2421     }
2422 
2423     /// Create a schemata from a bundle of fragments.
2424     Nullable!SchemataId putSchemata(SchemataChecksum cs,
2425             const SchemataFragment[] fragments, MutationStatusId[] mutants) @trusted {
2426         import std.zlib : compress;
2427 
2428         const schemId = cast(long) cs.value.c0;
2429 
2430         const exists = () {
2431             static immutable sql = format!"SELECT count(*) FROM %1$s WHERE id=:id"(schemataTable);
2432             auto stmt = db.prepare(sql);
2433             stmt.get.bind(":id", schemId);
2434             return stmt.get.execute.oneValue!long != 0;
2435 
2436         }();
2437 
2438         if (exists)
2439             return typeof(return)();
2440 
2441         {
2442             static immutable sql = format!"INSERT INTO %1$s VALUES(:id, :nr)"(schemataTable);
2443             auto stmt = db.prepare(sql);
2444             stmt.get.bind(":id", cast(long) cs.value.c0);
2445             stmt.get.bind(":nr", cast(long) fragments.length);
2446             stmt.get.execute;
2447         }
2448 
2449         foreach (f; fragments.enumerate) {
2450             const fileId = wrapperDb.getFileId(f.value.file);
2451             if (fileId.isNull) {
2452                 logger.warningf("Unable to add schemata fragment for file %s because it doesn't exist",
2453                         f.value.file);
2454                 continue;
2455             }
2456 
2457             db.run(insert!SchemataFragmentTable, SchemataFragmentTable(0,
2458                     schemId, cast(long) fileId.get, f.index,
2459                     compress(f.value.text), f.value.offset.begin, f.value.offset.end));
2460         }
2461 
2462         // relate mutants to this schemata.
2463         db.run(insertOrIgnore!SchemataMutantTable,
2464                 mutants.map!(a => SchemataMutantTable(cast(long) a, schemId)));
2465 
2466         return typeof(return)(schemId.SchemataId);
2467     }
2468 
2469     /// Prunes the database of schemas that where created by an older version.
2470     void deleteAllSchemas() @trusted {
2471         db.run(delete_!SchemataTable);
2472     }
2473 
2474     /// Prunes the database of schemas that are unusable.
2475     void pruneSchemas() @trusted {
2476         auto remove = () {
2477             auto remove = appender!(long[])();
2478 
2479             // remove those that have lost some fragments
2480             static immutable sqlFragment = format!"SELECT t0.id
2481             FROM
2482             %1$s t0,
2483             (SELECT schem_id id,count(*) fragments FROM %2$s GROUP BY schem_id) t1
2484             WHERE
2485             t0.id = t1.id AND
2486             t0.fragments != t1.fragments
2487             "(schemataTable,
2488                     schemataFragmentTable);
2489             auto stmt = db.prepare(sqlFragment);
2490             foreach (a; stmt.get.execute) {
2491                 remove.put(a.peek!long(0));
2492             }
2493 
2494             // remove those that have lost all fragments
2495             static immutable sqlNoFragment = format!"SELECT t0.id FROM %1$s t0 WHERE t0.id NOT IN (SELECT schem_id FROM %2$s)"(
2496                     schemataTable, schemataFragmentTable);
2497             stmt = db.prepare(sqlNoFragment);
2498             foreach (a; stmt.get.execute) {
2499                 remove.put(a.peek!long(0));
2500             }
2501 
2502             return remove.data;
2503         }();
2504 
2505         static immutable sql = format!"DELETE FROM %1$s WHERE id=:id"(schemataTable);
2506         auto stmt = db.prepare(sql);
2507         foreach (a; remove) {
2508             stmt.get.bind(":id", a);
2509             stmt.get.execute;
2510             stmt.get.reset;
2511         }
2512     }
2513 
2514     /** Removes all schemas that either do not compile or have zero mutants.
2515      *
2516      * Returns: number of schemas removed.
2517      */
2518     long pruneUsedSchemas(const SchemaStatus[] status) @trusted {
2519         auto remove = () {
2520             auto remove = appender!(long[])();
2521 
2522             auto sqlUsed = format!"SELECT id FROM %s WHERE status IN (%(%s,%))"(schemataUsedTable,
2523                     status.map!(a => cast(long) a));
2524             auto stmt = db.prepare(sqlUsed);
2525             foreach (a; stmt.get.execute) {
2526                 remove.put(a.peek!long(0));
2527             }
2528             return remove.data;
2529         }();
2530 
2531         static immutable sql = format!"DELETE FROM %1$s WHERE id=:id"(schemataTable);
2532         auto stmt = db.prepare(sql);
2533         foreach (a; remove) {
2534             stmt.get.bind(":id", a);
2535             stmt.get.execute;
2536             stmt.get.reset;
2537         }
2538 
2539         return remove.length;
2540     }
2541 
2542     int[Mutation.Kind][Checksum64] getMutantProbability() @trusted {
2543         typeof(return) rval;
2544 
2545         auto stmt = db.prepare(
2546                 format!"SELECT kind,probability,path FROM %1$s"(schemaMutantQTable));
2547         foreach (ref r; stmt.get.execute) {
2548             const ch = Checksum64(cast(ulong) r.peek!long(2));
2549             rval.require(ch, (int[Mutation.Kind]).init);
2550             rval[ch][r.peek!long(0).to!(Mutation.Kind)] = r.peek!int(1);
2551         }
2552         return rval;
2553     }
2554 
2555     void removeMutantProbability(const Checksum64 p) @trusted {
2556         static immutable sql = format!"DELETE FROM %1$s WHERE path=:path"(schemaMutantQTable);
2557         auto stmt = db.prepare(sql);
2558         stmt.get.bind(":path", cast(long) p.c0);
2559         stmt.get.execute;
2560     }
2561 
2562     /** Save the probability state for a path.
2563      *
2564      * Only states other than 100 are saved.
2565      *
2566      * Params:
2567      *  p = checksum of the path.
2568      */
2569     void saveMutantProbability(const Checksum64 p, int[Mutation.Kind] state, const int skipMax) @trusted {
2570         auto stmt = db.prepare(
2571                 format!"INSERT OR REPLACE INTO %1$s (kind,probability,path) VALUES(:kind,:q,:path)"(
2572                 schemaMutantQTable));
2573         foreach (a; state.byKeyValue) {
2574             if (a.value != skipMax) {
2575                 stmt.get.bind(":kind", cast(long) a.key);
2576                 stmt.get.bind(":q", cast(long) a.value);
2577                 stmt.get.bind(":path", cast(long) p.c0);
2578                 stmt.get.execute;
2579                 stmt.get.reset;
2580             }
2581         }
2582     }
2583 
2584     /// Returns: all mutant subtypes that has `status`, can occur multiple times.
2585     Mutation.Kind[] getSchemaUsedKinds(const Path p, const SchemaStatus status) @trusted {
2586         static immutable sql = format!"SELECT DISTINCT t1.kind
2587             FROM %1$s t0, %2$s t1, %3$s t2, %4$s t3, %5$s t4
2588             WHERE
2589             t4.status = :status AND
2590             t4.id = t0.schem_id AND
2591             t0.st_id = t1.st_id AND
2592             t1.mp_id = t2.id AND
2593             t2.file_id = t3.id AND
2594             t3.path = :path
2595             "(schemataMutantTable,
2596                 mutationTable, mutationPointTable, filesTable, schemataUsedTable);
2597 
2598         auto stmt = db.prepare(sql);
2599         stmt.get.bind(":status", cast(long) status);
2600         stmt.get.bind(":path", p.toString);
2601 
2602         auto app = appender!(Mutation.Kind[])();
2603         foreach (ref r; stmt.get.execute) {
2604             auto k = r.peek!long(0).to!(Mutation.Kind);
2605             app.put(k);
2606         }
2607 
2608         return app.data;
2609     }
2610 
2611     /// Returns: an array of the mutants that are in schemas with the specific status
2612     long[] schemaMutantCount(const SchemaStatus status) @trusted {
2613         static immutable sql = format!"SELECT (SELECT count(*) FROM %2$s WHERE schem_id=t0.id)
2614             FROM %1$s t0 WHERE status=:status"(
2615                 schemataUsedTable, schemataMutantTable);
2616         auto stmt = db.prepare(sql);
2617         stmt.get.bind(":status", cast(long) status);
2618         auto app = appender!(long[])();
2619         foreach (ref r; stmt.get.execute)
2620             app.put(r.peek!long(0));
2621         return app.data;
2622     }
2623 
2624     long getSchemaSize(const long defaultValue) @trusted {
2625         static immutable sql = "SELECT size FROM " ~ schemaSizeQTable ~ " WHERE id=0";
2626         auto stmt = db.prepare(sql);
2627         foreach (ref r; stmt.get.execute)
2628             return r.peek!long(0);
2629         return defaultValue;
2630     }
2631 
2632     void saveSchemaSize(const long v) @trusted {
2633         static immutable sql = "INSERT OR REPLACE INTO " ~ schemaSizeQTable
2634             ~ " (id,size) VALUES(0,:size)";
2635         auto stmt = db.prepare(sql);
2636         stmt.get.bind(":size", v);
2637         stmt.get.execute;
2638     }
2639 }
2640 
2641 struct DbMetaData {
2642     private Miniorm* db;
2643     private Database* wrapperDb;
2644 
2645     LineMetadata getLineMetadata(const FileId fid, const SourceLoc sloc) @trusted {
2646         // TODO: change this select to using microrm
2647         static immutable sql = format("SELECT nomut,tag,comment FROM %s
2648             WHERE
2649             file_id = :fid AND
2650             line = :line", rawSrcMetadataTable);
2651         auto stmt = db.prepare(sql);
2652         stmt.get.bind(":fid", cast(long) fid);
2653         stmt.get.bind(":line", sloc.line);
2654 
2655         auto rval = typeof(return)(fid, sloc.line);
2656         foreach (res; stmt.get.execute) {
2657             if (res.peek!long(0) != 0)
2658                 rval.set(NoMut(res.peek!string(1), res.peek!string(2)));
2659         }
2660 
2661         return rval;
2662     }
2663 
2664     /// Remove all metadata.
2665     void clearMetadata() {
2666         static immutable sql = format!"DELETE FROM %s"(rawSrcMetadataTable);
2667         db.run(sql);
2668     }
2669 
2670     /** Save line metadata to the database which is used to associate line
2671      * metadata with mutants.
2672      */
2673     void put(const LineMetadata[] mdata) {
2674         import sumtype;
2675 
2676         if (mdata.empty)
2677             return;
2678 
2679         // TODO: convert to microrm
2680         static immutable sql = format!"INSERT OR IGNORE INTO %s
2681             (file_id, line, nomut, tag, comment)
2682             VALUES(:fid, :line, :nomut, :tag, :comment)"(
2683                 rawSrcMetadataTable);
2684 
2685         auto stmt = db.prepare(sql);
2686         foreach (meta; mdata) {
2687             auto nomut = meta.attr.match!((NoMetadata a) => NoMut.init, (NoMut a) => a);
2688             stmt.get.bindAll(cast(long) meta.id, meta.line, meta.isNoMut,
2689                     nomut.tag, nomut.comment);
2690             stmt.get.execute;
2691             stmt.get.reset;
2692         }
2693     }
2694 
2695     /** Update the content of metadata tables with what has been added to the
2696      * raw table data.
2697      */
2698     void updateMetadata() @trusted {
2699         db.run(format!"DELETE FROM %s"(srcMetadataTable));
2700         db.run(format!"DELETE FROM %s"(nomutTable));
2701         db.run(format!"DELETE FROM %s"(nomutDataTable));
2702 
2703         static immutable nomut_tbl = "INSERT INTO %s
2704             SELECT
2705                 t0.id mp_id,
2706                 t1.line line,
2707                 count(*) status
2708                 FROM %s t0, %s t1
2709                 WHERE
2710                 t0.file_id = t1.file_id AND
2711                 (t1.line BETWEEN t0.line AND t0.line_end)
2712                 GROUP BY
2713                 t0.id";
2714         db.run(format!nomut_tbl(nomutTable, mutationPointTable, rawSrcMetadataTable));
2715 
2716         static immutable src_metadata_sql = "INSERT INTO %s
2717             SELECT DISTINCT
2718             t0.id AS mut_id,
2719             t1.id AS st_id,
2720             t2.id AS mp_id,
2721             t3.id AS file_id,
2722             (SELECT count(*) FROM %s WHERE nomut.mp_id = t2.id) as nomut
2723             FROM %s t0, %s t1, %s t2, %s t3
2724             WHERE
2725             t0.mp_id = t2.id AND
2726             t0.st_id = t1.id AND
2727             t2.file_id = t3.id";
2728         db.run(format!src_metadata_sql(srcMetadataTable, nomutTable,
2729                 mutationTable, mutationStatusTable, mutationPointTable, filesTable));
2730 
2731         static immutable nomut_data_tbl = "INSERT INTO %s
2732             SELECT
2733                 t0.id as mut_id,
2734                 t0.mp_id as mp_id,
2735                 t1.line as line,
2736                 t1.tag as tag,
2737                 t1.comment as comment
2738                 FROM %s t0, %s t1, %s t2
2739                 WHERE
2740                 t0.mp_id = t2.mp_id AND
2741                 t1.line = t2.line";
2742         db.run(format!nomut_data_tbl(nomutDataTable, mutationTable,
2743                 rawSrcMetadataTable, nomutTable));
2744     }
2745 }
2746 
2747 struct DbTestFile {
2748     private Miniorm* db;
2749     private Database* wrapperDb;
2750 
2751     void put(const TestFile tfile) @trusted {
2752         static immutable sql = format!"INSERT OR IGNORE INTO %s (path, checksum0, checksum1, timestamp)
2753             VALUES (:path, :checksum0, :checksum1, :timestamp)"(
2754                 testFilesTable);
2755         auto stmt = db.prepare(sql);
2756         stmt.get.bind(":path", tfile.file.get.toString);
2757         stmt.get.bind(":checksum0", cast(long) tfile.checksum.get.c0);
2758         stmt.get.bind(":checksum1", cast(long) tfile.checksum.get.c1);
2759         stmt.get.bind(":timestamp", tfile.timeStamp.toSqliteDateTime);
2760         stmt.get.execute;
2761     }
2762 
2763     TestFile[] getTestFiles() @trusted {
2764         static immutable sql = "SELECT path,checksum0,checksum1,timestamp FROM " ~ testFilesTable;
2765         auto stmt = db.prepare(sql);
2766         auto res = stmt.get.execute;
2767 
2768         auto app = appender!(TestFile[]);
2769         foreach (ref r; res) {
2770             app.put(TestFile(TestFilePath(Path(r.peek!string(0))),
2771                     TestFileChecksum(Checksum(r.peek!long(1), r.peek!long(2))),
2772                     r.peek!string(3).fromSqLiteDateTime));
2773         }
2774 
2775         return app.data;
2776     }
2777 
2778     /// Returns: the oldest test file, if it exists.
2779     Optional!TestFile getNewestTestFile() @trusted {
2780         auto stmt = db.prepare("SELECT path,checksum0,checksum1,timestamp
2781             FROM "
2782                 ~ testFilesTable ~ " ORDER BY datetime(timestamp) DESC LIMIT 1");
2783         auto res = stmt.get.execute;
2784 
2785         foreach (ref r; res) {
2786             return some(TestFile(TestFilePath(Path(r.peek!string(0))),
2787                     TestFileChecksum(Checksum(r.peek!long(1), r.peek!long(2))),
2788                     r.peek!string(3).fromSqLiteDateTime));
2789         }
2790 
2791         return none!TestFile;
2792     }
2793 
2794     /// Remove the file with all mutations that are coupled to it.
2795     void removeFile(const TestFilePath p) @trusted {
2796         auto stmt = db.prepare("DELETE FROM " ~ testFilesTable ~ " WHERE path=:path");
2797         stmt.get.bind(":path", p.get.toString);
2798         stmt.get.execute;
2799     }
2800 }
2801 
2802 private:
2803 
2804 MarkedMutant make(MarkedMutantTbl m) {
2805     import dextool.plugin.mutate.backend.type;
2806 
2807     return MarkedMutant(m.mutationStatusId.MutationStatusId, Checksum(m.checksum0,
2808             m.checksum1), m.mutationId.MutationId, SourceLoc(m.line, m.column),
2809             m.path.Path, m.toStatus.to!(Mutation.Status), m.time,
2810             m.rationale.Rationale, m.mutText);
2811 }
2812 
2813 string fromOrder(const MutationOrder userOrder) {
2814     final switch (userOrder) {
2815     case MutationOrder.random:
2816         return ":base_prio + t1.prio + abs(random() % 100)";
2817     case MutationOrder.consecutive:
2818         return ":base_prio";
2819     case MutationOrder.bySize:
2820         return ":base_prio + t1.prio";
2821     }
2822 }