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 public import miniorm : toSqliteDateTime, fromSqLiteDateTime, spinSql;
21 
22 import dextool.type : AbsolutePath, Path;
23 import dextool.plugin.mutate.backend.type;
24 
25 import dextool.plugin.mutate.backend.database.schema;
26 public import dextool.plugin.mutate.backend.database.type;
27 public import dextool.plugin.mutate.backend.database.standalone;
28 
29 /** Wrapper for a sqlite3 database that provide a uniform, easy-to-use
30  * interface for the mutation testing plugin.
31  */
32 struct Database {
33     import std.conv : to;
34     import std.exception : collectException;
35     import std.typecons : Nullable;
36     import dextool.plugin.mutate.backend.type : MutationPoint, Mutation, Checksum;
37     import dextool.plugin.mutate.type : MutationOrder;
38     import dextool.plugin.mutate.backend.database.standalone : SDatabase = Database;
39 
40     SDatabase db;
41     alias db this;
42 
43     private MutationOrder mut_order;
44 
45     static auto make(AbsolutePath db, MutationOrder mut_order) @safe {
46         return Database(SDatabase.make(db), mut_order);
47     }
48 
49     Nullable!Checksum getFileChecksum(const Path p) @trusted {
50         import dextool.plugin.mutate.backend.utility : checksum;
51 
52         auto stmt = db.prepare("SELECT checksum0,checksum1 FROM files WHERE path=:path");
53         stmt.bind(":path", cast(string) p);
54         auto res = stmt.execute;
55 
56         typeof(return) rval;
57         if (!res.empty) {
58             rval = checksum(res.front.peek!long(0), res.front.peek!long(1));
59         }
60 
61         return rval;
62     }
63 
64     /** Get the next mutation point + 1 mutant for it that has status unknown.
65      *
66      * TODO to run many instances in parallel the mutation should be locked.
67      *
68      * The chosen point is randomised.
69      *
70      * Params:
71      *  kind = kind of mutation to retrieve.
72      */
73     NextMutationEntry nextMutation(const(Mutation.Kind)[] kinds) @trusted {
74         import std.algorithm : map;
75         import std.exception : collectException;
76         import dextool.plugin.mutate.backend.type;
77         import dextool.type : FileName;
78 
79         typeof(return) rval;
80 
81         auto order = mut_order == MutationOrder.random ? "ORDER BY RANDOM()" : "";
82 
83         immutable sql = format("SELECT
84                                t0.id,
85                                t0.kind,
86                                t3.time,
87                                t1.offset_begin,
88                                t1.offset_end,
89                                t1.line,
90                                t1.column,
91                                t2.path,
92                                t2.lang
93                                FROM %s t0,%s t1,%s t2,%s t3
94                                WHERE
95                                t0.st_id = t3.id AND
96                                t3.status == 0 AND
97                                t0.mp_id == t1.id AND
98                                t1.file_id == t2.id AND
99                                t0.kind IN (%(%s,%)) %s LIMIT 1", mutationTable, mutationPointTable,
100                 filesTable, mutationStatusTable, kinds.map!(a => cast(int) a), order);
101         auto stmt = db.prepare(sql);
102         auto res = stmt.execute;
103         if (res.empty) {
104             rval.st = NextMutationEntry.Status.done;
105             return rval;
106         }
107 
108         auto v = res.front;
109 
110         auto mp = MutationPoint(Offset(v.peek!uint(3), v.peek!uint(4)));
111         mp.mutations = [Mutation(v.peek!long(1).to!(Mutation.Kind))];
112         auto pkey = MutationId(v.peek!long(0));
113         auto file = Path(FileName(v.peek!string(7)));
114         auto sloc = SourceLoc(v.peek!uint(5), v.peek!uint(6));
115         auto lang = v.peek!long(8).to!Language;
116 
117         rval.entry = MutationEntry(pkey, file, sloc, mp, v.peek!long(2).dur!"msecs", lang);
118 
119         return rval;
120     }
121 
122     void iterateMutants(const Mutation.Kind[] kinds, void delegate(const ref IterateMutantRow) dg) @trusted {
123         import std.algorithm : map;
124         import dextool.plugin.mutate.backend.utility : checksum;
125 
126         immutable all_mutants = format("SELECT
127             t0.id,
128             t3.status,
129             t0.kind,
130             t3.time,
131             t1.offset_begin,
132             t1.offset_end,
133             t1.line,
134             t1.column,
135             t1.line_end,
136             t1.column_end,
137             t2.path,
138             t2.checksum0,
139             t2.checksum1,
140             t2.lang,
141             t4.nomut
142             FROM %s t0,%s t1,%s t2, %s t3, %s t4
143             WHERE
144             t0.kind IN (%(%s,%)) AND
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,
150                 filesTable, mutationStatusTable, srcMetadataTable, kinds.map!(a => cast(int) a));
151 
152         try {
153             auto res = db.prepare(all_mutants).execute;
154             foreach (ref r; res) {
155                 IterateMutantRow d;
156                 d.id = MutationId(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(4), r.peek!uint(5));
160                 d.mutationPoint = MutationPoint(offset, null);
161                 d.file = r.peek!string(10);
162                 d.fileChecksum = checksum(r.peek!long(11), r.peek!long(12));
163                 d.sloc = SourceLoc(r.peek!uint(6), r.peek!uint(7));
164                 d.slocEnd = SourceLoc(r.peek!uint(8), r.peek!uint(9));
165                 d.lang = r.peek!long(13).to!Language;
166 
167                 d.testCases = db.getTestCases(d.id);
168 
169                 if (r.peek!long(14) != 0)
170                     d.attrs = MutantMetaData(d.id, MutantAttr(NoMut.init));
171                 dg(d);
172             }
173         } catch (Exception e) {
174             logger.error(e.msg).collectException;
175         }
176     }
177 
178     FileRow[] getDetailedFiles() @trusted {
179         import std.array : appender;
180         import dextool.plugin.mutate.backend.utility : checksum;
181 
182         enum files_q = format("SELECT t0.path, t0.checksum0, t0.checksum1, t0.lang, t0.id FROM %s t0",
183                     filesTable);
184         auto app = appender!(FileRow[])();
185         foreach (ref r; db.prepare(files_q).execute) {
186             auto fr = FileRow(r.peek!string(0).Path, checksum(r.peek!long(1),
187                     r.peek!long(2)), r.peek!Language(3), r.peek!long(4).FileId);
188             app.put(fr);
189         }
190 
191         return app.data;
192     }
193 
194     /** Iterate over the mutants in a specific file.
195      *
196      * Mutants are guaranteed to be ordered by their starting offset in the
197      * file.
198      *
199      * Params:
200      *  kinds = the type of mutation operators to have in the report
201      *  file = the file to retrieve mutants from
202      *  dg = callback for reach row
203      */
204     void iterateFileMutants(const Mutation.Kind[] kinds, Path file,
205             void delegate(ref const FileMutantRow) dg) @trusted {
206         import std.algorithm : map;
207 
208         immutable all_fmut = format("SELECT
209             t0.id,
210             t0.kind,
211             t3.status,
212             t1.offset_begin,
213             t1.offset_end,
214             t1.line,
215             t1.column,
216             t1.line_end,
217             t1.column_end,
218             t2.lang
219             FROM %s t0, %s t1, %s t2, %s t3
220             WHERE
221             t0.kind IN (%(%s,%)) AND
222             t0.st_id = t3.id AND
223             t0.mp_id = t1.id AND
224             t1.file_id = t2.id AND
225             t2.path = :path
226             ORDER BY t1.offset_begin
227             ", mutationTable, mutationPointTable,
228                 filesTable, mutationStatusTable, kinds.map!(a => cast(int) a));
229 
230         auto stmt = db.prepare(all_fmut);
231         stmt.bind(":path", cast(string) file);
232         foreach (ref r; stmt.execute) {
233             FileMutantRow fr;
234             fr.id = MutationId(r.peek!long(0));
235             fr.mutation = Mutation(r.peek!int(1).to!(Mutation.Kind),
236                     r.peek!int(2).to!(Mutation.Status));
237             auto offset = Offset(r.peek!uint(3), r.peek!uint(4));
238             fr.mutationPoint = MutationPoint(offset, null);
239             fr.sloc = SourceLoc(r.peek!uint(5), r.peek!uint(6));
240             fr.slocEnd = SourceLoc(r.peek!uint(7), r.peek!uint(8));
241             fr.lang = r.peek!int(9).to!Language;
242 
243             dg(fr);
244         }
245     }
246 }
247 
248 struct IterateMutantRow {
249     MutationId id;
250     Mutation mutation;
251     MutationPoint mutationPoint;
252     Path file;
253     Checksum fileChecksum;
254     SourceLoc sloc;
255     SourceLoc slocEnd;
256     TestCase[] testCases;
257     Language lang;
258     MutantMetaData attrs;
259 }
260 
261 struct FileRow {
262     Path file;
263     Checksum fileChecksum;
264     Language lang;
265     FileId id;
266 }
267 
268 struct FileMutantRow {
269     MutationId id;
270     Mutation mutation;
271     MutationPoint mutationPoint;
272     SourceLoc sloc;
273     SourceLoc slocEnd;
274     Language lang;
275 }