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