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;
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_;
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 st_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 s = format!"SELECT kind FROM %s WHERE id=:id"(mutationTable);
692         auto stmt = db.prepare(s);
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         // TODO: convert to microrm
975         enum sql = format!"INSERT OR IGNORE INTO %s
976             (file_id, line, nomut, tag, comment)
977             VALUES(:fid, :line, :nomut, :tag, :comment)"(
978                     rawSrcMetadataTable);
979 
980         auto stmt = db.prepare(sql);
981         foreach (meta; mdata) {
982             auto nomut = meta.attr.match!((NoMetadata a) => NoMut.init, (NoMut a) => a);
983             stmt.get.bindAll(cast(long) meta.id, meta.line, meta.isNoMut,
984                     nomut.tag, nomut.comment);
985             stmt.get.execute;
986             stmt.get.reset;
987         }
988     }
989 
990     /// Store all found mutants.
991     void put(MutationPointEntry2[] mps, AbsolutePath rel_dir) @trusted {
992         enum insert_mp_sql = format("INSERT OR IGNORE INTO %s
993             (file_id, offset_begin, offset_end, line, column, line_end, column_end)
994             SELECT id,:begin,:end,:line,:column,:line_end,:column_end
995             FROM %s
996             WHERE
997             path = :path", mutationPointTable, filesTable);
998         auto mp_stmt = db.prepare(insert_mp_sql);
999 
1000         foreach (mp; mps) {
1001             auto rel_file = relativePath(mp.file, rel_dir).Path;
1002             mp_stmt.get.bind(":begin", mp.offset.begin);
1003             mp_stmt.get.bind(":end", mp.offset.end);
1004             mp_stmt.get.bind(":line", mp.sloc.line);
1005             mp_stmt.get.bind(":column", mp.sloc.column);
1006             mp_stmt.get.bind(":line_end", mp.slocEnd.line);
1007             mp_stmt.get.bind(":column_end", mp.slocEnd.column);
1008             mp_stmt.get.bind(":path", cast(string) rel_file);
1009             mp_stmt.get.execute;
1010             mp_stmt.get.reset;
1011         }
1012 
1013         enum insert_cmut_sql = format("INSERT OR IGNORE INTO %s
1014             (status,test_cnt,update_ts,added_ts,checksum0,checksum1)
1015             VALUES(:st,0,:update_ts,:added_ts,:c0,:c1)",
1016                     mutationStatusTable);
1017         auto cmut_stmt = db.prepare(insert_cmut_sql);
1018         const ts = Clock.currTime.toUTC.toSqliteDateTime;
1019         cmut_stmt.get.bind(":st", Mutation.Status.unknown);
1020         cmut_stmt.get.bind(":update_ts", ts);
1021         cmut_stmt.get.bind(":added_ts", ts);
1022         foreach (cm; mps.map!(a => a.cms).joiner) {
1023             cmut_stmt.get.bind(":c0", cast(long) cm.id.c0);
1024             cmut_stmt.get.bind(":c1", cast(long) cm.id.c1);
1025             cmut_stmt.get.execute;
1026             cmut_stmt.get.reset;
1027         }
1028 
1029         enum insert_m_sql = format("INSERT OR IGNORE INTO %s
1030             (mp_id, st_id, kind)
1031             SELECT t0.id,t1.id,:kind FROM %s t0, %s t1, %s t2 WHERE
1032             t2.path = :path AND
1033             t0.file_id = t2.id AND
1034             t0.offset_begin = :off_begin AND
1035             t0.offset_end = :off_end AND
1036             t1.checksum0 = :c0 AND
1037             t1.checksum1 = :c1", mutationTable,
1038                     mutationPointTable, mutationStatusTable, filesTable);
1039         auto insert_m = db.prepare(insert_m_sql);
1040 
1041         foreach (mp; mps) {
1042             foreach (m; mp.cms) {
1043                 auto rel_file = relativePath(mp.file, rel_dir).Path;
1044                 insert_m.get.bind(":path", cast(string) rel_file);
1045                 insert_m.get.bind(":off_begin", mp.offset.begin);
1046                 insert_m.get.bind(":off_end", mp.offset.end);
1047                 insert_m.get.bind(":c0", cast(long) m.id.c0);
1048                 insert_m.get.bind(":c1", cast(long) m.id.c1);
1049                 insert_m.get.bind(":kind", m.mut.kind);
1050                 insert_m.get.execute;
1051                 insert_m.get.reset;
1052             }
1053         }
1054     }
1055 
1056     /** Remove all mutants points from the database.
1057      *
1058      * This removes all the mutants because of the cascade delete of the
1059      * tables. But it will keep the mutation statuses and thus the checksums
1060      * and the status of the code changes.
1061      *
1062      * This then mean that when mutations+mutation points are added back they
1063      * may reconnect with a mutation status.
1064      */
1065     void removeAllMutationPoints() @trusted {
1066         enum sql = format!"DELETE FROM %s"(mutationPointTable);
1067         db.run(sql);
1068     }
1069 
1070     /// ditto
1071     void removeAllFiles() @trusted {
1072         enum sql = format!"DELETE FROM %s"(filesTable);
1073         db.run(sql);
1074     }
1075 
1076     /// Remove mutants that have no connection to a mutation point, orphened mutants.
1077     void removeOrphanedMutants() @trusted {
1078         enum sql = format!"DELETE FROM %s WHERE id NOT IN (SELECT st_id FROM %s)"(
1079                     mutationStatusTable, mutationTable);
1080         db.run(sql);
1081     }
1082 
1083     /** Add a link between the mutation and what test case killed it.
1084      *
1085      * Params:
1086      *  id = ?
1087      *  tcs = test cases to add
1088      */
1089     void updateMutationTestCases(const MutationId id, const(TestCase)[] tcs) @trusted {
1090         if (tcs.length == 0)
1091             return;
1092 
1093         immutable st_id = () {
1094             enum st_id_for_mutation_q = format("SELECT st_id FROM %s WHERE id=:id", mutationTable);
1095             auto stmt = db.prepare(st_id_for_mutation_q);
1096             stmt.get.bind(":id", cast(long) id);
1097             return stmt.get.execute.oneValue!long;
1098         }();
1099         updateMutationTestCases(MutationStatusId(st_id), tcs);
1100     }
1101 
1102     /** Add a link between the mutation and what test case killed it.
1103      *
1104      * Params:
1105      *  id = ?
1106      *  tcs = test cases to add
1107      */
1108     void updateMutationTestCases(const MutationStatusId st_id, const(TestCase)[] tcs) @trusted {
1109         if (tcs.length == 0)
1110             return;
1111 
1112         try {
1113             enum remove_old_sql = format("DELETE FROM %s WHERE st_id=:id", killedTestCaseTable);
1114             auto stmt = db.prepare(remove_old_sql);
1115             stmt.get.bind(":id", cast(long) st_id);
1116             stmt.get.execute;
1117         } catch (Exception e) {
1118         }
1119 
1120         enum add_if_non_exist_tc_sql = format(
1121                     "INSERT INTO %s (name) SELECT :name1 WHERE NOT EXISTS (SELECT * FROM %s WHERE name = :name2)",
1122                     allTestCaseTable, allTestCaseTable);
1123         auto stmt_insert_tc = db.prepare(add_if_non_exist_tc_sql);
1124 
1125         enum add_new_sql = format(
1126                     "INSERT INTO %s (st_id, tc_id, location) SELECT :st_id,t1.id,:loc FROM %s t1 WHERE t1.name = :tc",
1127                     killedTestCaseTable, allTestCaseTable);
1128         auto stmt_insert = db.prepare(add_new_sql);
1129         foreach (const tc; tcs) {
1130             try {
1131                 stmt_insert_tc.get.reset;
1132                 stmt_insert_tc.get.bind(":name1", tc.name);
1133                 stmt_insert_tc.get.bind(":name2", tc.name);
1134                 stmt_insert_tc.get.execute;
1135 
1136                 stmt_insert.get.reset;
1137                 stmt_insert.get.bind(":st_id", cast(long) st_id);
1138                 stmt_insert.get.bind(":loc", tc.location);
1139                 stmt_insert.get.bind(":tc", tc.name);
1140                 stmt_insert.get.execute;
1141             } catch (Exception e) {
1142                 logger.warning(e.msg);
1143             }
1144         }
1145     }
1146 
1147     /** Set detected test cases.
1148      *
1149      * This will replace those that where previously stored.
1150      *
1151      * Returns: ID of affected mutation statuses.
1152      */
1153     MutationStatusId[] setDetectedTestCases(const(TestCase)[] tcs) @trusted {
1154         if (tcs.length == 0)
1155             return null;
1156 
1157         auto mut_status_ids = appender!(MutationStatusId[])();
1158 
1159         immutable tmp_name = "tmp_new_tc_" ~ __LINE__.to!string;
1160         internalAddDetectedTestCases(tcs, tmp_name);
1161 
1162         immutable mut_st_id = format!"SELECT DISTINCT t1.st_id
1163             FROM %s t0, %s t1
1164             WHERE
1165             t0.name NOT IN (SELECT name FROM %s) AND
1166             t0.id = t1.tc_id"(allTestCaseTable,
1167                 killedTestCaseTable, tmp_name);
1168         auto stmt = db.prepare(mut_st_id);
1169         foreach (res; stmt.get.execute)
1170             mut_status_ids.put(res.peek!long(0).MutationStatusId);
1171 
1172         immutable remove_old_sql = format!"DELETE FROM %s WHERE name NOT IN (SELECT name FROM %s)"(
1173                 allTestCaseTable, tmp_name);
1174         db.run(remove_old_sql);
1175 
1176         db.run(format!"DROP TABLE %s"(tmp_name));
1177 
1178         return mut_status_ids.data;
1179     }
1180 
1181     /** Add test cases to those that have been detected.
1182      *
1183      * They will be added if they are unique.
1184      */
1185     void addDetectedTestCases(const(TestCase)[] tcs) @trusted {
1186         if (tcs.length == 0)
1187             return;
1188 
1189         immutable tmp_name = "tmp_new_tc_" ~ __LINE__.to!string;
1190         internalAddDetectedTestCases(tcs, tmp_name);
1191         db.run(format!"DROP TABLE %s"(tmp_name));
1192     }
1193 
1194     /// ditto.
1195     private void internalAddDetectedTestCases(const(TestCase)[] tcs, string tmp_tbl) @trusted {
1196         db.run(format!"CREATE TEMP TABLE %s (id INTEGER PRIMARY KEY, name TEXT NOT NULL)"(
1197                 tmp_tbl));
1198 
1199         immutable add_tc_sql = format!"INSERT INTO %s (name) VALUES(:name)"(tmp_tbl);
1200         auto insert_s = db.prepare(add_tc_sql);
1201         foreach (tc; tcs) {
1202             insert_s.get.bind(":name", tc.name);
1203             insert_s.get.execute;
1204             insert_s.get.reset;
1205         }
1206 
1207         // https://stackoverflow.com/questions/2686254/how-to-select-all-records-from-one-table-that-do-not-exist-in-another-table
1208         //Q: What is happening here?
1209         //
1210         //A: Conceptually, we select all rows from table1 and for each row we
1211         //attempt to find a row in table2 with the same value for the name
1212         //column.  If there is no such row, we just leave the table2 portion of
1213         //our result empty for that row. Then we constrain our selection by
1214         //picking only those rows in the result where the matching row does not
1215         //exist. Finally, We ignore all fields from our result except for the
1216         //name column (the one we are sure that exists, from table1).
1217         //
1218         //While it may not be the most performant method possible in all cases,
1219         //it should work in basically every database engine ever that attempts
1220         //to implement ANSI 92 SQL
1221         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"(
1222                 allTestCaseTable, tmp_tbl, allTestCaseTable);
1223         db.run(add_missing_sql);
1224     }
1225 
1226     /// Returns: detected test cases.
1227     TestCase[] getDetectedTestCases() @trusted {
1228         auto rval = appender!(TestCase[])();
1229         db.run(select!AllTestCaseTbl).map!(a => TestCase(a.name)).copy(rval);
1230         return rval.data;
1231     }
1232 
1233     /// Returns: detected test cases.
1234     TestCaseId[] getDetectedTestCaseIds() @trusted {
1235         auto rval = appender!(TestCaseId[])();
1236         db.run(select!AllTestCaseTbl).map!(a => TestCaseId(a.id)).copy(rval);
1237         return rval.data;
1238     }
1239 
1240     /// Returns: test cases that has killed zero mutants.
1241     TestCase[] getTestCasesWithZeroKills() @trusted {
1242         enum sql = format("SELECT t1.name FROM %s t1 WHERE t1.id NOT IN (SELECT tc_id FROM %s)",
1243                     allTestCaseTable, killedTestCaseTable);
1244 
1245         auto rval = appender!(TestCase[])();
1246         auto stmt = db.prepare(sql);
1247         foreach (a; stmt.get.execute)
1248             rval.put(TestCase(a.peek!string(0)));
1249 
1250         return rval.data;
1251     }
1252 
1253     /** Guarantees that the there are no duplications of `TestCaseId`.
1254      *
1255      * Returns: test cases that has killed at least one mutant.
1256      */
1257     TestCaseId[] getTestCasesWithAtLeastOneKill(const Mutation.Kind[] kinds) @trusted {
1258         immutable sql = format!"SELECT DISTINCT t1.id
1259             FROM %s t1, %s t2, %s t3
1260             WHERE
1261             t1.id = t2.tc_id AND
1262             t2.st_id == t3.st_id AND
1263             t3.kind IN (%(%s,%))"(allTestCaseTable,
1264                 killedTestCaseTable, mutationTable, kinds.map!(a => cast(int) a));
1265 
1266         auto rval = appender!(TestCaseId[])();
1267         auto stmt = db.prepare(sql);
1268         foreach (a; stmt.get.execute)
1269             rval.put(TestCaseId(a.peek!long(0)));
1270 
1271         return rval.data;
1272     }
1273 
1274     /// Returns: the name of the test case.
1275     string getTestCaseName(const TestCaseId id) @trusted {
1276         enum sql = format!"SELECT name FROM %s WHERE id = :id"(allTestCaseTable);
1277         auto stmt = db.prepare(sql);
1278         stmt.get.bind(":id", cast(long) id);
1279         auto res = stmt.get.execute;
1280         return res.oneValue!string;
1281     }
1282 
1283     /// Returns: stats about the test case.
1284     Nullable!TestCaseInfo getTestCaseInfo(const TestCase tc, const Mutation.Kind[] kinds) @trusted {
1285         const sql = format("SELECT sum(t2.time),count(t1.st_id)
1286             FROM %s t0, %s t1, %s t2, %s t3
1287             WHERE
1288             t0.name = :name AND
1289             t0.id = t1.tc_id AND
1290             t1.st_id = t2.id AND
1291             t2.id = t3.st_id AND
1292             t3.kind IN (%(%s,%))", allTestCaseTable,
1293                 killedTestCaseTable, mutationStatusTable, mutationTable,
1294                 kinds.map!(a => cast(int) a));
1295         auto stmt = db.prepare(sql);
1296         stmt.get.bind(":name", tc.name);
1297 
1298         typeof(return) rval;
1299         foreach (a; stmt.get.execute)
1300             rval = TestCaseInfo(a.peek!long(0).dur!"msecs", a.peek!long(1));
1301         return rval;
1302     }
1303 
1304     /// Returns: all test cases for the file and the mutants they killed.
1305     TestCaseInfo2[] getAllTestCaseInfo2(const FileId file, const Mutation.Kind[] kinds) @trusted {
1306         // row of test case name and mutation id.
1307         const sql = format("SELECT t0.name,t3.id
1308             FROM %s t0, %s t1, %s t2, %s t3, %s t4
1309             WHERE
1310             t0.id = t1.tc_id AND
1311             t1.st_id = t2.id AND
1312             t2.id = t3.st_id AND
1313             t4.id = :file_id AND
1314             t3.kind IN (%(%s,%))", allTestCaseTable, killedTestCaseTable,
1315                 mutationStatusTable, mutationTable, filesTable, kinds.map!(a => cast(int) a));
1316         auto stmt = db.prepare(sql);
1317         stmt.get.bind(":file_id", cast(long) file);
1318 
1319         MutationId[][string] data;
1320         foreach (row; stmt.get.execute) {
1321             const name = row.peek!string(0);
1322             if (auto v = name in data) {
1323                 *v ~= MutationId(row.peek!long(1));
1324             } else {
1325                 data[name] = [MutationId(row.peek!long(1))];
1326             }
1327         }
1328 
1329         auto app = appender!(TestCaseInfo2[])();
1330         data.byKeyValue.map!(a => TestCaseInfo2(TestCase(a.key), a.value)).copy(app);
1331         return app.data;
1332     }
1333 
1334     /// Returns: the test case.
1335     Nullable!TestCase getTestCase(const TestCaseId id) @trusted {
1336         enum sql = format!"SELECT name FROM %s WHERE id = :id"(allTestCaseTable);
1337         auto stmt = db.prepare(sql);
1338         stmt.get.bind(":id", cast(long) id);
1339 
1340         typeof(return) rval;
1341         foreach (res; stmt.get.execute) {
1342             rval = TestCase(res.peek!string(0));
1343         }
1344         return rval;
1345     }
1346 
1347     /// Returns: the test case id.
1348     Nullable!TestCaseId getTestCaseId(const TestCase tc) @trusted {
1349         enum sql = format!"SELECT id FROM %s WHERE name = :name"(allTestCaseTable);
1350         auto stmt = db.prepare(sql);
1351         stmt.get.bind(":name", tc.name);
1352 
1353         typeof(return) rval;
1354         foreach (res; stmt.get.execute) {
1355             rval = TestCaseId(res.peek!long(0));
1356         }
1357         return rval;
1358     }
1359 
1360     /// The mutation ids are guaranteed to be sorted.
1361     /// Returns: the mutants the test case killed.
1362     MutationId[] getTestCaseMutantKills(const TestCaseId id, const Mutation.Kind[] kinds) @trusted {
1363         immutable sql = format!"SELECT t2.id
1364             FROM %s t1, %s t2
1365             WHERE
1366             t1.tc_id = :tid AND
1367             t1.st_id = t2.st_id AND
1368             t2.kind IN (%(%s,%))
1369             ORDER BY
1370             t2.id"(killedTestCaseTable,
1371                 mutationTable, kinds.map!(a => cast(int) a));
1372 
1373         auto rval = appender!(MutationId[])();
1374         auto stmt = db.prepare(sql);
1375         stmt.get.bind(":tid", cast(long) id);
1376         foreach (a; stmt.get.execute)
1377             rval.put(MutationId(a.peek!long(0)));
1378 
1379         return rval.data;
1380     }
1381 
1382     /// Returns: test cases that killed the mutant.
1383     TestCase[] getTestCases(const MutationId id) @trusted {
1384         Appender!(TestCase[]) rval;
1385 
1386         enum get_test_cases_sql = format!"SELECT t1.name,t2.location
1387             FROM %s t1, %s t2, %s t3
1388             WHERE
1389             t3.id = :id AND
1390             t3.st_id = t2.st_id AND
1391             t2.tc_id = t1.id"(allTestCaseTable,
1392                     killedTestCaseTable, mutationTable);
1393         auto stmt = db.prepare(get_test_cases_sql);
1394         stmt.get.bind(":id", cast(long) id);
1395         foreach (a; stmt.get.execute)
1396             rval.put(TestCase(a.peek!string(0), a.peek!string(1)));
1397 
1398         return rval.data;
1399     }
1400 
1401     /** Returns: number of test cases
1402      */
1403     long getNumOfTestCases() @trusted {
1404         enum num_test_cases_sql = format!"SELECT count(*) FROM %s"(allTestCaseTable);
1405         return db.execute(num_test_cases_sql).oneValue!long;
1406     }
1407 
1408     /** Returns: test cases that killed other mutants at the same mutation point as `id`.
1409       */
1410     TestCase[] getSurroundingTestCases(const MutationId id) @trusted {
1411         Appender!(TestCase[]) rval;
1412 
1413         // TODO: optimize this. should be able to merge the two first queries to one.
1414 
1415         // get the mutation point ID that id reside at
1416         long mp_id;
1417         {
1418             auto stmt = db.prepare(format!"SELECT mp_id FROM %s WHERE id=:id"(mutationTable));
1419             stmt.get.bind(":id", cast(long) id);
1420             auto res = stmt.get.execute;
1421             if (res.empty)
1422                 return null;
1423             mp_id = res.oneValue!long;
1424         }
1425 
1426         // get all the mutation status ids at the mutation point
1427         long[] mut_st_ids;
1428         {
1429             auto stmt = db.prepare(format!"SELECT st_id FROM %s WHERE mp_id=:id"(mutationTable));
1430             stmt.get.bind(":id", mp_id);
1431             auto res = stmt.get.execute;
1432             if (res.empty)
1433                 return null;
1434             mut_st_ids = res.map!(a => a.peek!long(0)).array;
1435         }
1436 
1437         // get all the test cases that are killed at the mutation point
1438         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,%))"(
1439                 killedTestCaseTable, allTestCaseTable, mut_st_ids);
1440         auto stmt = db.prepare(get_test_cases_sql);
1441         foreach (a; stmt.get.execute) {
1442             rval.put(TestCase(a.peek!string(0), a.peek!string(1)));
1443         }
1444 
1445         return rval.data;
1446     }
1447 
1448     void removeTestCase(const Regex!char rex, const(Mutation.Kind)[] kinds) @trusted {
1449         immutable sql = format!"SELECT t1.id,t1.name FROM %s t1,%s t2, %s t3 WHERE t1.id = t2.tc_id AND t2.st_id = t3.st_id AND t3.kind IN (%(%s,%))"(
1450                 allTestCaseTable, killedTestCaseTable, mutationTable,
1451                 kinds.map!(a => cast(long) a));
1452         auto stmt = db.prepare(sql);
1453 
1454         auto del_stmt = db.prepare(format!"DELETE FROM %s WHERE id=:id"(allTestCaseTable));
1455         foreach (row; stmt.get.execute) {
1456             string tc = row.peek!string(1);
1457             if (tc.matchFirst(rex).empty)
1458                 continue;
1459 
1460             long id = row.peek!long(0);
1461             del_stmt.get.bind(":id", id);
1462             del_stmt.get.execute;
1463             del_stmt.get.reset;
1464         }
1465     }
1466 
1467     /// Returns: the context for the timeout algorithm.
1468     MutantTimeoutCtx getMutantTimeoutCtx() @trusted {
1469         foreach (res; db.run(select!MutantTimeoutCtx))
1470             return res;
1471         return MutantTimeoutCtx.init;
1472     }
1473 
1474     void putMutantTimeoutCtx(const MutantTimeoutCtx ctx) @trusted {
1475         db.run(delete_!MutantTimeoutCtx);
1476         db.run(insert!MutantTimeoutCtx.insert, ctx);
1477     }
1478 
1479     void putMutantInTimeoutWorklist(const MutationStatusId id) @trusted {
1480         const sql = format!"INSERT OR IGNORE INTO %s (id) VALUES (:id)"(mutantTimeoutWorklistTable);
1481         auto stmt = db.prepare(sql);
1482         stmt.get.bind(":id", cast(long) id);
1483         stmt.get.execute;
1484     }
1485 
1486     /** Remove all mutants that are in the worklist that do NOT have the
1487      * mutation status timeout.
1488      */
1489     void reduceMutantTimeoutWorklist() @trusted {
1490         immutable sql = format!"DELETE FROM %1$s
1491             WHERE
1492             id IN (SELECT id FROM %2$s WHERE status != :status)"(
1493                 mutantTimeoutWorklistTable, mutationStatusTable);
1494         auto stmt = db.prepare(sql);
1495         stmt.get.bind(":status", cast(ubyte) Mutation.Status.timeout);
1496         stmt.get.execute;
1497     }
1498 
1499     /// Remove all mutants from the worklist.
1500     void clearMutantTimeoutWorklist() @trusted {
1501         immutable sql = format!"DELETE FROM %1$s"(mutantTimeoutWorklistTable);
1502         db.run(sql);
1503     }
1504 
1505     /// Returns: the number of mutants in the worklist.
1506     long countMutantTimeoutWorklist() @trusted {
1507         immutable sql = format!"SELECT count(*) FROM %1$s"(mutantTimeoutWorklistTable);
1508         auto stmt = db.prepare(sql);
1509         auto res = stmt.get.execute();
1510         return res.oneValue!long;
1511     }
1512 
1513     /// Changes the status of mutants in the timeout worklist to unknown.
1514     void resetMutantTimeoutWorklist() @trusted {
1515         immutable sql = format!"UPDATE %1$s SET status=:st WHERE id IN (SELECT id FROM %2$s)"(
1516                 mutationStatusTable, mutantTimeoutWorklistTable);
1517         auto stmt = db.prepare(sql);
1518         stmt.get.bind(":st", cast(ubyte) Mutation.Status.unknown);
1519         stmt.get.execute;
1520     }
1521 
1522     /** Update the content of metadata tables with what has been added to the
1523      * raw table data.
1524      */
1525     void updateMetadata() @trusted {
1526         db.run(format!"DELETE FROM %s"(srcMetadataTable));
1527         db.run(format!"DELETE FROM %s"(nomutTable));
1528         db.run(format!"DELETE FROM %s"(nomutDataTable));
1529 
1530         immutable nomut_tbl = "INSERT INTO %s
1531             SELECT
1532                 t0.id mp_id,
1533                 t1.line line,
1534                 count(*) status
1535                 FROM %s t0, %s t1
1536                 WHERE
1537                 t0.file_id = t1.file_id AND
1538                 (t1.line BETWEEN t0.line AND t0.line_end)
1539                 GROUP BY
1540                 t0.id";
1541         db.run(format!nomut_tbl(nomutTable, mutationPointTable, rawSrcMetadataTable));
1542 
1543         immutable src_metadata_sql = "INSERT INTO %s
1544             SELECT DISTINCT
1545             t0.id AS mut_id,
1546             t1.id AS st_id,
1547             t2.id AS mp_id,
1548             t3.id AS file_id,
1549             (SELECT count(*) FROM %s WHERE nomut.mp_id = t2.id) as nomut
1550             FROM %s t0, %s t1, %s t2, %s t3
1551             WHERE
1552             t0.mp_id = t2.id AND
1553             t0.st_id = t1.id AND
1554             t2.file_id = t3.id";
1555         db.run(format!src_metadata_sql(srcMetadataTable, nomutTable,
1556                 mutationTable, mutationStatusTable, mutationPointTable, filesTable));
1557 
1558         immutable nomut_data_tbl = "INSERT INTO %s
1559             SELECT
1560                 t0.id as mut_id,
1561                 t0.mp_id as mp_id,
1562                 t1.line as line,
1563                 t1.tag as tag,
1564                 t1.comment as comment
1565                 FROM %s t0, %s t1, %s t2
1566                 WHERE
1567                 t0.mp_id = t2.mp_id AND
1568                 t1.line = t2.line";
1569         db.run(format!nomut_data_tbl(nomutDataTable, mutationTable,
1570                 rawSrcMetadataTable, nomutTable));
1571     }
1572 
1573     /// Returns: a random schemata from the schemata worklist.
1574     Nullable!Schemata nextSchemata() @trusted {
1575         immutable sql = format!"SELECT
1576             t1.path, t0.text, t0.offset_begin, t0.offset_end
1577             FROM %1$s t0, %2$s t1
1578             WHERE
1579             t0.schem_id = :id AND
1580             t0.file_id = t1.id
1581             ORDER BY t0.order_ ASC
1582             "(schemataFragmentTable, filesTable);
1583 
1584         Schemata schemata(long id) {
1585             auto stmt = db.prepare(sql);
1586             stmt.get.bind(":id", id);
1587 
1588             auto app = appender!(SchemataFragment[])();
1589             foreach (a; stmt.get.execute) {
1590                 app.put(SchemataFragment(a.peek!string(0).Path,
1591                         Offset(a.peek!uint(2), a.peek!uint(3)), a.peek!(ubyte[])(1)));
1592             }
1593 
1594             return Schemata(SchemataId(id), app.data);
1595         }
1596 
1597         typeof(return) rval;
1598         auto stmt = db.prepare(
1599                 format!"SELECT id FROM %s ORDER BY RANDOM() LIMIT 1"(schemataWorkListTable));
1600         foreach (a; stmt.get.execute) {
1601             rval = schemata(a.peek!long(0));
1602         }
1603 
1604         return rval;
1605     }
1606 
1607     MutationStatusId[] getSchemataMutants(SchemataId id) @trusted {
1608         immutable sql = format!"SELECT st_id FROM %s WHERE schem_id = :id"(schemataMutantTable);
1609         auto stmt = db.prepare(sql);
1610         stmt.get.bind(":id", cast(long) id);
1611 
1612         auto app = appender!(MutationStatusId[])();
1613         foreach (a; stmt.get.execute) {
1614             app.put(a.peek!long(0).MutationStatusId);
1615         }
1616 
1617         return app.data;
1618     }
1619 
1620     /// Mark a schemata as finished.
1621     void finishSchemata(SchemataId id) @trusted {
1622         db.run(delete_!SchemataWorkListTable.where("id = :id", Bind("id")), cast(long) id);
1623     }
1624 
1625     /// Create a schemata from a bundle of fragments.
1626     SchemataId putSchemata(SchemataFragment[] fragments, MutationStatusId[] mutants) @trusted {
1627         import std.range : enumerate;
1628 
1629         const schemId = () {
1630             immutable sql = format!"INSERT INTO %1$s VALUES(NULL)"(schemataTable);
1631             auto stmt = db.prepare(sql);
1632             stmt.get.execute;
1633             return db.lastInsertRowid.SchemataId;
1634         }();
1635 
1636         foreach (f; fragments.enumerate) {
1637             const fileId = getFileId(f.value.file);
1638             if (fileId.isNull) {
1639                 logger.warning("Unable to add schemata fragment for file %s because it doesn't exist",
1640                         f.value.file);
1641                 continue;
1642             }
1643 
1644             db.run(insert!SchemataFragmentTable, SchemataFragmentTable(0,
1645                     cast(long) schemId, cast(long) fileId.get, f.index,
1646                     f.value.text, f.value.offset.begin, f.value.offset.end));
1647         }
1648 
1649         // relate mutants to this schemata.
1650         db.run(insertOrReplace!SchemataMutantTable,
1651                 mutants.map!(a => SchemataMutantTable(cast(long) a, cast(long) schemId)));
1652 
1653         db.run(insertOrReplace!SchemataWorkListTable, SchemataWorkListTable(schemId));
1654 
1655         return schemId;
1656     }
1657 }
1658 
1659 private:
1660 
1661 MarkedMutant make(MarkedMutantTbl m) {
1662     import dextool.plugin.mutate.backend.type;
1663 
1664     return MarkedMutant(m.mutationStatusId.MutationStatusId, Checksum(m.checksum0,
1665             m.checksum1), m.mutationId.MutationId, SourceLoc(m.line, m.column),
1666             m.path.Path, m.toStatus.to!(Mutation.Status), m.time,
1667             m.rationale.Rationale, m.mutText);
1668 }