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