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 uint maxParallel) @trusted {
64         import dextool.plugin.mutate.backend.type;
65 
66         typeof(return) rval;
67 
68         static immutable sql = "SELECT * FROM
69             (SELECT
70             t3.id,
71             t0.kind,
72             t3.compile_time_ms,
73             t3.test_time_ms,
74             t1.offset_begin,
75             t1.offset_end,
76             t1.line,
77             t1.column,
78             t2.path,
79             t2.lang
80             FROM " ~ mutationTable ~ " t0," ~ mutationPointTable ~ " t1," ~ filesTable
81             ~ " t2," ~ mutationStatusTable ~ " t3," ~ mutantWorklistTable ~ " 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 :parallel)
88             ORDER BY RANDOM() LIMIT 1";
89         auto stmt = db.db.prepare(sql);
90         stmt.get.bind(":parallel", maxParallel);
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 = MutationStatusId(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     void iterateMutantStatus(scope void delegate(const Mutation.Status, const SysTime added) dg) @trusted {
113         static immutable sql = "SELECT t1.status,t1.added_ts FROM "
114             ~ mutationTable ~ " t0, " ~ mutationStatusTable ~ " t1
115            WHERE t0.st_id = t1.id ORDER BY t1.added_ts";
116         auto stmt = db.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(scope void delegate(const ref IterateMutantRow) dg) @trusted {
127         import dextool.plugin.mutate.backend.utility : checksum;
128 
129         immutable all_mutants = format("SELECT
130             t0.st_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.checksum,
141             t2.lang,
142             t4.nomut
143             FROM %s t0,%s t1,%s t2, %s t3, %s t4
144             WHERE
145             t0.st_id = t3.id AND
146             t0.mp_id = t1.id AND
147             t1.file_id = t2.id AND
148             t0.id = t4.mut_id
149             ORDER BY t3.status", mutationTable, mutationPointTable, filesTable,
150                 mutationStatusTable, srcMetadataTable);
151 
152         try {
153             auto stmt = db.db.prepare(all_mutants);
154             foreach (ref r; stmt.get.execute) {
155                 IterateMutantRow d;
156                 d.id = MutationStatusId(r.peek!long(0));
157                 d.mutation = Mutation(r.peek!int(2).to!(Mutation.Kind),
158                         r.peek!int(1).to!(Mutation.Status));
159                 auto offset = Offset(r.peek!uint(3), r.peek!uint(4));
160                 d.mutationPoint = MutationPoint(offset, null);
161                 d.file = r.peek!string(9).Path;
162                 d.fileChecksum = checksum(r.peek!long(10));
163                 d.sloc = SourceLoc(r.peek!uint(5), r.peek!uint(6));
164                 d.slocEnd = SourceLoc(r.peek!uint(7), r.peek!uint(8));
165                 d.lang = r.peek!long(11).to!Language;
166 
167                 if (r.peek!long(12) != 0) {
168                     d.attrs = MutantMetaData(d.id, MutantAttr(NoMut.init));
169                 }
170                 dg(d);
171             }
172         } catch (Exception e) {
173             logger.error(e.msg).collectException;
174         }
175     }
176 
177     void iterateMutants(void delegate(const ref IterateMutantRow2) dg) @trusted {
178         static immutable sql = "SELECT
179             t3.id,
180             t0.kind,
181             t3.status,
182             t3.exit_code,
183             t3.prio,
184             t2.path,
185             t1.line,
186             t1.column,
187             t3.update_ts,
188             (SELECT count(*) FROM " ~ killedTestCaseTable ~ " WHERE t3.id=st_id) as vc_cnt,
189             t4.nomut
190             FROM " ~ mutationTable ~ " t0," ~ mutationPointTable ~ " t1," ~ filesTable
191             ~ " t2, " ~ mutationStatusTable ~ " t3, " ~ srcMetadataTable ~ " t4
192             WHERE
193             t0.st_id = t3.id AND
194             t0.mp_id = t1.id AND
195             t1.file_id = t2.id AND
196             t0.id = t4.mut_id
197             GROUP BY t3.id
198             ORDER BY t2.path,t1.line,t3.id";
199 
200         try {
201             auto stmt = db.db.prepare(sql);
202             foreach (ref r; stmt.get.execute) {
203                 IterateMutantRow2 d;
204                 d.stId = MutationStatusId(r.peek!long(0));
205                 d.mutant = Mutation(r.peek!int(1).to!(Mutation.Kind),
206                         r.peek!int(2).to!(Mutation.Status));
207                 d.exitStatus = r.peek!int(3).ExitStatus;
208                 d.prio = r.peek!long(4).MutantPrio;
209                 d.file = r.peek!string(5).Path;
210                 d.sloc = SourceLoc(r.peek!uint(6), r.peek!uint(7));
211                 d.tested = r.peek!string(8).fromSqLiteDateTime;
212                 d.killedByTestCases = r.peek!long(9);
213 
214                 if (r.peek!long(10) != 0) {
215                     d.attrs = MutantMetaData(d.stId, MutantAttr(NoMut.init));
216                 }
217 
218                 dg(d);
219             }
220         } catch (Exception e) {
221             logger.error(e.msg).collectException;
222         }
223     }
224 
225     FileRow[] getDetailedFiles() @trusted {
226         import std.array : appender;
227         import dextool.plugin.mutate.backend.utility : checksum;
228 
229         static immutable files_q = "SELECT t0.path, t0.checksum, t0.lang, t0.id FROM "
230             ~ filesTable ~ " t0";
231         auto app = appender!(FileRow[])();
232         auto stmt = db.db.prepare(files_q);
233         foreach (ref r; stmt.get.execute) {
234             auto fr = FileRow(r.peek!string(0).Path,
235                     checksum(r.peek!long(1)), r.peek!Language(2), r.peek!long(3).FileId);
236             app.put(fr);
237         }
238 
239         return app.data;
240     }
241 
242     /** Iterate over the mutants in a specific file.
243      *
244      * Mutants are guaranteed to be ordered by their starting offset in the
245      * file.
246      *
247      * Params:
248      *  file = the file to retrieve mutants from
249      *  dg = callback for reach row
250      */
251     void iterateFileMutants(Path file, scope void delegate(ref const FileMutantRow) dg) @trusted {
252         import std.algorithm : map;
253 
254         // TODO: remove the dummy value zero. it is just there to avoid having
255         // to update all the peeks.
256         static immutable sql = "SELECT
257             0,
258             t3.id,
259             t0.kind,
260             t3.status,
261             t1.offset_begin,
262             t1.offset_end,
263             t1.line,
264             t1.column,
265             t1.line_end,
266             t1.column_end,
267             t2.lang
268             FROM " ~ mutationTable ~ " t0, " ~ mutationPointTable ~ " t1, "
269             ~ filesTable ~ " t2, " ~ mutationStatusTable ~ " t3
270             WHERE
271             t0.st_id = t3.id AND
272             t0.mp_id = t1.id AND
273             t1.file_id = t2.id AND
274             t2.path = :path
275             GROUP BY t3.id
276             ORDER BY t1.offset_begin";
277 
278         auto stmt = db.db.prepare(sql);
279         stmt.get.bind(":path", cast(string) file);
280         foreach (ref r; stmt.get.execute) {
281             FileMutantRow fr;
282             fr.stId = MutationStatusId(r.peek!long(1));
283             fr.mutation = Mutation(r.peek!int(2).to!(Mutation.Kind),
284                     r.peek!int(3).to!(Mutation.Status));
285             auto offset = Offset(r.peek!uint(4), r.peek!uint(5));
286             fr.mutationPoint = MutationPoint(offset, null);
287             fr.sloc = SourceLoc(r.peek!uint(6), r.peek!uint(7));
288             fr.slocEnd = SourceLoc(r.peek!uint(8), r.peek!uint(9));
289             fr.lang = r.peek!int(10).to!Language;
290 
291             dg(fr);
292         }
293     }
294 }
295 
296 struct IterateMutantRow {
297     MutationStatusId id;
298     Mutation mutation;
299     MutationPoint mutationPoint;
300     Path file;
301     Checksum fileChecksum;
302     SourceLoc sloc;
303     SourceLoc slocEnd;
304     Language lang;
305     MutantMetaData attrs;
306 }
307 
308 struct IterateMutantRow2 {
309     MutationStatusId stId;
310     Mutation mutant;
311     ExitStatus exitStatus;
312     Path file;
313     SourceLoc sloc;
314     MutantPrio prio;
315     SysTime tested;
316     long killedByTestCases;
317     MutantMetaData attrs;
318 }
319 
320 struct FileRow {
321     Path file;
322     Checksum fileChecksum;
323     Language lang;
324     FileId id;
325 }
326 
327 struct FileMutantRow {
328     MutationStatusId stId;
329     Mutation mutation;
330     MutationPoint mutationPoint;
331     SourceLoc sloc;
332     SourceLoc slocEnd;
333     Language lang;
334 }