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