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 
19 import dextool.type : AbsolutePath, Path;
20 import dextool.plugin.mutate.backend.type;
21 
22 import dextool.plugin.mutate.backend.database.schema;
23 public import dextool.plugin.mutate.backend.database.type;
24 
25 /** Wrapper for a sqlite3 database that provide a uniform, easy-to-use
26  * interface for the mutation testing plugin.
27  */
28 struct Database {
29     import std.conv : to;
30     import std.exception : collectException;
31     import std.typecons : Nullable;
32     import dextool.plugin.mutate.backend.type : MutationPoint, Mutation,
33         Checksum;
34     import dextool.plugin.mutate.type : MutationOrder;
35     import dextool.plugin.mutate.backend.database.standalone : SDatabase = Database;
36 
37     SDatabase db;
38     alias db this;
39 
40     private MutationOrder mut_order;
41 
42     static auto make(AbsolutePath db, MutationOrder mut_order) @safe {
43         return Database(SDatabase.make(db), mut_order);
44     }
45 
46     // Not movable. The database should only be passed around as a reference,
47     // if at all.
48     @disable this(this);
49 
50     Nullable!Checksum getFileChecksum(const Path p) @trusted {
51         import dextool.plugin.mutate.backend.utility : checksum;
52 
53         auto stmt = db.prepare("SELECT checksum0,checksum1 FROM files WHERE path=:path");
54         stmt.bind(":path", cast(string) p);
55         auto res = stmt.execute;
56 
57         typeof(return) rval;
58         if (!res.empty) {
59             rval = checksum(res.front.peek!long(0), res.front.peek!long(1));
60         }
61 
62         return rval;
63     }
64 
65     /** Get the next mutation point + 1 mutant for it that has status unknown.
66      *
67      * TODO to run many instances in parallel the mutation should be locked.
68      * TODO remove nothrow or add a retry-loop
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) nothrow @trusted {
76         import std.algorithm : map;
77         import std.exception : collectException;
78         import std.format : format;
79         import dextool.plugin.mutate.backend.type;
80         import dextool.type : FileName;
81 
82         typeof(return) rval;
83 
84         auto order = mut_order == MutationOrder.random ? "ORDER BY RANDOM()" : "";
85 
86         try {
87             auto prep_str = format("SELECT
88                                    mutation.id,
89                                    mutation.kind,
90                                    mutation.time,
91                                    mutation_point.offset_begin,
92                                    mutation_point.offset_end,
93                                    mutation_point.line,
94                                    mutation_point.column,
95                                    files.path
96                                    FROM mutation,mutation_point,files
97                                    WHERE
98                                    mutation.status == 0 AND
99                                    mutation.mp_id == mutation_point.id AND
100                                    mutation_point.file_id == files.id AND
101                                    mutation.kind IN (%(%s,%)) %s LIMIT 1",
102                     kinds.map!(a => cast(int) a), order);
103             auto stmt = db.prepare(prep_str);
104             // TODO this should work. why doesn't it?
105             //stmt.bind(":kinds", format("%(%s,%)", kinds.map!(a => cast(int) a)));
106             auto res = stmt.execute;
107             if (res.empty) {
108                 rval.st = NextMutationEntry.Status.done;
109                 return rval;
110             }
111 
112             auto v = res.front;
113 
114             auto mp = MutationPoint(Offset(v.peek!uint(3), v.peek!uint(4)));
115             mp.mutations = [Mutation(v.peek!long(1).to!(Mutation.Kind))];
116             auto pkey = MutationId(v.peek!long(0));
117             auto file = Path(FileName(v.peek!string(7)));
118             auto sloc = SourceLoc(v.peek!uint(5), v.peek!uint(6));
119 
120             rval.entry = MutationEntry(pkey, file, sloc, mp, v.peek!long(2).dur!"msecs");
121         }
122         catch (Exception e) {
123             rval.st = NextMutationEntry.Status.queryError;
124             collectException(logger.warning(e.msg));
125         }
126 
127         return rval;
128     }
129 
130     void iterateMutants(const Mutation.Kind[] kinds, void delegate(const ref IterateMutantRow) dg) nothrow @trusted {
131         import std.algorithm : map;
132         import std.format : format;
133         import dextool.plugin.mutate.backend.utility : checksum;
134 
135         immutable all_mutants = "SELECT
136             mutation.id,
137             mutation.status,
138             mutation.kind,
139             mutation.time,
140             mutation_point.offset_begin,
141             mutation_point.offset_end,
142             mutation_point.line,
143             mutation_point.column,
144             files.path,
145             files.checksum0,
146             files.checksum1
147             FROM mutation,mutation_point,files
148             WHERE
149             mutation.kind IN (%(%s,%)) AND
150             mutation.mp_id == mutation_point.id AND
151             mutation_point.file_id == files.id
152             ORDER BY mutation.status";
153 
154         try {
155             auto res = db.prepare(format(all_mutants, kinds.map!(a => cast(int) a))).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(8);
164                 d.fileChecksum = checksum(r.peek!long(9), r.peek!long(10));
165                 d.sloc = SourceLoc(r.peek!uint(6), r.peek!uint(7));
166 
167                 d.testCases = db.getTestCases(d.id);
168 
169                 dg(d);
170             }
171         }
172         catch (Exception e) {
173             logger.error(e.msg).collectException;
174         }
175     }
176 }
177 
178 struct IterateMutantRow {
179     MutationId id;
180     Mutation mutation;
181     MutationPoint mutationPoint;
182     Path file;
183     Checksum fileChecksum;
184     SourceLoc sloc;
185     TestCase[] testCases;
186 }