1 /** 2 Copyright: Copyright (c) 2018, 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 module contains the a basic database interface that have minimal 11 dependencies on internal modules. It is intended to be reusable from the test 12 suite. 13 14 The only acceptable dependency are: 15 * ../type.d 16 * ..backend/type.d 17 * ../database/type.d 18 * ../database/schema.d 19 */ 20 module dextool.plugin.mutate.backend.database.standalone; 21 22 import core.time : Duration, dur; 23 import logger = std.experimental.logger; 24 import std.algorithm : copy, map, joiner; 25 import std.array : Appender, appender, array, empty; 26 import std.conv : to; 27 import std.datetime : SysTime, Clock; 28 import std.exception : collectException; 29 import std.format : format; 30 import std.path : relativePath; 31 import std.regex : Regex, matchFirst; 32 import std.typecons : Nullable, Flag, No; 33 34 import miniorm : toSqliteDateTime, fromSqLiteDateTime, Bind; 35 36 import dextool.type : AbsolutePath, Path, ExitStatusType; 37 38 import dextool.plugin.mutate.backend.database.schema; 39 import dextool.plugin.mutate.backend.database.type; 40 import dextool.plugin.mutate.backend.type : Language, Checksum; 41 42 /** Database wrapper with minimal dependencies. 43 */ 44 struct Database { 45 import miniorm : Miniorm, select, insert, insertOrReplace, delete_, insertOrIgnore; 46 import d2sqlite3 : SqlDatabase = Database; 47 import dextool.plugin.mutate.backend.type : MutationPoint, Mutation, Checksum; 48 49 Miniorm db; 50 alias db this; 51 52 /** Create a database by either opening an existing or initializing a new. 53 * 54 * Params: 55 * db = path to the database 56 */ 57 static auto make(string db) @safe { 58 return Database(initializeDB(db)); 59 } 60 61 /// If the file has already been analyzed. 62 bool isAnalyzed(const Path p) @trusted { 63 auto stmt = db.prepare("SELECT count(*) FROM files WHERE path=:path LIMIT 1"); 64 stmt.get.bind(":path", cast(string) p); 65 auto res = stmt.get.execute; 66 return res.oneValue!long != 0; 67 } 68 69 /// If the file has already been analyzed. 70 bool isAnalyzed(const Path p, const Checksum cs) @trusted { 71 auto stmt = db.prepare( 72 "SELECT count(*) FROM files WHERE path=:path AND checksum0=:cs0 AND checksum1=:cs1 LIMIT 1"); 73 stmt.get.bind(":path", cast(string) p); 74 stmt.get.bind(":cs0", cast(long) cs.c0); 75 stmt.get.bind(":cs1", cast(long) cs.c1); 76 auto res = stmt.get.execute; 77 return res.oneValue!long != 0; 78 } 79 80 bool exists(MutationStatusId status_id) { 81 immutable s = format!"SELECT COUNT(*) FROM %s WHERE id=:id LIMIT 1"(mutationStatusTable); 82 auto stmt = db.prepare(s); 83 stmt.get.bind(":id", cast(long) status_id); 84 auto res = stmt.get.execute; 85 return res.oneValue!long == 0; 86 } 87 88 bool isMarked(MutationId id) @trusted { 89 immutable s = format!"SELECT COUNT(*) FROM %s WHERE st_id IN 90 (SELECT st_id FROM %s WHERE id=:id)"(markedMutantTable, mutationTable); 91 auto stmt = db.prepare(s); 92 stmt.get.bind(":id", cast(long) id); 93 auto res = stmt.get.execute; 94 return res.oneValue!long != 0; 95 } 96 97 /// All marked mutants whom have a mutation status checksum that has been removed from the database. 98 MarkedMutant[] getLostMarkings() @trusted { 99 immutable sql = format!"SELECT checksum0 FROM %s 100 WHERE 101 checksum0 NOT IN (SELECT checksum0 FROM %s)"(markedMutantTable, mutationStatusTable); 102 103 auto stmt = db.prepare(sql); 104 auto app = appender!(MarkedMutant[])(); 105 foreach (res; stmt.get.execute) { 106 foreach (m; db.run(select!MarkedMutantTbl.where("checksum0 = :cs0", 107 Bind("cs0")), res.peek!long(0))) { 108 app.put(.make(m)); 109 } 110 } 111 112 return app.data; 113 } 114 115 Nullable!FileId getFileId(const Path p) @trusted { 116 enum sql = format("SELECT id FROM %s WHERE path=:path", filesTable); 117 auto stmt = db.prepare(sql); 118 stmt.get.bind(":path", cast(string) p); 119 auto res = stmt.get.execute; 120 121 typeof(return) rval; 122 if (!res.empty) 123 rval = FileId(res.oneValue!long); 124 return rval; 125 } 126 127 /// Returns: the path ID for the mutant. 128 Nullable!FileId getFileId(const MutationId id) @trusted { 129 enum sql = format("SELECT t1.file_id 130 FROM %s t0, %s t1 131 WHERE t0.id = :id AND t0.mp_id = t1.id", mutationTable, mutationPointTable); 132 auto stmt = db.prepare(sql); 133 stmt.get.bind(":id", cast(long) id); 134 135 typeof(return) rval; 136 foreach (r; stmt.get.execute) 137 rval = FileId(r.peek!long(0)); 138 return rval; 139 } 140 141 /// Returns: the file path that the id correspond to. 142 Nullable!Path getFile(const FileId id) @trusted { 143 enum sql = format("SELECT path FROM %s WHERE id = :id", filesTable); 144 auto stmt = db.prepare(sql); 145 stmt.get.bind(":id", cast(long) id); 146 147 typeof(return) rval; 148 foreach (r; stmt.get.execute) 149 rval = Path(r.peek!string(0)); 150 return rval; 151 } 152 153 /// Remove the file with all mutations that are coupled to it. 154 void removeFile(const Path p) @trusted { 155 auto stmt = db.prepare(format!"DELETE FROM %s WHERE path=:path"(filesTable)); 156 stmt.get.bind(":path", cast(string) p); 157 stmt.get.execute; 158 } 159 160 /// Returns: All files in the database as relative paths. 161 Path[] getFiles() @trusted { 162 auto stmt = db.prepare(format!"SELECT path from %s"(filesTable)); 163 auto res = stmt.get.execute; 164 165 auto app = appender!(Path[]); 166 foreach (ref r; res) { 167 app.put(Path(r.peek!string(0))); 168 } 169 170 return app.data; 171 } 172 173 enum CntAction { 174 /// Increment the counter 175 incr, 176 /// Reset the counter to zero 177 reset, 178 } 179 180 /** Update the status of a mutant. 181 * 182 * Params: 183 * id = ID of the mutant 184 * st = status to broadcast 185 * d = time spent on veryfing the mutant 186 * tcs = test cases that killed the mutant 187 * counter = how to act with the counter 188 */ 189 void updateMutation(const MutationId id, const Mutation.Status st, 190 const Duration d, const(TestCase)[] tcs, CntAction counter = CntAction.incr) @trusted { 191 enum sql = "UPDATE %s SET 192 status=:st,time=:time,update_ts=:update_ts,%s 193 WHERE 194 id IN (SELECT st_id FROM %s WHERE id = :id)"; 195 196 auto stmt = () { 197 final switch (counter) { 198 case CntAction.incr: 199 return db.prepare(format!sql(mutationStatusTable, 200 "test_cnt=test_cnt+1", mutationTable)); 201 case CntAction.reset: 202 return db.prepare(format!sql(mutationStatusTable, 203 "test_cnt=0", mutationTable)); 204 } 205 }(); 206 207 stmt.get.bind(":st", st.to!long); 208 stmt.get.bind(":id", id.to!long); 209 stmt.get.bind(":time", d.total!"msecs"); 210 stmt.get.bind(":update_ts", Clock.currTime.toUTC.toSqliteDateTime); 211 stmt.get.execute; 212 213 updateMutationTestCases(id, tcs); 214 } 215 216 /** Update the status of a mutant. 217 * 218 * Params: 219 * id = ID of the mutant 220 * st = status to broadcast 221 * d = time spent on veryfing the mutant 222 * tcs = test cases that killed the mutant 223 * counter = how to act with the counter 224 */ 225 void updateMutation(const MutationStatusId id, const Mutation.Status st, 226 const Duration d, CntAction counter = CntAction.incr) @trusted { 227 enum sql = "UPDATE %s SET 228 status=:st,time=:time,update_ts=:update_ts,%s 229 WHERE 230 id = :id"; 231 232 auto stmt = () { 233 final switch (counter) { 234 case CntAction.incr: 235 return db.prepare(format!sql(mutationStatusTable, 236 "test_cnt=test_cnt+1")); 237 case CntAction.reset: 238 return db.prepare(format!sql(mutationStatusTable, "test_cnt=0")); 239 } 240 }(); 241 242 stmt.get.bind(":id", cast(long) id); 243 stmt.get.bind(":st", cast(long) st); 244 stmt.get.bind(":time", d.total!"msecs"); 245 stmt.get.bind(":update_ts", Clock.currTime.toUTC.toSqliteDateTime); 246 stmt.get.execute; 247 } 248 249 /** Update the counter of how many times the mutants has been alive. 250 * 251 * Params: 252 * id = ID of the mutant 253 * counter = how to act with the counter 254 */ 255 void updateMutation(const MutationStatusId id, const CntAction counter) @trusted { 256 enum sql = "UPDATE %s SET %s WHERE id = :id"; 257 258 auto stmt = () { 259 final switch (counter) { 260 case CntAction.incr: 261 return db.prepare(format!sql(mutationStatusTable, 262 "test_cnt=test_cnt+1")); 263 case CntAction.reset: 264 return db.prepare(format!sql(mutationStatusTable, "test_cnt=0")); 265 } 266 }(); 267 268 stmt.get.bind(":id", id.to!long); 269 stmt.get.execute; 270 } 271 272 /// Update the time used to test the mutant. 273 void updateMutation(const MutationStatusId id, const Duration testTime) @trusted { 274 enum sql = format!"UPDATE %s SET time=:time WHERE id = :id"(mutationStatusTable); 275 auto stmt = db.prepare(sql); 276 stmt.get.bind(":id", id.to!long); 277 stmt.get.bind(":time", testTime.total!"msecs"); 278 stmt.get.execute; 279 } 280 281 /** Update the status of a mutant. 282 * 283 * Params: 284 * id = mutation status ID 285 * st = new status 286 * update_ts = if the update timestamp should be updated. 287 */ 288 void updateMutationStatus(const MutationStatusId id, const Mutation.Status st, 289 Flag!"updateTs" update_ts = No.updateTs) @trusted { 290 291 auto stmt = () { 292 if (update_ts) { 293 const ts = Clock.currTime.toUTC.toSqliteDateTime; 294 auto s = db.prepare(format!"UPDATE %s SET status=:st,update_ts=:update_ts WHERE id=:id"( 295 mutationStatusTable)); 296 s.get.bind(":update_ts", ts); 297 return s; 298 } else 299 return db.prepare(format!"UPDATE %s SET status=:st WHERE id=:id"( 300 mutationStatusTable)); 301 }(); 302 stmt.get.bind(":st", st.to!long); 303 stmt.get.bind(":id", id.to!long); 304 stmt.get.execute; 305 } 306 307 /// Returns: all mutation status IDs. 308 MutationStatusId[] getAllMutationStatus() @trusted { 309 enum sql = format!"SELECT id FROM %s"(mutationStatusTable); 310 311 auto app = appender!(MutationStatusId[])(); 312 auto stmt = db.prepare(sql); 313 foreach (r; stmt.get.execute) 314 app.put(MutationStatusId(r.peek!long(0))); 315 return app.data; 316 } 317 318 Nullable!(Mutation.Status) getMutationStatus(const MutationStatusId id) @trusted { 319 enum sql = format!"SELECT status FROM %s WHERE id=:id"(mutationStatusTable); 320 auto stmt = db.prepare(sql); 321 stmt.get.bind(":id", cast(long) id); 322 323 typeof(return) rval; 324 foreach (a; stmt.get.execute) { 325 rval = cast(Mutation.Status) a.peek!long(0); 326 } 327 return rval; 328 } 329 330 Nullable!MutationEntry getMutation(const MutationId id) @trusted { 331 import dextool.plugin.mutate.backend.type; 332 333 typeof(return) rval; 334 335 enum get_mut_sql = format("SELECT 336 t0.id, 337 t0.kind, 338 t3.time, 339 t1.offset_begin, 340 t1.offset_end, 341 t1.line, 342 t1.column, 343 t2.path, 344 t2.lang 345 FROM %s t0,%s t1,%s t2,%s t3 346 WHERE 347 t0.id = :id AND 348 t0.mp_id = t1.id AND 349 t1.file_id = t2.id AND 350 t3.id = t0.st_id 351 ", mutationTable, mutationPointTable, filesTable, mutationStatusTable); 352 353 auto stmt = db.prepare(get_mut_sql); 354 stmt.get.bind(":id", cast(long) id); 355 auto res = stmt.get.execute; 356 357 if (res.empty) 358 return rval; 359 360 auto v = res.front; 361 362 auto mp = MutationPoint(Offset(v.peek!uint(3), v.peek!uint(4))); 363 mp.mutations = [Mutation(v.peek!long(1).to!(Mutation.Kind))]; 364 auto pkey = MutationId(v.peek!long(0)); 365 auto file = Path(v.peek!string(7)); 366 auto sloc = SourceLoc(v.peek!uint(5), v.peek!uint(6)); 367 auto lang = v.peek!long(8).to!Language; 368 369 rval = MutationEntry(pkey, file, sloc, mp, v.peek!long(2).dur!"msecs", lang); 370 371 return rval; 372 } 373 374 MutantMetaData getMutantationMetaData(const MutationId id) @trusted { 375 auto rval = MutantMetaData(id); 376 foreach (res; db.run(select!NomutDataTbl.where("mut_id = :mutid", 377 Bind("mutid")), cast(long) id)) { 378 rval.set(NoMut(res.tag, res.comment)); 379 } 380 return rval; 381 } 382 383 //TODO: this is a bit inefficient. it should use a callback iterator 384 MutantMetaData[] getMutantationMetaData(const Mutation.Kind[] kinds, const Mutation 385 .Status status) @trusted { 386 const sql = format!"SELECT DISTINCT t.mut_id, t.tag, t.comment 387 FROM %s t, %s t1, %s t2 388 WHERE 389 t.mut_id = t1.id AND 390 t1.st_id = t2.id AND 391 t2.status = :status AND 392 t1.kind IN (%(%s,%)) 393 ORDER BY 394 t.mut_id"(nomutDataTable, mutationTable, mutationStatusTable, 395 kinds.map!(a => cast(long) a)); 396 auto stmt = db.prepare(sql); 397 stmt.get.bind(":status", cast(long) status); 398 399 auto app = appender!(MutantMetaData[])(); 400 foreach (res; stmt.get.execute) { 401 app.put(MutantMetaData(MutationId(res.peek!long(0)), 402 MutantAttr(NoMut(res.peek!string(1), res.peek!string(2))))); 403 } 404 return app.data; 405 } 406 407 Nullable!Path getPath(const MutationId id) @trusted { 408 enum get_path_sql = format("SELECT t2.path 409 FROM 410 %s t0, %s t1, %s t2 411 WHERE 412 t0.id = :id AND 413 t0.mp_id = t1.id AND 414 t1.file_id = t2.id 415 ", mutationTable, mutationPointTable, filesTable); 416 417 auto stmt = db.prepare(get_path_sql); 418 stmt.get.bind(":id", cast(long) id); 419 auto res = stmt.get.execute; 420 421 typeof(return) rval; 422 if (!res.empty) 423 rval = Path(res.front.peek!string(0)); 424 return rval; 425 } 426 427 /// Returns: the mutants that are connected to the mutation statuses. 428 MutantInfo[] getMutantsInfo(const Mutation.Kind[] kinds, const(MutationStatusId)[] id) @trusted { 429 const get_mutid_sql = format("SELECT t0.id,t2.status,t0.kind,t1.line,t1.column 430 FROM %s t0,%s t1, %s t2 431 WHERE 432 t0.st_id IN (%(%s,%)) AND 433 t0.st_id = t2.id AND 434 t0.kind IN (%(%s,%)) AND 435 t0.mp_id = t1.id", mutationTable, mutationPointTable, 436 mutationStatusTable, id.map!(a => cast(long) a), kinds.map!(a => cast(int) a)); 437 auto stmt = db.prepare(get_mutid_sql); 438 439 auto app = appender!(MutantInfo[])(); 440 foreach (res; stmt.get.execute) 441 app.put(MutantInfo(MutationId(res.peek!long(0)), res.peek!long(1) 442 .to!(Mutation.Status), res.peek!long(2).to!(Mutation.Kind), 443 SourceLoc(res.peek!uint(3), res.peek!uint(4)))); 444 445 return app.data; 446 } 447 448 /// Returns: the mutants that are connected to the mutation statuses. 449 MutationId[] getMutationIds(const(Mutation.Kind)[] kinds, const(MutationStatusId)[] id) @trusted { 450 if (id.length == 0) 451 return null; 452 453 const get_mutid_sql = format!"SELECT id FROM %s t0 454 WHERE 455 t0.st_id IN (%(%s,%)) AND 456 t0.kind IN (%(%s,%))"(mutationTable, id.map!(a => cast(long) a), 457 kinds.map!(a => cast(int) a)); 458 auto stmt = db.prepare(get_mutid_sql); 459 460 auto app = appender!(MutationId[])(); 461 foreach (res; stmt.get.execute) 462 app.put(MutationId(res.peek!long(0))); 463 return app.data; 464 } 465 466 Nullable!MutationId getMutationId(const MutationStatusId id) @trusted { 467 immutable sql = format!"SELECT id FROM %s WHERE st_id=:st_id"(mutationTable); 468 auto stmt = db.prepare(sql); 469 stmt.get.bind(":st_id", cast(long) id); 470 471 typeof(return) rval; 472 foreach (res; stmt.get.execute) { 473 rval = res.peek!long(0).MutationId; 474 break; 475 } 476 return rval; 477 } 478 479 Nullable!MutationStatusId getMutationStatusId(const MutationId id) @trusted { 480 immutable sql = format!"SELECT st_id FROM %s WHERE id=:id"(mutationTable); 481 auto stmt = db.prepare(sql); 482 stmt.get.bind(":id", cast(long) id); 483 484 typeof(return) rval; 485 foreach (res; stmt.get.execute) { 486 rval = MutationStatusId(res.peek!long(0)); 487 } 488 return rval; 489 } 490 491 Nullable!MutationStatusId getMutationStatusId(const Checksum cs) @trusted { 492 immutable sql = format!"SELECT id FROM %s WHERE checksum0=:cs0 AND checksum1=:cs1"( 493 mutationStatusTable); 494 auto stmt = db.prepare(sql); 495 stmt.get.bind(":cs0", cast(long) cs.c0); 496 stmt.get.bind(":cs1", cast(long) cs.c1); 497 498 typeof(return) rval; 499 foreach (res; stmt.get.execute) { 500 rval = MutationStatusId(res.peek!long(0)); 501 } 502 return rval; 503 } 504 505 // Returns: the status of the mutant 506 Nullable!(Mutation.Status) getMutationStatus(const MutationId id) @trusted { 507 auto s = format!"SELECT status FROM %s WHERE id IN (SELECT st_id FROM %s WHERE id=:mut_id)"( 508 mutationStatusTable, mutationTable); 509 auto stmt = db.prepare(s); 510 stmt.get.bind(":mut_id", cast(long) id); 511 typeof(return) rval; 512 foreach (res; stmt.get.execute) 513 rval = res.peek!long(0).to!(Mutation.Status); 514 return rval; 515 } 516 517 /// Returns: the mutants in the file at the line. 518 MutationStatusId[] getMutationsOnLine(const(Mutation.Kind)[] kinds, FileId fid, SourceLoc sloc) @trusted { 519 // TODO: should it also be line_end? 520 const sql = format("SELECT DISTINCT t0.id FROM %s t0, %s t1, %s t2 521 WHERE 522 t1.st_id = t0.id AND 523 t1.kind IN (%(%s,%)) AND 524 t1.mp_id = t2.id AND 525 t2.file_id = :fid AND 526 (:line BETWEEN t2.line AND t2.line_end) 527 ", mutationStatusTable, mutationTable, mutationPointTable, 528 kinds.map!(a => cast(int) a)); 529 auto stmt = db.prepare(sql); 530 stmt.get.bind(":fid", cast(long) fid); 531 stmt.get.bind(":line", sloc.line); 532 533 auto app = appender!(typeof(return))(); 534 foreach (res; stmt.get.execute) 535 app.put(MutationStatusId(res.peek!long(0))); 536 return app.data; 537 } 538 539 LineMetadata getLineMetadata(const FileId fid, const SourceLoc sloc) @trusted { 540 // TODO: change this select to using microrm 541 enum sql = format("SELECT nomut,tag,comment FROM %s 542 WHERE 543 file_id = :fid AND 544 line = :line", rawSrcMetadataTable); 545 auto stmt = db.prepare(sql); 546 stmt.get.bind(":fid", cast(long) fid); 547 stmt.get.bind(":line", sloc.line); 548 549 auto rval = typeof(return)(fid, sloc.line); 550 foreach (res; stmt.get.execute) { 551 if (res.peek!long(0) != 0) 552 rval.set(NoMut(res.peek!string(1), res.peek!string(2))); 553 } 554 555 return rval; 556 } 557 558 /// Returns: the `nr` mutants that where the longst since they where tested. 559 MutationStatusTime[] getOldestMutants(const(Mutation.Kind)[] kinds, const long nr) @trusted { 560 const sql = format("SELECT t0.id,t0.update_ts FROM %s t0, %s t1 561 WHERE 562 t0.update_ts IS NOT NULL AND 563 t1.st_id = t0.id AND 564 t1.kind IN (%(%s,%)) 565 ORDER BY t0.update_ts ASC LIMIT :limit", 566 mutationStatusTable, mutationTable, kinds.map!(a => cast(int) a)); 567 auto stmt = db.prepare(sql); 568 stmt.get.bind(":limit", nr); 569 570 auto app = appender!(MutationStatusTime[])(); 571 foreach (res; stmt.get.execute) 572 app.put(MutationStatusTime(MutationStatusId(res.peek!long(0)), 573 res.peek!string(1).fromSqLiteDateTime)); 574 return app.data; 575 } 576 577 /// Returns: the `nr` mutant with the highest count that has not been killed and existed in the system the longest. 578 MutationStatus[] getHardestToKillMutant(const(Mutation.Kind)[] kinds, 579 const Mutation.Status status, const long nr) @trusted { 580 const sql = format("SELECT t0.id,t0.status,t0.test_cnt,t0.update_ts,t0.added_ts 581 FROM %s t0, %s t1 582 WHERE 583 t0.update_ts IS NOT NULL AND 584 t0.status = :status AND 585 t1.st_id = t0.id AND 586 t1.kind IN (%(%s,%)) AND 587 t1.st_id NOT IN (SELECT st_id FROM %s WHERE nomut != 0) 588 ORDER BY 589 t0.test_cnt DESC, 590 t0.added_ts ASC, 591 t0.update_ts ASC 592 LIMIT :limit", mutationStatusTable, mutationTable, 593 kinds.map!(a => cast(int) a), srcMetadataTable); 594 auto stmt = db.prepare(sql); 595 stmt.get.bind(":status", cast(long) status); 596 stmt.get.bind(":limit", nr); 597 598 auto app = appender!(MutationStatus[])(); 599 foreach (res; stmt.get.execute) { 600 auto added = () { 601 auto raw = res.peek!string(4); 602 if (raw.length == 0) 603 return Nullable!SysTime(); 604 return Nullable!SysTime(raw.fromSqLiteDateTime); 605 }(); 606 607 // dfmt off 608 app.put(MutationStatus( 609 MutationStatusId(res.peek!long(0)), 610 res.peek!long(1).to!(Mutation.Status), 611 res.peek!long(2).MutantTestCount, 612 res.peek!string(3).fromSqLiteDateTime, 613 added, 614 )); 615 // dfmt on 616 } 617 618 return app.data; 619 } 620 621 /** Get SourceLoc for a specific mutation id. 622 */ 623 Nullable!SourceLoc getSourceLocation(MutationId id) @trusted { 624 auto s = format!"SELECT line, column FROM %s WHERE id IN (SELECT mp_id FROM %s WHERE id=:mut_id)"( 625 mutationPointTable, mutationTable); 626 auto stmt = db.prepare(s); 627 stmt.get.bind(":mut_id", cast(long) id); 628 typeof(return) rval; 629 foreach (res; stmt.get.execute) 630 rval = SourceLoc(res.peek!uint(0), res.peek!uint(1)); 631 return rval; 632 } 633 634 /** Remove all mutations of kinds. 635 */ 636 void removeMutant(const Mutation.Kind[] kinds) @trusted { 637 const s = format!"DELETE FROM %s WHERE id IN (SELECT mp_id FROM %s WHERE kind IN (%(%s,%)))"( 638 mutationPointTable, mutationTable, kinds.map!(a => cast(int) a)); 639 auto stmt = db.prepare(s); 640 stmt.get.execute; 641 } 642 643 /** Reset all mutations of kinds with the status `st` to unknown. 644 */ 645 void resetMutant(const Mutation.Kind[] kinds, Mutation.Status st, Mutation.Status to_st) @trusted { 646 const s = format!"UPDATE %s SET status=%s WHERE status = %s AND id IN(SELECT st_id FROM %s WHERE kind IN (%(%s,%)))"( 647 mutationStatusTable, to_st.to!long, st.to!long, 648 mutationTable, kinds.map!(a => cast(int) a)); 649 auto stmt = db.prepare(s); 650 stmt.get.execute; 651 } 652 653 /** Mark a mutant with status and rationale (also adds metadata). 654 */ 655 void markMutant(const MutationId id, const Path file, const SourceLoc sloc, 656 const MutationStatusId st_id, const Checksum cs, 657 const Mutation.Status s, const Rationale r, string mutationTxt) @trusted { 658 db.run(insertOrReplace!MarkedMutantTbl, MarkedMutantTbl(cs.c0, cs.c1, 659 st_id, id, sloc.line, sloc.column, file, s, Clock.currTime.toUTC, r, mutationTxt)); 660 } 661 662 void removeMarkedMutant(const Checksum cs) @trusted { 663 db.run(delete_!MarkedMutantTbl.where("checksum0 = :cs0", Bind("cs0")), cast(long) cs.c0); 664 } 665 666 void removeMarkedMutant(const MutationStatusId id) @trusted { 667 db.run(delete_!MarkedMutantTbl.where("st_id = :st_id", Bind("st_id")), cast(long) id); 668 } 669 670 /// Returns: All mutants with that are marked orderd by their path 671 MarkedMutant[] getMarkedMutants() @trusted { 672 import miniorm : OrderingTermSort; 673 674 auto app = appender!(MarkedMutant[])(); 675 foreach (m; db.run(select!MarkedMutantTbl.orderBy(OrderingTermSort.ASC, [ 676 "path" 677 ]))) { 678 app.put(.make(m)); 679 } 680 681 return app.data; 682 } 683 684 Mutation.Kind getKind(MutationId id) @trusted { 685 immutable sql = format!"SELECT kind FROM %s WHERE id=:id"(mutationTable); 686 auto stmt = db.prepare(sql); 687 stmt.get.bind(":id", cast(long) id); 688 689 typeof(return) rval; 690 foreach (res; stmt.get.execute) { 691 rval = res.peek!long(0).to!(Mutation.Kind); 692 } 693 return rval; 694 } 695 696 import dextool.plugin.mutate.backend.type; 697 698 alias aliveMutants = countMutants!([Mutation.Status.alive], false); 699 alias killedMutants = countMutants!([Mutation.Status.killed], false); 700 alias timeoutMutants = countMutants!([Mutation.Status.timeout], false); 701 702 /// Returns: Total that should be counted when calculating the mutation score. 703 alias totalMutants = countMutants!([ 704 Mutation.Status.alive, Mutation.Status.killed, Mutation.Status.timeout 705 ], false); 706 707 alias unknownMutants = countMutants!([Mutation.Status.unknown], false); 708 alias killedByCompilerMutants = countMutants!([ 709 Mutation.Status.killedByCompiler 710 ], false); 711 712 alias aliveSrcMutants = countMutants!([Mutation.Status.alive], true); 713 alias killedSrcMutants = countMutants!([Mutation.Status.killed], true); 714 alias timeoutSrcMutants = countMutants!([Mutation.Status.timeout], true); 715 716 /// Returns: Total that should be counted when calculating the mutation score. 717 alias totalSrcMutants = countMutants!([ 718 Mutation.Status.alive, Mutation.Status.killed, 719 Mutation.Status.timeout, Mutation.Status.unknown 720 ], true); 721 722 alias unknownSrcMutants = countMutants!([Mutation.Status.unknown], true); 723 alias killedByCompilerSrcMutants = countMutants!([ 724 Mutation.Status.killedByCompiler 725 ], true); 726 727 /** Count the mutants with the nomut metadata. 728 * 729 * Params: 730 * status = status the mutants must be in to be counted. 731 * distinc = count based on unique source code changes. 732 * kinds = the kind of mutants to count. 733 * file = file to count mutants in. 734 */ 735 private MutationReportEntry countMutants(int[] status, bool distinct)( 736 const Mutation.Kind[] kinds, string file = null) @trusted { 737 static if (distinct) { 738 auto qq = " 739 SELECT count(*),sum(time) 740 FROM ( 741 SELECT count(*),sum(t1.time) time 742 FROM %s t0, %s t1%s 743 WHERE 744 %s 745 t0.st_id = t1.id AND 746 t1.status IN (%(%s,%)) AND 747 t0.kind IN (%(%s,%)) 748 GROUP BY t1.id)"; 749 } else { 750 auto qq = " 751 SELECT count(*),sum(t1.time) time 752 FROM %s t0, %s t1%s 753 WHERE 754 %s 755 t0.st_id = t1.id AND 756 t1.status IN (%(%s,%)) AND 757 t0.kind IN (%(%s,%))"; 758 } 759 const query = () { 760 auto fq = file.length == 0 761 ? null : "t0.mp_id = t2.id AND t2.file_id = t3.id AND t3.path = :path AND"; 762 auto fq_from = file.length == 0 ? null : format(", %s t2, %s t3", 763 mutationPointTable, filesTable); 764 return format(qq, mutationTable, mutationStatusTable, fq_from, fq, 765 status, kinds.map!(a => cast(int) a)); 766 }(); 767 768 typeof(return) rval; 769 auto stmt = db.prepare(query); 770 if (file.length != 0) 771 stmt.get.bind(":path", file); 772 auto res = stmt.get.execute; 773 if (!res.empty) 774 rval = MutationReportEntry(res.front.peek!long(0), 775 res.front.peek!long(1).dur!"msecs"); 776 return rval; 777 } 778 779 /** Count the mutants with the nomut metadata. 780 * 781 * Params: 782 * status = status the mutants must be in to be counted. 783 * distinc = count based on unique source code changes. 784 * kinds = the kind of mutants to count. 785 * file = file to count mutants in. 786 */ 787 private MetadataNoMutEntry countNoMutMutants(int[] status, bool distinct)( 788 const Mutation.Kind[] kinds, string file = null) @trusted { 789 static if (distinct) { 790 auto sql_base = " 791 SELECT count(*) 792 FROM ( 793 SELECT count(*) 794 FROM %s t0, %s t1,%s t4%s 795 WHERE 796 %s 797 t0.st_id = t1.id AND 798 t0.st_id = t4.st_id AND 799 t4.nomut != 0 AND 800 t1.status IN (%(%s,%)) AND 801 t0.kind IN (%(%s,%)) 802 GROUP BY t1.id)"; 803 } else { 804 auto sql_base = " 805 SELECT count(*) 806 FROM %s t0, %s t1,%s t4%s 807 WHERE 808 %s 809 t0.st_id = t1.id AND 810 t0.st_id = t4.st_id AND 811 t4.nomut != 0 AND 812 t1.status IN (%(%s,%)) AND 813 t0.kind IN (%(%s,%))"; 814 } 815 const query = () { 816 auto fq = file.length == 0 817 ? null : "t0.mp_id = t2.id AND t2.file_id = t3.id AND t3.path = :path AND"; 818 auto fq_from = file.length == 0 ? null : format(", %s t2, %s t3", 819 mutationPointTable, filesTable); 820 return format(sql_base, mutationTable, mutationStatusTable, 821 srcMetadataTable, fq_from, fq, status, kinds.map!(a => cast(int) a)); 822 }(); 823 824 typeof(return) rval; 825 auto stmt = db.prepare(query); 826 if (file.length != 0) 827 stmt.get.bind(":path", file); 828 auto res = stmt.get.execute; 829 if (!res.empty) 830 rval = MetadataNoMutEntry(res.front.peek!long(0)); 831 return rval; 832 } 833 834 /// ditto. 835 alias aliveNoMutSrcMutants = countNoMutMutants!([Mutation.Status.alive], true); 836 837 /// Returns: mutants killed by the test case. 838 MutationStatusId[] testCaseKilledSrcMutants(const Mutation.Kind[] kinds, TestCase tc) @trusted { 839 const query = format(" 840 SELECT t1.id 841 FROM %s t0, %s t1, %s t2, %s t3 842 WHERE 843 t0.st_id = t1.id AND 844 t1.status = :st AND 845 t0.kind IN (%(%s,%)) AND 846 t2.name = :name AND 847 t2.id = t3.tc_id AND 848 t3.st_id = t1.id 849 GROUP BY t1.id", mutationTable, mutationStatusTable, 850 allTestCaseTable, killedTestCaseTable, kinds.map!(a => cast(int) a)); 851 852 auto stmt = db.prepare(query); 853 stmt.get.bind(":st", cast(long) Mutation.Status.killed); 854 stmt.get.bind(":name", tc.name); 855 856 auto app = appender!(MutationStatusId[])(); 857 foreach (res; stmt.get.execute) 858 app.put(MutationStatusId(res.peek!long(0))); 859 860 return app.data; 861 } 862 863 /// Returns: mutants at mutations points that the test case has killed mutants at. 864 alias testCaseMutationPointAliveSrcMutants = testCaseCountSrcMutants!([ 865 Mutation.Status.alive 866 ]); 867 /// ditto 868 alias testCaseMutationPointTimeoutSrcMutants = testCaseCountSrcMutants!( 869 [Mutation.Status.timeout]); 870 /// ditto 871 alias testCaseMutationPointKilledSrcMutants = testCaseCountSrcMutants!([ 872 Mutation.Status.killed 873 ]); 874 /// ditto 875 alias testCaseMutationPointUnknownSrcMutants = testCaseCountSrcMutants!( 876 [Mutation.Status.unknown]); 877 /// ditto 878 alias testCaseMutationPointKilledByCompilerSrcMutants = testCaseCountSrcMutants!( 879 [Mutation.Status.killedByCompiler]); 880 /// ditto 881 alias testCaseMutationPointTotalSrcMutants = testCaseCountSrcMutants!( 882 [ 883 Mutation.Status.alive, Mutation.Status.killed, Mutation.Status.timeout 884 ]); 885 886 private MutationStatusId[] testCaseCountSrcMutants(int[] status)( 887 const Mutation.Kind[] kinds, TestCase tc) @trusted { 888 const query = format(" 889 SELECT t1.id 890 FROM %s t0, %s t1 891 WHERE 892 t0.mp_id IN (SELECT t1.id 893 FROM %s t0,%s t1, %s t2, %s t3 894 WHERE 895 t0.mp_id = t1.id AND 896 t2.name = :name AND 897 t2.id = t3.tc_id AND 898 t3.st_id = t0.st_id 899 ) 900 AND 901 t0.st_id = t1.id AND 902 t1.status IN (%(%s,%)) AND 903 t0.kind IN (%(%s,%)) 904 GROUP BY t1.id", mutationTable, mutationStatusTable, mutationTable, mutationPointTable, 905 allTestCaseTable, killedTestCaseTable, status, kinds.map!(a => cast(int) a)); 906 907 auto stmt = db.prepare(query); 908 stmt.get.bind(":name", tc.name); 909 910 auto app = appender!(MutationStatusId[])(); 911 foreach (res; stmt.get.execute) 912 app.put(MutationStatusId(res.peek!long(0))); 913 914 return app.data; 915 } 916 917 Nullable!Checksum getChecksum(MutationStatusId id) @trusted { 918 immutable sql = format!"SELECT checksum0, checksum1 FROM %s WHERE id=:id"( 919 mutationStatusTable); 920 auto stmt = db.prepare(sql); 921 stmt.get.bind(":id", cast(long) id); 922 923 typeof(return) rval; 924 foreach (res; stmt.get.execute) { 925 rval = Checksum(res.peek!long(0), res.peek!long(1)); 926 break; 927 } 928 return rval; 929 } 930 931 Nullable!Checksum getFileChecksum(const Path p) @trusted { 932 immutable sql = format!"SELECT checksum0,checksum1 FROM %s WHERE path=:path"(filesTable); 933 auto stmt = db.prepare(sql); 934 stmt.get.bind(":path", cast(string) p); 935 auto res = stmt.get.execute; 936 937 typeof(return) rval; 938 if (!res.empty) { 939 rval = Checksum(res.front.peek!long(0), res.front.peek!long(1)); 940 } 941 942 return rval; 943 } 944 945 void put(const Path p, Checksum cs, const Language lang) @trusted { 946 immutable sql = format!"INSERT OR IGNORE INTO %s (path, checksum0, checksum1, lang) 947 VALUES (:path, :checksum0, :checksum1, :lang)"(filesTable); 948 auto stmt = db.prepare(sql); 949 stmt.get.bind(":path", cast(string) p); 950 stmt.get.bind(":checksum0", cast(long) cs.c0); 951 stmt.get.bind(":checksum1", cast(long) cs.c1); 952 stmt.get.bind(":lang", cast(long) lang); 953 stmt.get.execute; 954 } 955 956 /// Remove all metadata. 957 void clearMetadata() { 958 immutable sql = format!"DELETE FROM %s"(rawSrcMetadataTable); 959 db.run(sql); 960 } 961 962 /** Save line metadata to the database which is used to associate line 963 * metadata with mutants. 964 */ 965 void put(const LineMetadata[] mdata) { 966 import sumtype; 967 968 if (mdata.empty) 969 return; 970 971 // TODO: convert to microrm 972 enum sql = format!"INSERT OR IGNORE INTO %s 973 (file_id, line, nomut, tag, comment) 974 VALUES(:fid, :line, :nomut, :tag, :comment)"(rawSrcMetadataTable); 975 976 auto stmt = db.prepare(sql); 977 foreach (meta; mdata) { 978 auto nomut = meta.attr.match!((NoMetadata a) => NoMut.init, (NoMut a) => a); 979 stmt.get.bindAll(cast(long) meta.id, meta.line, meta.isNoMut, 980 nomut.tag, nomut.comment); 981 stmt.get.execute; 982 stmt.get.reset; 983 } 984 } 985 986 /// Store all found mutants. 987 void put(MutationPointEntry2[] mps, AbsolutePath root) @trusted { 988 if (mps.empty) 989 return; 990 991 enum insert_mp_sql = format("INSERT OR IGNORE INTO %s 992 (file_id, offset_begin, offset_end, line, column, line_end, column_end) 993 SELECT id,:begin,:end,:line,:column,:line_end,:column_end 994 FROM %s 995 WHERE 996 path = :path", mutationPointTable, filesTable); 997 auto mp_stmt = db.prepare(insert_mp_sql); 998 999 foreach (mp; mps) { 1000 auto rel_file = relativePath(mp.file, root).Path; 1001 mp_stmt.get.bind(":begin", mp.offset.begin); 1002 mp_stmt.get.bind(":end", mp.offset.end); 1003 mp_stmt.get.bind(":line", mp.sloc.line); 1004 mp_stmt.get.bind(":column", mp.sloc.column); 1005 mp_stmt.get.bind(":line_end", mp.slocEnd.line); 1006 mp_stmt.get.bind(":column_end", mp.slocEnd.column); 1007 mp_stmt.get.bind(":path", cast(string) rel_file); 1008 mp_stmt.get.execute; 1009 mp_stmt.get.reset; 1010 } 1011 1012 enum insert_cmut_sql = format("INSERT OR IGNORE INTO %s 1013 (status,test_cnt,update_ts,added_ts,checksum0,checksum1) 1014 VALUES(:st,0,:update_ts,:added_ts,:c0,:c1)", mutationStatusTable); 1015 auto cmut_stmt = db.prepare(insert_cmut_sql); 1016 const ts = Clock.currTime.toUTC.toSqliteDateTime; 1017 cmut_stmt.get.bind(":st", Mutation.Status.unknown); 1018 cmut_stmt.get.bind(":update_ts", ts); 1019 cmut_stmt.get.bind(":added_ts", ts); 1020 foreach (cm; mps.map!(a => a.cms).joiner) { 1021 cmut_stmt.get.bind(":c0", cast(long) cm.id.c0); 1022 cmut_stmt.get.bind(":c1", cast(long) cm.id.c1); 1023 cmut_stmt.get.execute; 1024 cmut_stmt.get.reset; 1025 } 1026 1027 enum insert_m_sql = format("INSERT OR IGNORE INTO %s 1028 (mp_id, st_id, kind) 1029 SELECT t0.id,t1.id,:kind FROM %s t0, %s t1, %s t2 WHERE 1030 t2.path = :path AND 1031 t0.file_id = t2.id AND 1032 t0.offset_begin = :off_begin AND 1033 t0.offset_end = :off_end AND 1034 t1.checksum0 = :c0 AND 1035 t1.checksum1 = :c1", mutationTable, mutationPointTable, 1036 mutationStatusTable, filesTable); 1037 auto insert_m = db.prepare(insert_m_sql); 1038 1039 foreach (mp; mps) { 1040 foreach (m; mp.cms) { 1041 auto rel_file = relativePath(mp.file, root).Path; 1042 insert_m.get.bind(":path", cast(string) rel_file); 1043 insert_m.get.bind(":off_begin", mp.offset.begin); 1044 insert_m.get.bind(":off_end", mp.offset.end); 1045 insert_m.get.bind(":c0", cast(long) m.id.c0); 1046 insert_m.get.bind(":c1", cast(long) m.id.c1); 1047 insert_m.get.bind(":kind", m.mut.kind); 1048 insert_m.get.execute; 1049 insert_m.get.reset; 1050 } 1051 } 1052 } 1053 1054 /** Remove all mutants points from the database. 1055 * 1056 * This removes all the mutants because of the cascade delete of the 1057 * tables. But it will keep the mutation statuses and thus the checksums 1058 * and the status of the code changes. 1059 * 1060 * This then mean that when mutations+mutation points are added back they 1061 * may reconnect with a mutation status. 1062 */ 1063 void removeAllMutationPoints() @trusted { 1064 enum sql = format!"DELETE FROM %s"(mutationPointTable); 1065 db.run(sql); 1066 } 1067 1068 /// ditto 1069 void removeAllFiles() @trusted { 1070 enum sql = format!"DELETE FROM %s"(filesTable); 1071 db.run(sql); 1072 } 1073 1074 /// Remove mutants that have no connection to a mutation point, orphaned mutants. 1075 void removeOrphanedMutants() @trusted { 1076 import std.datetime.stopwatch : StopWatch, AutoStart; 1077 1078 const removeIds = () { 1079 immutable sql = format!"SELECT id FROM %1$s WHERE id NOT IN (SELECT st_id FROM %2$s)"( 1080 mutationStatusTable, mutationTable); 1081 auto stmt = db.prepare(sql); 1082 auto removeIds = appender!(long[])(); 1083 foreach (res; stmt.get.execute) 1084 removeIds.put(res.peek!long(0)); 1085 return removeIds.data; 1086 }(); 1087 1088 immutable batchNr = 1000; 1089 immutable sql = format!"DELETE FROM %1$s WHERE id=:id"(mutationStatusTable); 1090 auto stmt = db.prepare(sql); 1091 auto sw = StopWatch(AutoStart.yes); 1092 foreach (const i, const id; removeIds) { 1093 stmt.get.bind(":id", id); 1094 stmt.get.execute; 1095 stmt.get.reset; 1096 1097 // continuously print to inform the user of the progress and avoid 1098 // e.g. timeout on jenkins. 1099 if (i > 0 && i % batchNr == 0) { 1100 const avg = cast(long)(cast(double) sw.peek.total!"msecs" / cast(double) batchNr); 1101 const t = dur!"msecs"(avg * (removeIds.length - i)); 1102 logger.infof("%s/%s removed (average %sms) (%s) (%s)", i, 1103 removeIds.length, avg, t, (Clock.currTime + t).toSimpleString); 1104 sw.reset; 1105 } 1106 } 1107 } 1108 1109 /** Add a link between the mutation and what test case killed it. 1110 * 1111 * Params: 1112 * id = ? 1113 * tcs = test cases to add 1114 */ 1115 void updateMutationTestCases(const MutationId id, const(TestCase)[] tcs) @trusted { 1116 if (tcs.length == 0) 1117 return; 1118 1119 immutable st_id = () { 1120 enum st_id_for_mutation_q = format!"SELECT st_id FROM %s WHERE id=:id"(mutationTable); 1121 auto stmt = db.prepare(st_id_for_mutation_q); 1122 stmt.get.bind(":id", cast(long) id); 1123 return stmt.get.execute.oneValue!long; 1124 }(); 1125 updateMutationTestCases(MutationStatusId(st_id), tcs); 1126 } 1127 1128 /** Add a link between the mutation and what test case killed it. 1129 * 1130 * Params: 1131 * id = ? 1132 * tcs = test cases to add 1133 */ 1134 void updateMutationTestCases(const MutationStatusId st_id, const(TestCase)[] tcs) @trusted { 1135 if (tcs.length == 0) 1136 return; 1137 1138 try { 1139 enum remove_old_sql = format!"DELETE FROM %s WHERE st_id=:id"(killedTestCaseTable); 1140 auto stmt = db.prepare(remove_old_sql); 1141 stmt.get.bind(":id", cast(long) st_id); 1142 stmt.get.execute; 1143 } catch (Exception e) { 1144 } 1145 1146 enum add_if_non_exist_tc_sql = format!"INSERT INTO %s (name) SELECT :name1 WHERE NOT EXISTS (SELECT * FROM %s WHERE name = :name2)"( 1147 allTestCaseTable, allTestCaseTable); 1148 auto stmt_insert_tc = db.prepare(add_if_non_exist_tc_sql); 1149 1150 enum add_new_sql = format!"INSERT INTO %s (st_id, tc_id, location) SELECT :st_id,t1.id,:loc FROM %s t1 WHERE t1.name = :tc"( 1151 killedTestCaseTable, allTestCaseTable); 1152 auto stmt_insert = db.prepare(add_new_sql); 1153 foreach (const tc; tcs) { 1154 try { 1155 stmt_insert_tc.get.reset; 1156 stmt_insert_tc.get.bind(":name1", tc.name); 1157 stmt_insert_tc.get.bind(":name2", tc.name); 1158 stmt_insert_tc.get.execute; 1159 1160 stmt_insert.get.reset; 1161 stmt_insert.get.bind(":st_id", cast(long) st_id); 1162 stmt_insert.get.bind(":loc", tc.location); 1163 stmt_insert.get.bind(":tc", tc.name); 1164 stmt_insert.get.execute; 1165 } catch (Exception e) { 1166 logger.warning(e.msg); 1167 } 1168 } 1169 } 1170 1171 /** Set detected test cases. 1172 * 1173 * This will replace those that where previously stored. 1174 * 1175 * Returns: ID of affected mutation statuses. 1176 */ 1177 MutationStatusId[] setDetectedTestCases(const(TestCase)[] tcs) @trusted { 1178 if (tcs.length == 0) 1179 return null; 1180 1181 auto mut_status_ids = appender!(MutationStatusId[])(); 1182 1183 immutable tmp_name = "tmp_new_tc_" ~ __LINE__.to!string; 1184 internalAddDetectedTestCases(tcs, tmp_name); 1185 1186 immutable mut_st_id = format!"SELECT DISTINCT t1.st_id 1187 FROM %s t0, %s t1 1188 WHERE 1189 t0.name NOT IN (SELECT name FROM %s) AND 1190 t0.id = t1.tc_id"(allTestCaseTable, killedTestCaseTable, tmp_name); 1191 auto stmt = db.prepare(mut_st_id); 1192 foreach (res; stmt.get.execute) 1193 mut_status_ids.put(res.peek!long(0).MutationStatusId); 1194 1195 immutable remove_old_sql = format!"DELETE FROM %s WHERE name NOT IN (SELECT name FROM %s)"( 1196 allTestCaseTable, tmp_name); 1197 db.run(remove_old_sql); 1198 1199 db.run(format!"DROP TABLE %s"(tmp_name)); 1200 1201 return mut_status_ids.data; 1202 } 1203 1204 /** Add test cases to those that have been detected. 1205 * 1206 * They will be added if they are unique. 1207 */ 1208 void addDetectedTestCases(const(TestCase)[] tcs) @trusted { 1209 if (tcs.length == 0) 1210 return; 1211 1212 immutable tmp_name = "tmp_new_tc_" ~ __LINE__.to!string; 1213 internalAddDetectedTestCases(tcs, tmp_name); 1214 db.run(format!"DROP TABLE %s"(tmp_name)); 1215 } 1216 1217 /// ditto. 1218 private void internalAddDetectedTestCases(const(TestCase)[] tcs, string tmp_tbl) @trusted { 1219 db.run(format!"CREATE TEMP TABLE %s (id INTEGER PRIMARY KEY, name TEXT NOT NULL)"( 1220 tmp_tbl)); 1221 1222 immutable add_tc_sql = format!"INSERT INTO %s (name) VALUES(:name)"(tmp_tbl); 1223 auto insert_s = db.prepare(add_tc_sql); 1224 foreach (tc; tcs) { 1225 insert_s.get.bind(":name", tc.name); 1226 insert_s.get.execute; 1227 insert_s.get.reset; 1228 } 1229 1230 // https://stackoverflow.com/questions/2686254/how-to-select-all-records-from-one-table-that-do-not-exist-in-another-table 1231 //Q: What is happening here? 1232 // 1233 //A: Conceptually, we select all rows from table1 and for each row we 1234 //attempt to find a row in table2 with the same value for the name 1235 //column. If there is no such row, we just leave the table2 portion of 1236 //our result empty for that row. Then we constrain our selection by 1237 //picking only those rows in the result where the matching row does not 1238 //exist. Finally, We ignore all fields from our result except for the 1239 //name column (the one we are sure that exists, from table1). 1240 // 1241 //While it may not be the most performant method possible in all cases, 1242 //it should work in basically every database engine ever that attempts 1243 //to implement ANSI 92 SQL 1244 immutable add_missing_sql = format!"INSERT INTO %s (name) SELECT t1.name FROM %s t1 LEFT JOIN %s t2 ON t2.name = t1.name WHERE t2.name IS NULL"( 1245 allTestCaseTable, tmp_tbl, allTestCaseTable); 1246 db.run(add_missing_sql); 1247 } 1248 1249 /// Returns: detected test cases. 1250 TestCase[] getDetectedTestCases() @trusted { 1251 auto rval = appender!(TestCase[])(); 1252 db.run(select!AllTestCaseTbl).map!(a => TestCase(a.name)).copy(rval); 1253 return rval.data; 1254 } 1255 1256 /// Returns: detected test cases. 1257 TestCaseId[] getDetectedTestCaseIds() @trusted { 1258 auto rval = appender!(TestCaseId[])(); 1259 db.run(select!AllTestCaseTbl).map!(a => TestCaseId(a.id)).copy(rval); 1260 return rval.data; 1261 } 1262 1263 /// Returns: test cases that has killed zero mutants. 1264 TestCase[] getTestCasesWithZeroKills() @trusted { 1265 enum sql = format("SELECT t1.name FROM %s t1 WHERE t1.id NOT IN (SELECT tc_id FROM %s)", 1266 allTestCaseTable, killedTestCaseTable); 1267 1268 auto rval = appender!(TestCase[])(); 1269 auto stmt = db.prepare(sql); 1270 foreach (a; stmt.get.execute) 1271 rval.put(TestCase(a.peek!string(0))); 1272 1273 return rval.data; 1274 } 1275 1276 /** Guarantees that the there are no duplications of `TestCaseId`. 1277 * 1278 * Returns: test cases that has killed at least one mutant. 1279 */ 1280 TestCaseId[] getTestCasesWithAtLeastOneKill(const Mutation.Kind[] kinds) @trusted { 1281 immutable sql = format!"SELECT DISTINCT t1.id 1282 FROM %s t1, %s t2, %s t3 1283 WHERE 1284 t1.id = t2.tc_id AND 1285 t2.st_id == t3.st_id AND 1286 t3.kind IN (%(%s,%))"(allTestCaseTable, killedTestCaseTable, 1287 mutationTable, kinds.map!(a => cast(int) a)); 1288 1289 auto rval = appender!(TestCaseId[])(); 1290 auto stmt = db.prepare(sql); 1291 foreach (a; stmt.get.execute) 1292 rval.put(TestCaseId(a.peek!long(0))); 1293 1294 return rval.data; 1295 } 1296 1297 /// Returns: the name of the test case. 1298 string getTestCaseName(const TestCaseId id) @trusted { 1299 enum sql = format!"SELECT name FROM %s WHERE id = :id"(allTestCaseTable); 1300 auto stmt = db.prepare(sql); 1301 stmt.get.bind(":id", cast(long) id); 1302 auto res = stmt.get.execute; 1303 return res.oneValue!string; 1304 } 1305 1306 /// Returns: stats about the test case. 1307 Nullable!TestCaseInfo getTestCaseInfo(const TestCase tc, const Mutation.Kind[] kinds) @trusted { 1308 const sql = format("SELECT sum(t2.time),count(t1.st_id) 1309 FROM %s t0, %s t1, %s t2, %s t3 1310 WHERE 1311 t0.name = :name AND 1312 t0.id = t1.tc_id AND 1313 t1.st_id = t2.id AND 1314 t2.id = t3.st_id AND 1315 t3.kind IN (%(%s,%))", allTestCaseTable, killedTestCaseTable, 1316 mutationStatusTable, mutationTable, kinds.map!(a => cast(int) a)); 1317 auto stmt = db.prepare(sql); 1318 stmt.get.bind(":name", tc.name); 1319 1320 typeof(return) rval; 1321 foreach (a; stmt.get.execute) 1322 rval = TestCaseInfo(a.peek!long(0).dur!"msecs", a.peek!long(1)); 1323 return rval; 1324 } 1325 1326 /// Returns: all test cases for the file and the mutants they killed. 1327 TestCaseInfo2[] getAllTestCaseInfo2(const FileId file, const Mutation.Kind[] kinds) @trusted { 1328 // row of test case name and mutation id. 1329 const sql = format("SELECT t0.name,t3.id 1330 FROM %s t0, %s t1, %s t2, %s t3, %s t4 1331 WHERE 1332 t0.id = t1.tc_id AND 1333 t1.st_id = t2.id AND 1334 t2.id = t3.st_id AND 1335 t4.id = :file_id AND 1336 t3.kind IN (%(%s,%))", allTestCaseTable, killedTestCaseTable, 1337 mutationStatusTable, mutationTable, filesTable, kinds.map!(a => cast(int) a)); 1338 auto stmt = db.prepare(sql); 1339 stmt.get.bind(":file_id", cast(long) file); 1340 1341 MutationId[][string] data; 1342 foreach (row; stmt.get.execute) { 1343 const name = row.peek!string(0); 1344 if (auto v = name in data) { 1345 *v ~= MutationId(row.peek!long(1)); 1346 } else { 1347 data[name] = [MutationId(row.peek!long(1))]; 1348 } 1349 } 1350 1351 auto app = appender!(TestCaseInfo2[])(); 1352 data.byKeyValue.map!(a => TestCaseInfo2(TestCase(a.key), a.value)).copy(app); 1353 return app.data; 1354 } 1355 1356 /// Returns: the test case. 1357 Nullable!TestCase getTestCase(const TestCaseId id) @trusted { 1358 enum sql = format!"SELECT name FROM %s WHERE id = :id"(allTestCaseTable); 1359 auto stmt = db.prepare(sql); 1360 stmt.get.bind(":id", cast(long) id); 1361 1362 typeof(return) rval; 1363 foreach (res; stmt.get.execute) { 1364 rval = TestCase(res.peek!string(0)); 1365 } 1366 return rval; 1367 } 1368 1369 /// Returns: the test case id. 1370 Nullable!TestCaseId getTestCaseId(const TestCase tc) @trusted { 1371 enum sql = format!"SELECT id FROM %s WHERE name = :name"(allTestCaseTable); 1372 auto stmt = db.prepare(sql); 1373 stmt.get.bind(":name", tc.name); 1374 1375 typeof(return) rval; 1376 foreach (res; stmt.get.execute) { 1377 rval = TestCaseId(res.peek!long(0)); 1378 } 1379 return rval; 1380 } 1381 1382 /// The mutation ids are guaranteed to be sorted. 1383 /// Returns: the mutants the test case killed. 1384 MutationId[] getTestCaseMutantKills(const TestCaseId id, const Mutation.Kind[] kinds) @trusted { 1385 immutable sql = format!"SELECT t2.id 1386 FROM %s t1, %s t2 1387 WHERE 1388 t1.tc_id = :tid AND 1389 t1.st_id = t2.st_id AND 1390 t2.kind IN (%(%s,%)) 1391 ORDER BY 1392 t2.id"(killedTestCaseTable, mutationTable, kinds.map!(a => cast(int) a)); 1393 1394 auto rval = appender!(MutationId[])(); 1395 auto stmt = db.prepare(sql); 1396 stmt.get.bind(":tid", cast(long) id); 1397 foreach (a; stmt.get.execute) 1398 rval.put(MutationId(a.peek!long(0))); 1399 1400 return rval.data; 1401 } 1402 1403 /// Returns: test cases that killed the mutant. 1404 TestCase[] getTestCases(const MutationId id) @trusted { 1405 Appender!(TestCase[]) rval; 1406 1407 enum get_test_cases_sql = format!"SELECT t1.name,t2.location 1408 FROM %s t1, %s t2, %s t3 1409 WHERE 1410 t3.id = :id AND 1411 t3.st_id = t2.st_id AND 1412 t2.tc_id = t1.id"(allTestCaseTable, killedTestCaseTable, mutationTable); 1413 auto stmt = db.prepare(get_test_cases_sql); 1414 stmt.get.bind(":id", cast(long) id); 1415 foreach (a; stmt.get.execute) 1416 rval.put(TestCase(a.peek!string(0), a.peek!string(1))); 1417 1418 return rval.data; 1419 } 1420 1421 /** Returns: number of test cases 1422 */ 1423 long getNumOfTestCases() @trusted { 1424 enum num_test_cases_sql = format!"SELECT count(*) FROM %s"(allTestCaseTable); 1425 return db.execute(num_test_cases_sql).oneValue!long; 1426 } 1427 1428 /** Returns: test cases that killed other mutants at the same mutation point as `id`. 1429 */ 1430 TestCase[] getSurroundingTestCases(const MutationId id) @trusted { 1431 Appender!(TestCase[]) rval; 1432 1433 // TODO: optimize this. should be able to merge the two first queries to one. 1434 1435 // get the mutation point ID that id reside at 1436 long mp_id; 1437 { 1438 auto stmt = db.prepare(format!"SELECT mp_id FROM %s WHERE id=:id"(mutationTable)); 1439 stmt.get.bind(":id", cast(long) id); 1440 auto res = stmt.get.execute; 1441 if (res.empty) 1442 return null; 1443 mp_id = res.oneValue!long; 1444 } 1445 1446 // get all the mutation status ids at the mutation point 1447 long[] mut_st_ids; 1448 { 1449 auto stmt = db.prepare(format!"SELECT st_id FROM %s WHERE mp_id=:id"(mutationTable)); 1450 stmt.get.bind(":id", mp_id); 1451 auto res = stmt.get.execute; 1452 if (res.empty) 1453 return null; 1454 mut_st_ids = res.map!(a => a.peek!long(0)).array; 1455 } 1456 1457 // get all the test cases that are killed at the mutation point 1458 immutable get_test_cases_sql = format!"SELECT t2.name,t1.location FROM %s t1,%s t2 WHERE t1.tc_id == t2.id AND t0.st_id IN (%(%s,%))"( 1459 killedTestCaseTable, allTestCaseTable, mut_st_ids); 1460 auto stmt = db.prepare(get_test_cases_sql); 1461 foreach (a; stmt.get.execute) { 1462 rval.put(TestCase(a.peek!string(0), a.peek!string(1))); 1463 } 1464 1465 return rval.data; 1466 } 1467 1468 void removeTestCase(const TestCaseId id) @trusted { 1469 auto stmt = db.prepare(format!"DELETE FROM %s WHERE id=:id"(allTestCaseTable)); 1470 stmt.get.bind(":id", cast(long) id); 1471 stmt.get.execute; 1472 } 1473 1474 /// Change the status of all mutants that the test case has killed to unknown. 1475 void resetTestCaseId(const TestCaseId id) @trusted { 1476 { 1477 immutable sql = format!"UPDATE %1$s SET status=0 WHERE id IN (SELECT t1.id FROM %2$s t0, %1$s t1 WHERE t0.tc_id = :id AND t0.st_id = t1.id)"( 1478 mutationStatusTable, killedTestCaseTable); 1479 auto stmt = db.prepare(sql); 1480 stmt.get.bind(":id", cast(long) id); 1481 stmt.get.execute; 1482 } 1483 { 1484 immutable sql = format!"DELETE FROM %1$s WHERE tc_id = :id"(killedTestCaseTable); 1485 auto stmt = db.prepare(sql); 1486 stmt.get.bind(":id", cast(long) id); 1487 stmt.get.execute; 1488 } 1489 } 1490 1491 /// Returns: the context for the timeout algorithm. 1492 MutantTimeoutCtx getMutantTimeoutCtx() @trusted { 1493 foreach (res; db.run(select!MutantTimeoutCtx)) 1494 return res; 1495 return MutantTimeoutCtx.init; 1496 } 1497 1498 void putMutantTimeoutCtx(const MutantTimeoutCtx ctx) @trusted { 1499 db.run(delete_!MutantTimeoutCtx); 1500 db.run(insert!MutantTimeoutCtx.insert, ctx); 1501 } 1502 1503 void putMutantInTimeoutWorklist(const MutationStatusId id) @trusted { 1504 const sql = format!"INSERT OR IGNORE INTO %s (id) VALUES (:id)"(mutantTimeoutWorklistTable); 1505 auto stmt = db.prepare(sql); 1506 stmt.get.bind(":id", cast(long) id); 1507 stmt.get.execute; 1508 } 1509 1510 /** Remove all mutants that are in the worklist that do NOT have the 1511 * mutation status timeout. 1512 */ 1513 void reduceMutantTimeoutWorklist() @trusted { 1514 immutable sql = format!"DELETE FROM %1$s 1515 WHERE 1516 id IN (SELECT id FROM %2$s WHERE status != :status)"(mutantTimeoutWorklistTable, 1517 mutationStatusTable); 1518 auto stmt = db.prepare(sql); 1519 stmt.get.bind(":status", cast(ubyte) Mutation.Status.timeout); 1520 stmt.get.execute; 1521 } 1522 1523 /// Remove all mutants from the worklist. 1524 void clearMutantTimeoutWorklist() @trusted { 1525 immutable sql = format!"DELETE FROM %1$s"(mutantTimeoutWorklistTable); 1526 db.run(sql); 1527 } 1528 1529 /// Returns: the number of mutants in the worklist. 1530 long countMutantTimeoutWorklist() @trusted { 1531 immutable sql = format!"SELECT count(*) FROM %1$s"(mutantTimeoutWorklistTable); 1532 auto stmt = db.prepare(sql); 1533 auto res = stmt.get.execute(); 1534 return res.oneValue!long; 1535 } 1536 1537 /// Changes the status of mutants in the timeout worklist to unknown. 1538 void resetMutantTimeoutWorklist(Mutation.Status toStatus) @trusted { 1539 immutable sql = format!"UPDATE %1$s SET status=:st WHERE id IN (SELECT id FROM %2$s)"( 1540 mutationStatusTable, mutantTimeoutWorklistTable); 1541 auto stmt = db.prepare(sql); 1542 stmt.get.bind(":st", cast(ubyte) toStatus); 1543 stmt.get.execute; 1544 } 1545 1546 /** Update the content of metadata tables with what has been added to the 1547 * raw table data. 1548 */ 1549 void updateMetadata() @trusted { 1550 db.run(format!"DELETE FROM %s"(srcMetadataTable)); 1551 db.run(format!"DELETE FROM %s"(nomutTable)); 1552 db.run(format!"DELETE FROM %s"(nomutDataTable)); 1553 1554 immutable nomut_tbl = "INSERT INTO %s 1555 SELECT 1556 t0.id mp_id, 1557 t1.line line, 1558 count(*) status 1559 FROM %s t0, %s t1 1560 WHERE 1561 t0.file_id = t1.file_id AND 1562 (t1.line BETWEEN t0.line AND t0.line_end) 1563 GROUP BY 1564 t0.id"; 1565 db.run(format!nomut_tbl(nomutTable, mutationPointTable, rawSrcMetadataTable)); 1566 1567 immutable src_metadata_sql = "INSERT INTO %s 1568 SELECT DISTINCT 1569 t0.id AS mut_id, 1570 t1.id AS st_id, 1571 t2.id AS mp_id, 1572 t3.id AS file_id, 1573 (SELECT count(*) FROM %s WHERE nomut.mp_id = t2.id) as nomut 1574 FROM %s t0, %s t1, %s t2, %s t3 1575 WHERE 1576 t0.mp_id = t2.id AND 1577 t0.st_id = t1.id AND 1578 t2.file_id = t3.id"; 1579 db.run(format!src_metadata_sql(srcMetadataTable, nomutTable, 1580 mutationTable, mutationStatusTable, mutationPointTable, filesTable)); 1581 1582 immutable nomut_data_tbl = "INSERT INTO %s 1583 SELECT 1584 t0.id as mut_id, 1585 t0.mp_id as mp_id, 1586 t1.line as line, 1587 t1.tag as tag, 1588 t1.comment as comment 1589 FROM %s t0, %s t1, %s t2 1590 WHERE 1591 t0.mp_id = t2.mp_id AND 1592 t1.line = t2.line"; 1593 db.run(format!nomut_data_tbl(nomutDataTable, mutationTable, 1594 rawSrcMetadataTable, nomutTable)); 1595 } 1596 1597 /// Returns: all schematas that excluding those that are known to not be possible to compile. 1598 SchemataId[] getSchematas() @trusted { 1599 immutable sql = format!"SELECT t0.id 1600 FROM %1$s t0 1601 WHERE 1602 t0.id NOT IN (SELECT id FROM %2$s)"(schemataTable, invalidSchemataTable); 1603 auto stmt = db.prepare(sql); 1604 auto app = appender!(SchemataId[])(); 1605 foreach (a; stmt.get.execute) { 1606 app.put(SchemataId(a.peek!long(0))); 1607 } 1608 return app.data; 1609 } 1610 1611 Nullable!Schemata getSchemata(SchemataId id) @trusted { 1612 immutable sql = format!"SELECT 1613 t1.path, t0.text, t0.offset_begin, t0.offset_end 1614 FROM %1$s t0, %2$s t1 1615 WHERE 1616 t0.schem_id = :id AND 1617 t0.file_id = t1.id 1618 ORDER BY t0.order_ ASC 1619 "(schemataFragmentTable, filesTable); 1620 1621 typeof(return) rval; 1622 auto stmt = db.prepare(sql); 1623 stmt.get.bind(":id", cast(long) id); 1624 1625 auto app = appender!(SchemataFragment[])(); 1626 foreach (a; stmt.get.execute) { 1627 app.put(SchemataFragment(a.peek!string(0).Path, 1628 Offset(a.peek!uint(2), a.peek!uint(3)), a.peek!(ubyte[])(1))); 1629 } 1630 1631 if (!app.data.empty) { 1632 rval = Schemata(SchemataId(id), app.data); 1633 } 1634 1635 return rval; 1636 } 1637 1638 /// Returns: number of mutants in a schemata with the specified kind and status. 1639 long schemataMutantsWithStatus(const SchemataId id, 1640 const Mutation.Kind[] kinds, const Mutation.Status status) @trusted { 1641 const sql = format!"SELECT count(*) 1642 FROM %s t1, %s t2, %s t3 1643 WHERE 1644 t1.schem_id = :id AND 1645 t1.st_id = t2.id AND 1646 t2.status = :status AND 1647 t3.st_id = t1.st_id AND 1648 t3.kind IN (%(%s,%)) 1649 "(schemataMutantTable, mutationStatusTable, mutationTable, kinds.map!(a => cast(int) a)); 1650 1651 auto stmt = db.prepare(sql); 1652 stmt.get.bind(":id", cast(long) id); 1653 stmt.get.bind(":status", cast(long) status); 1654 return stmt.get.execute.oneValue!long; 1655 } 1656 1657 MutationStatusId[] getSchemataMutants(const SchemataId id, 1658 const Mutation.Kind[] kinds, const Mutation.Status status) @trusted { 1659 // TODO: DISTINCT should not be needed. Instead use e.g. a constraint on the table or something 1660 immutable sql = format!"SELECT DISTINCT t1.st_id 1661 FROM %s t1, %s t2, %s t3 1662 WHERE 1663 t1.schem_id = :id AND 1664 t1.st_id = t2.id AND 1665 t2.status = :status AND 1666 t3.st_id = t1.st_id AND 1667 t3.kind IN (%(%s,%)) 1668 "(schemataMutantTable, mutationStatusTable, mutationTable, 1669 kinds.map!(a => cast(int) a)); 1670 auto stmt = db.prepare(sql); 1671 stmt.get.bind(":id", cast(long) id); 1672 stmt.get.bind(":status", cast(long) status); 1673 1674 auto app = appender!(MutationStatusId[])(); 1675 foreach (a; stmt.get.execute) { 1676 app.put(a.peek!long(0).MutationStatusId); 1677 } 1678 1679 return app.data; 1680 } 1681 1682 /// Returns: the kind of mutants a schemata contains. 1683 Mutation.Kind[] getSchemataKinds(const SchemataId id) @trusted { 1684 immutable sql = format!"SELECT DISTINCT t1.kind 1685 FROM %1$s t0, %2$s t1 1686 WHERE 1687 t0.schem_id = :id AND 1688 t0.st_id = t1.st_id 1689 "(schemataMutantTable, mutationTable); 1690 auto stmt = db.prepare(sql); 1691 stmt.get.bind(":id", cast(long) id); 1692 1693 auto app = appender!(Mutation.Kind[])(); 1694 foreach (a; stmt.get.execute) { 1695 app.put(a.peek!long(0).to!(Mutation.Kind)); 1696 } 1697 1698 return app.data; 1699 } 1700 1701 /// Mark a schemata as invalid. 1702 void markInvalid(const SchemataId id) @trusted { 1703 immutable sql = format!"INSERT OR IGNORE INTO %1$s VALUES(:id)"(invalidSchemataTable); 1704 auto stmt = db.prepare(sql); 1705 stmt.get.bind(":id", cast(long) id); 1706 stmt.get.execute; 1707 } 1708 1709 /// Create a schemata from a bundle of fragments. 1710 Nullable!SchemataId putSchemata(SchemataChecksum cs, 1711 const SchemataFragment[] fragments, MutationStatusId[] mutants) @trusted { 1712 import std.range : enumerate; 1713 import dextool.utility : dextoolBinaryId; 1714 1715 const schemId = cast(long) cs.value.c0; 1716 1717 const exists = () { 1718 immutable sql = format!"SELECT count(*) FROM %1$s WHERE id=:id"(schemataTable); 1719 auto stmt = db.prepare(sql); 1720 stmt.get.bind(":id", schemId); 1721 return stmt.get.execute.oneValue!long != 0; 1722 1723 }(); 1724 1725 if (exists) 1726 return typeof(return)(); 1727 1728 () { 1729 immutable sql = format!"INSERT INTO %1$s VALUES(:id, :nr, :version)"(schemataTable); 1730 auto stmt = db.prepare(sql); 1731 stmt.get.bind(":id", cast(long) cs.value.c0); 1732 stmt.get.bind(":nr", cast(long) fragments.length); 1733 stmt.get.bind(":version", dextoolBinaryId); 1734 stmt.get.execute; 1735 }(); 1736 1737 foreach (f; fragments.enumerate) { 1738 const fileId = getFileId(f.value.file); 1739 if (fileId.isNull) { 1740 logger.warning("Unable to add schemata fragment for file %s because it doesn't exist", 1741 f.value.file); 1742 continue; 1743 } 1744 1745 db.run(insert!SchemataFragmentTable, SchemataFragmentTable(0, 1746 schemId, cast(long) fileId.get, f.index, f.value.text, 1747 f.value.offset.begin, f.value.offset.end)); 1748 } 1749 1750 // relate mutants to this schemata. 1751 db.run(insertOrIgnore!SchemataMutantTable, 1752 mutants.map!(a => SchemataMutantTable(cast(long) a, schemId))); 1753 1754 return typeof(return)(schemId.SchemataId); 1755 } 1756 1757 void pruneSchemas() @trusted { 1758 import dextool.utility : dextoolBinaryId; 1759 1760 auto remove = () { 1761 auto remove = appender!(long[])(); 1762 1763 // remove those that where created by a another version of the tool 1764 immutable sqlVersion = format!"SELECT t0.id 1765 FROM %1$s t0 1766 WHERE t0.version != %2$s 1767 "(schemataTable, dextoolBinaryId); 1768 auto stmt = db.prepare(sqlVersion); 1769 foreach (a; stmt.get.execute) { 1770 remove.put(a.peek!long(0)); 1771 } 1772 1773 // remove those that have lost some fragments 1774 immutable sqlFragment = format!"SELECT t0.id 1775 FROM 1776 %1$s t0, 1777 (SELECT schem_id id,count(*) fragments FROM %2$s GROUP BY schem_id) t1 1778 WHERE 1779 t0.id = t1.id AND 1780 t0.fragments != t1.fragments 1781 "(schemataTable, schemataFragmentTable); 1782 stmt = db.prepare(sqlFragment); 1783 foreach (a; stmt.get.execute) { 1784 remove.put(a.peek!long(0)); 1785 } 1786 1787 // remove those that have lost all fragments 1788 immutable sqlNoFragment = format!"SELECT t0.id FROM %1$s t0 WHERE t0.id NOT IN (SELECT schem_id FROM %2$s)"( 1789 schemataTable, schemataFragmentTable); 1790 stmt = db.prepare(sqlNoFragment); 1791 foreach (a; stmt.get.execute) { 1792 remove.put(a.peek!long(0)); 1793 } 1794 1795 return remove.data; 1796 }(); 1797 1798 immutable sql = format!"DELETE FROM %1$s WHERE id=:id"(schemataTable); 1799 auto stmt = db.prepare(sql); 1800 foreach (a; remove) { 1801 stmt.get.bind(":id", a); 1802 stmt.get.execute; 1803 stmt.get.reset; 1804 } 1805 } 1806 } 1807 1808 private: 1809 1810 MarkedMutant make(MarkedMutantTbl m) { 1811 import dextool.plugin.mutate.backend.type; 1812 1813 return MarkedMutant(m.mutationStatusId.MutationStatusId, Checksum(m.checksum0, 1814 m.checksum1), m.mutationId.MutationId, SourceLoc(m.line, m.column), 1815 m.path.Path, m.toStatus.to!(Mutation.Status), m.time, 1816 m.rationale.Rationale, m.mutText); 1817 }