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      * TODO: assuming that there are no more than 100 instances running in
54      * parallel.
55      *
56      * The chosen point is randomised.
57      *
58      * Params:
59      *  kind = kind of mutation to retrieve.
60      */
61     NextMutationEntry nextMutation(const(Mutation.Kind)[] kinds,
62             const MutationOrder userOrder = MutationOrder.random) @trusted {
63         import dextool.plugin.mutate.backend.type;
64 
65         const order = () {
66             final switch (userOrder) {
67             case MutationOrder.random:
68                 return "100";
69             case MutationOrder.consecutive:
70                 return "1";
71             case MutationOrder.bySize:
72                 return "1";
73             }
74         }();
75 
76         typeof(return) rval;
77 
78         immutable sql = format("
79             SELECT * FROM
80             (SELECT
81             t0.id,
82             t0.kind,
83             t3.compile_time_ms,
84             t3.test_time_ms,
85             t1.offset_begin,
86             t1.offset_end,
87             t1.line,
88             t1.column,
89             t2.path,
90             t2.lang
91             FROM %1$s t0,%2$s t1,%3$s t2,%4$s t3, %5$s t4
92             WHERE
93             t0.st_id = t3.id AND
94             t3.id = t4.id AND
95             t0.mp_id == t1.id AND
96             t1.file_id == t2.id
97             ORDER BY t4.prio DESC LIMIT %6$s)
98             ORDER BY RANDOM() LIMIT 1", mutationTable, mutationPointTable,
99                 filesTable, mutationStatusTable, mutantWorklistTable, order);
100         auto stmt = db.prepare(sql);
101         auto res = stmt.get.execute;
102         if (res.empty) {
103             rval.st = NextMutationEntry.Status.done;
104             return rval;
105         }
106 
107         auto v = res.front;
108 
109         auto mp = MutationPoint(Offset(v.peek!uint(4), v.peek!uint(5)));
110         mp.mutations = [Mutation(v.peek!long(1).to!(Mutation.Kind))];
111         auto pkey = MutationId(v.peek!long(0));
112         auto file = Path(v.peek!string(8));
113         auto sloc = SourceLoc(v.peek!uint(6), v.peek!uint(7));
114         auto lang = v.peek!long(9).to!Language;
115 
116         rval.entry = MutationEntry(pkey, file, sloc, mp,
117                 MutantTimeProfile(v.peek!long(2).dur!"msecs", v.peek!long(3).dur!"msecs"), lang);
118 
119         return rval;
120     }
121 
122     /// Iterate over the mutants of `kinds` in oldest->newest datum order.
123     void iterateMutantStatus(const Mutation.Kind[] kinds,
124             void delegate(const Mutation.Status, const SysTime added) dg) @trusted {
125         immutable sql = format("SELECT t1.status,t1.added_ts FROM %s t0, %s t1
126            WHERE
127            t0.st_id = t1.id AND
128            t0.kind IN (%(%s,%))
129            ORDER BY t1.added_ts",
130                 mutationTable, mutationStatusTable, kinds.map!(a => cast(int) a));
131         auto stmt = db.prepare(sql);
132         try {
133             foreach (ref r; stmt.get.execute) {
134                 dg(r.peek!int(0).to!(Mutation.Status), r.peek!string(1).fromSqLiteDateTime);
135             }
136         } catch (Exception e) {
137             logger.error(e.msg).collectException;
138         }
139     }
140 
141     void iterateMutants(const Mutation.Kind[] kinds, void delegate(const ref IterateMutantRow) dg) @trusted {
142         import dextool.plugin.mutate.backend.utility : checksum;
143 
144         immutable all_mutants = format("SELECT
145             t0.id,
146             t3.status,
147             t0.kind,
148             t1.offset_begin,
149             t1.offset_end,
150             t1.line,
151             t1.column,
152             t1.line_end,
153             t1.column_end,
154             t2.path,
155             t2.checksum0,
156             t2.checksum1,
157             t2.lang,
158             t4.nomut
159             FROM %s t0,%s t1,%s t2, %s t3, %s t4
160             WHERE
161             t0.kind IN (%(%s,%)) AND
162             t0.st_id = t3.id AND
163             t0.mp_id = t1.id AND
164             t1.file_id = t2.id AND
165             t0.id = t4.mut_id
166             ORDER BY t3.status", mutationTable, mutationPointTable,
167                 filesTable, mutationStatusTable, srcMetadataTable, kinds.map!(a => cast(int) a));
168 
169         try {
170             auto stmt = db.prepare(all_mutants);
171             foreach (ref r; stmt.get.execute) {
172                 IterateMutantRow d;
173                 d.id = MutationId(r.peek!long(0));
174                 d.mutation = Mutation(r.peek!int(2).to!(Mutation.Kind),
175                         r.peek!int(1).to!(Mutation.Status));
176                 auto offset = Offset(r.peek!uint(3), r.peek!uint(4));
177                 d.mutationPoint = MutationPoint(offset, null);
178                 d.file = r.peek!string(9).Path;
179                 d.fileChecksum = checksum(r.peek!long(10), r.peek!long(11));
180                 d.sloc = SourceLoc(r.peek!uint(5), r.peek!uint(6));
181                 d.slocEnd = SourceLoc(r.peek!uint(7), r.peek!uint(8));
182                 d.lang = r.peek!long(12).to!Language;
183 
184                 if (r.peek!long(13) != 0) {
185                     d.attrs = MutantMetaData(d.id, MutantAttr(NoMut.init));
186                 }
187                 dg(d);
188             }
189         } catch (Exception e) {
190             logger.error(e.msg).collectException;
191         }
192     }
193 
194     FileRow[] getDetailedFiles() @trusted {
195         import std.array : appender;
196         import dextool.plugin.mutate.backend.utility : checksum;
197 
198         enum files_q = format("SELECT t0.path, t0.checksum0, t0.checksum1, t0.lang, t0.id FROM %s t0",
199                     filesTable);
200         auto app = appender!(FileRow[])();
201         auto stmt = db.prepare(files_q);
202         foreach (ref r; stmt.get.execute) {
203             auto fr = FileRow(r.peek!string(0).Path, checksum(r.peek!long(1),
204                     r.peek!long(2)), r.peek!Language(3), r.peek!long(4).FileId);
205             app.put(fr);
206         }
207 
208         return app.data;
209     }
210 
211     /** Iterate over the mutants in a specific file.
212      *
213      * Mutants are guaranteed to be ordered by their starting offset in the
214      * file.
215      *
216      * Params:
217      *  kinds = the type of mutation operators to have in the report
218      *  file = the file to retrieve mutants from
219      *  dg = callback for reach row
220      */
221     void iterateFileMutants(const Mutation.Kind[] kinds, Path file,
222             void delegate(ref const FileMutantRow) dg) @trusted {
223         import std.algorithm : map;
224 
225         immutable all_fmut = format("SELECT
226             t0.id,
227             t0.kind,
228             t3.status,
229             t1.offset_begin,
230             t1.offset_end,
231             t1.line,
232             t1.column,
233             t1.line_end,
234             t1.column_end,
235             t2.lang
236             FROM %s t0, %s t1, %s t2, %s t3
237             WHERE
238             t0.kind IN (%(%s,%)) AND
239             t0.st_id = t3.id AND
240             t0.mp_id = t1.id AND
241             t1.file_id = t2.id AND
242             t2.path = :path
243             ORDER BY t1.offset_begin
244             ", mutationTable, mutationPointTable,
245                 filesTable, mutationStatusTable, kinds.map!(a => cast(int) a));
246 
247         auto stmt = db.prepare(all_fmut);
248         stmt.get.bind(":path", cast(string) file);
249         foreach (ref r; stmt.get.execute) {
250             FileMutantRow fr;
251             fr.id = MutationId(r.peek!long(0));
252             fr.mutation = Mutation(r.peek!int(1).to!(Mutation.Kind),
253                     r.peek!int(2).to!(Mutation.Status));
254             auto offset = Offset(r.peek!uint(3), r.peek!uint(4));
255             fr.mutationPoint = MutationPoint(offset, null);
256             fr.sloc = SourceLoc(r.peek!uint(5), r.peek!uint(6));
257             fr.slocEnd = SourceLoc(r.peek!uint(7), r.peek!uint(8));
258             fr.lang = r.peek!int(9).to!Language;
259 
260             dg(fr);
261         }
262     }
263 }
264 
265 struct IterateMutantRow {
266     MutationId id;
267     Mutation mutation;
268     MutationPoint mutationPoint;
269     Path file;
270     Checksum fileChecksum;
271     SourceLoc sloc;
272     SourceLoc slocEnd;
273     Language lang;
274     MutantMetaData attrs;
275 }
276 
277 struct FileRow {
278     Path file;
279     Checksum fileChecksum;
280     Language lang;
281     FileId id;
282 }
283 
284 struct FileMutantRow {
285     MutationId id;
286     Mutation mutation;
287     MutationPoint mutationPoint;
288     SourceLoc sloc;
289     SourceLoc slocEnd;
290     Language lang;
291 }