1 /**
2 Copyright: Copyright (c) 2017, 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 file contains a sqlite3 wrapper that is responsible for providing *nice*, easy to use functions for accessing the data in the database.
11 
12 This module may have dependencies on many internal mutation modules.
13 */
14 module dextool.plugin.mutate.backend.database;
15 
16 import core.time : Duration, dur;
17 import logger = std.experimental.logger;
18 import std.algorithm : map;
19 import std.exception : collectException;
20 import std.format : format;
21 
22 public import miniorm : toSqliteDateTime, fromSqLiteDateTime, spinSql;
23 
24 import dextool.type : AbsolutePath, Path;
25 import dextool.plugin.mutate.backend.type;
26 
27 import dextool.plugin.mutate.backend.database.schema;
28 import dextool.plugin.mutate.type : MutationOrder;
29 public import dextool.plugin.mutate.backend.database.standalone;
30 public import dextool.plugin.mutate.backend.database.type;
31 
32 /** Wrapper for a sqlite3 database that provide a uniform, easy-to-use
33  * interface for the mutation testing plugin.
34  */
35 struct Database {
36     import std.conv : to;
37     import std.datetime : SysTime;
38     import std.exception : collectException;
39     import std.typecons : Nullable;
40     import dextool.plugin.mutate.backend.database.standalone : SDatabase = Database;
41     import dextool.plugin.mutate.backend.type : MutationPoint, Mutation, Checksum;
42 
43     SDatabase db;
44     alias db this;
45 
46     static auto make(AbsolutePath db) @safe {
47         return Database(SDatabase.make(db));
48     }
49 
50     /** Get the next mutation from the worklist to test by the highest
51      * priority.
52      *
53      * The chosen point is randomised.
54      *
55      * Params:
56      *  kind = kind of mutation to retrieve.
57      */
58     NextMutationEntry nextMutation(const(Mutation.Kind)[] kinds, const uint maxParallel) @trusted {
59         import dextool.plugin.mutate.backend.type;
60 
61         typeof(return) rval;
62 
63         immutable sql = format("
64             SELECT * FROM
65             (SELECT
66             t0.id,
67             t0.kind,
68             t3.compile_time_ms,
69             t3.test_time_ms,
70             t1.offset_begin,
71             t1.offset_end,
72             t1.line,
73             t1.column,
74             t2.path,
75             t2.lang
76             FROM %1$s t0,%2$s t1,%3$s t2,%4$s t3, %5$s t4
77             WHERE
78             t0.st_id = t3.id AND
79             t3.id = t4.id AND
80             t0.mp_id == t1.id AND
81             t1.file_id == t2.id
82             ORDER BY t4.prio DESC LIMIT %6$s)
83             ORDER BY RANDOM() LIMIT 1", mutationTable, mutationPointTable,
84                 filesTable, mutationStatusTable, mutantWorklistTable, maxParallel);
85         auto stmt = db.prepare(sql);
86         auto res = stmt.get.execute;
87         if (res.empty) {
88             rval.st = NextMutationEntry.Status.done;
89             return rval;
90         }
91 
92         auto v = res.front;
93 
94         auto mp = MutationPoint(Offset(v.peek!uint(4), v.peek!uint(5)));
95         mp.mutations = [Mutation(v.peek!long(1).to!(Mutation.Kind))];
96         auto pkey = MutationId(v.peek!long(0));
97         auto file = Path(v.peek!string(8));
98         auto sloc = SourceLoc(v.peek!uint(6), v.peek!uint(7));
99         auto lang = v.peek!long(9).to!Language;
100 
101         rval.entry = MutationEntry(pkey, file, sloc, mp,
102                 MutantTimeProfile(v.peek!long(2).dur!"msecs", v.peek!long(3).dur!"msecs"), lang);
103 
104         return rval;
105     }
106 
107     /// Iterate over the mutants of `kinds` in oldest->newest datum order.
108     void iterateMutantStatus(const Mutation.Kind[] kinds,
109             void delegate(const Mutation.Status, const SysTime added) dg) @trusted {
110         immutable sql = format("SELECT t1.status,t1.added_ts FROM %s t0, %s t1
111            WHERE
112            t0.st_id = t1.id AND
113            t0.kind IN (%(%s,%))
114            ORDER BY t1.added_ts",
115                 mutationTable, mutationStatusTable, kinds.map!(a => cast(int) a));
116         auto stmt = db.prepare(sql);
117         try {
118             foreach (ref r; stmt.get.execute) {
119                 dg(r.peek!int(0).to!(Mutation.Status), r.peek!string(1).fromSqLiteDateTime);
120             }
121         } catch (Exception e) {
122             logger.error(e.msg).collectException;
123         }
124     }
125 
126     void iterateMutants(const Mutation.Kind[] kinds, void delegate(const ref IterateMutantRow) dg) @trusted {
127         import dextool.plugin.mutate.backend.utility : checksum;
128 
129         immutable all_mutants = format("SELECT
130             t0.id,
131             t3.status,
132             t0.kind,
133             t1.offset_begin,
134             t1.offset_end,
135             t1.line,
136             t1.column,
137             t1.line_end,
138             t1.column_end,
139             t2.path,
140             t2.checksum0,
141             t2.checksum1,
142             t2.lang,
143             t4.nomut
144             FROM %s t0,%s t1,%s t2, %s t3, %s t4
145             WHERE
146             t0.kind IN (%(%s,%)) AND
147             t0.st_id = t3.id AND
148             t0.mp_id = t1.id AND
149             t1.file_id = t2.id AND
150             t0.id = t4.mut_id
151             ORDER BY t3.status", mutationTable, mutationPointTable,
152                 filesTable, mutationStatusTable, srcMetadataTable, kinds.map!(a => cast(int) a));
153 
154         try {
155             auto stmt = db.prepare(all_mutants);
156             foreach (ref r; stmt.get.execute) {
157                 IterateMutantRow d;
158                 d.id = MutationId(r.peek!long(0));
159                 d.mutation = Mutation(r.peek!int(2).to!(Mutation.Kind),
160                         r.peek!int(1).to!(Mutation.Status));
161                 auto offset = Offset(r.peek!uint(3), r.peek!uint(4));
162                 d.mutationPoint = MutationPoint(offset, null);
163                 d.file = r.peek!string(9).Path;
164                 d.fileChecksum = checksum(r.peek!long(10), r.peek!long(11));
165                 d.sloc = SourceLoc(r.peek!uint(5), r.peek!uint(6));
166                 d.slocEnd = SourceLoc(r.peek!uint(7), r.peek!uint(8));
167                 d.lang = r.peek!long(12).to!Language;
168 
169                 if (r.peek!long(13) != 0) {
170                     d.attrs = MutantMetaData(d.id, MutantAttr(NoMut.init));
171                 }
172                 dg(d);
173             }
174         } catch (Exception e) {
175             logger.error(e.msg).collectException;
176         }
177     }
178 
179     FileRow[] getDetailedFiles() @trusted {
180         import std.array : appender;
181         import dextool.plugin.mutate.backend.utility : checksum;
182 
183         enum files_q = format("SELECT t0.path, t0.checksum0, t0.checksum1, t0.lang, t0.id FROM %s t0",
184                     filesTable);
185         auto app = appender!(FileRow[])();
186         auto stmt = db.prepare(files_q);
187         foreach (ref r; stmt.get.execute) {
188             auto fr = FileRow(r.peek!string(0).Path, checksum(r.peek!long(1),
189                     r.peek!long(2)), r.peek!Language(3), r.peek!long(4).FileId);
190             app.put(fr);
191         }
192 
193         return app.data;
194     }
195 
196     /** Iterate over the mutants in a specific file.
197      *
198      * Mutants are guaranteed to be ordered by their starting offset in the
199      * file.
200      *
201      * Params:
202      *  kinds = the type of mutation operators to have in the report
203      *  file = the file to retrieve mutants from
204      *  dg = callback for reach row
205      */
206     void iterateFileMutants(const Mutation.Kind[] kinds, Path file,
207             void delegate(ref const FileMutantRow) dg) @trusted {
208         import std.algorithm : map;
209 
210         immutable all_fmut = format("SELECT
211             t0.id,
212             t0.kind,
213             t3.status,
214             t1.offset_begin,
215             t1.offset_end,
216             t1.line,
217             t1.column,
218             t1.line_end,
219             t1.column_end,
220             t2.lang
221             FROM %s t0, %s t1, %s t2, %s t3
222             WHERE
223             t0.kind IN (%(%s,%)) AND
224             t0.st_id = t3.id AND
225             t0.mp_id = t1.id AND
226             t1.file_id = t2.id AND
227             t2.path = :path
228             ORDER BY t1.offset_begin
229             ", mutationTable, mutationPointTable,
230                 filesTable, mutationStatusTable, kinds.map!(a => cast(int) a));
231 
232         auto stmt = db.prepare(all_fmut);
233         stmt.get.bind(":path", cast(string) file);
234         foreach (ref r; stmt.get.execute) {
235             FileMutantRow fr;
236             fr.id = MutationId(r.peek!long(0));
237             fr.mutation = Mutation(r.peek!int(1).to!(Mutation.Kind),
238                     r.peek!int(2).to!(Mutation.Status));
239             auto offset = Offset(r.peek!uint(3), r.peek!uint(4));
240             fr.mutationPoint = MutationPoint(offset, null);
241             fr.sloc = SourceLoc(r.peek!uint(5), r.peek!uint(6));
242             fr.slocEnd = SourceLoc(r.peek!uint(7), r.peek!uint(8));
243             fr.lang = r.peek!int(9).to!Language;
244 
245             dg(fr);
246         }
247     }
248 }
249 
250 struct IterateMutantRow {
251     MutationId id;
252     Mutation mutation;
253     MutationPoint mutationPoint;
254     Path file;
255     Checksum fileChecksum;
256     SourceLoc sloc;
257     SourceLoc slocEnd;
258     Language lang;
259     MutantMetaData attrs;
260 }
261 
262 struct FileRow {
263     Path file;
264     Checksum fileChecksum;
265     Language lang;
266     FileId id;
267 }
268 
269 struct FileMutantRow {
270     MutationId id;
271     Mutation mutation;
272     MutationPoint mutationPoint;
273     SourceLoc sloc;
274     SourceLoc slocEnd;
275     Language lang;
276 }