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 uint maxParallel) @trusted { 64 import dextool.plugin.mutate.backend.type; 65 66 typeof(return) rval; 67 68 static immutable sql = "SELECT * FROM 69 (SELECT 70 t3.id, 71 t0.kind, 72 t3.compile_time_ms, 73 t3.test_time_ms, 74 t1.offset_begin, 75 t1.offset_end, 76 t1.line, 77 t1.column, 78 t2.path, 79 t2.lang 80 FROM " ~ mutationTable ~ " t0," ~ mutationPointTable ~ " t1," ~ filesTable 81 ~ " t2," ~ mutationStatusTable ~ " t3," ~ mutantWorklistTable ~ " 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 :parallel) 88 ORDER BY RANDOM() LIMIT 1"; 89 auto stmt = db.db.prepare(sql); 90 stmt.get.bind(":parallel", maxParallel); 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 = MutationStatusId(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 void iterateMutantStatus(scope void delegate(const Mutation.Status, const SysTime added) dg) @trusted { 113 static immutable sql = "SELECT t1.status,t1.added_ts FROM " 114 ~ mutationTable ~ " t0, " ~ mutationStatusTable ~ " t1 115 WHERE t0.st_id = t1.id ORDER BY t1.added_ts"; 116 auto stmt = db.db.prepare(sql); 117 try { 118 foreach (ref r; stmt.get.execute) { 119 dg(r.peek!int(0).to!(Mutation.Status), r.peek!string(1).fromSqLiteDateTime); 120 } 121 } catch (Exception e) { 122 logger.error(e.msg).collectException; 123 } 124 } 125 126 void iterateMutants(scope void delegate(const ref IterateMutantRow) dg) @trusted { 127 import dextool.plugin.mutate.backend.utility : checksum; 128 129 immutable all_mutants = format("SELECT 130 t0.st_id, 131 t3.status, 132 t0.kind, 133 t1.offset_begin, 134 t1.offset_end, 135 t1.line, 136 t1.column, 137 t1.line_end, 138 t1.column_end, 139 t2.path, 140 t2.checksum, 141 t2.lang, 142 t4.nomut 143 FROM %s t0,%s t1,%s t2, %s t3, %s t4 144 WHERE 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, filesTable, 150 mutationStatusTable, srcMetadataTable); 151 152 try { 153 auto stmt = db.db.prepare(all_mutants); 154 foreach (ref r; stmt.get.execute) { 155 IterateMutantRow d; 156 d.id = MutationStatusId(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(3), r.peek!uint(4)); 160 d.mutationPoint = MutationPoint(offset, null); 161 d.file = r.peek!string(9).Path; 162 d.fileChecksum = checksum(r.peek!long(10)); 163 d.sloc = SourceLoc(r.peek!uint(5), r.peek!uint(6)); 164 d.slocEnd = SourceLoc(r.peek!uint(7), r.peek!uint(8)); 165 d.lang = r.peek!long(11).to!Language; 166 167 if (r.peek!long(12) != 0) { 168 d.attrs = MutantMetaData(d.id, MutantAttr(NoMut.init)); 169 } 170 dg(d); 171 } 172 } catch (Exception e) { 173 logger.error(e.msg).collectException; 174 } 175 } 176 177 void iterateMutants(void delegate(const ref IterateMutantRow2) dg) @trusted { 178 static immutable sql = "SELECT 179 t3.id, 180 t0.kind, 181 t3.status, 182 t3.exit_code, 183 t3.prio, 184 t2.path, 185 t1.line, 186 t1.column, 187 t3.update_ts, 188 (SELECT count(*) FROM " ~ killedTestCaseTable ~ " WHERE t3.id=st_id) as vc_cnt, 189 t4.nomut 190 FROM " ~ mutationTable ~ " t0," ~ mutationPointTable ~ " t1," ~ filesTable 191 ~ " t2, " ~ mutationStatusTable ~ " t3, " ~ srcMetadataTable ~ " t4 192 WHERE 193 t0.st_id = t3.id AND 194 t0.mp_id = t1.id AND 195 t1.file_id = t2.id AND 196 t0.id = t4.mut_id 197 GROUP BY t3.id 198 ORDER BY t2.path,t1.line,t3.id"; 199 200 try { 201 auto stmt = db.db.prepare(sql); 202 foreach (ref r; stmt.get.execute) { 203 IterateMutantRow2 d; 204 d.stId = MutationStatusId(r.peek!long(0)); 205 d.mutant = Mutation(r.peek!int(1).to!(Mutation.Kind), 206 r.peek!int(2).to!(Mutation.Status)); 207 d.exitStatus = r.peek!int(3).ExitStatus; 208 d.prio = r.peek!long(4).MutantPrio; 209 d.file = r.peek!string(5).Path; 210 d.sloc = SourceLoc(r.peek!uint(6), r.peek!uint(7)); 211 d.tested = r.peek!string(8).fromSqLiteDateTime; 212 d.killedByTestCases = r.peek!long(9); 213 214 if (r.peek!long(10) != 0) { 215 d.attrs = MutantMetaData(d.stId, MutantAttr(NoMut.init)); 216 } 217 218 dg(d); 219 } 220 } catch (Exception e) { 221 logger.error(e.msg).collectException; 222 } 223 } 224 225 FileRow[] getDetailedFiles() @trusted { 226 import std.array : appender; 227 import dextool.plugin.mutate.backend.utility : checksum; 228 229 static immutable files_q = "SELECT t0.path, t0.checksum, t0.lang, t0.id FROM " 230 ~ filesTable ~ " t0"; 231 auto app = appender!(FileRow[])(); 232 auto stmt = db.db.prepare(files_q); 233 foreach (ref r; stmt.get.execute) { 234 auto fr = FileRow(r.peek!string(0).Path, 235 checksum(r.peek!long(1)), r.peek!Language(2), r.peek!long(3).FileId); 236 app.put(fr); 237 } 238 239 return app.data; 240 } 241 242 /** Iterate over the mutants in a specific file. 243 * 244 * Mutants are guaranteed to be ordered by their starting offset in the 245 * file. 246 * 247 * Params: 248 * file = the file to retrieve mutants from 249 * dg = callback for reach row 250 */ 251 void iterateFileMutants(Path file, scope void delegate(ref const FileMutantRow) dg) @trusted { 252 import std.algorithm : map; 253 254 // TODO: remove the dummy value zero. it is just there to avoid having 255 // to update all the peeks. 256 static immutable sql = "SELECT 257 0, 258 t3.id, 259 t0.kind, 260 t3.status, 261 t1.offset_begin, 262 t1.offset_end, 263 t1.line, 264 t1.column, 265 t1.line_end, 266 t1.column_end, 267 t2.lang 268 FROM " ~ mutationTable ~ " t0, " ~ mutationPointTable ~ " t1, " 269 ~ filesTable ~ " t2, " ~ mutationStatusTable ~ " t3 270 WHERE 271 t0.st_id = t3.id AND 272 t0.mp_id = t1.id AND 273 t1.file_id = t2.id AND 274 t2.path = :path 275 GROUP BY t3.id 276 ORDER BY t1.offset_begin"; 277 278 auto stmt = db.db.prepare(sql); 279 stmt.get.bind(":path", cast(string) file); 280 foreach (ref r; stmt.get.execute) { 281 FileMutantRow fr; 282 fr.stId = MutationStatusId(r.peek!long(1)); 283 fr.mutation = Mutation(r.peek!int(2).to!(Mutation.Kind), 284 r.peek!int(3).to!(Mutation.Status)); 285 auto offset = Offset(r.peek!uint(4), r.peek!uint(5)); 286 fr.mutationPoint = MutationPoint(offset, null); 287 fr.sloc = SourceLoc(r.peek!uint(6), r.peek!uint(7)); 288 fr.slocEnd = SourceLoc(r.peek!uint(8), r.peek!uint(9)); 289 fr.lang = r.peek!int(10).to!Language; 290 291 dg(fr); 292 } 293 } 294 } 295 296 struct IterateMutantRow { 297 MutationStatusId id; 298 Mutation mutation; 299 MutationPoint mutationPoint; 300 Path file; 301 Checksum fileChecksum; 302 SourceLoc sloc; 303 SourceLoc slocEnd; 304 Language lang; 305 MutantMetaData attrs; 306 } 307 308 struct IterateMutantRow2 { 309 MutationStatusId stId; 310 Mutation mutant; 311 ExitStatus exitStatus; 312 Path file; 313 SourceLoc sloc; 314 MutantPrio prio; 315 SysTime tested; 316 long killedByTestCases; 317 MutantMetaData attrs; 318 } 319 320 struct FileRow { 321 Path file; 322 Checksum fileChecksum; 323 Language lang; 324 FileId id; 325 } 326 327 struct FileMutantRow { 328 MutationStatusId stId; 329 Mutation mutation; 330 MutationPoint mutationPoint; 331 SourceLoc sloc; 332 SourceLoc slocEnd; 333 Language lang; 334 }