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 }