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