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