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