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