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 /** Get the next mutation point + 1 mutant for it that has status unknown. 50 * 51 * TODO to run many instances in parallel the mutation should be locked. 52 * 53 * The chosen point is randomised. 54 * 55 * Params: 56 * kind = kind of mutation to retrieve. 57 */ 58 NextMutationEntry nextMutation(const(Mutation.Kind)[] kinds) @trusted { 59 import std.algorithm : map; 60 import std.exception : collectException; 61 import dextool.plugin.mutate.backend.type; 62 import dextool.type : FileName; 63 64 typeof(return) rval; 65 66 auto order = mut_order == MutationOrder.random ? "ORDER BY RANDOM()" : ""; 67 68 immutable sql = format("SELECT 69 t0.id, 70 t0.kind, 71 t3.time, 72 t1.offset_begin, 73 t1.offset_end, 74 t1.line, 75 t1.column, 76 t2.path, 77 t2.lang 78 FROM %s t0,%s t1,%s t2,%s t3 79 WHERE 80 t0.st_id = t3.id AND 81 t3.status == 0 AND 82 t0.mp_id == t1.id AND 83 t1.file_id == t2.id AND 84 t0.kind IN (%(%s,%)) %s LIMIT 1", mutationTable, mutationPointTable, 85 filesTable, mutationStatusTable, kinds.map!(a => cast(int) a), order); 86 auto stmt = 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(3), v.peek!uint(4))); 96 mp.mutations = [Mutation(v.peek!long(1).to!(Mutation.Kind))]; 97 auto pkey = MutationId(v.peek!long(0)); 98 auto file = Path(FileName(v.peek!string(7))); 99 auto sloc = SourceLoc(v.peek!uint(5), v.peek!uint(6)); 100 auto lang = v.peek!long(8).to!Language; 101 102 rval.entry = MutationEntry(pkey, file, sloc, mp, v.peek!long(2).dur!"msecs", lang); 103 104 return rval; 105 } 106 107 void iterateMutants(const Mutation.Kind[] kinds, void delegate(const ref IterateMutantRow) dg) @trusted { 108 import std.algorithm : map; 109 import dextool.plugin.mutate.backend.utility : checksum; 110 111 immutable all_mutants = format("SELECT 112 t0.id, 113 t3.status, 114 t0.kind, 115 t3.time, 116 t1.offset_begin, 117 t1.offset_end, 118 t1.line, 119 t1.column, 120 t1.line_end, 121 t1.column_end, 122 t2.path, 123 t2.checksum0, 124 t2.checksum1, 125 t2.lang, 126 t4.nomut 127 FROM %s t0,%s t1,%s t2, %s t3, %s t4 128 WHERE 129 t0.kind IN (%(%s,%)) AND 130 t0.st_id = t3.id AND 131 t0.mp_id = t1.id AND 132 t1.file_id = t2.id AND 133 t0.id = t4.mut_id 134 ORDER BY t3.status", mutationTable, mutationPointTable, 135 filesTable, mutationStatusTable, srcMetadataTable, kinds.map!(a => cast(int) a)); 136 137 try { 138 auto stmt = db.prepare(all_mutants); 139 foreach (ref r; stmt.get.execute) { 140 IterateMutantRow d; 141 d.id = MutationId(r.peek!long(0)); 142 d.mutation = Mutation(r.peek!int(2).to!(Mutation.Kind), 143 r.peek!int(1).to!(Mutation.Status)); 144 auto offset = Offset(r.peek!uint(4), r.peek!uint(5)); 145 d.mutationPoint = MutationPoint(offset, null); 146 d.file = r.peek!string(10); 147 d.fileChecksum = checksum(r.peek!long(11), r.peek!long(12)); 148 d.sloc = SourceLoc(r.peek!uint(6), r.peek!uint(7)); 149 d.slocEnd = SourceLoc(r.peek!uint(8), r.peek!uint(9)); 150 d.lang = r.peek!long(13).to!Language; 151 152 if (r.peek!long(14) != 0) 153 d.attrs = MutantMetaData(d.id, MutantAttr(NoMut.init)); 154 dg(d); 155 } 156 } catch (Exception e) { 157 logger.error(e.msg).collectException; 158 } 159 } 160 161 FileRow[] getDetailedFiles() @trusted { 162 import std.array : appender; 163 import dextool.plugin.mutate.backend.utility : checksum; 164 165 enum files_q = format("SELECT t0.path, t0.checksum0, t0.checksum1, t0.lang, t0.id FROM %s t0", 166 filesTable); 167 auto app = appender!(FileRow[])(); 168 auto stmt = db.prepare(files_q); 169 foreach (ref r; stmt.get.execute) { 170 auto fr = FileRow(r.peek!string(0).Path, checksum(r.peek!long(1), 171 r.peek!long(2)), r.peek!Language(3), r.peek!long(4).FileId); 172 app.put(fr); 173 } 174 175 return app.data; 176 } 177 178 /** Iterate over the mutants in a specific file. 179 * 180 * Mutants are guaranteed to be ordered by their starting offset in the 181 * file. 182 * 183 * Params: 184 * kinds = the type of mutation operators to have in the report 185 * file = the file to retrieve mutants from 186 * dg = callback for reach row 187 */ 188 void iterateFileMutants(const Mutation.Kind[] kinds, Path file, 189 void delegate(ref const FileMutantRow) dg) @trusted { 190 import std.algorithm : map; 191 192 immutable all_fmut = format("SELECT 193 t0.id, 194 t0.kind, 195 t3.status, 196 t1.offset_begin, 197 t1.offset_end, 198 t1.line, 199 t1.column, 200 t1.line_end, 201 t1.column_end, 202 t2.lang 203 FROM %s t0, %s t1, %s t2, %s t3 204 WHERE 205 t0.kind IN (%(%s,%)) AND 206 t0.st_id = t3.id AND 207 t0.mp_id = t1.id AND 208 t1.file_id = t2.id AND 209 t2.path = :path 210 ORDER BY t1.offset_begin 211 ", mutationTable, mutationPointTable, 212 filesTable, mutationStatusTable, kinds.map!(a => cast(int) a)); 213 214 auto stmt = db.prepare(all_fmut); 215 stmt.get.bind(":path", cast(string) file); 216 foreach (ref r; stmt.get.execute) { 217 FileMutantRow fr; 218 fr.id = MutationId(r.peek!long(0)); 219 fr.mutation = Mutation(r.peek!int(1).to!(Mutation.Kind), 220 r.peek!int(2).to!(Mutation.Status)); 221 auto offset = Offset(r.peek!uint(3), r.peek!uint(4)); 222 fr.mutationPoint = MutationPoint(offset, null); 223 fr.sloc = SourceLoc(r.peek!uint(5), r.peek!uint(6)); 224 fr.slocEnd = SourceLoc(r.peek!uint(7), r.peek!uint(8)); 225 fr.lang = r.peek!int(9).to!Language; 226 227 dg(fr); 228 } 229 } 230 } 231 232 struct IterateMutantRow { 233 MutationId id; 234 Mutation mutation; 235 MutationPoint mutationPoint; 236 Path file; 237 Checksum fileChecksum; 238 SourceLoc sloc; 239 SourceLoc slocEnd; 240 Language lang; 241 MutantMetaData attrs; 242 } 243 244 struct FileRow { 245 Path file; 246 Checksum fileChecksum; 247 Language lang; 248 FileId id; 249 } 250 251 struct FileMutantRow { 252 MutationId id; 253 Mutation mutation; 254 MutationPoint mutationPoint; 255 SourceLoc sloc; 256 SourceLoc slocEnd; 257 Language lang; 258 }