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.algorithm : map; 19 import std.datetime : SysTime; 20 import std.exception : collectException; 21 import std.format : format; 22 23 public import miniorm : toSqliteDateTime, fromSqLiteDateTime, spinSql; 24 25 import dextool.type : AbsolutePath, Path; 26 import dextool.plugin.mutate.backend.type; 27 28 import dextool.plugin.mutate.backend.database.schema; 29 import dextool.plugin.mutate.type : MutationOrder; 30 public import dextool.plugin.mutate.backend.database.standalone; 31 public import dextool.plugin.mutate.backend.database.type; 32 33 // feels good constant. If it takes more than 5 minutes to open the database 34 // something is wrong... 35 immutable dbOpenTimeout = 5.dur!"minutes"; 36 37 /** Wrapper for a sqlite3 database that provide a uniform, easy-to-use 38 * interface for the mutation testing plugin. 39 */ 40 struct Database { 41 import std.conv : to; 42 import std.datetime : SysTime; 43 import std.exception : collectException; 44 import std.typecons : Nullable; 45 import dextool.plugin.mutate.backend.database.standalone : SDatabase = Database; 46 import dextool.plugin.mutate.backend.type : MutationPoint, Mutation, Checksum; 47 48 SDatabase db; 49 alias db this; 50 51 static auto make(AbsolutePath db) @safe { 52 return Database(SDatabase.make(db)); 53 } 54 55 /** Get the next mutation from the worklist to test by the highest 56 * priority. 57 * 58 * The chosen point is randomised. 59 * 60 * Params: 61 * kind = kind of mutation to retrieve. 62 */ 63 NextMutationEntry nextMutation(const(Mutation.Kind)[] kinds, const uint maxParallel) @trusted { 64 import dextool.plugin.mutate.backend.type; 65 66 typeof(return) rval; 67 68 immutable sql = format(" 69 SELECT * FROM 70 (SELECT 71 t0.id, 72 t0.kind, 73 t3.compile_time_ms, 74 t3.test_time_ms, 75 t1.offset_begin, 76 t1.offset_end, 77 t1.line, 78 t1.column, 79 t2.path, 80 t2.lang 81 FROM %1$s t0,%2$s t1,%3$s t2,%4$s t3, %5$s t4 82 WHERE 83 t0.st_id = t3.id AND 84 t3.id = t4.id AND 85 t0.mp_id == t1.id AND 86 t1.file_id == t2.id 87 ORDER BY t4.prio DESC LIMIT %6$s) 88 ORDER BY RANDOM() LIMIT 1", mutationTable, mutationPointTable, 89 filesTable, mutationStatusTable, mutantWorklistTable, maxParallel); 90 auto stmt = db.db.prepare(sql); 91 auto res = stmt.get.execute; 92 if (res.empty) { 93 rval.st = NextMutationEntry.Status.done; 94 return rval; 95 } 96 97 auto v = res.front; 98 99 auto mp = MutationPoint(Offset(v.peek!uint(4), v.peek!uint(5))); 100 mp.mutations = [Mutation(v.peek!long(1).to!(Mutation.Kind))]; 101 auto pkey = MutationId(v.peek!long(0)); 102 auto file = Path(v.peek!string(8)); 103 auto sloc = SourceLoc(v.peek!uint(6), v.peek!uint(7)); 104 auto lang = v.peek!long(9).to!Language; 105 106 rval.entry = MutationEntry(pkey, file, sloc, mp, 107 MutantTimeProfile(v.peek!long(2).dur!"msecs", v.peek!long(3).dur!"msecs"), lang); 108 109 return rval; 110 } 111 112 /// Iterate over the mutants of `kinds` in oldest->newest datum order. 113 void iterateMutantStatus(const Mutation.Kind[] kinds, 114 scope void delegate(const Mutation.Status, const SysTime added) dg) @trusted { 115 immutable sql = format("SELECT t1.status,t1.added_ts FROM %s t0, %s t1 116 WHERE 117 t0.st_id = t1.id AND 118 t0.kind IN (%(%s,%)) 119 ORDER BY t1.added_ts", 120 mutationTable, mutationStatusTable, kinds.map!(a => cast(int) a)); 121 auto stmt = db.db.prepare(sql); 122 try { 123 foreach (ref r; stmt.get.execute) { 124 dg(r.peek!int(0).to!(Mutation.Status), r.peek!string(1).fromSqLiteDateTime); 125 } 126 } catch (Exception e) { 127 logger.error(e.msg).collectException; 128 } 129 } 130 131 void iterateMutants(const Mutation.Kind[] kinds, scope void delegate(const ref IterateMutantRow) dg) @trusted { 132 import dextool.plugin.mutate.backend.utility : checksum; 133 134 immutable all_mutants = format("SELECT 135 t0.id, 136 t3.status, 137 t0.kind, 138 t1.offset_begin, 139 t1.offset_end, 140 t1.line, 141 t1.column, 142 t1.line_end, 143 t1.column_end, 144 t2.path, 145 t2.checksum0, 146 t2.checksum1, 147 t2.lang, 148 t4.nomut 149 FROM %s t0,%s t1,%s t2, %s t3, %s t4 150 WHERE 151 t0.kind IN (%(%s,%)) AND 152 t0.st_id = t3.id AND 153 t0.mp_id = t1.id AND 154 t1.file_id = t2.id AND 155 t0.id = t4.mut_id 156 ORDER BY t3.status", mutationTable, mutationPointTable, 157 filesTable, mutationStatusTable, srcMetadataTable, kinds.map!(a => cast(int) a)); 158 159 try { 160 auto stmt = db.db.prepare(all_mutants); 161 foreach (ref r; stmt.get.execute) { 162 IterateMutantRow d; 163 d.id = MutationId(r.peek!long(0)); 164 d.mutation = Mutation(r.peek!int(2).to!(Mutation.Kind), 165 r.peek!int(1).to!(Mutation.Status)); 166 auto offset = Offset(r.peek!uint(3), r.peek!uint(4)); 167 d.mutationPoint = MutationPoint(offset, null); 168 d.file = r.peek!string(9).Path; 169 d.fileChecksum = checksum(r.peek!long(10), r.peek!long(11)); 170 d.sloc = SourceLoc(r.peek!uint(5), r.peek!uint(6)); 171 d.slocEnd = SourceLoc(r.peek!uint(7), r.peek!uint(8)); 172 d.lang = r.peek!long(12).to!Language; 173 174 if (r.peek!long(13) != 0) { 175 d.attrs = MutantMetaData(d.id, MutantAttr(NoMut.init)); 176 } 177 dg(d); 178 } 179 } catch (Exception e) { 180 logger.error(e.msg).collectException; 181 } 182 } 183 184 void iterateMutants(const Mutation.Kind[] kinds, void delegate(const ref IterateMutantRow2) dg) @trusted { 185 immutable sql = format("SELECT 186 t0.id, 187 t0.kind, 188 t3.status, 189 t3.exit_code, 190 t3.prio, 191 t2.path, 192 t1.line, 193 t1.column, 194 t3.update_ts, 195 (SELECT count(*) FROM %s WHERE t3.id=st_id) as vc_cnt, 196 t4.nomut 197 FROM %s t0,%s t1,%s t2, %s t3, %s t4 198 WHERE 199 t0.kind IN (%(%s,%)) AND 200 t0.st_id = t3.id AND 201 t0.mp_id = t1.id AND 202 t1.file_id = t2.id AND 203 t0.id = t4.mut_id 204 GROUP BY t3.id 205 ORDER BY t2.path,t1.line,t3.id", killedTestCaseTable, mutationTable, mutationPointTable, 206 filesTable, mutationStatusTable, srcMetadataTable, kinds.map!"cast(int) a"); 207 208 try { 209 auto stmt = db.db.prepare(sql); 210 foreach (ref r; stmt.get.execute) { 211 IterateMutantRow2 d; 212 d.id = MutationId(r.peek!long(0)); 213 d.mutant = Mutation(r.peek!int(1).to!(Mutation.Kind), 214 r.peek!int(2).to!(Mutation.Status)); 215 d.exitStatus = r.peek!int(3).ExitStatus; 216 d.prio = r.peek!long(4).MutantPrio; 217 d.file = r.peek!string(5).Path; 218 d.sloc = SourceLoc(r.peek!uint(6), r.peek!uint(7)); 219 d.tested = r.peek!string(8).fromSqLiteDateTime; 220 d.killedByTestCases = r.peek!long(9); 221 222 if (r.peek!long(10) != 0) { 223 d.attrs = MutantMetaData(d.id, MutantAttr(NoMut.init)); 224 } 225 226 dg(d); 227 } 228 } catch (Exception e) { 229 logger.error(e.msg).collectException; 230 } 231 } 232 233 FileRow[] getDetailedFiles() @trusted { 234 import std.array : appender; 235 import dextool.plugin.mutate.backend.utility : checksum; 236 237 enum files_q = format("SELECT t0.path, t0.checksum0, t0.checksum1, t0.lang, t0.id FROM %s t0", 238 filesTable); 239 auto app = appender!(FileRow[])(); 240 auto stmt = db.db.prepare(files_q); 241 foreach (ref r; stmt.get.execute) { 242 auto fr = FileRow(r.peek!string(0).Path, checksum(r.peek!long(1), 243 r.peek!long(2)), r.peek!Language(3), r.peek!long(4).FileId); 244 app.put(fr); 245 } 246 247 return app.data; 248 } 249 250 /** Iterate over the mutants in a specific file. 251 * 252 * Mutants are guaranteed to be ordered by their starting offset in the 253 * file. 254 * 255 * Params: 256 * kinds = the type of mutation operators to have in the report 257 * file = the file to retrieve mutants from 258 * dg = callback for reach row 259 */ 260 void iterateFileMutants(const Mutation.Kind[] kinds, Path file, 261 scope void delegate(ref const FileMutantRow) dg) @trusted { 262 import std.algorithm : map; 263 264 immutable all_fmut = format("SELECT 265 t0.id, 266 t0.kind, 267 t3.status, 268 t1.offset_begin, 269 t1.offset_end, 270 t1.line, 271 t1.column, 272 t1.line_end, 273 t1.column_end, 274 t2.lang 275 FROM %s t0, %s t1, %s t2, %s t3 276 WHERE 277 t0.kind IN (%(%s,%)) AND 278 t0.st_id = t3.id AND 279 t0.mp_id = t1.id AND 280 t1.file_id = t2.id AND 281 t2.path = :path 282 ORDER BY t1.offset_begin 283 ", mutationTable, mutationPointTable, 284 filesTable, mutationStatusTable, kinds.map!(a => cast(int) a)); 285 286 auto stmt = db.db.prepare(all_fmut); 287 stmt.get.bind(":path", cast(string) file); 288 foreach (ref r; stmt.get.execute) { 289 FileMutantRow fr; 290 fr.id = MutationId(r.peek!long(0)); 291 fr.mutation = Mutation(r.peek!int(1).to!(Mutation.Kind), 292 r.peek!int(2).to!(Mutation.Status)); 293 auto offset = Offset(r.peek!uint(3), r.peek!uint(4)); 294 fr.mutationPoint = MutationPoint(offset, null); 295 fr.sloc = SourceLoc(r.peek!uint(5), r.peek!uint(6)); 296 fr.slocEnd = SourceLoc(r.peek!uint(7), r.peek!uint(8)); 297 fr.lang = r.peek!int(9).to!Language; 298 299 dg(fr); 300 } 301 } 302 } 303 304 struct IterateMutantRow { 305 MutationId id; 306 Mutation mutation; 307 MutationPoint mutationPoint; 308 Path file; 309 Checksum fileChecksum; 310 SourceLoc sloc; 311 SourceLoc slocEnd; 312 Language lang; 313 MutantMetaData attrs; 314 } 315 316 struct IterateMutantRow2 { 317 MutationId id; 318 Mutation mutant; 319 ExitStatus exitStatus; 320 Path file; 321 SourceLoc sloc; 322 MutantPrio prio; 323 SysTime tested; 324 long killedByTestCases; 325 MutantMetaData attrs; 326 } 327 328 struct FileRow { 329 Path file; 330 Checksum fileChecksum; 331 Language lang; 332 FileId id; 333 } 334 335 struct FileMutantRow { 336 MutationId id; 337 Mutation mutation; 338 MutationPoint mutationPoint; 339 SourceLoc sloc; 340 SourceLoc slocEnd; 341 Language lang; 342 }