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 19 import dextool.type : AbsolutePath, Path; 20 import dextool.plugin.mutate.backend.type; 21 22 import dextool.plugin.mutate.backend.database.schema; 23 public import dextool.plugin.mutate.backend.database.type; 24 25 /** Wrapper for a sqlite3 database that provide a uniform, easy-to-use 26 * interface for the mutation testing plugin. 27 */ 28 struct Database { 29 import std.conv : to; 30 import std.exception : collectException; 31 import std.typecons : Nullable; 32 import dextool.plugin.mutate.backend.type : MutationPoint, Mutation, 33 Checksum; 34 import dextool.plugin.mutate.type : MutationOrder; 35 import dextool.plugin.mutate.backend.database.standalone : SDatabase = Database; 36 37 SDatabase db; 38 alias db this; 39 40 private MutationOrder mut_order; 41 42 static auto make(AbsolutePath db, MutationOrder mut_order) @safe { 43 return Database(SDatabase.make(db), mut_order); 44 } 45 46 // Not movable. The database should only be passed around as a reference, 47 // if at all. 48 @disable this(this); 49 50 Nullable!Checksum getFileChecksum(const Path p) @trusted { 51 import dextool.plugin.mutate.backend.utility : checksum; 52 53 auto stmt = db.prepare("SELECT checksum0,checksum1 FROM files WHERE path=:path"); 54 stmt.bind(":path", cast(string) p); 55 auto res = stmt.execute; 56 57 typeof(return) rval; 58 if (!res.empty) { 59 rval = checksum(res.front.peek!long(0), res.front.peek!long(1)); 60 } 61 62 return rval; 63 } 64 65 /** Get the next mutation point + 1 mutant for it that has status unknown. 66 * 67 * TODO to run many instances in parallel the mutation should be locked. 68 * TODO remove nothrow or add a retry-loop 69 * 70 * The chosen point is randomised. 71 * 72 * Params: 73 * kind = kind of mutation to retrieve. 74 */ 75 NextMutationEntry nextMutation(const(Mutation.Kind)[] kinds) nothrow @trusted { 76 import std.algorithm : map; 77 import std.exception : collectException; 78 import std.format : format; 79 import dextool.plugin.mutate.backend.type; 80 import dextool.type : FileName; 81 82 typeof(return) rval; 83 84 auto order = mut_order == MutationOrder.random ? "ORDER BY RANDOM()" : ""; 85 86 try { 87 auto prep_str = format("SELECT 88 mutation.id, 89 mutation.kind, 90 mutation.time, 91 mutation_point.offset_begin, 92 mutation_point.offset_end, 93 mutation_point.line, 94 mutation_point.column, 95 files.path 96 FROM mutation,mutation_point,files 97 WHERE 98 mutation.status == 0 AND 99 mutation.mp_id == mutation_point.id AND 100 mutation_point.file_id == files.id AND 101 mutation.kind IN (%(%s,%)) %s LIMIT 1", 102 kinds.map!(a => cast(int) a), order); 103 auto stmt = db.prepare(prep_str); 104 // TODO this should work. why doesn't it? 105 //stmt.bind(":kinds", format("%(%s,%)", kinds.map!(a => cast(int) a))); 106 auto res = stmt.execute; 107 if (res.empty) { 108 rval.st = NextMutationEntry.Status.done; 109 return rval; 110 } 111 112 auto v = res.front; 113 114 auto mp = MutationPoint(Offset(v.peek!uint(3), v.peek!uint(4))); 115 mp.mutations = [Mutation(v.peek!long(1).to!(Mutation.Kind))]; 116 auto pkey = MutationId(v.peek!long(0)); 117 auto file = Path(FileName(v.peek!string(7))); 118 auto sloc = SourceLoc(v.peek!uint(5), v.peek!uint(6)); 119 120 rval.entry = MutationEntry(pkey, file, sloc, mp, v.peek!long(2).dur!"msecs"); 121 } 122 catch (Exception e) { 123 rval.st = NextMutationEntry.Status.queryError; 124 collectException(logger.warning(e.msg)); 125 } 126 127 return rval; 128 } 129 130 void iterateMutants(const Mutation.Kind[] kinds, void delegate(const ref IterateMutantRow) dg) nothrow @trusted { 131 import std.algorithm : map; 132 import std.format : format; 133 import dextool.plugin.mutate.backend.utility : checksum; 134 135 immutable all_mutants = "SELECT 136 mutation.id, 137 mutation.status, 138 mutation.kind, 139 mutation.time, 140 mutation_point.offset_begin, 141 mutation_point.offset_end, 142 mutation_point.line, 143 mutation_point.column, 144 files.path, 145 files.checksum0, 146 files.checksum1 147 FROM mutation,mutation_point,files 148 WHERE 149 mutation.kind IN (%(%s,%)) AND 150 mutation.mp_id == mutation_point.id AND 151 mutation_point.file_id == files.id 152 ORDER BY mutation.status"; 153 154 try { 155 auto res = db.prepare(format(all_mutants, kinds.map!(a => cast(int) a))).execute; 156 foreach (ref r; res) { 157 IterateMutantRow d; 158 d.id = MutationId(r.peek!long(0)); 159 d.mutation = Mutation(r.peek!int(2).to!(Mutation.Kind), 160 r.peek!int(1).to!(Mutation.Status)); 161 auto offset = Offset(r.peek!uint(4), r.peek!uint(5)); 162 d.mutationPoint = MutationPoint(offset, null); 163 d.file = r.peek!string(8); 164 d.fileChecksum = checksum(r.peek!long(9), r.peek!long(10)); 165 d.sloc = SourceLoc(r.peek!uint(6), r.peek!uint(7)); 166 167 d.testCases = db.getTestCases(d.id); 168 169 dg(d); 170 } 171 } 172 catch (Exception e) { 173 logger.error(e.msg).collectException; 174 } 175 } 176 } 177 178 struct IterateMutantRow { 179 MutationId id; 180 Mutation mutation; 181 MutationPoint mutationPoint; 182 Path file; 183 Checksum fileChecksum; 184 SourceLoc sloc; 185 TestCase[] testCases; 186 }