1 /**
2 Copyright: Copyright (c) 2018, 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;
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.regex : Regex, matchFirst;
32 import std.typecons : Nullable, Flag, No;
33 
34 import miniorm : toSqliteDateTime, fromSqLiteDateTime, Bind;
35 
36 import dextool.type : AbsolutePath, Path, ExitStatusType;
37 
38 import dextool.plugin.mutate.backend.database.schema;
39 import dextool.plugin.mutate.backend.database.type;
40 import dextool.plugin.mutate.backend.type : Language, Checksum;
41 
42 /** Database wrapper with minimal dependencies.
43  */
44 struct Database {
45     import miniorm : Miniorm, select, insert, insertOrReplace, delete_, insertOrIgnore;
46     import d2sqlite3 : SqlDatabase = Database;
47     import dextool.plugin.mutate.backend.type : MutationPoint, Mutation, Checksum;
48 
49     Miniorm db;
50     alias db this;
51 
52     /** Create a database by either opening an existing or initializing a new.
53      *
54      * Params:
55      *  db = path to the database
56      */
57     static auto make(string db) @safe {
58         return Database(initializeDB(db));
59     }
60 
61     /// If the file has already been analyzed.
62     bool isAnalyzed(const Path p) @trusted {
63         auto stmt = db.prepare("SELECT count(*) FROM files WHERE path=:path LIMIT 1");
64         stmt.get.bind(":path", cast(string) p);
65         auto res = stmt.get.execute;
66         return res.oneValue!long != 0;
67     }
68 
69     /// If the file has already been analyzed.
70     bool isAnalyzed(const Path p, const Checksum cs) @trusted {
71         auto stmt = db.prepare(
72                 "SELECT count(*) FROM files WHERE path=:path AND checksum0=:cs0 AND checksum1=:cs1 LIMIT 1");
73         stmt.get.bind(":path", cast(string) p);
74         stmt.get.bind(":cs0", cast(long) cs.c0);
75         stmt.get.bind(":cs1", cast(long) cs.c1);
76         auto res = stmt.get.execute;
77         return res.oneValue!long != 0;
78     }
79 
80     bool exists(MutationStatusId status_id) {
81         immutable s = format!"SELECT COUNT(*) FROM %s WHERE id=:id LIMIT 1"(mutationStatusTable);
82         auto stmt = db.prepare(s);
83         stmt.get.bind(":id", cast(long) status_id);
84         auto res = stmt.get.execute;
85         return res.oneValue!long == 0;
86     }
87 
88     bool isMarked(MutationId id) @trusted {
89         immutable s = format!"SELECT COUNT(*) FROM %s WHERE st_id IN
90             (SELECT st_id FROM %s WHERE id=:id)"(
91                 markedMutantTable, mutationTable);
92         auto stmt = db.prepare(s);
93         stmt.get.bind(":id", cast(long) id);
94         auto res = stmt.get.execute;
95         return res.oneValue!long != 0;
96     }
97 
98     /// All marked mutants whom have a mutation status checksum that has been removed from the database.
99     MarkedMutant[] getLostMarkings() @trusted {
100         immutable sql = format!"SELECT checksum0 FROM %s
101             WHERE
102             checksum0 NOT IN (SELECT checksum0 FROM %s)"(
103                 markedMutantTable, mutationStatusTable);
104 
105         auto stmt = db.prepare(sql);
106         auto app = appender!(MarkedMutant[])();
107         foreach (res; stmt.get.execute) {
108             foreach (m; db.run(select!MarkedMutantTbl.where("checksum0 = :cs0",
109                     Bind("cs0")), res.peek!long(0))) {
110                 app.put(.make(m));
111             }
112         }
113 
114         return app.data;
115     }
116 
117     Nullable!FileId getFileId(const Path p) @trusted {
118         enum sql = format("SELECT id FROM %s WHERE path=:path", filesTable);
119         auto stmt = db.prepare(sql);
120         stmt.get.bind(":path", cast(string) p);
121         auto res = stmt.get.execute;
122 
123         typeof(return) rval;
124         if (!res.empty)
125             rval = FileId(res.oneValue!long);
126         return rval;
127     }
128 
129     /// Returns: the path ID for the mutant.
130     Nullable!FileId getFileId(const MutationId id) @trusted {
131         enum sql = format("SELECT t1.file_id
132             FROM %s t0, %s t1
133             WHERE t0.id = :id AND t0.mp_id = t1.id",
134                     mutationTable, mutationPointTable);
135         auto stmt = db.prepare(sql);
136         stmt.get.bind(":id", cast(long) id);
137 
138         typeof(return) rval;
139         foreach (r; stmt.get.execute)
140             rval = FileId(r.peek!long(0));
141         return rval;
142     }
143 
144     /// Returns: the file path that the id correspond to.
145     Nullable!Path getFile(const FileId id) @trusted {
146         enum sql = format("SELECT path FROM %s WHERE id = :id", filesTable);
147         auto stmt = db.prepare(sql);
148         stmt.get.bind(":id", cast(long) id);
149 
150         typeof(return) rval;
151         foreach (r; stmt.get.execute)
152             rval = Path(r.peek!string(0));
153         return rval;
154     }
155 
156     /// Remove the file with all mutations that are coupled to it.
157     void removeFile(const Path p) @trusted {
158         auto stmt = db.prepare(format!"DELETE FROM %s WHERE path=:path"(filesTable));
159         stmt.get.bind(":path", cast(string) p);
160         stmt.get.execute;
161     }
162 
163     /// Returns: All files in the database as relative paths.
164     Path[] getFiles() @trusted {
165         auto stmt = db.prepare(format!"SELECT path from %s"(filesTable));
166         auto res = stmt.get.execute;
167 
168         auto app = appender!(Path[]);
169         foreach (ref r; res) {
170             app.put(Path(r.peek!string(0)));
171         }
172 
173         return app.data;
174     }
175 
176     enum CntAction {
177         /// Increment the counter
178         incr,
179         /// Reset the counter to zero
180         reset,
181     }
182 
183     /** Update the status of a mutant.
184      *
185      * Params:
186      *  id = ID of the mutant
187      *  st = status to broadcast
188      *  d = time spent on veryfing the mutant
189      *  tcs = test cases that killed the mutant
190      *  counter = how to act with the counter
191      */
192     void updateMutation(const MutationId id, const Mutation.Status st,
193             const Duration d, const(TestCase)[] tcs, CntAction counter = CntAction.incr) @trusted {
194         enum sql = "UPDATE %s SET
195             status=:st,time=:time,update_ts=:update_ts,%s
196             WHERE
197             id IN (SELECT st_id FROM %s WHERE id = :id)";
198 
199         auto stmt = () {
200             final switch (counter) {
201             case CntAction.incr:
202                 return db.prepare(format!sql(mutationStatusTable,
203                         "test_cnt=test_cnt+1", mutationTable));
204             case CntAction.reset:
205                 return db.prepare(format!sql(mutationStatusTable,
206                         "test_cnt=0", mutationTable));
207             }
208         }();
209 
210         stmt.get.bind(":st", st.to!long);
211         stmt.get.bind(":id", id.to!long);
212         stmt.get.bind(":time", d.total!"msecs");
213         stmt.get.bind(":update_ts", Clock.currTime.toUTC.toSqliteDateTime);
214         stmt.get.execute;
215 
216         updateMutationTestCases(id, tcs);
217     }
218 
219     /** Update the status of a mutant.
220      *
221      * Params:
222      *  id = ID of the mutant
223      *  st = status to broadcast
224      *  d = time spent on veryfing the mutant
225      *  tcs = test cases that killed the mutant
226      *  counter = how to act with the counter
227      */
228     void updateMutation(const MutationStatusId id, const Mutation.Status st,
229             const Duration d, CntAction counter = CntAction.incr) @trusted {
230         enum sql = "UPDATE %s SET
231             status=:st,time=:time,update_ts=:update_ts,%s
232             WHERE
233             id = :id";
234 
235         auto stmt = () {
236             final switch (counter) {
237             case CntAction.incr:
238                 return db.prepare(format!sql(mutationStatusTable,
239                         "test_cnt=test_cnt+1"));
240             case CntAction.reset:
241                 return db.prepare(format!sql(mutationStatusTable, "test_cnt=0"));
242             }
243         }();
244 
245         stmt.get.bind(":id", cast(long) id);
246         stmt.get.bind(":st", cast(long) st);
247         stmt.get.bind(":time", d.total!"msecs");
248         stmt.get.bind(":update_ts", Clock.currTime.toUTC.toSqliteDateTime);
249         stmt.get.execute;
250     }
251 
252     /** Update the counter of how many times the mutants has been alive.
253      *
254      * Params:
255      *  id = ID of the mutant
256      *  counter = how to act with the counter
257      */
258     void updateMutation(const MutationStatusId id, const CntAction counter) @trusted {
259         enum sql = "UPDATE %s SET %s WHERE id = :id";
260 
261         auto stmt = () {
262             final switch (counter) {
263             case CntAction.incr:
264                 return db.prepare(format!sql(mutationStatusTable,
265                         "test_cnt=test_cnt+1"));
266             case CntAction.reset:
267                 return db.prepare(format!sql(mutationStatusTable, "test_cnt=0"));
268             }
269         }();
270 
271         stmt.get.bind(":id", id.to!long);
272         stmt.get.execute;
273     }
274 
275     /// Update the time used to test the mutant.
276     void updateMutation(const MutationStatusId id, const Duration testTime) @trusted {
277         enum sql = format!"UPDATE %s SET time=:time WHERE id = :id"(mutationStatusTable);
278         auto stmt = db.prepare(sql);
279         stmt.get.bind(":id", id.to!long);
280         stmt.get.bind(":time", testTime.total!"msecs");
281         stmt.get.execute;
282     }
283 
284     /** Update the status of a mutant.
285      *
286      * Params:
287      *  id = mutation status ID
288      *  st = new status
289      *  update_ts = if the update timestamp should be updated.
290      */
291     void updateMutationStatus(const MutationStatusId id, const Mutation.Status st,
292             Flag!"updateTs" update_ts = No.updateTs) @trusted {
293 
294         auto stmt = () {
295             if (update_ts) {
296                 const ts = Clock.currTime.toUTC.toSqliteDateTime;
297                 auto s = db.prepare(format!"UPDATE %s SET status=:st,update_ts=:update_ts WHERE id=:id"(
298                         mutationStatusTable));
299                 s.get.bind(":update_ts", ts);
300                 return s;
301             } else
302                 return db.prepare(format!"UPDATE %s SET status=:st WHERE id=:id"(
303                         mutationStatusTable));
304         }();
305         stmt.get.bind(":st", st.to!long);
306         stmt.get.bind(":id", id.to!long);
307         stmt.get.execute;
308     }
309 
310     /// Returns: all mutation status IDs.
311     MutationStatusId[] getAllMutationStatus() @trusted {
312         enum sql = format!"SELECT id FROM %s"(mutationStatusTable);
313 
314         auto app = appender!(MutationStatusId[])();
315         auto stmt = db.prepare(sql);
316         foreach (r; stmt.get.execute)
317             app.put(MutationStatusId(r.peek!long(0)));
318         return app.data;
319     }
320 
321     Nullable!(Mutation.Status) getMutationStatus(const MutationStatusId id) @trusted {
322         enum sql = format!"SELECT status FROM %s WHERE id=:id"(mutationStatusTable);
323         auto stmt = db.prepare(sql);
324         stmt.get.bind(":id", cast(long) id);
325 
326         typeof(return) rval;
327         foreach (a; stmt.get.execute) {
328             rval = cast(Mutation.Status) a.peek!long(0);
329         }
330         return rval;
331     }
332 
333     Nullable!MutationEntry getMutation(const MutationId id) @trusted {
334         import dextool.plugin.mutate.backend.type;
335 
336         typeof(return) rval;
337 
338         enum get_mut_sql = format("SELECT
339             t0.id,
340             t0.kind,
341             t3.time,
342             t1.offset_begin,
343             t1.offset_end,
344             t1.line,
345             t1.column,
346             t2.path,
347             t2.lang
348             FROM %s t0,%s t1,%s t2,%s t3
349             WHERE
350             t0.id = :id AND
351             t0.mp_id = t1.id AND
352             t1.file_id = t2.id AND
353             t3.id = t0.st_id
354             ", mutationTable, mutationPointTable,
355                     filesTable, mutationStatusTable);
356 
357         auto stmt = db.prepare(get_mut_sql);
358         stmt.get.bind(":id", cast(long) id);
359         auto res = stmt.get.execute;
360 
361         if (res.empty)
362             return rval;
363 
364         auto v = res.front;
365 
366         auto mp = MutationPoint(Offset(v.peek!uint(3), v.peek!uint(4)));
367         mp.mutations = [Mutation(v.peek!long(1).to!(Mutation.Kind))];
368         auto pkey = MutationId(v.peek!long(0));
369         auto file = Path(v.peek!string(7));
370         auto sloc = SourceLoc(v.peek!uint(5), v.peek!uint(6));
371         auto lang = v.peek!long(8).to!Language;
372 
373         rval = MutationEntry(pkey, file, sloc, mp, v.peek!long(2).dur!"msecs", lang);
374 
375         return rval;
376     }
377 
378     MutantMetaData getMutantationMetaData(const MutationId id) @trusted {
379         auto rval = MutantMetaData(id);
380         foreach (res; db.run(select!NomutDataTbl.where("mut_id = :mutid",
381                 Bind("mutid")), cast(long) id)) {
382             rval.set(NoMut(res.tag, res.comment));
383         }
384         return rval;
385     }
386 
387     //TODO: this is a bit inefficient. it should use a callback iterator
388     MutantMetaData[] getMutantationMetaData(const Mutation.Kind[] kinds, const Mutation
389             .Status status) @trusted {
390         const sql = format!"SELECT DISTINCT t.mut_id, t.tag, t.comment
391         FROM %s t, %s t1, %s t2
392         WHERE
393         t.mut_id = t1.id AND
394         t1.st_id = t2.id AND
395         t2.status = :status AND
396         t1.kind IN (%(%s,%))
397         ORDER BY
398         t.mut_id"(nomutDataTable, mutationTable,
399                 mutationStatusTable, kinds.map!(a => cast(long) a));
400         auto stmt = db.prepare(sql);
401         stmt.get.bind(":status", cast(long) status);
402 
403         auto app = appender!(MutantMetaData[])();
404         foreach (res; stmt.get.execute) {
405             app.put(MutantMetaData(MutationId(res.peek!long(0)),
406                     MutantAttr(NoMut(res.peek!string(1), res.peek!string(2)))));
407         }
408         return app.data;
409     }
410 
411     Nullable!Path getPath(const MutationId id) @trusted {
412         enum get_path_sql = format("SELECT t2.path
413             FROM
414             %s t0, %s t1, %s t2
415             WHERE
416             t0.id = :id AND
417             t0.mp_id = t1.id AND
418             t1.file_id = t2.id
419             ", mutationTable, mutationPointTable, filesTable);
420 
421         auto stmt = db.prepare(get_path_sql);
422         stmt.get.bind(":id", cast(long) id);
423         auto res = stmt.get.execute;
424 
425         typeof(return) rval;
426         if (!res.empty)
427             rval = Path(res.front.peek!string(0));
428         return rval;
429     }
430 
431     /// Returns: the mutants that are connected to the mutation statuses.
432     MutantInfo[] getMutantsInfo(const Mutation.Kind[] kinds, const(MutationStatusId)[] id) @trusted {
433         const get_mutid_sql = format("SELECT t0.id,t2.status,t0.kind,t1.line,t1.column
434             FROM %s t0,%s t1, %s t2
435             WHERE
436             t0.st_id IN (%(%s,%)) AND
437             t0.st_id = t2.id AND
438             t0.kind IN (%(%s,%)) AND
439             t0.mp_id = t1.id",
440                 mutationTable, mutationPointTable,
441                 mutationStatusTable, id.map!(a => cast(long) a), kinds.map!(a => cast(int) a));
442         auto stmt = db.prepare(get_mutid_sql);
443 
444         auto app = appender!(MutantInfo[])();
445         foreach (res; stmt.get.execute)
446             app.put(MutantInfo(MutationId(res.peek!long(0)), res.peek!long(1)
447                     .to!(Mutation.Status), res.peek!long(2).to!(Mutation.Kind),
448                     SourceLoc(res.peek!uint(3), res.peek!uint(4))));
449 
450         return app.data;
451     }
452 
453     /// Returns: the mutants that are connected to the mutation statuses.
454     MutationId[] getMutationIds(const(Mutation.Kind)[] kinds, const(MutationStatusId)[] id) @trusted {
455         if (id.length == 0)
456             return null;
457 
458         const get_mutid_sql = format!"SELECT id FROM %s t0
459             WHERE
460             t0.st_id IN (%(%s,%)) AND
461             t0.kind IN (%(%s,%))"(mutationTable,
462                 id.map!(a => cast(long) a), kinds.map!(a => cast(int) a));
463         auto stmt = db.prepare(get_mutid_sql);
464 
465         auto app = appender!(MutationId[])();
466         foreach (res; stmt.get.execute)
467             app.put(MutationId(res.peek!long(0)));
468         return app.data;
469     }
470 
471     Nullable!MutationId getMutationId(const MutationStatusId id) @trusted {
472         immutable sql = format!"SELECT id FROM %s WHERE st_id=:st_id"(mutationTable);
473         auto stmt = db.prepare(sql);
474         stmt.get.bind(":st_id", cast(long) id);
475 
476         typeof(return) rval;
477         foreach (res; stmt.get.execute) {
478             rval = res.peek!long(0).MutationId;
479             break;
480         }
481         return rval;
482     }
483 
484     Nullable!MutationStatusId getMutationStatusId(const MutationId id) @trusted {
485         immutable sql = format!"SELECT st_id FROM %s WHERE id=:id"(mutationTable);
486         auto stmt = db.prepare(sql);
487         stmt.get.bind(":id", cast(long) id);
488 
489         typeof(return) rval;
490         foreach (res; stmt.get.execute) {
491             rval = MutationStatusId(res.peek!long(0));
492         }
493         return rval;
494     }
495 
496     Nullable!MutationStatusId getMutationStatusId(const Checksum cs) @trusted {
497         immutable sql = format!"SELECT id FROM %s WHERE checksum0=:cs0 AND checksum1=:cs1"(
498                 mutationStatusTable);
499         auto stmt = db.prepare(sql);
500         stmt.get.bind(":cs0", cast(long) cs.c0);
501         stmt.get.bind(":cs1", cast(long) cs.c1);
502 
503         typeof(return) rval;
504         foreach (res; stmt.get.execute) {
505             rval = MutationStatusId(res.peek!long(0));
506         }
507         return rval;
508     }
509 
510     // Returns: the status of the mutant
511     Nullable!(Mutation.Status) getMutationStatus(const MutationId id) @trusted {
512         auto s = format!"SELECT status FROM %s WHERE id IN (SELECT st_id FROM %s WHERE id=:mut_id)"(
513                 mutationStatusTable, mutationTable);
514         auto stmt = db.prepare(s);
515         stmt.get.bind(":mut_id", cast(long) id);
516         typeof(return) rval;
517         foreach (res; stmt.get.execute)
518             rval = res.peek!long(0).to!(Mutation.Status);
519         return rval;
520     }
521 
522     /// Returns: the mutants in the file at the line.
523     MutationStatusId[] getMutationsOnLine(const(Mutation.Kind)[] kinds, FileId fid, SourceLoc sloc) @trusted {
524         // TODO: should it also be line_end?
525         const sql = format("SELECT DISTINCT t0.id FROM %s t0, %s t1, %s t2
526                     WHERE
527                     t1.st_id = t0.id AND
528                     t1.kind IN (%(%s,%)) AND
529                     t1.mp_id = t2.id AND
530                     t2.file_id = :fid AND
531                     (:line BETWEEN t2.line AND t2.line_end)
532                     ",
533                 mutationStatusTable, mutationTable, mutationPointTable,
534                 kinds.map!(a => cast(int) a));
535         auto stmt = db.prepare(sql);
536         stmt.get.bind(":fid", cast(long) fid);
537         stmt.get.bind(":line", sloc.line);
538 
539         auto app = appender!(typeof(return))();
540         foreach (res; stmt.get.execute)
541             app.put(MutationStatusId(res.peek!long(0)));
542         return app.data;
543     }
544 
545     LineMetadata getLineMetadata(const FileId fid, const SourceLoc sloc) @trusted {
546         // TODO: change this select to using microrm
547         enum sql = format("SELECT nomut,tag,comment FROM %s
548             WHERE
549             file_id = :fid AND
550             line = :line", rawSrcMetadataTable);
551         auto stmt = db.prepare(sql);
552         stmt.get.bind(":fid", cast(long) fid);
553         stmt.get.bind(":line", sloc.line);
554 
555         auto rval = typeof(return)(fid, sloc.line);
556         foreach (res; stmt.get.execute) {
557             if (res.peek!long(0) != 0)
558                 rval.set(NoMut(res.peek!string(1), res.peek!string(2)));
559         }
560 
561         return rval;
562     }
563 
564     /// Returns: the `nr` mutants that where the longst since they where tested.
565     MutationStatusTime[] getOldestMutants(const(Mutation.Kind)[] kinds, const long nr) @trusted {
566         const sql = format("SELECT t0.id,t0.update_ts FROM %s t0, %s t1
567                     WHERE
568                     t0.update_ts IS NOT NULL AND
569                     t1.st_id = t0.id AND
570                     t1.kind IN (%(%s,%))
571                     ORDER BY t0.update_ts ASC LIMIT :limit",
572                 mutationStatusTable, mutationTable, kinds.map!(a => cast(int) a));
573         auto stmt = db.prepare(sql);
574         stmt.get.bind(":limit", nr);
575 
576         auto app = appender!(MutationStatusTime[])();
577         foreach (res; stmt.get.execute)
578             app.put(MutationStatusTime(MutationStatusId(res.peek!long(0)),
579                     res.peek!string(1).fromSqLiteDateTime));
580         return app.data;
581     }
582 
583     /// Returns: the `nr` mutant with the highest count that has not been killed and existed in the system the longest.
584     MutationStatus[] getHardestToKillMutant(const(Mutation.Kind)[] kinds,
585             const Mutation.Status status, const long nr) @trusted {
586         const sql = format("SELECT t0.id,t0.status,t0.test_cnt,t0.update_ts,t0.added_ts
587             FROM %s t0, %s t1
588             WHERE
589             t0.update_ts IS NOT NULL AND
590             t0.status = :status AND
591             t1.st_id = t0.id AND
592             t1.kind IN (%(%s,%)) AND
593             t1.st_id NOT IN (SELECT st_id FROM %s WHERE nomut != 0)
594             ORDER BY
595             t0.test_cnt DESC,
596             t0.added_ts ASC,
597             t0.update_ts ASC
598             LIMIT :limit",
599                 mutationStatusTable, mutationTable, kinds.map!(a => cast(int) a), srcMetadataTable);
600         auto stmt = db.prepare(sql);
601         stmt.get.bind(":status", cast(long) status);
602         stmt.get.bind(":limit", nr);
603 
604         auto app = appender!(MutationStatus[])();
605         foreach (res; stmt.get.execute) {
606             auto added = () {
607                 auto raw = res.peek!string(4);
608                 if (raw.length == 0)
609                     return Nullable!SysTime();
610                 return Nullable!SysTime(raw.fromSqLiteDateTime);
611             }();
612 
613             // dfmt off
614             app.put(MutationStatus(
615                 MutationStatusId(res.peek!long(0)),
616                 res.peek!long(1).to!(Mutation.Status),
617                 res.peek!long(2).MutantTestCount,
618                 res.peek!string(3).fromSqLiteDateTime,
619                 added,
620             ));
621             // dfmt on
622         }
623 
624         return app.data;
625     }
626 
627     /** Get SourceLoc for a specific mutation id.
628      */
629     Nullable!SourceLoc getSourceLocation(MutationId id) @trusted {
630         auto s = format!"SELECT line, column FROM %s WHERE id IN (SELECT mp_id FROM %s WHERE id=:mut_id)"(
631                 mutationPointTable, mutationTable);
632         auto stmt = db.prepare(s);
633         stmt.get.bind(":mut_id", cast(long) id);
634         typeof(return) rval;
635         foreach (res; stmt.get.execute)
636             rval = SourceLoc(res.peek!uint(0), res.peek!uint(1));
637         return rval;
638     }
639 
640     /** Remove all mutations of kinds.
641      */
642     void removeMutant(const Mutation.Kind[] kinds) @trusted {
643         const s = format!"DELETE FROM %s WHERE id IN (SELECT mp_id FROM %s WHERE kind IN (%(%s,%)))"(
644                 mutationPointTable, mutationTable, kinds.map!(a => cast(int) a));
645         auto stmt = db.prepare(s);
646         stmt.get.execute;
647     }
648 
649     /** Reset all mutations of kinds with the status `st` to unknown.
650      */
651     void resetMutant(const Mutation.Kind[] kinds, Mutation.Status st, Mutation.Status to_st) @trusted {
652         const s = format!"UPDATE %s SET status=%s WHERE status = %s AND id IN(SELECT st_id FROM %s WHERE kind IN (%(%s,%)))"(
653                 mutationStatusTable, to_st.to!long, st.to!long,
654                 mutationTable, kinds.map!(a => cast(int) a));
655         auto stmt = db.prepare(s);
656         stmt.get.execute;
657     }
658 
659     /** Mark a mutant with status and rationale (also adds metadata).
660      */
661     void markMutant(const MutationId id, const Path file, const SourceLoc sloc,
662             const MutationStatusId st_id, const Checksum cs,
663             const Mutation.Status s, const Rationale r, string mutationTxt) @trusted {
664         db.run(insertOrReplace!MarkedMutantTbl, MarkedMutantTbl(cs.c0, cs.c1,
665                 st_id, id, sloc.line, sloc.column, file, s, Clock.currTime.toUTC, r, mutationTxt));
666     }
667 
668     void removeMarkedMutant(const Checksum cs) @trusted {
669         db.run(delete_!MarkedMutantTbl.where("checksum0 = :cs0", Bind("cs0")), cast(long) cs.c0);
670     }
671 
672     void removeMarkedMutant(const MutationStatusId id) @trusted {
673         db.run(delete_!MarkedMutantTbl.where("st_id = :st_id", Bind("st_id")), cast(long) id);
674     }
675 
676     /// Returns: All mutants with that are marked orderd by their path
677     MarkedMutant[] getMarkedMutants() @trusted {
678         import miniorm : OrderingTermSort;
679 
680         auto app = appender!(MarkedMutant[])();
681         foreach (m; db.run(select!MarkedMutantTbl.orderBy(OrderingTermSort.ASC, [
682                     "path"
683                 ]))) {
684             app.put(.make(m));
685         }
686 
687         return app.data;
688     }
689 
690     Mutation.Kind getKind(MutationId id) @trusted {
691         immutable sql = format!"SELECT kind FROM %s WHERE id=:id"(mutationTable);
692         auto stmt = db.prepare(sql);
693         stmt.get.bind(":id", cast(long) id);
694 
695         typeof(return) rval;
696         foreach (res; stmt.get.execute) {
697             rval = res.peek!long(0).to!(Mutation.Kind);
698         }
699         return rval;
700     }
701 
702     import dextool.plugin.mutate.backend.type;
703 
704     alias aliveMutants = countMutants!([Mutation.Status.alive], false);
705     alias killedMutants = countMutants!([Mutation.Status.killed], false);
706     alias timeoutMutants = countMutants!([Mutation.Status.timeout], false);
707 
708     /// Returns: Total that should be counted when calculating the mutation score.
709     alias totalMutants = countMutants!([
710             Mutation.Status.alive, Mutation.Status.killed, Mutation.Status.timeout
711             ], false);
712 
713     alias unknownMutants = countMutants!([Mutation.Status.unknown], false);
714     alias killedByCompilerMutants = countMutants!([
715             Mutation.Status.killedByCompiler
716             ], false);
717 
718     alias aliveSrcMutants = countMutants!([Mutation.Status.alive], true);
719     alias killedSrcMutants = countMutants!([Mutation.Status.killed], true);
720     alias timeoutSrcMutants = countMutants!([Mutation.Status.timeout], true);
721 
722     /// Returns: Total that should be counted when calculating the mutation score.
723     alias totalSrcMutants = countMutants!([
724             Mutation.Status.alive, Mutation.Status.killed, Mutation.Status.timeout
725             ], true);
726 
727     alias unknownSrcMutants = countMutants!([Mutation.Status.unknown], true);
728     alias killedByCompilerSrcMutants = countMutants!([
729             Mutation.Status.killedByCompiler
730             ], true);
731 
732     /** Count the mutants with the nomut metadata.
733      *
734      * Params:
735      *  status = status the mutants must be in to be counted.
736      *  distinc = count based on unique source code changes.
737      *  kinds = the kind of mutants to count.
738      *  file = file to count mutants in.
739      */
740     private MutationReportEntry countMutants(int[] status, bool distinct)(
741             const Mutation.Kind[] kinds, string file = null) @trusted {
742         static if (distinct) {
743             auto qq = "
744                 SELECT count(*),sum(time)
745                 FROM (
746                 SELECT count(*),sum(t1.time) time
747                 FROM %s t0, %s t1%s
748                 WHERE
749                 %s
750                 t0.st_id = t1.id AND
751                 t1.status IN (%(%s,%)) AND
752                 t0.kind IN (%(%s,%))
753                 GROUP BY t1.id)";
754         } else {
755             auto qq = "
756                 SELECT count(*),sum(t1.time) time
757                 FROM %s t0, %s t1%s
758                 WHERE
759                 %s
760                 t0.st_id = t1.id AND
761                 t1.status IN (%(%s,%)) AND
762                 t0.kind IN (%(%s,%))";
763         }
764         const query = () {
765             auto fq = file.length == 0
766                 ? null : "t0.mp_id = t2.id AND t2.file_id = t3.id AND t3.path = :path AND";
767             auto fq_from = file.length == 0 ? null : format(", %s t2, %s t3",
768                     mutationPointTable, filesTable);
769             return format(qq, mutationTable, mutationStatusTable, fq_from, fq,
770                     status, kinds.map!(a => cast(int) a));
771         }();
772 
773         typeof(return) rval;
774         auto stmt = db.prepare(query);
775         if (file.length != 0)
776             stmt.get.bind(":path", file);
777         auto res = stmt.get.execute;
778         if (!res.empty)
779             rval = MutationReportEntry(res.front.peek!long(0),
780                     res.front.peek!long(1).dur!"msecs");
781         return rval;
782     }
783 
784     /** Count the mutants with the nomut metadata.
785      *
786      * Params:
787      *  status = status the mutants must be in to be counted.
788      *  distinc = count based on unique source code changes.
789      *  kinds = the kind of mutants to count.
790      *  file = file to count mutants in.
791      */
792     private MetadataNoMutEntry countNoMutMutants(int[] status, bool distinct)(
793             const Mutation.Kind[] kinds, string file = null) @trusted {
794         static if (distinct) {
795             auto sql_base = "
796                 SELECT count(*)
797                 FROM (
798                 SELECT count(*)
799                 FROM %s t0, %s t1,%s t4%s
800                 WHERE
801                 %s
802                 t0.st_id = t1.id AND
803                 t0.st_id = t4.st_id AND
804                 t4.nomut != 0 AND
805                 t1.status IN (%(%s,%)) AND
806                 t0.kind IN (%(%s,%))
807                 GROUP BY t1.id)";
808         } else {
809             auto sql_base = "
810                 SELECT count(*)
811                 FROM %s t0, %s t1,%s t4%s
812                 WHERE
813                 %s
814                 t0.st_id = t1.id AND
815                 t0.st_id = t4.st_id AND
816                 t4.nomut != 0 AND
817                 t1.status IN (%(%s,%)) AND
818                 t0.kind IN (%(%s,%))";
819         }
820         const query = () {
821             auto fq = file.length == 0
822                 ? null : "t0.mp_id = t2.id AND t2.file_id = t3.id AND t3.path = :path AND";
823             auto fq_from = file.length == 0 ? null : format(", %s t2, %s t3",
824                     mutationPointTable, filesTable);
825             return format(sql_base, mutationTable, mutationStatusTable,
826                     srcMetadataTable, fq_from, fq, status, kinds.map!(a => cast(int) a));
827         }();
828 
829         typeof(return) rval;
830         auto stmt = db.prepare(query);
831         if (file.length != 0)
832             stmt.get.bind(":path", file);
833         auto res = stmt.get.execute;
834         if (!res.empty)
835             rval = MetadataNoMutEntry(res.front.peek!long(0));
836         return rval;
837     }
838 
839     /// ditto.
840     alias aliveNoMutSrcMutants = countNoMutMutants!([Mutation.Status.alive], true);
841 
842     /// Returns: mutants killed by the test case.
843     MutationStatusId[] testCaseKilledSrcMutants(const Mutation.Kind[] kinds, TestCase tc) @trusted {
844         const query = format("
845             SELECT t1.id
846             FROM %s t0, %s t1, %s t2, %s t3
847             WHERE
848             t0.st_id = t1.id AND
849             t1.status = :st AND
850             t0.kind IN (%(%s,%)) AND
851             t2.name = :name AND
852             t2.id = t3.tc_id AND
853             t3.st_id = t1.id
854             GROUP BY t1.id", mutationTable, mutationStatusTable,
855                 allTestCaseTable, killedTestCaseTable, kinds.map!(a => cast(int) a));
856 
857         auto stmt = db.prepare(query);
858         stmt.get.bind(":st", cast(long) Mutation.Status.killed);
859         stmt.get.bind(":name", tc.name);
860 
861         auto app = appender!(MutationStatusId[])();
862         foreach (res; stmt.get.execute)
863             app.put(MutationStatusId(res.peek!long(0)));
864 
865         return app.data;
866     }
867 
868     /// Returns: mutants at mutations points that the test case has killed mutants at.
869     alias testCaseMutationPointAliveSrcMutants = testCaseCountSrcMutants!([
870             Mutation.Status.alive
871             ]);
872     /// ditto
873     alias testCaseMutationPointTimeoutSrcMutants = testCaseCountSrcMutants!(
874             [Mutation.Status.timeout]);
875     /// ditto
876     alias testCaseMutationPointKilledSrcMutants = testCaseCountSrcMutants!([
877             Mutation.Status.killed
878             ]);
879     /// ditto
880     alias testCaseMutationPointUnknownSrcMutants = testCaseCountSrcMutants!(
881             [Mutation.Status.unknown]);
882     /// ditto
883     alias testCaseMutationPointKilledByCompilerSrcMutants = testCaseCountSrcMutants!(
884             [Mutation.Status.killedByCompiler]);
885     /// ditto
886     alias testCaseMutationPointTotalSrcMutants = testCaseCountSrcMutants!(
887             [
888             Mutation.Status.alive, Mutation.Status.killed, Mutation.Status.timeout
889             ]);
890 
891     private MutationStatusId[] testCaseCountSrcMutants(int[] status)(
892             const Mutation.Kind[] kinds, TestCase tc) @trusted {
893         const query = format("
894             SELECT t1.id
895             FROM %s t0, %s t1
896             WHERE
897             t0.mp_id IN (SELECT t1.id
898                       FROM %s t0,%s t1, %s t2, %s t3
899                       WHERE
900                       t0.mp_id = t1.id AND
901                       t2.name = :name AND
902                       t2.id = t3.tc_id AND
903                       t3.st_id = t0.st_id
904                       )
905             AND
906             t0.st_id = t1.id AND
907             t1.status IN (%(%s,%)) AND
908             t0.kind IN (%(%s,%))
909             GROUP BY t1.id", mutationTable, mutationStatusTable, mutationTable, mutationPointTable,
910                 allTestCaseTable, killedTestCaseTable, status, kinds.map!(a => cast(int) a));
911 
912         auto stmt = db.prepare(query);
913         stmt.get.bind(":name", tc.name);
914 
915         auto app = appender!(MutationStatusId[])();
916         foreach (res; stmt.get.execute)
917             app.put(MutationStatusId(res.peek!long(0)));
918 
919         return app.data;
920     }
921 
922     Nullable!Checksum getChecksum(MutationStatusId id) @trusted {
923         immutable sql = format!"SELECT checksum0, checksum1 FROM %s WHERE id=:id"(
924                 mutationStatusTable);
925         auto stmt = db.prepare(sql);
926         stmt.get.bind(":id", cast(long) id);
927 
928         typeof(return) rval;
929         foreach (res; stmt.get.execute) {
930             rval = Checksum(res.peek!long(0), res.peek!long(1));
931             break;
932         }
933         return rval;
934     }
935 
936     Nullable!Checksum getFileChecksum(const Path p) @trusted {
937         immutable sql = format!"SELECT checksum0,checksum1 FROM %s WHERE path=:path"(filesTable);
938         auto stmt = db.prepare(sql);
939         stmt.get.bind(":path", cast(string) p);
940         auto res = stmt.get.execute;
941 
942         typeof(return) rval;
943         if (!res.empty) {
944             rval = Checksum(res.front.peek!long(0), res.front.peek!long(1));
945         }
946 
947         return rval;
948     }
949 
950     void put(const Path p, Checksum cs, const Language lang) @trusted {
951         immutable sql = format!"INSERT OR IGNORE INTO %s (path, checksum0, checksum1, lang)
952             VALUES (:path, :checksum0, :checksum1, :lang)"(
953                 filesTable);
954         auto stmt = db.prepare(sql);
955         stmt.get.bind(":path", cast(string) p);
956         stmt.get.bind(":checksum0", cast(long) cs.c0);
957         stmt.get.bind(":checksum1", cast(long) cs.c1);
958         stmt.get.bind(":lang", cast(long) lang);
959         stmt.get.execute;
960     }
961 
962     /// Remove all metadata.
963     void clearMetadata() {
964         immutable sql = format!"DELETE FROM %s"(rawSrcMetadataTable);
965         db.run(sql);
966     }
967 
968     /** Save line metadata to the database which is used to associate line
969      * metadata with mutants.
970      */
971     void put(const LineMetadata[] mdata) {
972         import sumtype;
973 
974         if (mdata.empty)
975             return;
976 
977         // TODO: convert to microrm
978         enum sql = format!"INSERT OR IGNORE INTO %s
979             (file_id, line, nomut, tag, comment)
980             VALUES(:fid, :line, :nomut, :tag, :comment)"(
981                     rawSrcMetadataTable);
982 
983         auto stmt = db.prepare(sql);
984         foreach (meta; mdata) {
985             auto nomut = meta.attr.match!((NoMetadata a) => NoMut.init, (NoMut a) => a);
986             stmt.get.bindAll(cast(long) meta.id, meta.line, meta.isNoMut,
987                     nomut.tag, nomut.comment);
988             stmt.get.execute;
989             stmt.get.reset;
990         }
991     }
992 
993     /// Store all found mutants.
994     void put(MutationPointEntry2[] mps, AbsolutePath root) @trusted {
995         if (mps.empty)
996             return;
997 
998         enum insert_mp_sql = format("INSERT OR IGNORE INTO %s
999             (file_id, offset_begin, offset_end, line, column, line_end, column_end)
1000             SELECT id,:begin,:end,:line,:column,:line_end,:column_end
1001             FROM %s
1002             WHERE
1003             path = :path", mutationPointTable, filesTable);
1004         auto mp_stmt = db.prepare(insert_mp_sql);
1005 
1006         foreach (mp; mps) {
1007             auto rel_file = relativePath(mp.file, root).Path;
1008             mp_stmt.get.bind(":begin", mp.offset.begin);
1009             mp_stmt.get.bind(":end", mp.offset.end);
1010             mp_stmt.get.bind(":line", mp.sloc.line);
1011             mp_stmt.get.bind(":column", mp.sloc.column);
1012             mp_stmt.get.bind(":line_end", mp.slocEnd.line);
1013             mp_stmt.get.bind(":column_end", mp.slocEnd.column);
1014             mp_stmt.get.bind(":path", cast(string) rel_file);
1015             mp_stmt.get.execute;
1016             mp_stmt.get.reset;
1017         }
1018 
1019         enum insert_cmut_sql = format("INSERT OR IGNORE INTO %s
1020             (status,test_cnt,update_ts,added_ts,checksum0,checksum1)
1021             VALUES(:st,0,:update_ts,:added_ts,:c0,:c1)",
1022                     mutationStatusTable);
1023         auto cmut_stmt = db.prepare(insert_cmut_sql);
1024         const ts = Clock.currTime.toUTC.toSqliteDateTime;
1025         cmut_stmt.get.bind(":st", Mutation.Status.unknown);
1026         cmut_stmt.get.bind(":update_ts", ts);
1027         cmut_stmt.get.bind(":added_ts", ts);
1028         foreach (cm; mps.map!(a => a.cms).joiner) {
1029             cmut_stmt.get.bind(":c0", cast(long) cm.id.c0);
1030             cmut_stmt.get.bind(":c1", cast(long) cm.id.c1);
1031             cmut_stmt.get.execute;
1032             cmut_stmt.get.reset;
1033         }
1034 
1035         enum insert_m_sql = format("INSERT OR IGNORE INTO %s
1036             (mp_id, st_id, kind)
1037             SELECT t0.id,t1.id,:kind FROM %s t0, %s t1, %s t2 WHERE
1038             t2.path = :path AND
1039             t0.file_id = t2.id AND
1040             t0.offset_begin = :off_begin AND
1041             t0.offset_end = :off_end AND
1042             t1.checksum0 = :c0 AND
1043             t1.checksum1 = :c1", mutationTable,
1044                     mutationPointTable, mutationStatusTable, filesTable);
1045         auto insert_m = db.prepare(insert_m_sql);
1046 
1047         foreach (mp; mps) {
1048             foreach (m; mp.cms) {
1049                 auto rel_file = relativePath(mp.file, root).Path;
1050                 insert_m.get.bind(":path", cast(string) rel_file);
1051                 insert_m.get.bind(":off_begin", mp.offset.begin);
1052                 insert_m.get.bind(":off_end", mp.offset.end);
1053                 insert_m.get.bind(":c0", cast(long) m.id.c0);
1054                 insert_m.get.bind(":c1", cast(long) m.id.c1);
1055                 insert_m.get.bind(":kind", m.mut.kind);
1056                 insert_m.get.execute;
1057                 insert_m.get.reset;
1058             }
1059         }
1060     }
1061 
1062     /** Remove all mutants points from the database.
1063      *
1064      * This removes all the mutants because of the cascade delete of the
1065      * tables. But it will keep the mutation statuses and thus the checksums
1066      * and the status of the code changes.
1067      *
1068      * This then mean that when mutations+mutation points are added back they
1069      * may reconnect with a mutation status.
1070      */
1071     void removeAllMutationPoints() @trusted {
1072         enum sql = format!"DELETE FROM %s"(mutationPointTable);
1073         db.run(sql);
1074     }
1075 
1076     /// ditto
1077     void removeAllFiles() @trusted {
1078         enum sql = format!"DELETE FROM %s"(filesTable);
1079         db.run(sql);
1080     }
1081 
1082     /// Remove mutants that have no connection to a mutation point, orphaned mutants.
1083     void removeOrphanedMutants() @trusted {
1084         import std.datetime.stopwatch : StopWatch, AutoStart;
1085 
1086         const removeIds = () {
1087             immutable sql = format!"SELECT id FROM %1$s WHERE id NOT IN (SELECT st_id FROM %2$s)"(
1088                     mutationStatusTable, mutationTable);
1089             auto stmt = db.prepare(sql);
1090             auto removeIds = appender!(long[])();
1091             foreach (res; stmt.get.execute)
1092                 removeIds.put(res.peek!long(0));
1093             return removeIds.data;
1094         }();
1095 
1096         immutable batchNr = 1000;
1097         immutable sql = format!"DELETE FROM %1$s WHERE id=:id"(mutationStatusTable);
1098         auto stmt = db.prepare(sql);
1099         auto sw = StopWatch(AutoStart.yes);
1100         foreach (const i, const id; removeIds) {
1101             stmt.get.bind(":id", id);
1102             stmt.get.execute;
1103             stmt.get.reset;
1104 
1105             // continuously print to inform the user of the progress and avoid
1106             // e.g. timeout on jenkins.
1107             if (i > 0 && i % batchNr == 0) {
1108                 const avg = cast(long)(cast(double) sw.peek.total!"msecs" / cast(double) batchNr);
1109                 const t = dur!"msecs"(avg * (removeIds.length - i));
1110                 logger.infof("%s/%s removed (average %sms) (%s) (%s)", i,
1111                         removeIds.length, avg, t, (Clock.currTime + t).toSimpleString);
1112                 sw.reset;
1113             }
1114         }
1115     }
1116 
1117     /** Add a link between the mutation and what test case killed it.
1118      *
1119      * Params:
1120      *  id = ?
1121      *  tcs = test cases to add
1122      */
1123     void updateMutationTestCases(const MutationId id, const(TestCase)[] tcs) @trusted {
1124         if (tcs.length == 0)
1125             return;
1126 
1127         immutable st_id = () {
1128             enum st_id_for_mutation_q = format!"SELECT st_id FROM %s WHERE id=:id"(mutationTable);
1129             auto stmt = db.prepare(st_id_for_mutation_q);
1130             stmt.get.bind(":id", cast(long) id);
1131             return stmt.get.execute.oneValue!long;
1132         }();
1133         updateMutationTestCases(MutationStatusId(st_id), tcs);
1134     }
1135 
1136     /** Add a link between the mutation and what test case killed it.
1137      *
1138      * Params:
1139      *  id = ?
1140      *  tcs = test cases to add
1141      */
1142     void updateMutationTestCases(const MutationStatusId st_id, const(TestCase)[] tcs) @trusted {
1143         if (tcs.length == 0)
1144             return;
1145 
1146         try {
1147             enum remove_old_sql = format!"DELETE FROM %s WHERE st_id=:id"(killedTestCaseTable);
1148             auto stmt = db.prepare(remove_old_sql);
1149             stmt.get.bind(":id", cast(long) st_id);
1150             stmt.get.execute;
1151         } catch (Exception e) {
1152         }
1153 
1154         enum add_if_non_exist_tc_sql = format!"INSERT INTO %s (name) SELECT :name1 WHERE NOT EXISTS (SELECT * FROM %s WHERE name = :name2)"(
1155                     allTestCaseTable, allTestCaseTable);
1156         auto stmt_insert_tc = db.prepare(add_if_non_exist_tc_sql);
1157 
1158         enum add_new_sql = format!"INSERT INTO %s (st_id, tc_id, location) SELECT :st_id,t1.id,:loc FROM %s t1 WHERE t1.name = :tc"(
1159                     killedTestCaseTable, allTestCaseTable);
1160         auto stmt_insert = db.prepare(add_new_sql);
1161         foreach (const tc; tcs) {
1162             try {
1163                 stmt_insert_tc.get.reset;
1164                 stmt_insert_tc.get.bind(":name1", tc.name);
1165                 stmt_insert_tc.get.bind(":name2", tc.name);
1166                 stmt_insert_tc.get.execute;
1167 
1168                 stmt_insert.get.reset;
1169                 stmt_insert.get.bind(":st_id", cast(long) st_id);
1170                 stmt_insert.get.bind(":loc", tc.location);
1171                 stmt_insert.get.bind(":tc", tc.name);
1172                 stmt_insert.get.execute;
1173             } catch (Exception e) {
1174                 logger.warning(e.msg);
1175             }
1176         }
1177     }
1178 
1179     /** Set detected test cases.
1180      *
1181      * This will replace those that where previously stored.
1182      *
1183      * Returns: ID of affected mutation statuses.
1184      */
1185     MutationStatusId[] setDetectedTestCases(const(TestCase)[] tcs) @trusted {
1186         if (tcs.length == 0)
1187             return null;
1188 
1189         auto mut_status_ids = appender!(MutationStatusId[])();
1190 
1191         immutable tmp_name = "tmp_new_tc_" ~ __LINE__.to!string;
1192         internalAddDetectedTestCases(tcs, tmp_name);
1193 
1194         immutable mut_st_id = format!"SELECT DISTINCT t1.st_id
1195             FROM %s t0, %s t1
1196             WHERE
1197             t0.name NOT IN (SELECT name FROM %s) AND
1198             t0.id = t1.tc_id"(allTestCaseTable,
1199                 killedTestCaseTable, tmp_name);
1200         auto stmt = db.prepare(mut_st_id);
1201         foreach (res; stmt.get.execute)
1202             mut_status_ids.put(res.peek!long(0).MutationStatusId);
1203 
1204         immutable remove_old_sql = format!"DELETE FROM %s WHERE name NOT IN (SELECT name FROM %s)"(
1205                 allTestCaseTable, tmp_name);
1206         db.run(remove_old_sql);
1207 
1208         db.run(format!"DROP TABLE %s"(tmp_name));
1209 
1210         return mut_status_ids.data;
1211     }
1212 
1213     /** Add test cases to those that have been detected.
1214      *
1215      * They will be added if they are unique.
1216      */
1217     void addDetectedTestCases(const(TestCase)[] tcs) @trusted {
1218         if (tcs.length == 0)
1219             return;
1220 
1221         immutable tmp_name = "tmp_new_tc_" ~ __LINE__.to!string;
1222         internalAddDetectedTestCases(tcs, tmp_name);
1223         db.run(format!"DROP TABLE %s"(tmp_name));
1224     }
1225 
1226     /// ditto.
1227     private void internalAddDetectedTestCases(const(TestCase)[] tcs, string tmp_tbl) @trusted {
1228         db.run(format!"CREATE TEMP TABLE %s (id INTEGER PRIMARY KEY, name TEXT NOT NULL)"(
1229                 tmp_tbl));
1230 
1231         immutable add_tc_sql = format!"INSERT INTO %s (name) VALUES(:name)"(tmp_tbl);
1232         auto insert_s = db.prepare(add_tc_sql);
1233         foreach (tc; tcs) {
1234             insert_s.get.bind(":name", tc.name);
1235             insert_s.get.execute;
1236             insert_s.get.reset;
1237         }
1238 
1239         // https://stackoverflow.com/questions/2686254/how-to-select-all-records-from-one-table-that-do-not-exist-in-another-table
1240         //Q: What is happening here?
1241         //
1242         //A: Conceptually, we select all rows from table1 and for each row we
1243         //attempt to find a row in table2 with the same value for the name
1244         //column.  If there is no such row, we just leave the table2 portion of
1245         //our result empty for that row. Then we constrain our selection by
1246         //picking only those rows in the result where the matching row does not
1247         //exist. Finally, We ignore all fields from our result except for the
1248         //name column (the one we are sure that exists, from table1).
1249         //
1250         //While it may not be the most performant method possible in all cases,
1251         //it should work in basically every database engine ever that attempts
1252         //to implement ANSI 92 SQL
1253         immutable add_missing_sql = format!"INSERT INTO %s (name) SELECT t1.name FROM %s t1 LEFT JOIN %s t2 ON t2.name = t1.name WHERE t2.name IS NULL"(
1254                 allTestCaseTable, tmp_tbl, allTestCaseTable);
1255         db.run(add_missing_sql);
1256     }
1257 
1258     /// Returns: detected test cases.
1259     TestCase[] getDetectedTestCases() @trusted {
1260         auto rval = appender!(TestCase[])();
1261         db.run(select!AllTestCaseTbl).map!(a => TestCase(a.name)).copy(rval);
1262         return rval.data;
1263     }
1264 
1265     /// Returns: detected test cases.
1266     TestCaseId[] getDetectedTestCaseIds() @trusted {
1267         auto rval = appender!(TestCaseId[])();
1268         db.run(select!AllTestCaseTbl).map!(a => TestCaseId(a.id)).copy(rval);
1269         return rval.data;
1270     }
1271 
1272     /// Returns: test cases that has killed zero mutants.
1273     TestCase[] getTestCasesWithZeroKills() @trusted {
1274         enum sql = format("SELECT t1.name FROM %s t1 WHERE t1.id NOT IN (SELECT tc_id FROM %s)",
1275                     allTestCaseTable, killedTestCaseTable);
1276 
1277         auto rval = appender!(TestCase[])();
1278         auto stmt = db.prepare(sql);
1279         foreach (a; stmt.get.execute)
1280             rval.put(TestCase(a.peek!string(0)));
1281 
1282         return rval.data;
1283     }
1284 
1285     /** Guarantees that the there are no duplications of `TestCaseId`.
1286      *
1287      * Returns: test cases that has killed at least one mutant.
1288      */
1289     TestCaseId[] getTestCasesWithAtLeastOneKill(const Mutation.Kind[] kinds) @trusted {
1290         immutable sql = format!"SELECT DISTINCT t1.id
1291             FROM %s t1, %s t2, %s t3
1292             WHERE
1293             t1.id = t2.tc_id AND
1294             t2.st_id == t3.st_id AND
1295             t3.kind IN (%(%s,%))"(allTestCaseTable,
1296                 killedTestCaseTable, mutationTable, kinds.map!(a => cast(int) a));
1297 
1298         auto rval = appender!(TestCaseId[])();
1299         auto stmt = db.prepare(sql);
1300         foreach (a; stmt.get.execute)
1301             rval.put(TestCaseId(a.peek!long(0)));
1302 
1303         return rval.data;
1304     }
1305 
1306     /// Returns: the name of the test case.
1307     string getTestCaseName(const TestCaseId id) @trusted {
1308         enum sql = format!"SELECT name FROM %s WHERE id = :id"(allTestCaseTable);
1309         auto stmt = db.prepare(sql);
1310         stmt.get.bind(":id", cast(long) id);
1311         auto res = stmt.get.execute;
1312         return res.oneValue!string;
1313     }
1314 
1315     /// Returns: stats about the test case.
1316     Nullable!TestCaseInfo getTestCaseInfo(const TestCase tc, const Mutation.Kind[] kinds) @trusted {
1317         const sql = format("SELECT sum(t2.time),count(t1.st_id)
1318             FROM %s t0, %s t1, %s t2, %s t3
1319             WHERE
1320             t0.name = :name AND
1321             t0.id = t1.tc_id AND
1322             t1.st_id = t2.id AND
1323             t2.id = t3.st_id AND
1324             t3.kind IN (%(%s,%))", allTestCaseTable,
1325                 killedTestCaseTable, mutationStatusTable, mutationTable,
1326                 kinds.map!(a => cast(int) a));
1327         auto stmt = db.prepare(sql);
1328         stmt.get.bind(":name", tc.name);
1329 
1330         typeof(return) rval;
1331         foreach (a; stmt.get.execute)
1332             rval = TestCaseInfo(a.peek!long(0).dur!"msecs", a.peek!long(1));
1333         return rval;
1334     }
1335 
1336     /// Returns: all test cases for the file and the mutants they killed.
1337     TestCaseInfo2[] getAllTestCaseInfo2(const FileId file, const Mutation.Kind[] kinds) @trusted {
1338         // row of test case name and mutation id.
1339         const sql = format("SELECT t0.name,t3.id
1340             FROM %s t0, %s t1, %s t2, %s t3, %s t4
1341             WHERE
1342             t0.id = t1.tc_id AND
1343             t1.st_id = t2.id AND
1344             t2.id = t3.st_id AND
1345             t4.id = :file_id AND
1346             t3.kind IN (%(%s,%))", allTestCaseTable, killedTestCaseTable,
1347                 mutationStatusTable, mutationTable, filesTable, kinds.map!(a => cast(int) a));
1348         auto stmt = db.prepare(sql);
1349         stmt.get.bind(":file_id", cast(long) file);
1350 
1351         MutationId[][string] data;
1352         foreach (row; stmt.get.execute) {
1353             const name = row.peek!string(0);
1354             if (auto v = name in data) {
1355                 *v ~= MutationId(row.peek!long(1));
1356             } else {
1357                 data[name] = [MutationId(row.peek!long(1))];
1358             }
1359         }
1360 
1361         auto app = appender!(TestCaseInfo2[])();
1362         data.byKeyValue.map!(a => TestCaseInfo2(TestCase(a.key), a.value)).copy(app);
1363         return app.data;
1364     }
1365 
1366     /// Returns: the test case.
1367     Nullable!TestCase getTestCase(const TestCaseId id) @trusted {
1368         enum sql = format!"SELECT name FROM %s WHERE id = :id"(allTestCaseTable);
1369         auto stmt = db.prepare(sql);
1370         stmt.get.bind(":id", cast(long) id);
1371 
1372         typeof(return) rval;
1373         foreach (res; stmt.get.execute) {
1374             rval = TestCase(res.peek!string(0));
1375         }
1376         return rval;
1377     }
1378 
1379     /// Returns: the test case id.
1380     Nullable!TestCaseId getTestCaseId(const TestCase tc) @trusted {
1381         enum sql = format!"SELECT id FROM %s WHERE name = :name"(allTestCaseTable);
1382         auto stmt = db.prepare(sql);
1383         stmt.get.bind(":name", tc.name);
1384 
1385         typeof(return) rval;
1386         foreach (res; stmt.get.execute) {
1387             rval = TestCaseId(res.peek!long(0));
1388         }
1389         return rval;
1390     }
1391 
1392     /// The mutation ids are guaranteed to be sorted.
1393     /// Returns: the mutants the test case killed.
1394     MutationId[] getTestCaseMutantKills(const TestCaseId id, const Mutation.Kind[] kinds) @trusted {
1395         immutable sql = format!"SELECT t2.id
1396             FROM %s t1, %s t2
1397             WHERE
1398             t1.tc_id = :tid AND
1399             t1.st_id = t2.st_id AND
1400             t2.kind IN (%(%s,%))
1401             ORDER BY
1402             t2.id"(killedTestCaseTable,
1403                 mutationTable, kinds.map!(a => cast(int) a));
1404 
1405         auto rval = appender!(MutationId[])();
1406         auto stmt = db.prepare(sql);
1407         stmt.get.bind(":tid", cast(long) id);
1408         foreach (a; stmt.get.execute)
1409             rval.put(MutationId(a.peek!long(0)));
1410 
1411         return rval.data;
1412     }
1413 
1414     /// Returns: test cases that killed the mutant.
1415     TestCase[] getTestCases(const MutationId id) @trusted {
1416         Appender!(TestCase[]) rval;
1417 
1418         enum get_test_cases_sql = format!"SELECT t1.name,t2.location
1419             FROM %s t1, %s t2, %s t3
1420             WHERE
1421             t3.id = :id AND
1422             t3.st_id = t2.st_id AND
1423             t2.tc_id = t1.id"(allTestCaseTable,
1424                     killedTestCaseTable, mutationTable);
1425         auto stmt = db.prepare(get_test_cases_sql);
1426         stmt.get.bind(":id", cast(long) id);
1427         foreach (a; stmt.get.execute)
1428             rval.put(TestCase(a.peek!string(0), a.peek!string(1)));
1429 
1430         return rval.data;
1431     }
1432 
1433     /** Returns: number of test cases
1434      */
1435     long getNumOfTestCases() @trusted {
1436         enum num_test_cases_sql = format!"SELECT count(*) FROM %s"(allTestCaseTable);
1437         return db.execute(num_test_cases_sql).oneValue!long;
1438     }
1439 
1440     /** Returns: test cases that killed other mutants at the same mutation point as `id`.
1441       */
1442     TestCase[] getSurroundingTestCases(const MutationId id) @trusted {
1443         Appender!(TestCase[]) rval;
1444 
1445         // TODO: optimize this. should be able to merge the two first queries to one.
1446 
1447         // get the mutation point ID that id reside at
1448         long mp_id;
1449         {
1450             auto stmt = db.prepare(format!"SELECT mp_id FROM %s WHERE id=:id"(mutationTable));
1451             stmt.get.bind(":id", cast(long) id);
1452             auto res = stmt.get.execute;
1453             if (res.empty)
1454                 return null;
1455             mp_id = res.oneValue!long;
1456         }
1457 
1458         // get all the mutation status ids at the mutation point
1459         long[] mut_st_ids;
1460         {
1461             auto stmt = db.prepare(format!"SELECT st_id FROM %s WHERE mp_id=:id"(mutationTable));
1462             stmt.get.bind(":id", mp_id);
1463             auto res = stmt.get.execute;
1464             if (res.empty)
1465                 return null;
1466             mut_st_ids = res.map!(a => a.peek!long(0)).array;
1467         }
1468 
1469         // get all the test cases that are killed at the mutation point
1470         immutable get_test_cases_sql = format!"SELECT t2.name,t1.location FROM %s t1,%s t2 WHERE t1.tc_id == t2.id AND t0.st_id IN (%(%s,%))"(
1471                 killedTestCaseTable, allTestCaseTable, mut_st_ids);
1472         auto stmt = db.prepare(get_test_cases_sql);
1473         foreach (a; stmt.get.execute) {
1474             rval.put(TestCase(a.peek!string(0), a.peek!string(1)));
1475         }
1476 
1477         return rval.data;
1478     }
1479 
1480     void removeTestCase(const TestCaseId id) @trusted {
1481         auto stmt = db.prepare(format!"DELETE FROM %s WHERE id=:id"(allTestCaseTable));
1482         stmt.get.bind(":id", cast(long) id);
1483         stmt.get.execute;
1484     }
1485 
1486     /// Change the status of all mutants that the test case has killed to unknown.
1487     void resetTestCaseId(const TestCaseId id) @trusted {
1488         {
1489             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)"(
1490                     mutationStatusTable, killedTestCaseTable);
1491             auto stmt = db.prepare(sql);
1492             stmt.get.bind(":id", cast(long) id);
1493             stmt.get.execute;
1494         }
1495         {
1496             immutable sql = format!"DELETE FROM %1$s WHERE tc_id = :id"(killedTestCaseTable);
1497             auto stmt = db.prepare(sql);
1498             stmt.get.bind(":id", cast(long) id);
1499             stmt.get.execute;
1500         }
1501     }
1502 
1503     /// Returns: the context for the timeout algorithm.
1504     MutantTimeoutCtx getMutantTimeoutCtx() @trusted {
1505         foreach (res; db.run(select!MutantTimeoutCtx))
1506             return res;
1507         return MutantTimeoutCtx.init;
1508     }
1509 
1510     void putMutantTimeoutCtx(const MutantTimeoutCtx ctx) @trusted {
1511         db.run(delete_!MutantTimeoutCtx);
1512         db.run(insert!MutantTimeoutCtx.insert, ctx);
1513     }
1514 
1515     void putMutantInTimeoutWorklist(const MutationStatusId id) @trusted {
1516         const sql = format!"INSERT OR IGNORE INTO %s (id) VALUES (:id)"(mutantTimeoutWorklistTable);
1517         auto stmt = db.prepare(sql);
1518         stmt.get.bind(":id", cast(long) id);
1519         stmt.get.execute;
1520     }
1521 
1522     /** Remove all mutants that are in the worklist that do NOT have the
1523      * mutation status timeout.
1524      */
1525     void reduceMutantTimeoutWorklist() @trusted {
1526         immutable sql = format!"DELETE FROM %1$s
1527             WHERE
1528             id IN (SELECT id FROM %2$s WHERE status != :status)"(
1529                 mutantTimeoutWorklistTable, mutationStatusTable);
1530         auto stmt = db.prepare(sql);
1531         stmt.get.bind(":status", cast(ubyte) Mutation.Status.timeout);
1532         stmt.get.execute;
1533     }
1534 
1535     /// Remove all mutants from the worklist.
1536     void clearMutantTimeoutWorklist() @trusted {
1537         immutable sql = format!"DELETE FROM %1$s"(mutantTimeoutWorklistTable);
1538         db.run(sql);
1539     }
1540 
1541     /// Returns: the number of mutants in the worklist.
1542     long countMutantTimeoutWorklist() @trusted {
1543         immutable sql = format!"SELECT count(*) FROM %1$s"(mutantTimeoutWorklistTable);
1544         auto stmt = db.prepare(sql);
1545         auto res = stmt.get.execute();
1546         return res.oneValue!long;
1547     }
1548 
1549     /// Changes the status of mutants in the timeout worklist to unknown.
1550     void resetMutantTimeoutWorklist() @trusted {
1551         immutable sql = format!"UPDATE %1$s SET status=:st WHERE id IN (SELECT id FROM %2$s)"(
1552                 mutationStatusTable, mutantTimeoutWorklistTable);
1553         auto stmt = db.prepare(sql);
1554         stmt.get.bind(":st", cast(ubyte) Mutation.Status.unknown);
1555         stmt.get.execute;
1556     }
1557 
1558     /** Update the content of metadata tables with what has been added to the
1559      * raw table data.
1560      */
1561     void updateMetadata() @trusted {
1562         db.run(format!"DELETE FROM %s"(srcMetadataTable));
1563         db.run(format!"DELETE FROM %s"(nomutTable));
1564         db.run(format!"DELETE FROM %s"(nomutDataTable));
1565 
1566         immutable nomut_tbl = "INSERT INTO %s
1567             SELECT
1568                 t0.id mp_id,
1569                 t1.line line,
1570                 count(*) status
1571                 FROM %s t0, %s t1
1572                 WHERE
1573                 t0.file_id = t1.file_id AND
1574                 (t1.line BETWEEN t0.line AND t0.line_end)
1575                 GROUP BY
1576                 t0.id";
1577         db.run(format!nomut_tbl(nomutTable, mutationPointTable, rawSrcMetadataTable));
1578 
1579         immutable src_metadata_sql = "INSERT INTO %s
1580             SELECT DISTINCT
1581             t0.id AS mut_id,
1582             t1.id AS st_id,
1583             t2.id AS mp_id,
1584             t3.id AS file_id,
1585             (SELECT count(*) FROM %s WHERE nomut.mp_id = t2.id) as nomut
1586             FROM %s t0, %s t1, %s t2, %s t3
1587             WHERE
1588             t0.mp_id = t2.id AND
1589             t0.st_id = t1.id AND
1590             t2.file_id = t3.id";
1591         db.run(format!src_metadata_sql(srcMetadataTable, nomutTable,
1592                 mutationTable, mutationStatusTable, mutationPointTable, filesTable));
1593 
1594         immutable nomut_data_tbl = "INSERT INTO %s
1595             SELECT
1596                 t0.id as mut_id,
1597                 t0.mp_id as mp_id,
1598                 t1.line as line,
1599                 t1.tag as tag,
1600                 t1.comment as comment
1601                 FROM %s t0, %s t1, %s t2
1602                 WHERE
1603                 t0.mp_id = t2.mp_id AND
1604                 t1.line = t2.line";
1605         db.run(format!nomut_data_tbl(nomutDataTable, mutationTable,
1606                 rawSrcMetadataTable, nomutTable));
1607     }
1608 
1609     /// Returns: all schematas that excluding those that are known to not be possible to compile.
1610     SchemataId[] getSchematas() @trusted {
1611         immutable sql = format!"SELECT t0.id
1612             FROM %1$s t0
1613             WHERE
1614             t0.id NOT IN (SELECT id FROM %2$s)"(schemataTable,
1615                 invalidSchemataTable);
1616         auto stmt = db.prepare(sql);
1617         auto app = appender!(SchemataId[])();
1618         foreach (a; stmt.get.execute) {
1619             app.put(SchemataId(a.peek!long(0)));
1620         }
1621         return app.data;
1622     }
1623 
1624     Nullable!Schemata getSchemata(SchemataId id) @trusted {
1625         immutable sql = format!"SELECT
1626             t1.path, t0.text, t0.offset_begin, t0.offset_end
1627             FROM %1$s t0, %2$s t1
1628             WHERE
1629             t0.schem_id = :id AND
1630             t0.file_id = t1.id
1631             ORDER BY t0.order_ ASC
1632             "(schemataFragmentTable, filesTable);
1633 
1634         typeof(return) rval;
1635         auto stmt = db.prepare(sql);
1636         stmt.get.bind(":id", cast(long) id);
1637 
1638         auto app = appender!(SchemataFragment[])();
1639         foreach (a; stmt.get.execute) {
1640             app.put(SchemataFragment(a.peek!string(0).Path,
1641                     Offset(a.peek!uint(2), a.peek!uint(3)), a.peek!(ubyte[])(1)));
1642         }
1643 
1644         if (!app.data.empty) {
1645             rval = Schemata(SchemataId(id), app.data);
1646         }
1647 
1648         return rval;
1649     }
1650 
1651     /// Returns: number of mutants in a schemata with the specified kind and status.
1652     long schemataMutantsWithStatus(const SchemataId id,
1653             const Mutation.Kind[] kinds, const Mutation.Status status) @trusted {
1654         const sql = format!"SELECT count(*)
1655         FROM %s t1, %s t2, %s t3
1656         WHERE
1657         t1.schem_id = :id AND
1658         t1.st_id = t2.id AND
1659         t2.status = :status AND
1660         t3.st_id = t1.st_id AND
1661         t3.kind IN (%(%s,%))
1662         "(schemataMutantTable, mutationStatusTable,
1663                 mutationTable, kinds.map!(a => cast(int) a));
1664 
1665         auto stmt = db.prepare(sql);
1666         stmt.get.bind(":id", cast(long) id);
1667         stmt.get.bind(":status", cast(long) status);
1668         return stmt.get.execute.oneValue!long;
1669     }
1670 
1671     MutationStatusId[] getSchemataMutants(const SchemataId id,
1672             const Mutation.Kind[] kinds, const Mutation.Status status) @trusted {
1673         immutable sql = format!"SELECT t1.st_id
1674             FROM %s t1, %s t2, %s t3
1675             WHERE
1676             t1.schem_id = :id AND
1677             t1.st_id = t2.id AND
1678             t2.status = :status AND
1679             t3.st_id = t1.st_id AND
1680             t3.kind IN (%(%s,%))
1681             "(schemataMutantTable,
1682                 mutationStatusTable, mutationTable, kinds.map!(a => cast(int) a));
1683         auto stmt = db.prepare(sql);
1684         stmt.get.bind(":id", cast(long) id);
1685         stmt.get.bind(":status", cast(long) status);
1686 
1687         auto app = appender!(MutationStatusId[])();
1688         foreach (a; stmt.get.execute) {
1689             app.put(a.peek!long(0).MutationStatusId);
1690         }
1691 
1692         return app.data;
1693     }
1694 
1695     /// Returns: the kind of mutants a schemata contains.
1696     Mutation.Kind[] getSchemataKinds(const SchemataId id) @trusted {
1697         immutable sql = format!"SELECT DISTINCT t1.kind
1698             FROM %1$s t0, %2$s t1
1699             WHERE
1700             t0.schem_id = :id AND
1701             t0.st_id = t1.st_id
1702             "(schemataMutantTable, mutationTable);
1703         auto stmt = db.prepare(sql);
1704         stmt.get.bind(":id", cast(long) id);
1705 
1706         auto app = appender!(Mutation.Kind[])();
1707         foreach (a; stmt.get.execute) {
1708             app.put(a.peek!long(0).to!(Mutation.Kind));
1709         }
1710 
1711         return app.data;
1712     }
1713 
1714     /// Mark a schemata as invalid.
1715     void markInvalid(const SchemataId id) @trusted {
1716         immutable sql = format!"INSERT OR IGNORE INTO %1$s VALUES(:id)"(invalidSchemataTable);
1717         auto stmt = db.prepare(sql);
1718         stmt.get.bind(":id", cast(long) id);
1719         stmt.get.execute;
1720     }
1721 
1722     /// Create a schemata from a bundle of fragments.
1723     Nullable!SchemataId putSchemata(SchemataChecksum cs,
1724             const SchemataFragment[] fragments, MutationStatusId[] mutants) @trusted {
1725         import std.range : enumerate;
1726         import dextool.utility : dextoolBinaryId;
1727 
1728         const schemId = cast(long) cs.value.c0;
1729 
1730         const exists = () {
1731             immutable sql = format!"SELECT count(*) FROM %1$s WHERE id=:id"(schemataTable);
1732             auto stmt = db.prepare(sql);
1733             stmt.get.bind(":id", schemId);
1734             return stmt.get.execute.oneValue!long != 0;
1735 
1736         }();
1737 
1738         if (exists)
1739             return typeof(return)();
1740 
1741         () {
1742             immutable sql = format!"INSERT INTO %1$s VALUES(:id, :nr, :version)"(schemataTable);
1743             auto stmt = db.prepare(sql);
1744             stmt.get.bind(":id", cast(long) cs.value.c0);
1745             stmt.get.bind(":nr", cast(long) fragments.length);
1746             stmt.get.bind(":version", dextoolBinaryId);
1747             stmt.get.execute;
1748         }();
1749 
1750         foreach (f; fragments.enumerate) {
1751             const fileId = getFileId(f.value.file);
1752             if (fileId.isNull) {
1753                 logger.warning("Unable to add schemata fragment for file %s because it doesn't exist",
1754                         f.value.file);
1755                 continue;
1756             }
1757 
1758             db.run(insert!SchemataFragmentTable, SchemataFragmentTable(0,
1759                     schemId, cast(long) fileId.get, f.index, f.value.text,
1760                     f.value.offset.begin, f.value.offset.end));
1761         }
1762 
1763         // relate mutants to this schemata.
1764         db.run(insertOrReplace!SchemataMutantTable,
1765                 mutants.map!(a => SchemataMutantTable(cast(long) a, schemId)));
1766 
1767         return typeof(return)(schemId.SchemataId);
1768     }
1769 
1770     void pruneSchemas() @trusted {
1771         import dextool.utility : dextoolBinaryId;
1772 
1773         auto remove = () {
1774             auto remove = appender!(long[])();
1775 
1776             immutable sqlVersion = format!"SELECT t0.id
1777             FROM %1$s t0
1778             WHERE t0.version != %2$s
1779             "(schemataTable, dextoolBinaryId);
1780             auto stmt = db.prepare(sqlVersion);
1781 
1782             foreach (a; stmt.get.execute) {
1783                 remove.put(a.peek!long(0));
1784             }
1785 
1786             immutable sqlFragment = format!"SELECT t0.id
1787             FROM
1788             %1$s t0,
1789             (SELECT schem_id id,count(*) fragments FROM %2$s GROUP BY schem_id) t1
1790             WHERE
1791             t0.id = t1.id AND
1792             t0.fragments != t1.fragments
1793             "(schemataTable, schemataFragmentTable);
1794             stmt = db.prepare(sqlFragment);
1795 
1796             foreach (a; stmt.get.execute) {
1797                 remove.put(a.peek!long(0));
1798             }
1799             return remove.data;
1800         }();
1801 
1802         immutable sql = format!"DELETE FROM %1$s WHERE id=:id"(schemataTable);
1803         auto stmt = db.prepare(sql);
1804         foreach (a; remove) {
1805             stmt.get.bind(":id", a);
1806             stmt.get.execute;
1807             stmt.get.reset;
1808         }
1809     }
1810 }
1811 
1812 private:
1813 
1814 MarkedMutant make(MarkedMutantTbl m) {
1815     import dextool.plugin.mutate.backend.type;
1816 
1817     return MarkedMutant(m.mutationStatusId.MutationStatusId, Checksum(m.checksum0,
1818             m.checksum1), m.mutationId.MutationId, SourceLoc(m.line, m.column),
1819             m.path.Path, m.toStatus.to!(Mutation.Status), m.time,
1820             m.rationale.Rationale, m.mutText);
1821 }