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.format : format;
19 
20 import dextool.type : AbsolutePath, Path;
21 import dextool.plugin.mutate.backend.type;
22 
23 import dextool.plugin.mutate.backend.database.schema;
24 public import dextool.plugin.mutate.backend.database.type;
25 public import dextool.plugin.mutate.backend.database.standalone : spinSqlQuery;
26 
27 /** Wrapper for a sqlite3 database that provide a uniform, easy-to-use
28  * interface for the mutation testing plugin.
29  */
30 struct Database {
31     import std.conv : to;
32     import std.exception : collectException;
33     import std.typecons : Nullable;
34     import dextool.plugin.mutate.backend.type : MutationPoint, Mutation, Checksum;
35     import dextool.plugin.mutate.type : MutationOrder;
36     import dextool.plugin.mutate.backend.database.standalone : SDatabase = Database;
37 
38     SDatabase db;
39     alias db this;
40 
41     private MutationOrder mut_order;
42 
43     static auto make(AbsolutePath db, MutationOrder mut_order) @safe {
44         return Database(SDatabase.make(db), mut_order);
45     }
46 
47     // Not movable. The database should only be passed around as a reference,
48     // if at all.
49     @disable this(this);
50 
51     Nullable!Checksum getFileChecksum(const Path p) @trusted {
52         import dextool.plugin.mutate.backend.utility : checksum;
53 
54         auto stmt = db.prepare("SELECT checksum0,checksum1 FROM files WHERE path=:path");
55         stmt.bind(":path", cast(string) p);
56         auto res = stmt.execute;
57 
58         typeof(return) rval;
59         if (!res.empty) {
60             rval = checksum(res.front.peek!long(0), res.front.peek!long(1));
61         }
62 
63         return rval;
64     }
65 
66     /** Get the next mutation point + 1 mutant for it that has status unknown.
67      *
68      * TODO to run many instances in parallel the mutation should be locked.
69      *
70      * The chosen point is randomised.
71      *
72      * Params:
73      *  kind = kind of mutation to retrieve.
74      */
75     NextMutationEntry nextMutation(const(Mutation.Kind)[] kinds) @trusted {
76         import std.algorithm : map;
77         import std.exception : collectException;
78         import dextool.plugin.mutate.backend.type;
79         import dextool.type : FileName;
80 
81         typeof(return) rval;
82 
83         auto order = mut_order == MutationOrder.random ? "ORDER BY RANDOM()" : "";
84 
85         immutable sql = format("SELECT
86                                t0.id,
87                                t0.kind,
88                                t3.time,
89                                t1.offset_begin,
90                                t1.offset_end,
91                                t1.line,
92                                t1.column,
93                                t2.path,
94                                t2.lang
95                                FROM %s t0,%s t1,%s t2,%s t3
96                                WHERE
97                                t0.st_id = t3.id AND
98                                t3.status == 0 AND
99                                t0.mp_id == t1.id AND
100                                t1.file_id == t2.id AND
101                                t0.kind IN (%(%s,%)) %s LIMIT 1", mutationTable, mutationPointTable,
102                 filesTable, mutationStatusTable, kinds.map!(a => cast(int) a), order);
103         auto stmt = db.prepare(sql);
104         auto res = stmt.execute;
105         if (res.empty) {
106             rval.st = NextMutationEntry.Status.done;
107             return rval;
108         }
109 
110         auto v = res.front;
111 
112         auto mp = MutationPoint(Offset(v.peek!uint(3), v.peek!uint(4)));
113         mp.mutations = [Mutation(v.peek!long(1).to!(Mutation.Kind))];
114         auto pkey = MutationId(v.peek!long(0));
115         auto file = Path(FileName(v.peek!string(7)));
116         auto sloc = SourceLoc(v.peek!uint(5), v.peek!uint(6));
117         auto lang = v.peek!long(8).to!Language;
118 
119         rval.entry = MutationEntry(pkey, file, sloc, mp, v.peek!long(2).dur!"msecs", lang);
120 
121         return rval;
122     }
123 
124     void iterateMutants(const Mutation.Kind[] kinds, void delegate(const ref IterateMutantRow) dg) @trusted {
125         import std.algorithm : map;
126         import dextool.plugin.mutate.backend.utility : checksum;
127 
128         immutable all_mutants = format("SELECT
129             t0.id,
130             t3.status,
131             t0.kind,
132             t3.time,
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 res = db.prepare(all_mutants).execute;
156             foreach (ref r; res) {
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(4), r.peek!uint(5));
162                 d.mutationPoint = MutationPoint(offset, null);
163                 d.file = r.peek!string(10);
164                 d.fileChecksum = checksum(r.peek!long(11), r.peek!long(12));
165                 d.sloc = SourceLoc(r.peek!uint(6), r.peek!uint(7));
166                 d.slocEnd = SourceLoc(r.peek!uint(8), r.peek!uint(9));
167                 d.lang = r.peek!long(13).to!Language;
168 
169                 d.testCases = db.getTestCases(d.id);
170 
171                 if (r.peek!long(14) != 0)
172                     d.attrs = MutantMetaData(d.id, MutantAttr(NoMut.init));
173                 dg(d);
174             }
175         } catch (Exception e) {
176             logger.error(e.msg).collectException;
177         }
178     }
179 
180     FileRow[] getDetailedFiles() @trusted {
181         import std.array : appender;
182         import dextool.plugin.mutate.backend.utility : checksum;
183 
184         enum files_q = format("SELECT t0.path, t0.checksum0, t0.checksum1, t0.lang, t0.id FROM %s t0",
185                     filesTable);
186         auto app = appender!(FileRow[])();
187         foreach (ref r; db.prepare(files_q).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.bind(":path", cast(string) file);
234         foreach (ref r; stmt.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     TestCase[] testCases;
259     Language lang;
260     MutantMetaData attrs;
261 }
262 
263 struct FileRow {
264     Path file;
265     Checksum fileChecksum;
266     Language lang;
267     FileId id;
268 }
269 
270 struct FileMutantRow {
271     MutationId id;
272     Mutation mutation;
273     MutationPoint mutationPoint;
274     SourceLoc sloc;
275     SourceLoc slocEnd;
276     Language lang;
277 }