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