1 /** 2 Copyright: Copyright (c) 2018-2021, 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, filter; 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.range : enumerate; 32 import std.regex : Regex, matchFirst; 33 import std.typecons : Nullable, Flag, No; 34 35 import d2sqlite3 : SqlDatabase = Database; 36 import miniorm : Miniorm, select, insert, insertOrReplace, delete_, 37 insertOrIgnore, toSqliteDateTime, fromSqLiteDateTime, Bind; 38 import my.named_type; 39 import my.optional; 40 import my.term_color; 41 import my.set; 42 43 import dextool.type : AbsolutePath, Path, ExitStatusType; 44 45 import dextool.plugin.mutate.backend.database.schema; 46 import dextool.plugin.mutate.backend.database.type; 47 import dextool.plugin.mutate.backend.type : MutationPoint, Mutation, Checksum, 48 Language, Offset, TestCase, SourceLoc, SchemataChecksum; 49 import dextool.plugin.mutate.type : MutationOrder; 50 51 /** Database wrapper with minimal dependencies. 52 */ 53 struct Database { 54 package Miniorm db; 55 56 /** Create a database by either opening an existing or initializing a new. 57 * 58 * Params: 59 * db = path to the database 60 */ 61 static auto make(string db) @safe { 62 return Database(initializeDB(db)); 63 } 64 65 scope auto transaction() @trusted { 66 return db.transaction; 67 } 68 69 void run(string sql) { 70 db.run(sql); 71 } 72 73 bool isToolVersionDifferent(ToolVersion compareTo) @trusted { 74 foreach (a; db.run(select!DextoolVersionTable)) { 75 return a.checksum != compareTo.get; 76 } 77 // if there is no tool version recorded then assume it is different. 78 return true; 79 } 80 81 /// Update the version of the tool. 82 void updateToolVersion(const ToolVersion tv) @trusted { 83 db.run(delete_!DextoolVersionTable); 84 db.run(insert!DextoolVersionTable, DextoolVersionTable(tv.get)); 85 } 86 87 /// If the file has already been analyzed. 88 bool isAnalyzed(const Path p) @trusted { 89 auto stmt = db.prepare("SELECT count(*) FROM files WHERE path=:path LIMIT 1"); 90 stmt.get.bind(":path", cast(string) p); 91 auto res = stmt.get.execute; 92 return res.oneValue!long != 0; 93 } 94 95 /// If the file has already been analyzed. 96 bool isAnalyzed(const Path p, const Checksum cs) @trusted { 97 auto stmt = db.prepare( 98 "SELECT count(*) FROM files WHERE path=:path AND checksum0=:cs0 AND checksum1=:cs1 LIMIT 1"); 99 stmt.get.bind(":path", cast(string) p); 100 stmt.get.bind(":cs0", cast(long) cs.c0); 101 stmt.get.bind(":cs1", cast(long) cs.c1); 102 auto res = stmt.get.execute; 103 return res.oneValue!long != 0; 104 } 105 106 Nullable!FileId getFileId(const Path p) @trusted { 107 static immutable sql = format("SELECT id FROM %s WHERE path=:path", filesTable); 108 auto stmt = db.prepare(sql); 109 stmt.get.bind(":path", p.toString); 110 auto res = stmt.get.execute; 111 112 typeof(return) rval; 113 if (!res.empty) 114 rval = FileId(res.oneValue!long); 115 return rval; 116 } 117 118 /// Returns: the path ID for the mutant. 119 Nullable!FileId getFileId(const MutationId id) @trusted { 120 static immutable sql = format("SELECT t1.file_id 121 FROM %s t0, %s t1 122 WHERE t0.id = :id AND t0.mp_id = t1.id", 123 mutationTable, mutationPointTable); 124 auto stmt = db.prepare(sql); 125 stmt.get.bind(":id", cast(long) id); 126 127 typeof(return) rval; 128 foreach (ref r; stmt.get.execute) 129 rval = FileId(r.peek!long(0)); 130 return rval; 131 } 132 133 /// Returns: the file path that the id correspond to. 134 Nullable!Path getFile(const FileId id) @trusted { 135 static immutable sql = format("SELECT path FROM %s WHERE id = :id", filesTable); 136 auto stmt = db.prepare(sql); 137 stmt.get.bind(":id", id.get); 138 139 typeof(return) rval; 140 foreach (ref r; stmt.get.execute) 141 rval = Path(r.peek!string(0)); 142 return rval; 143 } 144 145 Optional!Language getFileIdLanguage(const FileId id) @trusted { 146 static immutable sql = format!"SELECT lang FROM %s WHERE id = :id"(filesTable); 147 auto stmt = db.prepare(sql); 148 stmt.get.bind(":id", id.get); 149 150 foreach (ref r; stmt.get.execute) 151 return some(r.peek!ubyte(0).to!Language); 152 return none!Language; 153 } 154 155 /// Returns: all files tagged as a root. 156 FileId[] getRootFiles() @trusted { 157 static immutable sql = format!"SELECT id FROM %s WHERE root=1"(filesTable); 158 159 auto app = appender!(FileId[])(); 160 auto stmt = db.prepare(sql); 161 foreach (ref r; stmt.get.execute) { 162 app.put(r.peek!long(0).FileId); 163 } 164 return app.data; 165 } 166 167 /// Remove the file with all mutations that are coupled to it. 168 void removeFile(const Path p) @trusted { 169 auto stmt = db.prepare(format!"DELETE FROM %s WHERE path=:path"(filesTable)); 170 stmt.get.bind(":path", p.toString); 171 stmt.get.execute; 172 } 173 174 /// Returns: All files in the database as relative paths. 175 Path[] getFiles() @trusted { 176 auto stmt = db.prepare(format!"SELECT path FROM %s"(filesTable)); 177 auto res = stmt.get.execute; 178 179 auto app = appender!(Path[]); 180 foreach (ref r; res) { 181 app.put(Path(r.peek!string(0))); 182 } 183 184 return app.data; 185 } 186 187 Nullable!Checksum getFileChecksum(const Path p) @trusted { 188 static immutable sql = format!"SELECT checksum0,checksum1 FROM %s WHERE path=:path"( 189 filesTable); 190 auto stmt = db.prepare(sql); 191 stmt.get.bind(":path", p.toString); 192 auto res = stmt.get.execute; 193 194 typeof(return) rval; 195 if (!res.empty) { 196 rval = Checksum(res.front.peek!long(0), res.front.peek!long(1)); 197 } 198 199 return rval; 200 } 201 202 /// Returns: the timestamp of the newest file that was added. 203 Optional!SysTime getNewestFile() @trusted { 204 auto stmt = db.prepare(format!"SELECT timestamp 205 FROM %s ORDER BY datetime(timestamp) DESC LIMIT 1"( 206 filesTable)); 207 auto res = stmt.get.execute; 208 209 foreach (ref r; res) { 210 return some(r.peek!string(0).fromSqLiteDateTime); 211 } 212 213 return none!SysTime; 214 } 215 216 void put(const Path p, Checksum cs, const Language lang, bool isRoot) @trusted { 217 static immutable sql = format!"INSERT OR IGNORE INTO %s (path, checksum0, checksum1, lang, timestamp, root) 218 VALUES (:path, :checksum0, :checksum1, :lang, :time, :root)"( 219 filesTable); 220 auto stmt = db.prepare(sql); 221 stmt.get.bind(":path", p.toString); 222 stmt.get.bind(":checksum0", cast(long) cs.c0); 223 stmt.get.bind(":checksum1", cast(long) cs.c1); 224 stmt.get.bind(":lang", cast(long) lang); 225 stmt.get.bind(":time", Clock.currTime.toSqliteDateTime); 226 stmt.get.bind(":root", isRoot); 227 stmt.get.execute; 228 } 229 230 /** Remove all mutants points from the database. 231 * 232 * This removes all the mutants because of the cascade delete of the 233 * tables. But it will keep the mutation statuses and thus the checksums 234 * and the status of the code changes. 235 * 236 * This then mean that when mutations+mutation points are added back they 237 * may reconnect with a mutation status. 238 */ 239 void removeAllMutationPoints() @trusted { 240 static immutable sql = format!"DELETE FROM %s"(mutationPointTable); 241 db.run(sql); 242 } 243 244 /// ditto 245 void removeAllFiles() @trusted { 246 static immutable sql = format!"DELETE FROM %s"(filesTable); 247 db.run(sql); 248 } 249 250 /// Compact the database by running a VACUUM operation 251 void vacuum() @trusted { 252 db.run("VACUUM"); 253 } 254 255 /// Returns: the stored scores in ascending order by their `time`. 256 MutationScore[] getMutationScoreHistory() @trusted { 257 import std.algorithm : sort; 258 259 auto app = appender!(MutationScore[])(); 260 foreach (r; db.run(select!MutationScoreHistoryTable)) { 261 app.put(MutationScore(r.timeStamp, typeof(MutationScore.score)(r.score))); 262 } 263 264 return app.data.sort!((a, b) => a.timeStamp < b.timeStamp).array; 265 } 266 267 /// Add a mutation score to the history table. 268 void putMutationScore(const MutationScore score) @trusted { 269 db.run(insert!MutationScoreHistoryTable, MutationScoreHistoryTable(0, 270 score.timeStamp, score.score.get)); 271 } 272 273 /// Trim the mutation score history table to only contain the last `keep` scores. 274 void trimMutationScore(const long keep) @trusted { 275 auto stmt = db.prepare(format!"SELECT count(*) FROM %s"(mutationScoreHistoryTable)); 276 const sz = stmt.get.execute.oneValue!long; 277 278 if (sz < keep) 279 return; 280 281 auto ids = appender!(long[])(); 282 stmt = db.prepare(format!"SELECT t0.id FROM t0 %s ORDER BY t0.time ASC LIMIT :limit"( 283 mutationScoreHistoryTable)); 284 stmt.get.bind(":limit", sz - keep); 285 foreach (a; stmt.get.execute) 286 ids.put(a.peek!long(0)); 287 288 stmt = db.prepare(format!"DELETE FROM %s WHERE id = :id"(mutationScoreHistoryTable)); 289 foreach (a; ids.data) { 290 stmt.get.bind(":id", a); 291 stmt.get.execute; 292 stmt.get.reset; 293 } 294 } 295 296 /// Returns: the latest/newest timestamp of the tracked SUT or test files. 297 Optional!SysTime getLatestTimeStampOfTestOrSut() @trusted { 298 import std.algorithm : max; 299 300 auto test = testFileApi.getNewestTestFile; 301 auto sut = getNewestFile; 302 303 if (!(test.hasValue || sut.hasValue)) 304 return none!SysTime; 305 306 return some(max(test.orElse(TestFile.init).timeStamp, sut.orElse(SysTime.init))); 307 } 308 309 DbDependency dependencyApi() return @safe { 310 return typeof(return)(&db, &this); 311 } 312 313 DbTestCmd testCmdApi() return @safe { 314 return typeof(return)(&db, &this); 315 } 316 317 DbTestCase testCaseApi() return @safe { 318 return typeof(return)(&db, &this); 319 } 320 321 DbMutant mutantApi() return @safe { 322 return typeof(return)(&db, &this); 323 } 324 325 DbWorklist worklistApi() return @safe { 326 return typeof(return)(&db, &this); 327 } 328 329 DbMarkMutant markMutantApi() return @safe { 330 return typeof(return)(&db, &this); 331 } 332 333 DbTimeout timeoutApi() return @safe { 334 return typeof(return)(&db, &this); 335 } 336 337 DbCoverage coverageApi() return @safe { 338 return typeof(return)(&db, &this); 339 } 340 341 DbSchema schemaApi() return @safe { 342 return typeof(return)(&db, &this); 343 } 344 345 DbTestFile testFileApi() return @safe { 346 return typeof(return)(&db, &this); 347 } 348 349 DbMetaData metaDataApi() return @safe { 350 return typeof(return)(&db, &this); 351 } 352 } 353 354 /** Dependencies between root and those files that should trigger a re-analyze 355 * of the root if they are changed. 356 */ 357 struct DbDependency { 358 private Miniorm* db; 359 private Database* wrapperDb; 360 361 /// The root must already exist or the whole operation will fail with an sql error. 362 void set(const Path path, const DepFile[] deps) @trusted { 363 static immutable insertDepSql = format!"INSERT OR IGNORE INTO %1$s (file,checksum0,checksum1) 364 VALUES(:file,:cs0,:cs1) 365 ON CONFLICT (file) DO UPDATE SET checksum0=:cs0,checksum1=:cs1 WHERE file=:file"( 366 depFileTable); 367 368 auto stmt = db.prepare(insertDepSql); 369 auto ids = appender!(long[])(); 370 foreach (a; deps) { 371 stmt.get.bind(":file", a.file.toString); 372 stmt.get.bind(":cs0", cast(long) a.checksum.c0); 373 stmt.get.bind(":cs1", cast(long) a.checksum.c1); 374 stmt.get.execute; 375 stmt.get.reset; 376 377 // can't use lastInsertRowid because a conflict would not update 378 // the ID. 379 auto id = getId(a.file); 380 if (id.hasValue) 381 ids.put(id.orElse(0L)); 382 } 383 384 static immutable addRelSql = format!"INSERT OR IGNORE INTO %1$s (dep_id,file_id) VALUES(:did, :fid)"( 385 depRootTable); 386 stmt = db.prepare(addRelSql); 387 const fid = () { 388 auto a = wrapperDb.getFileId(path); 389 if (a.isNull) { 390 throw new Exception( 391 "File is not tracked (is missing from the files table in the database) " 392 ~ path); 393 } 394 return a.get; 395 }(); 396 397 foreach (id; ids.data) { 398 stmt.get.bind(":did", id); 399 stmt.get.bind(":fid", fid.get); 400 stmt.get.execute; 401 stmt.get.reset; 402 } 403 } 404 405 private Optional!long getId(const Path file) { 406 foreach (a; db.run(select!DependencyFileTable.where("file = :file", 407 Bind("file")), file.toString)) { 408 return some(a.id); 409 } 410 return none!long; 411 } 412 413 /// Returns: all dependencies. 414 DepFile[] getAll() @trusted { 415 return db.run(select!DependencyFileTable) 416 .map!(a => DepFile(Path(a.file), Checksum(a.checksum0, a.checksum1))).array; 417 } 418 419 /// Returns: all files that a root is dependent on. 420 Path[] get(const Path root) @trusted { 421 static immutable sql = format!"SELECT t0.file 422 FROM %1$s t0, %2$s t1, %3$s t2 423 WHERE 424 t0.id = t1.dep_id AND 425 t1.file_id = t2.id AND 426 t2.path = :file"(depFileTable, 427 depRootTable, filesTable); 428 429 auto stmt = db.prepare(sql); 430 stmt.get.bind(":file", root.toString); 431 auto app = appender!(Path[])(); 432 foreach (ref a; stmt.get.execute) { 433 app.put(Path(a.peek!string(0))); 434 } 435 436 return app.data; 437 } 438 439 /// Remove all dependencies that have no relation to a root. 440 void cleanup() @trusted { 441 db.run(format!"DELETE FROM %1$s 442 WHERE id NOT IN (SELECT dep_id FROM %2$s)"(depFileTable, 443 depRootTable)); 444 } 445 446 } 447 448 struct DbTestCmd { 449 import my.hash : Checksum64; 450 451 private Miniorm* db; 452 private Database* wrapperDb; 453 454 void set(string testCmd, ChecksumTestCmdOriginal cs) @trusted { 455 static immutable sql = format!"INSERT OR IGNORE INTO %1$s (checksum, cmd) VALUES(:cs, :cmd)"( 456 testCmdOriginalTable); 457 458 auto stmt = db.prepare(sql); 459 stmt.get.bind(":cs", cast(long) cs.get.c0); 460 stmt.get.bind(":cmd", testCmd); 461 stmt.get.execute; 462 } 463 464 void removeOriginal(string testCmd) @trusted { 465 static immutable sql = "DELETE FROM " ~ testCmdOriginalTable ~ " WHERE cmd = :cmd"; 466 auto stmt = db.prepare(sql); 467 stmt.get.bind(":cmd", testCmd); 468 stmt.get.execute; 469 } 470 471 void remove(ChecksumTestCmdOriginal cs) @trusted { 472 static immutable sql = "DELETE FROM " ~ testCmdOriginalTable ~ " WHERE checksum = :cs"; 473 auto stmt = db.prepare(sql); 474 stmt.get.bind(":cs", cast(long) cs.get.c0); 475 stmt.get.execute; 476 } 477 478 Set!Checksum64 original() @trusted { 479 static immutable sql = "SELECT checksum FROM " ~ testCmdOriginalTable; 480 481 auto stmt = db.prepare(sql); 482 typeof(return) rval; 483 foreach (ref r; stmt.get.execute) 484 rval.add(Checksum64(cast(ulong) r.peek!long(0))); 485 return rval; 486 } 487 488 void add(ChecksumTestCmdMutated cs, Mutation.Status status) @trusted { 489 static immutable sql = format!"INSERT OR REPLACE INTO %1$s (checksum,status,timestamp) VALUES(:cs,:status,:ts)"( 490 testCmdMutatedTable); 491 492 auto stmt = db.prepare(sql); 493 stmt.get.bind(":cs", cast(long) cs.get.c0); 494 stmt.get.bind(":status", cast(long) status); 495 stmt.get.bind(":ts", Clock.currTime.toSqliteDateTime); 496 stmt.get.execute; 497 } 498 499 /// Trim the saved checksums to only the latest `keep`. 500 void trimMutated(const long keep) @trusted { 501 auto stmt = db.prepare(format!"SELECT count(*) FROM %s"(testCmdMutatedTable)); 502 const sz = stmt.get.execute.oneValue!long; 503 if (sz < keep) 504 return; 505 506 auto ids = appender!(long[])(); 507 stmt = db.prepare(format!"SELECT checksum FROM %s ORDER BY timestamp ASC LIMIT :limit"( 508 testCmdMutatedTable)); 509 stmt.get.bind(":limit", sz - keep); 510 foreach (a; stmt.get.execute) 511 ids.put(a.peek!long(0)); 512 513 stmt = db.prepare(format!"DELETE FROM %s WHERE checksum = :cs"(testCmdMutatedTable)); 514 foreach (a; ids.data) { 515 stmt.get.bind(":cs", a); 516 stmt.get.execute; 517 stmt.get.reset; 518 } 519 } 520 521 Mutation.Status[Checksum64] mutated() @trusted { 522 static immutable sql = "SELECT checksum,status FROM " ~ testCmdMutatedTable; 523 524 auto stmt = db.prepare(sql); 525 typeof(return) rval; 526 foreach (ref r; stmt.get.execute) 527 rval[Checksum64(cast(ulong) r.peek!long(0))] = r.peek!long(1).to!(Mutation.Status); 528 return rval; 529 } 530 531 /// Returns: the stored runtimes in ascending order by their `timeStamp`. 532 TestCmdRuntime[] getTestCmdRuntimes() @trusted { 533 import std.algorithm : sort; 534 535 auto app = appender!(TestCmdRuntime[])(); 536 foreach (r; db.run(select!RuntimeHistoryTable)) { 537 app.put(TestCmdRuntime(r.timeStamp, r.timeMs.dur!"msecs")); 538 } 539 540 return app.data.sort!((a, b) => a.timeStamp < b.timeStamp).array; 541 } 542 543 /// Drop all currently stored runtimes and replaces with `runtime`. 544 void setTestCmdRuntimes(const TestCmdRuntime[] runtimes) @trusted { 545 db.run(format!"DELETE FROM %s"(runtimeHistoryTable)); 546 db.run(insertOrReplace!RuntimeHistoryTable, 547 runtimes.enumerate.map!(a => RuntimeHistoryTable(a.index, 548 a.value.timeStamp, a.value.runtime.total!"msecs"))); 549 } 550 } 551 552 struct DbTestCase { 553 private Miniorm* db; 554 private Database* wrapperDb; 555 556 /** Add a link between the mutation and what test case killed it. 557 * 558 * Params: 559 * id = ? 560 * tcs = test cases to add 561 */ 562 void updateMutationTestCases(const MutationId id, const(TestCase)[] tcs) @trusted { 563 if (tcs.length == 0) 564 return; 565 566 immutable statusId = () { 567 static immutable st_id_for_mutation_q = format!"SELECT st_id FROM %s WHERE id=:id"( 568 mutationTable); 569 auto stmt = db.prepare(st_id_for_mutation_q); 570 stmt.get.bind(":id", cast(long) id); 571 return stmt.get.execute.oneValue!long; 572 }(); 573 updateMutationTestCases(MutationStatusId(statusId), tcs); 574 } 575 576 /** Add a link between the mutation and what test case killed it. 577 * 578 * Params: 579 * id = ? 580 * tcs = test cases to add 581 */ 582 void updateMutationTestCases(const MutationStatusId statusId, const(TestCase)[] tcs) @trusted { 583 if (tcs.length == 0) 584 return; 585 586 try { 587 static immutable remove_old_sql = format!"DELETE FROM %s WHERE st_id=:id"( 588 killedTestCaseTable); 589 auto stmt = db.prepare(remove_old_sql); 590 stmt.get.bind(":id", statusId.get); 591 stmt.get.execute; 592 } catch (Exception e) { 593 } 594 595 static immutable add_if_non_exist_tc_sql = format!"INSERT OR IGNORE INTO %s (name) SELECT :name1 WHERE NOT EXISTS (SELECT * FROM %s WHERE name = :name2)"( 596 allTestCaseTable, allTestCaseTable); 597 auto stmt_insert_tc = db.prepare(add_if_non_exist_tc_sql); 598 599 static immutable add_new_sql = format!"INSERT OR IGNORE INTO %s (st_id, tc_id, location) SELECT :st_id,t1.id,:loc FROM %s t1 WHERE t1.name = :tc"( 600 killedTestCaseTable, allTestCaseTable); 601 auto stmt_insert = db.prepare(add_new_sql); 602 foreach (const tc; tcs) { 603 try { 604 stmt_insert_tc.get.reset; 605 stmt_insert_tc.get.bind(":name1", tc.name); 606 stmt_insert_tc.get.bind(":name2", tc.name); 607 stmt_insert_tc.get.execute; 608 609 stmt_insert.get.reset; 610 stmt_insert.get.bind(":st_id", statusId.get); 611 stmt_insert.get.bind(":loc", tc.location); 612 stmt_insert.get.bind(":tc", tc.name); 613 stmt_insert.get.execute; 614 } catch (Exception e) { 615 logger.warning(e.msg); 616 } 617 } 618 } 619 620 /** Set detected test cases. 621 * 622 * This will replace those that where previously stored. 623 * 624 * Returns: ID of affected mutation statuses. 625 */ 626 MutationStatusId[] setDetectedTestCases(const(TestCase)[] tcs) @trusted { 627 if (tcs.length == 0) 628 return null; 629 630 auto ids = appender!(MutationStatusId[])(); 631 632 static immutable tmp_name = "tmp_new_tc_" ~ __LINE__.to!string; 633 internalAddDetectedTestCases(tcs, tmp_name); 634 635 static immutable mut_st_id = format!"SELECT DISTINCT t1.st_id 636 FROM %s t0, %s t1 637 WHERE 638 t0.name NOT IN (SELECT name FROM %s) AND 639 t0.id = t1.tc_id"(allTestCaseTable, 640 killedTestCaseTable, tmp_name); 641 auto stmt = db.prepare(mut_st_id); 642 foreach (res; stmt.get.execute) { 643 ids.put(res.peek!long(0).MutationStatusId); 644 } 645 646 static immutable remove_old_sql = format!"DELETE FROM %s WHERE name NOT IN (SELECT name FROM %s)"( 647 allTestCaseTable, tmp_name); 648 db.run(remove_old_sql); 649 650 db.run(format!"DROP TABLE %s"(tmp_name)); 651 652 return ids.data; 653 } 654 655 /** Add test cases to those that have been detected. 656 * 657 * They will be added if they are unique. 658 */ 659 void addDetectedTestCases(const(TestCase)[] tcs) @trusted { 660 if (tcs.length == 0) 661 return; 662 663 static immutable tmp_name = "tmp_new_tc_" ~ __LINE__.to!string; 664 internalAddDetectedTestCases(tcs, tmp_name); 665 db.run(format!"DROP TABLE %s"(tmp_name)); 666 } 667 668 /// ditto. 669 private void internalAddDetectedTestCases(const(TestCase)[] tcs, string tmp_tbl) @trusted { 670 db.run(format!"CREATE TEMP TABLE %s (id INTEGER PRIMARY KEY, name TEXT NOT NULL)"( 671 tmp_tbl)); 672 673 const add_tc_sql = format!"INSERT OR IGNORE INTO %s (name) VALUES(:name)"(tmp_tbl); 674 auto insert_s = db.prepare(add_tc_sql); 675 foreach (tc; tcs.filter!(a => !a.name.empty)) { 676 insert_s.get.bind(":name", tc.name); 677 insert_s.get.execute; 678 insert_s.get.reset; 679 } 680 681 // https://stackoverflow.com/questions/2686254/how-to-select-all-records-from-one-table-that-do-not-exist-in-another-table 682 //Q: What is happening here? 683 // 684 //A: Conceptually, we select all rows from table1 and for each row we 685 //attempt to find a row in table2 with the same value for the name 686 //column. If there is no such row, we just leave the table2 portion of 687 //our result empty for that row. Then we constrain our selection by 688 //picking only those rows in the result where the matching row does not 689 //exist. Finally, We ignore all fields from our result except for the 690 //name column (the one we are sure that exists, from table1). 691 // 692 //While it may not be the most performant method possible in all cases, 693 //it should work in basically every database engine ever that attempts 694 //to implement ANSI 92 SQL 695 const add_missing_sql = format!"INSERT OR IGNORE INTO %s (name) SELECT t1.name FROM %s t1 LEFT JOIN %s t2 ON t2.name = t1.name WHERE t2.name IS NULL"( 696 allTestCaseTable, tmp_tbl, allTestCaseTable); 697 db.run(add_missing_sql); 698 } 699 700 /// Returns: detected test cases. 701 TestCase[] getDetectedTestCases() @trusted { 702 auto rval = appender!(TestCase[])(); 703 db.run(select!AllTestCaseTbl).map!(a => TestCase(a.name)).copy(rval); 704 return rval.data; 705 } 706 707 /// Returns: detected test cases. 708 TestCaseId[] getDetectedTestCaseIds() @trusted { 709 auto rval = appender!(TestCaseId[])(); 710 db.run(select!AllTestCaseTbl).map!(a => TestCaseId(a.id)).copy(rval); 711 return rval.data; 712 } 713 714 /// Returns: test cases that has killed zero mutants. 715 TestCase[] getTestCasesWithZeroKills() @trusted { 716 static immutable sql = format("SELECT t1.name FROM %s t1 WHERE t1.id NOT IN (SELECT tc_id FROM %s)", 717 allTestCaseTable, killedTestCaseTable); 718 719 auto rval = appender!(TestCase[])(); 720 auto stmt = db.prepare(sql); 721 foreach (a; stmt.get.execute) 722 rval.put(TestCase(a.peek!string(0))); 723 724 return rval.data; 725 } 726 727 /** Guarantees that the there are no duplications of `TestCaseId`. 728 * 729 * Returns: test cases that has killed at least one mutant. 730 */ 731 TestCaseId[] getTestCasesWithAtLeastOneKill(const Mutation.Kind[] kinds) @trusted { 732 const sql = format!"SELECT DISTINCT t1.id 733 FROM %s t1, %s t2, %s t3 734 WHERE 735 t1.id = t2.tc_id AND 736 t2.st_id == t3.st_id AND 737 t3.kind IN (%(%s,%))"(allTestCaseTable, 738 killedTestCaseTable, mutationTable, kinds.map!(a => cast(int) a)); 739 740 auto rval = appender!(TestCaseId[])(); 741 auto stmt = db.prepare(sql); 742 foreach (a; stmt.get.execute) 743 rval.put(TestCaseId(a.peek!long(0))); 744 745 return rval.data; 746 } 747 748 /// Returns: the name of the test case. 749 string getTestCaseName(const TestCaseId id) @trusted { 750 static immutable sql = format!"SELECT name FROM %s WHERE id = :id"(allTestCaseTable); 751 auto stmt = db.prepare(sql); 752 stmt.get.bind(":id", cast(long) id); 753 auto res = stmt.get.execute; 754 return res.oneValue!string; 755 } 756 757 /// Returns: stats about the test case. 758 TestCaseInfo getTestCaseInfo(const TestCaseId tcId, const Mutation.Kind[] kinds) @trusted { 759 const sql = format("SELECT sum(ctime),sum(ttime),count(*) 760 FROM ( 761 SELECT sum(t2.compile_time_ms) ctime,sum(t2.test_time_ms) ttime 762 FROM %s t1, %s t2, %s t3 763 WHERE 764 :id = t1.tc_id AND 765 t1.st_id = t2.id AND 766 t1.st_id = t3.st_id AND 767 t3.kind IN (%(%s,%)) 768 GROUP BY t1.st_id)", killedTestCaseTable, 769 mutationStatusTable, mutationTable, kinds.map!(a => cast(int) a)); 770 auto stmt = db.prepare(sql); 771 stmt.get.bind(":id", tcId.get); 772 773 typeof(return) rval; 774 foreach (a; stmt.get.execute) { 775 rval = TestCaseInfo(MutantTimeProfile(a.peek!long(0).dur!"msecs", 776 a.peek!long(1).dur!"msecs"), a.peek!long(2)); 777 } 778 return rval; 779 } 780 781 Nullable!TestCaseInfo getTestCaseInfo(const TestCase tc, const Mutation.Kind[] kinds) @safe { 782 typeof(return) rval; 783 784 auto id = getTestCaseId(tc); 785 if (!id.isNull) 786 rval = getTestCaseInfo(id.get, kinds); 787 788 return rval; 789 } 790 791 /// Returns: all test cases for the file and the mutants they killed. 792 TestCaseInfo2[] getAllTestCaseInfo2(const FileId file, const Mutation.Kind[] kinds) @trusted { 793 // row of test case name and mutation id. 794 const sql = format("SELECT t0.name,t3.id 795 FROM %s t0, %s t1, %s t2, %s t3, %s t4 796 WHERE 797 t0.id = t1.tc_id AND 798 t1.st_id = t2.id AND 799 t2.id = t3.st_id AND 800 t4.id = :file_id AND 801 t3.kind IN (%(%s,%))", allTestCaseTable, killedTestCaseTable, 802 mutationStatusTable, mutationTable, filesTable, kinds.map!(a => cast(int) a)); 803 auto stmt = db.prepare(sql); 804 stmt.get.bind(":file_id", cast(long) file); 805 806 MutationId[][string] data; 807 foreach (row; stmt.get.execute) { 808 const name = row.peek!string(0); 809 auto id = MutationId(row.peek!long(1)); 810 data.update(name, () => [id], (ref MutationId[] a) { a ~= id; }); 811 } 812 813 auto app = appender!(TestCaseInfo2[])(); 814 data.byKeyValue.map!(a => TestCaseInfo2(TestCase(a.key), a.value)).copy(app); 815 return app.data; 816 } 817 818 /// Returns: the test case. 819 Nullable!TestCase getTestCase(const TestCaseId id) @trusted { 820 static immutable sql = format!"SELECT name FROM %s WHERE id = :id"(allTestCaseTable); 821 auto stmt = db.prepare(sql); 822 stmt.get.bind(":id", cast(long) id); 823 824 typeof(return) rval; 825 foreach (res; stmt.get.execute) { 826 rval = TestCase(res.peek!string(0)); 827 } 828 return rval; 829 } 830 831 /// Returns: the test case id. 832 Nullable!TestCaseId getTestCaseId(const TestCase tc) @trusted { 833 static immutable sql = format!"SELECT id FROM %s WHERE name = :name"(allTestCaseTable); 834 auto stmt = db.prepare(sql); 835 stmt.get.bind(":name", tc.name); 836 837 typeof(return) rval; 838 foreach (res; stmt.get.execute) { 839 rval = TestCaseId(res.peek!long(0)); 840 } 841 return rval; 842 } 843 844 /// The mutation ids are guaranteed to be sorted. 845 /// Returns: the mutants the test case killed. 846 MutationId[] getTestCaseMutantKills(const TestCaseId id, const Mutation.Kind[] kinds) @trusted { 847 immutable sql = format!"SELECT t2.id 848 FROM %s t1, %s t2 849 WHERE 850 t1.tc_id = :tid AND 851 t1.st_id = t2.st_id AND 852 t2.kind IN (%(%s,%)) 853 GROUP BY t2.st_id 854 ORDER BY t2.id"(killedTestCaseTable, 855 mutationTable, kinds.map!(a => cast(int) a)); 856 857 auto rval = appender!(MutationId[])(); 858 auto stmt = db.prepare(sql); 859 stmt.get.bind(":tid", cast(long) id); 860 foreach (a; stmt.get.execute) 861 rval.put(MutationId(a.peek!long(0))); 862 863 return rval.data; 864 } 865 866 /// Returns: test cases that killed the mutant. 867 TestCase[] getTestCases(const MutationId id) @trusted { 868 Appender!(TestCase[]) rval; 869 870 static immutable get_test_cases_sql = format!"SELECT t1.name,t2.location 871 FROM %s t1, %s t2, %s t3 872 WHERE 873 t3.id = :id AND 874 t3.st_id = t2.st_id AND 875 t2.tc_id = t1.id"( 876 allTestCaseTable, killedTestCaseTable, mutationTable); 877 auto stmt = db.prepare(get_test_cases_sql); 878 stmt.get.bind(":id", cast(long) id); 879 foreach (a; stmt.get.execute) 880 rval.put(TestCase(a.peek!string(0), a.peek!string(1))); 881 882 return rval.data; 883 } 884 885 /// Returns: if the mutant have any test cases recorded that killed it 886 bool hasTestCases(const MutationStatusId id) @trusted { 887 static immutable sql = format!"SELECT count(*) FROM %s t0 WHERE t0.st_id = :id"( 888 killedTestCaseTable); 889 auto stmt = db.prepare(sql); 890 stmt.get.bind(":id", id.get); 891 foreach (a; stmt.get.execute) { 892 return a.peek!long(0) != 0; 893 } 894 return false; 895 } 896 897 /** Returns: number of test cases 898 */ 899 long getNumOfTestCases() @trusted { 900 static immutable num_test_cases_sql = format!"SELECT count(*) FROM %s"(allTestCaseTable); 901 return db.execute(num_test_cases_sql).oneValue!long; 902 } 903 904 void removeTestCase(const TestCaseId id) @trusted { 905 auto stmt = db.prepare(format!"DELETE FROM %s WHERE id=:id"(allTestCaseTable)); 906 stmt.get.bind(":id", cast(long) id); 907 stmt.get.execute; 908 } 909 910 /// Change the status of all mutants that the test case has killed to unknown. 911 void resetTestCaseId(const TestCaseId id) @trusted { 912 { 913 static 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)"( 914 mutationStatusTable, killedTestCaseTable); 915 auto stmt = db.prepare(sql); 916 stmt.get.bind(":id", cast(long) id); 917 stmt.get.execute; 918 } 919 { 920 static immutable sql2 = format!"DELETE FROM %1$s WHERE tc_id = :id"(killedTestCaseTable); 921 auto stmt = db.prepare(sql2); 922 stmt.get.bind(":id", cast(long) id); 923 stmt.get.execute; 924 } 925 } 926 927 /// Returns: mutants killed by the test case. 928 MutationStatusId[] testCaseKilledSrcMutants(const Mutation.Kind[] kinds, const TestCaseId id) @trusted { 929 const sql = format("SELECT t1.id 930 FROM %s t0, %s t1, %s t3 931 WHERE 932 t0.st_id = t1.id AND 933 t1.status = :st AND 934 t0.kind IN (%(%s,%)) AND 935 t3.tc_id = :id AND 936 t3.st_id = t1.id 937 GROUP BY t1.id", mutationTable, mutationStatusTable, 938 killedTestCaseTable, kinds.map!(a => cast(int) a)); 939 940 auto stmt = db.prepare(sql); 941 stmt.get.bind(":st", cast(long) Mutation.Status.killed); 942 stmt.get.bind(":id", id.get); 943 944 auto app = appender!(MutationStatusId[])(); 945 foreach (res; stmt.get.execute) 946 app.put(MutationStatusId(res.peek!long(0))); 947 948 return app.data; 949 } 950 951 MutationStatusId[] testCaseKilledSrcMutants(const Mutation.Kind[] kinds, const TestCase tc) @safe { 952 auto id = getTestCaseId(tc); 953 if (id.isNull) 954 return null; 955 return testCaseKilledSrcMutants(kinds, id.get); 956 } 957 958 /// Returns: mutants at mutations points that the test case has killed mutants at. 959 alias testCaseMutationPointAliveSrcMutants = testCaseCountSrcMutants!([ 960 Mutation.Status.alive 961 ]); 962 /// ditto 963 alias testCaseMutationPointTimeoutSrcMutants = testCaseCountSrcMutants!( 964 [Mutation.Status.timeout]); 965 /// ditto 966 alias testCaseMutationPointKilledSrcMutants = testCaseCountSrcMutants!([ 967 Mutation.Status.killed 968 ]); 969 /// ditto 970 alias testCaseMutationPointUnknownSrcMutants = testCaseCountSrcMutants!( 971 [Mutation.Status.unknown]); 972 /// ditto 973 alias testCaseMutationPointKilledByCompilerSrcMutants = testCaseCountSrcMutants!( 974 [Mutation.Status.killedByCompiler]); 975 /// ditto 976 alias testCaseMutationPointTotalSrcMutants = testCaseCountSrcMutants!( 977 [ 978 Mutation.Status.alive, Mutation.Status.killed, Mutation.Status.timeout 979 ]); 980 981 private MutationStatusId[] testCaseCountSrcMutants(int[] status)( 982 const Mutation.Kind[] kinds, TestCase tc) @trusted { 983 const query = format(" 984 SELECT t1.id 985 FROM %s t0, %s t1 986 WHERE 987 t0.mp_id IN (SELECT t1.id 988 FROM %s t0,%s t1, %s t2, %s t3 989 WHERE 990 t0.mp_id = t1.id AND 991 t2.name = :name AND 992 t2.id = t3.tc_id AND 993 t3.st_id = t0.st_id 994 ) 995 AND 996 t0.st_id = t1.id AND 997 t1.status IN (%(%s,%)) AND 998 t0.kind IN (%(%s,%)) 999 GROUP BY t1.id", mutationTable, mutationStatusTable, mutationTable, mutationPointTable, 1000 allTestCaseTable, killedTestCaseTable, status, kinds.map!(a => cast(int) a)); 1001 1002 auto stmt = db.prepare(query); 1003 stmt.get.bind(":name", tc.name); 1004 1005 auto app = appender!(MutationStatusId[])(); 1006 foreach (res; stmt.get.execute) 1007 app.put(MutationStatusId(res.peek!long(0))); 1008 1009 return app.data; 1010 } 1011 } 1012 1013 struct DbMutant { 1014 private Miniorm* db; 1015 private Database* wrapperDb; 1016 1017 bool exists(MutationStatusId id) { 1018 static immutable s = format!"SELECT COUNT(*) FROM %s WHERE id=:id LIMIT 1"( 1019 mutationStatusTable); 1020 auto stmt = db.prepare(s); 1021 stmt.get.bind(":id", cast(long) id); 1022 auto res = stmt.get.execute; 1023 return res.oneValue!long == 0; 1024 } 1025 1026 /** Update the status of a mutant. 1027 * 1028 * Params: 1029 * id = ID of the mutant 1030 * st = status to broadcast 1031 * d = time spent on veryfing the mutant 1032 * tcs = test cases that killed the mutant 1033 * counter = how to act with the counter 1034 */ 1035 void updateMutation(const MutationId id, const Mutation.Status st, 1036 const ExitStatus ecode, const MutantTimeProfile p, const(TestCase)[] tcs) @trusted { 1037 static immutable sql = "UPDATE %s SET 1038 status=:st,compile_time_ms=:compile,test_time_ms=:test,update_ts=:update_ts 1039 WHERE id IN (SELECT st_id FROM %s WHERE id = :id)"; 1040 1041 auto stmt = db.prepare(format!sql(mutationStatusTable, mutationTable)); 1042 stmt.get.bind(":st", cast(long) st); 1043 stmt.get.bind(":id", id.get); 1044 stmt.get.bind(":compile", p.compile.total!"msecs"); 1045 stmt.get.bind(":test", p.test.total!"msecs"); 1046 stmt.get.bind(":update_ts", Clock.currTime.toSqliteDateTime); 1047 stmt.get.execute; 1048 1049 wrapperDb.testCaseApi.updateMutationTestCases(id, tcs); 1050 } 1051 1052 /** Update the status of a mutant. 1053 * 1054 * Params: 1055 * id = ID of the mutant 1056 * st = status to broadcast 1057 * d = time spent on veryfing the mutant 1058 * tcs = test cases that killed the mutant 1059 * counter = how to act with the counter 1060 */ 1061 void updateMutation(const MutationStatusId id, const Mutation.Status st, 1062 const ExitStatus ecode, const MutantTimeProfile p) @trusted { 1063 static immutable sql = "UPDATE %s SET 1064 status=:st,compile_time_ms=:compile,test_time_ms=:test,update_ts=:update_ts 1065 WHERE id = :id"; 1066 1067 auto stmt = db.prepare(format!sql(mutationStatusTable)); 1068 stmt.get.bind(":id", id.get); 1069 stmt.get.bind(":st", cast(long) st); 1070 stmt.get.bind(":compile", p.compile.total!"msecs"); 1071 stmt.get.bind(":test", p.test.total!"msecs"); 1072 stmt.get.bind(":update_ts", Clock.currTime.toSqliteDateTime); 1073 stmt.get.execute; 1074 } 1075 1076 /// Update the time used to test the mutant. 1077 void updateMutation(const MutationStatusId id, const MutantTimeProfile p) @trusted { 1078 static immutable sql = format!"UPDATE %s SET compile_time_ms=:compile,test_time_ms=:test WHERE id = :id"( 1079 mutationStatusTable); 1080 auto stmt = db.prepare(sql); 1081 stmt.get.bind(":id", id.get); 1082 stmt.get.bind(":compile", p.compile.total!"msecs"); 1083 stmt.get.bind(":test", p.test.total!"msecs"); 1084 stmt.get.execute; 1085 } 1086 1087 /** Update the status of a mutant. 1088 * 1089 * Params: 1090 * id = mutation status ID 1091 * st = new status 1092 * update_ts = if the update timestamp should be updated. 1093 */ 1094 void updateMutationStatus(const MutationStatusId id, const Mutation.Status st, 1095 const ExitStatus ecode, Flag!"updateTs" update_ts = No.updateTs) @trusted { 1096 1097 auto stmt = () { 1098 if (update_ts) { 1099 const ts = Clock.currTime.toSqliteDateTime; 1100 auto s = db.prepare( 1101 format!"UPDATE %s SET status=:st,exit_code=:ecode,update_ts=:update_ts WHERE id=:id"( 1102 mutationStatusTable)); 1103 s.get.bind(":update_ts", ts); 1104 return s; 1105 } else 1106 return db.prepare(format!"UPDATE %s SET status=:st,exit_code=:ecode WHERE id=:id"( 1107 mutationStatusTable)); 1108 }(); 1109 stmt.get.bind(":st", st.to!long); 1110 stmt.get.bind(":id", id.to!long); 1111 stmt.get.bind(":ecode", ecode.get); 1112 stmt.get.execute; 1113 } 1114 1115 /// Returns: all mutants and how many test cases that have killed them. 1116 long[] getAllTestCaseKills() @trusted { 1117 static immutable sql = format!"SELECT (SELECT count(*) FROM %s WHERE t0.id=st_id) as vc_cnt FROM %s t0"( 1118 killedTestCaseTable, mutationStatusTable); 1119 auto stmt = db.prepare(sql); 1120 1121 auto app = appender!(long[])(); 1122 foreach (res; stmt.get.execute) 1123 app.put(res.peek!long(0)); 1124 1125 return app.data; 1126 } 1127 1128 /// Returns: all mutation status IDs. 1129 MutationStatusId[] getAllMutationStatus() @trusted { 1130 static immutable sql = format!"SELECT id FROM %s"(mutationStatusTable); 1131 1132 auto app = appender!(MutationStatusId[])(); 1133 auto stmt = db.prepare(sql); 1134 foreach (r; stmt.get.execute) 1135 app.put(MutationStatusId(r.peek!long(0))); 1136 return app.data; 1137 } 1138 1139 // TODO: change to my.optional 1140 Nullable!(Mutation.Status) getMutationStatus(const MutationStatusId id) @trusted { 1141 static immutable sql = format!"SELECT status FROM %s WHERE id=:id"(mutationStatusTable); 1142 auto stmt = db.prepare(sql); 1143 stmt.get.bind(":id", id.get); 1144 1145 typeof(return) rval; 1146 foreach (a; stmt.get.execute) { 1147 rval = cast(Mutation.Status) a.peek!long(0); 1148 } 1149 return rval; 1150 } 1151 1152 Nullable!MutationEntry getMutation(const MutationId id) @trusted { 1153 import dextool.plugin.mutate.backend.type; 1154 1155 typeof(return) rval; 1156 1157 static immutable get_mut_sql = format("SELECT 1158 t0.id, 1159 t0.kind, 1160 t3.compile_time_ms, 1161 t3.test_time_ms, 1162 t1.offset_begin, 1163 t1.offset_end, 1164 t1.line, 1165 t1.column, 1166 t2.path, 1167 t2.lang 1168 FROM %s t0,%s t1,%s t2,%s t3 1169 WHERE 1170 t0.id = :id AND 1171 t0.mp_id = t1.id AND 1172 t1.file_id = t2.id AND 1173 t3.id = t0.st_id 1174 ", mutationTable, 1175 mutationPointTable, filesTable, mutationStatusTable); 1176 1177 auto stmt = db.prepare(get_mut_sql); 1178 stmt.get.bind(":id", cast(long) id); 1179 auto res = stmt.get.execute; 1180 1181 if (res.empty) 1182 return rval; 1183 1184 auto v = res.front; 1185 1186 auto mp = MutationPoint(Offset(v.peek!uint(4), v.peek!uint(5))); 1187 mp.mutations = [Mutation(v.peek!long(1).to!(Mutation.Kind))]; 1188 auto pkey = MutationId(v.peek!long(0)); 1189 auto file = Path(v.peek!string(8)); 1190 auto sloc = SourceLoc(v.peek!uint(6), v.peek!uint(7)); 1191 auto lang = v.peek!long(9).to!Language; 1192 1193 rval = MutationEntry(pkey, file, sloc, mp, 1194 MutantTimeProfile(v.peek!long(2).dur!"msecs", v.peek!long(3).dur!"msecs"), lang); 1195 1196 return rval; 1197 } 1198 1199 MutantMetaData getMutantationMetaData(const MutationId id) @trusted { 1200 auto rval = MutantMetaData(id); 1201 foreach (res; db.run(select!NomutDataTbl.where("mut_id = :mutid", Bind("mutid")), id.get)) { 1202 rval.set(NoMut(res.tag, res.comment)); 1203 } 1204 return rval; 1205 } 1206 1207 // TODO: fix spelling error 1208 // TODO: this is a bit inefficient. it should use a callback iterator 1209 MutantMetaData[] getMutantationMetaData(const Mutation.Kind[] kinds, const Mutation 1210 .Status status) @trusted { 1211 const sql = format!"SELECT DISTINCT t.mut_id, t.tag, t.comment 1212 FROM %s t, %s t1, %s t2 1213 WHERE 1214 t.mut_id = t1.id AND 1215 t1.st_id = t2.id AND 1216 t2.status = :status AND 1217 t1.kind IN (%(%s,%)) 1218 ORDER BY 1219 t.mut_id"(nomutDataTable, mutationTable, 1220 mutationStatusTable, kinds.map!(a => cast(long) a)); 1221 auto stmt = db.prepare(sql); 1222 stmt.get.bind(":status", cast(long) status); 1223 1224 auto app = appender!(MutantMetaData[])(); 1225 foreach (res; stmt.get.execute) { 1226 app.put(MutantMetaData(MutationId(res.peek!long(0)), 1227 MutantAttr(NoMut(res.peek!string(1), res.peek!string(2))))); 1228 } 1229 return app.data; 1230 } 1231 1232 Nullable!Path getPath(const MutationId id) @trusted { 1233 static immutable get_path_sql = format("SELECT t2.path 1234 FROM 1235 %s t0, %s t1, %s t2 1236 WHERE 1237 t0.id = :id AND 1238 t0.mp_id = t1.id AND 1239 t1.file_id = t2.id 1240 ", mutationTable, 1241 mutationPointTable, filesTable); 1242 1243 auto stmt = db.prepare(get_path_sql); 1244 stmt.get.bind(":id", cast(long) id); 1245 auto res = stmt.get.execute; 1246 1247 typeof(return) rval; 1248 if (!res.empty) 1249 rval = Path(res.front.peek!string(0)); 1250 return rval; 1251 } 1252 1253 Nullable!Path getPath(const MutationStatusId id) @trusted { 1254 static immutable get_path_sql = format("SELECT t2.path 1255 FROM 1256 %s t0, %s t1, %s t2 1257 WHERE 1258 t0.st_id = :id AND t0.mp_id = t1.id AND t1.file_id = t2.id 1259 ", mutationTable, 1260 mutationPointTable, filesTable); 1261 1262 auto stmt = db.prepare(get_path_sql); 1263 stmt.get.bind(":id", id.get); 1264 auto res = stmt.get.execute; 1265 1266 typeof(return) rval; 1267 if (!res.empty) 1268 rval = Path(res.front.peek!string(0)); 1269 return rval; 1270 } 1271 1272 /// Returns: the mutants that are connected to the mutation statuses. 1273 MutantInfo[] getMutantsInfo(const Mutation.Kind[] kinds, const(MutationStatusId)[] id) @trusted { 1274 const get_mutid_sql = format("SELECT t0.id,t2.status,t2.exit_code,t0.kind,t1.line,t1.column 1275 FROM %s t0,%s t1, %s t2 1276 WHERE 1277 t0.st_id IN (%(%s,%)) AND 1278 t0.st_id = t2.id AND 1279 t0.kind IN (%(%s,%)) AND 1280 t0.mp_id = t1.id", mutationTable, 1281 mutationPointTable, mutationStatusTable, id.map!(a => a.get), 1282 kinds.map!(a => cast(int) a)); 1283 auto stmt = db.prepare(get_mutid_sql); 1284 1285 auto app = appender!(MutantInfo[])(); 1286 foreach (res; stmt.get.execute) { 1287 app.put(MutantInfo(MutationId(res.peek!long(0)), res.peek!long(1) 1288 .to!(Mutation.Status), res.peek!int(2).ExitStatus, 1289 res.peek!long(3).to!(Mutation.Kind), 1290 SourceLoc(res.peek!uint(4), res.peek!uint(5)))); 1291 } 1292 1293 return app.data; 1294 } 1295 1296 /// Returns: the mutants that are connected to the mutation statuses. 1297 Optional!MutantInfo2 getMutantInfo(const MutationStatusId id) @trusted { 1298 static const sql = format("SELECT t0.id,t2.status,t2.exit_code,t3.path,t1.line,t1.column,t2.prio,t2.update_ts, 1299 (SELECT count(*) FROM %s WHERE st_id = :id) as vc_cnt 1300 FROM %s t0,%s t1, %s t2, %s t3 1301 WHERE 1302 t2.id = :id AND 1303 t0.st_id = :id AND 1304 t0.mp_id = t1.id AND 1305 t1.file_id = t3.id 1306 ", 1307 killedTestCaseTable, mutationTable, mutationPointTable, 1308 mutationStatusTable, filesTable); 1309 auto stmt = db.prepare(sql); 1310 stmt.get.bind(":id", id.get); 1311 1312 foreach (res; stmt.get.execute) { 1313 // dfmt off 1314 return MutantInfo2( 1315 res.peek!long(0).MutationId, 1316 res.peek!long(1).to!(Mutation.Status), 1317 res.peek!int(2).to!ExitStatus, 1318 res.peek!string(3).Path, 1319 SourceLoc(res.peek!uint(4), res.peek!uint(5)), 1320 res.peek!long(6).MutantPrio, 1321 res.peek!string(7).fromSqLiteDateTime, 1322 res.peek!int(8)).some; 1323 // dfmt on 1324 } 1325 1326 return none!MutantInfo2; 1327 } 1328 1329 /// Returns: the mutants that are connected to the mutation statuses. 1330 MutationId[] getMutationIds(const(Mutation.Kind)[] kinds, const(MutationStatusId)[] id) @trusted { 1331 if (id.length == 0) 1332 return null; 1333 1334 const get_mutid_sql = format!"SELECT id FROM %s t0 1335 WHERE 1336 t0.st_id IN (%(%s,%)) AND 1337 t0.kind IN (%(%s,%))"(mutationTable, 1338 id.map!(a => cast(long) a), kinds.map!(a => cast(int) a)); 1339 auto stmt = db.prepare(get_mutid_sql); 1340 1341 auto app = appender!(MutationId[])(); 1342 foreach (res; stmt.get.execute) 1343 app.put(MutationId(res.peek!long(0))); 1344 return app.data; 1345 } 1346 1347 Nullable!MutationId getMutationId(const MutationStatusId id) @trusted { 1348 static immutable sql = format!"SELECT id FROM %s WHERE st_id=:st_id"(mutationTable); 1349 auto stmt = db.prepare(sql); 1350 stmt.get.bind(":st_id", id.get); 1351 1352 typeof(return) rval; 1353 foreach (res; stmt.get.execute) { 1354 rval = res.peek!long(0).MutationId; 1355 break; 1356 } 1357 return rval; 1358 } 1359 1360 MutationStatus getMutationStatus2(const MutationStatusId id) @trusted { 1361 const sql = format("SELECT t0.id,t0.status,t0.prio,t0.update_ts,t0.added_ts 1362 FROM %s t0 1363 WHERE 1364 t0.update_ts IS NOT NULL AND 1365 t0.id = :id", 1366 mutationStatusTable); 1367 auto stmt = db.prepare(sql); 1368 stmt.get.bind(":id", id.get); 1369 1370 foreach (res; stmt.get.execute) { 1371 auto added = () { 1372 auto raw = res.peek!string(4); 1373 if (raw.length == 0) 1374 return Nullable!SysTime(); 1375 return Nullable!SysTime(raw.fromSqLiteDateTime); 1376 }(); 1377 1378 return MutationStatus(MutationStatusId(res.peek!long(0)), 1379 res.peek!long(1).to!(Mutation.Status), res.peek!long(2) 1380 .MutantPrio, res.peek!string(3).fromSqLiteDateTime, added,); 1381 } 1382 1383 return MutationStatus.init; 1384 } 1385 1386 Nullable!MutationStatusId getMutationStatusId(const MutationId id) @trusted { 1387 static immutable sql = format!"SELECT st_id FROM %s WHERE id=:id"(mutationTable); 1388 auto stmt = db.prepare(sql); 1389 stmt.get.bind(":id", cast(long) id); 1390 1391 typeof(return) rval; 1392 foreach (res; stmt.get.execute) { 1393 rval = MutationStatusId(res.peek!long(0)); 1394 } 1395 return rval; 1396 } 1397 1398 Nullable!MutationStatusId getMutationStatusId(const Checksum cs) @trusted { 1399 static immutable sql = format!"SELECT id FROM %s WHERE checksum0=:cs0 AND checksum1=:cs1"( 1400 mutationStatusTable); 1401 auto stmt = db.prepare(sql); 1402 stmt.get.bind(":cs0", cast(long) cs.c0); 1403 stmt.get.bind(":cs1", cast(long) cs.c1); 1404 1405 typeof(return) rval; 1406 foreach (res; stmt.get.execute) { 1407 rval = MutationStatusId(res.peek!long(0)); 1408 } 1409 return rval; 1410 } 1411 1412 // TODO: maybe this need to return the exit code too? 1413 // Returns: the status of the mutant 1414 Nullable!(Mutation.Status) getMutationStatus(const MutationId id) @trusted { 1415 auto s = format!"SELECT status FROM %s WHERE id IN (SELECT st_id FROM %s WHERE id=:mut_id)"( 1416 mutationStatusTable, mutationTable); 1417 auto stmt = db.prepare(s); 1418 stmt.get.bind(":mut_id", cast(long) id); 1419 typeof(return) rval; 1420 foreach (res; stmt.get.execute) 1421 rval = res.peek!long(0).to!(Mutation.Status); 1422 return rval; 1423 } 1424 1425 /// Returns: the mutants in the file at the line. 1426 MutationStatusId[] getMutationsOnLine(const(Mutation.Kind)[] kinds, FileId fid, SourceLoc sloc) @trusted { 1427 // TODO: should it also be line_end? 1428 const sql = format("SELECT DISTINCT t0.id FROM %s t0, %s t1, %s t2 1429 WHERE 1430 t1.st_id = t0.id AND 1431 t1.kind IN (%(%s,%)) AND 1432 t1.mp_id = t2.id AND 1433 t2.file_id = :fid AND 1434 (:line BETWEEN t2.line AND t2.line_end)", 1435 mutationStatusTable, mutationTable, mutationPointTable, 1436 kinds.map!(a => cast(int) a)); 1437 auto stmt = db.prepare(sql); 1438 stmt.get.bind(":fid", cast(long) fid); 1439 stmt.get.bind(":line", sloc.line); 1440 1441 auto app = appender!(typeof(return))(); 1442 foreach (res; stmt.get.execute) 1443 app.put(MutationStatusId(res.peek!long(0))); 1444 return app.data; 1445 } 1446 1447 /// Returns: the `nr` mutants that where the longst since they where tested. 1448 MutationStatusTime[] getOldestMutants(const(Mutation.Kind)[] kinds, const long nr) @trusted { 1449 const sql = format("SELECT t0.id,t0.update_ts FROM %s t0, %s t1 1450 WHERE 1451 t0.update_ts IS NOT NULL AND 1452 t1.st_id = t0.id AND 1453 t1.kind IN (%(%s,%)) 1454 ORDER BY datetime(t0.update_ts) ASC LIMIT :limit", 1455 mutationStatusTable, mutationTable, kinds.map!(a => cast(int) a)); 1456 auto stmt = db.prepare(sql); 1457 stmt.get.bind(":limit", nr); 1458 1459 auto app = appender!(MutationStatusTime[])(); 1460 foreach (res; stmt.get.execute) 1461 app.put(MutationStatusTime(MutationStatusId(res.peek!long(0)), 1462 res.peek!string(1).fromSqLiteDateTime)); 1463 return app.data; 1464 } 1465 1466 /// Returns: the `nr` mutants that where last tested. 1467 MutationStatusTime[] getLatestMutants(const(Mutation.Kind)[] kinds, const long nr) @trusted { 1468 const sql = format("SELECT t0.id,t0.update_ts FROM %s t0, %s t1 1469 WHERE 1470 t0.update_ts IS NOT NULL AND 1471 t1.st_id = t0.id AND 1472 t1.kind IN (%(%s,%)) 1473 ORDER BY datetime(t0.update_ts) DESC LIMIT :limit", 1474 mutationStatusTable, mutationTable, kinds.map!(a => cast(int) a)); 1475 auto stmt = db.prepare(sql); 1476 stmt.get.bind(":limit", nr); 1477 1478 auto app = appender!(MutationStatusTime[])(); 1479 foreach (res; stmt.get.execute) 1480 app.put(MutationStatusTime(MutationStatusId(res.peek!long(0)), 1481 res.peek!string(1).fromSqLiteDateTime)); 1482 return app.data; 1483 } 1484 1485 /// Returns: the `nr` mutant with the highest count that has not been killed and existed in the system the longest. 1486 MutationStatus[] getHighestPrioMutant(const(Mutation.Kind)[] kinds, 1487 const Mutation.Status status, const long nr) @trusted { 1488 const sql = format("SELECT t0.id,t0.status,t0.prio,t0.update_ts,t0.added_ts 1489 FROM %s t0, %s t1 1490 WHERE 1491 t0.update_ts IS NOT NULL AND 1492 t0.status = :status AND 1493 t1.st_id = t0.id AND 1494 t1.kind IN (%(%s,%)) AND 1495 t1.st_id NOT IN (SELECT st_id FROM %s WHERE nomut != 0) 1496 ORDER BY t0.prio DESC LIMIT :limit", 1497 mutationStatusTable, mutationTable, kinds.map!(a => cast(int) a), srcMetadataTable); 1498 auto stmt = db.prepare(sql); 1499 stmt.get.bind(":status", cast(long) status); 1500 stmt.get.bind(":limit", nr); 1501 1502 auto app = appender!(MutationStatus[])(); 1503 foreach (res; stmt.get.execute) { 1504 auto added = () { 1505 auto raw = res.peek!string(4); 1506 if (raw.length == 0) 1507 return Nullable!SysTime(); 1508 return Nullable!SysTime(raw.fromSqLiteDateTime); 1509 }(); 1510 1511 // dfmt off 1512 app.put(MutationStatus( 1513 MutationStatusId(res.peek!long(0)), 1514 res.peek!long(1).to!(Mutation.Status), 1515 res.peek!long(2).MutantPrio, 1516 res.peek!string(3).fromSqLiteDateTime, 1517 added, 1518 )); 1519 // dfmt on 1520 } 1521 1522 return app.data; 1523 } 1524 1525 /** Get SourceLoc for a specific mutation id. 1526 */ 1527 Nullable!SourceLoc getSourceLocation(MutationId id) @trusted { 1528 auto s = format!"SELECT line, column FROM %s WHERE id IN (SELECT mp_id FROM %s WHERE id=:mut_id)"( 1529 mutationPointTable, mutationTable); 1530 auto stmt = db.prepare(s); 1531 stmt.get.bind(":mut_id", cast(long) id); 1532 typeof(return) rval; 1533 foreach (res; stmt.get.execute) 1534 rval = SourceLoc(res.peek!uint(0), res.peek!uint(1)); 1535 return rval; 1536 } 1537 1538 /** Remove all mutations of kinds. 1539 */ 1540 void removeMutant(const Mutation.Kind[] kinds) @trusted { 1541 const s = format!"DELETE FROM %s WHERE id IN (SELECT mp_id FROM %s WHERE kind IN (%(%s,%)))"( 1542 mutationPointTable, mutationTable, kinds.map!(a => cast(int) a)); 1543 auto stmt = db.prepare(s); 1544 stmt.get.execute; 1545 } 1546 1547 /** Reset all mutations of kinds with the status `st` to unknown. 1548 */ 1549 void resetMutant(const Mutation.Kind[] kinds, Mutation.Status st, Mutation.Status to_st) @trusted { 1550 const s = format!"UPDATE %s SET status=%s WHERE status = %s AND id IN(SELECT st_id FROM %s WHERE kind IN (%(%s,%)))"( 1551 mutationStatusTable, to_st.to!long, st.to!long, 1552 mutationTable, kinds.map!(a => cast(int) a)); 1553 auto stmt = db.prepare(s); 1554 stmt.get.execute; 1555 } 1556 1557 Mutation.Kind getKind(MutationId id) @trusted { 1558 static immutable sql = format!"SELECT kind FROM %s WHERE id=:id"(mutationTable); 1559 auto stmt = db.prepare(sql); 1560 stmt.get.bind(":id", cast(long) id); 1561 1562 typeof(return) rval; 1563 foreach (res; stmt.get.execute) { 1564 rval = res.peek!long(0).to!(Mutation.Kind); 1565 } 1566 return rval; 1567 } 1568 1569 import dextool.plugin.mutate.backend.type; 1570 1571 alias aliveSrcMutants = countMutants!([ 1572 Mutation.Status.alive, Mutation.Status.noCoverage 1573 ]); 1574 alias killedSrcMutants = countMutants!([Mutation.Status.killed]); 1575 alias timeoutSrcMutants = countMutants!([Mutation.Status.timeout]); 1576 alias noCovSrcMutants = countMutants!([Mutation.Status.noCoverage]); 1577 alias equivalentMutants = countMutants!([Mutation.Status.equivalent]); 1578 alias skippedMutants = countMutants!([Mutation.Status.skipped]); 1579 1580 /// Returns: Total that should be counted when calculating the mutation score. 1581 alias totalSrcMutants = countMutants!([ 1582 Mutation.Status.alive, Mutation.Status.killed, 1583 Mutation.Status.timeout, Mutation.Status.noCoverage 1584 ]); 1585 1586 alias unknownSrcMutants = countMutants!([Mutation.Status.unknown]); 1587 alias killedByCompilerSrcMutants = countMutants!([ 1588 Mutation.Status.killedByCompiler 1589 ]); 1590 1591 /** Count the mutants with the nomut metadata. 1592 * 1593 * Params: 1594 * status = status the mutants must be in to be counted. 1595 * distinc = count based on unique source code changes. 1596 * kinds = the kind of mutants to count. 1597 * file = file to count mutants in. 1598 */ 1599 private MutationReportEntry countMutants(int[] status)(const Mutation.Kind[] kinds, 1600 string file = null) @trusted { 1601 const qq = " 1602 SELECT count(*),sum(compile_time_ms),sum(test_time_ms) 1603 FROM ( 1604 SELECT sum(t1.compile_time_ms) compile_time_ms,sum(t1.test_time_ms) test_time_ms 1605 FROM %s t0, %s t1%s 1606 WHERE 1607 %s 1608 t0.st_id = t1.id AND 1609 t1.status IN (%(%s,%)) AND 1610 t0.kind IN (%(%s,%)) 1611 GROUP BY t1.id)"; 1612 const query = () { 1613 auto fq = file.length == 0 1614 ? null : "t0.mp_id = t2.id AND t2.file_id = t3.id AND t3.path = :path AND"; 1615 auto fq_from = file.length == 0 ? null : format(", %s t2, %s t3", 1616 mutationPointTable, filesTable); 1617 return format(qq, mutationTable, mutationStatusTable, fq_from, fq, 1618 status, kinds.map!(a => cast(int) a)); 1619 }(); 1620 1621 typeof(return) rval; 1622 auto stmt = db.prepare(query); 1623 if (file.length != 0) 1624 stmt.get.bind(":path", file); 1625 auto res = stmt.get.execute; 1626 if (!res.empty) { 1627 rval = MutationReportEntry(res.front.peek!long(0), 1628 MutantTimeProfile(res.front.peek!long(1).dur!"msecs", 1629 res.front.peek!long(2).dur!"msecs")); 1630 } 1631 return rval; 1632 } 1633 1634 /** Count the mutants with the nomut metadata. 1635 * 1636 * Params: 1637 * status = status the mutants must be in to be counted. 1638 * distinc = count based on unique source code changes. 1639 * kinds = the kind of mutants to count. 1640 * file = file to count mutants in. 1641 */ 1642 private MetadataNoMutEntry countNoMutMutants(int[] status, bool distinct)( 1643 const Mutation.Kind[] kinds, string file = null) @trusted { 1644 static if (distinct) { 1645 auto sql_base = " 1646 SELECT count(*) 1647 FROM ( 1648 SELECT count(*) 1649 FROM %s t0, %s t1,%s t4%s 1650 WHERE 1651 %s 1652 t0.st_id = t1.id AND 1653 t0.st_id = t4.st_id AND 1654 t4.nomut != 0 AND 1655 t1.status IN (%(%s,%)) AND 1656 t0.kind IN (%(%s,%)) 1657 GROUP BY t1.id)"; 1658 } else { 1659 auto sql_base = " 1660 SELECT count(*) 1661 FROM %s t0, %s t1,%s t4%s 1662 WHERE 1663 %s 1664 t0.st_id = t1.id AND 1665 t0.st_id = t4.st_id AND 1666 t4.nomut != 0 AND 1667 t1.status IN (%(%s,%)) AND 1668 t0.kind IN (%(%s,%))"; 1669 } 1670 const query = () { 1671 auto fq = file.length == 0 1672 ? null : "t0.mp_id = t2.id AND t2.file_id = t3.id AND t3.path = :path AND"; 1673 auto fq_from = file.length == 0 ? null : format(", %s t2, %s t3", 1674 mutationPointTable, filesTable); 1675 return format(sql_base, mutationTable, mutationStatusTable, 1676 srcMetadataTable, fq_from, fq, status, kinds.map!(a => cast(int) a)); 1677 }(); 1678 1679 typeof(return) rval; 1680 auto stmt = db.prepare(query); 1681 if (file.length != 0) 1682 stmt.get.bind(":path", file); 1683 auto res = stmt.get.execute; 1684 if (!res.empty) 1685 rval = MetadataNoMutEntry(res.front.peek!long(0)); 1686 return rval; 1687 } 1688 1689 /// ditto. 1690 alias aliveNoMutSrcMutants = countNoMutMutants!([Mutation.Status.alive], true); 1691 1692 Nullable!Checksum getChecksum(MutationStatusId id) @trusted { 1693 static immutable sql = format!"SELECT checksum0, checksum1 FROM %s WHERE id=:id"( 1694 mutationStatusTable); 1695 auto stmt = db.prepare(sql); 1696 stmt.get.bind(":id", id.get); 1697 1698 typeof(return) rval; 1699 foreach (res; stmt.get.execute) { 1700 rval = Checksum(res.peek!long(0), res.peek!long(1)); 1701 break; 1702 } 1703 return rval; 1704 } 1705 1706 /// Store all found mutants. 1707 void put(MutationPointEntry2[] mps, AbsolutePath root) @trusted { 1708 if (mps.empty) 1709 return; 1710 1711 static immutable insert_mp_sql = format("INSERT OR IGNORE INTO %s 1712 (file_id, offset_begin, offset_end, line, column, line_end, column_end) 1713 SELECT id,:begin,:end,:line,:column,:line_end,:column_end 1714 FROM %s 1715 WHERE 1716 path = :path", 1717 mutationPointTable, filesTable); 1718 auto mp_stmt = db.prepare(insert_mp_sql); 1719 1720 foreach (mp; mps) { 1721 auto rel_file = relativePath(mp.file, root).Path; 1722 mp_stmt.get.bind(":begin", mp.offset.begin); 1723 mp_stmt.get.bind(":end", mp.offset.end); 1724 mp_stmt.get.bind(":line", mp.sloc.line); 1725 mp_stmt.get.bind(":column", mp.sloc.column); 1726 mp_stmt.get.bind(":line_end", mp.slocEnd.line); 1727 mp_stmt.get.bind(":column_end", mp.slocEnd.column); 1728 mp_stmt.get.bind(":path", cast(string) rel_file); 1729 mp_stmt.get.execute; 1730 mp_stmt.get.reset; 1731 } 1732 1733 static immutable insert_cmut_sql = format("INSERT OR IGNORE INTO %s 1734 (status,exit_code,compile_time_ms,test_time_ms,update_ts,added_ts,checksum0,checksum1,prio) 1735 VALUES(:st,0,0,0,:update_ts,:added_ts,:c0,:c1,:prio)", 1736 mutationStatusTable); 1737 auto cmut_stmt = db.prepare(insert_cmut_sql); 1738 const ts = Clock.currTime.toSqliteDateTime; 1739 cmut_stmt.get.bind(":st", Mutation.Status.unknown); 1740 cmut_stmt.get.bind(":update_ts", ts); 1741 cmut_stmt.get.bind(":added_ts", ts); 1742 foreach (mp; mps) { 1743 const prio = (mp.offset.begin < mp.offset.end) ? mp.offset.end - mp.offset.begin : 0; 1744 foreach (cm; mp.cms) { 1745 cmut_stmt.get.bind(":c0", cast(long) cm.id.c0); 1746 cmut_stmt.get.bind(":c1", cast(long) cm.id.c1); 1747 cmut_stmt.get.bind(":prio", prio); 1748 cmut_stmt.get.execute; 1749 cmut_stmt.get.reset; 1750 } 1751 } 1752 1753 static immutable insert_m_sql = format("INSERT OR IGNORE INTO %s 1754 (mp_id, st_id, kind) 1755 SELECT t0.id,t1.id,:kind FROM %s t0, %s t1, %s t2 WHERE 1756 t2.path = :path AND 1757 t0.file_id = t2.id AND 1758 t0.offset_begin = :off_begin AND 1759 t0.offset_end = :off_end AND 1760 t1.checksum0 = :c0 AND 1761 t1.checksum1 = :c1", 1762 mutationTable, mutationPointTable, mutationStatusTable, filesTable); 1763 auto insert_m = db.prepare(insert_m_sql); 1764 1765 foreach (mp; mps) { 1766 foreach (m; mp.cms) { 1767 auto rel_file = relativePath(mp.file, root).Path; 1768 insert_m.get.bind(":path", cast(string) rel_file); 1769 insert_m.get.bind(":off_begin", mp.offset.begin); 1770 insert_m.get.bind(":off_end", mp.offset.end); 1771 insert_m.get.bind(":c0", cast(long) m.id.c0); 1772 insert_m.get.bind(":c1", cast(long) m.id.c1); 1773 insert_m.get.bind(":kind", m.mut.kind); 1774 insert_m.get.execute; 1775 insert_m.get.reset; 1776 } 1777 } 1778 } 1779 1780 /// Remove mutants that have no connection to a mutation point, orphaned mutants. 1781 void removeOrphanedMutants() @trusted { 1782 import std.datetime.stopwatch : StopWatch, AutoStart; 1783 1784 const removeIds = () { 1785 static immutable sql = format!"SELECT id FROM %1$s WHERE id NOT IN (SELECT st_id FROM %2$s)"( 1786 mutationStatusTable, mutationTable); 1787 auto stmt = db.prepare(sql); 1788 auto removeIds = appender!(long[])(); 1789 foreach (res; stmt.get.execute) 1790 removeIds.put(res.peek!long(0)); 1791 return removeIds.data; 1792 }(); 1793 1794 immutable batchNr = 1000; 1795 static immutable sql = format!"DELETE FROM %1$s WHERE id=:id"(mutationStatusTable); 1796 auto stmt = db.prepare(sql); 1797 auto sw = StopWatch(AutoStart.yes); 1798 foreach (const i, const id; removeIds) { 1799 stmt.get.bind(":id", id); 1800 stmt.get.execute; 1801 stmt.get.reset; 1802 1803 // continuously print to inform the user of the progress and avoid 1804 // e.g. timeout on jenkins. 1805 if (i > 0 && i % batchNr == 0) { 1806 const avg = cast(long)(cast(double) sw.peek.total!"msecs" / cast(double) batchNr); 1807 const t = dur!"msecs"(avg * (removeIds.length - i)); 1808 logger.infof("%s/%s removed (average %sms) (%s) (%s)", i, 1809 removeIds.length, avg, t, (Clock.currTime + t).toSimpleString); 1810 sw.reset; 1811 } 1812 } 1813 1814 logger.infof(!removeIds.empty, "%1$s/%1$s removed", removeIds.length); 1815 } 1816 1817 /// Returns: all alive mutants on the same mutation point as `id`. 1818 MutationStatusId[] getSurroundingAliveMutants(const MutationStatusId id) @trusted { 1819 long mp_id; 1820 { 1821 auto stmt = db.prepare(format!"SELECT mp_id FROM %s WHERE st_id=:id"(mutationTable)); 1822 stmt.get.bind(":id", id.get); 1823 auto res = stmt.get.execute; 1824 if (res.empty) 1825 return null; 1826 mp_id = res.oneValue!long; 1827 } 1828 1829 static immutable sql = format!"SELECT DISTINCT t0.st_id FROM %s t0, %s t1 WHERE 1830 t0.mp_id = :id AND 1831 t0.st_id = t1.id AND 1832 t1.status = %s"(mutationTable, 1833 mutationStatusTable, cast(int) Mutation.Status.alive); 1834 1835 auto stmt = db.prepare(sql); 1836 stmt.get.bind(":id", mp_id); 1837 1838 auto rval = appender!(MutationStatusId[])(); 1839 foreach (a; stmt.get.execute) 1840 rval.put(a.peek!long(0).MutationStatusId); 1841 return rval.data; 1842 } 1843 1844 MutationStatusId[] mutantsInRegion(const FileId id, const Offset region, 1845 const Mutation.Status status, const Mutation.Kind[] kinds) @trusted { 1846 const sql = format!"SELECT DISTINCT t1.st_id 1847 FROM %s t0, %s t1, %s t2 1848 WHERE t0.file_id = :file_id AND 1849 t0.id = t1.mp_id AND 1850 (t0.offset_begin BETWEEN :begin AND :end) AND 1851 (t0.offset_end BETWEEN :begin AND :end) AND 1852 t1.st_id = t2.id AND 1853 t2.status = :status AND 1854 t1.kind IN (%(%s,%)) 1855 "(mutationPointTable, mutationTable, 1856 mutationStatusTable, kinds.map!(a => cast(int) a)); 1857 1858 auto stmt = db.prepare(sql); 1859 stmt.get.bind(":file_id", id.get); 1860 stmt.get.bind(":begin", region.begin); 1861 stmt.get.bind(":end", region.end); 1862 stmt.get.bind(":status", cast(int) status); 1863 auto app = appender!(MutationStatusId[])(); 1864 foreach (ref r; stmt.get.execute) 1865 app.put(MutationStatusId(r.peek!long(0))); 1866 return app.data; 1867 } 1868 } 1869 1870 struct DbWorklist { 1871 private Miniorm* db; 1872 private Database* wrapperDb; 1873 1874 /** Add all mutants with the specific status to the worklist. 1875 */ 1876 void updateWorklist(const Mutation.Kind[] kinds, const Mutation.Status[] status, 1877 const long basePrio = 100, const MutationOrder userOrder = MutationOrder.random) @trusted { 1878 const order = fromOrder(userOrder); 1879 1880 const sql = format!"INSERT OR IGNORE INTO %s (id,prio) 1881 SELECT t1.id,%s FROM %s t0, %s t1 WHERE t0.kind IN (%(%s,%)) AND 1882 t0.st_id = t1.id AND 1883 t1.status IN (%(%s,%)) 1884 "(mutantWorklistTable, order, mutationTable, 1885 mutationStatusTable, kinds.map!(a => cast(int) a), status.map!(a => cast(int) a)); 1886 auto stmt = db.prepare(sql); 1887 stmt.get.bind(":base_prio", basePrio); 1888 stmt.get.execute; 1889 } 1890 1891 /// Add a mutant to the worklist. 1892 void addToWorklist(const MutationStatusId id, const long basePrio = 0, 1893 const MutationOrder userOrder = MutationOrder.consecutive) @trusted { 1894 const order = fromOrder(userOrder); 1895 const sql = format!"INSERT OR REPLACE INTO %s (id,prio) 1896 SELECT t1.id,%s FROM %s t1 WHERE t1.id = :id 1897 "(mutantWorklistTable, order, mutationStatusTable); 1898 auto stmt = db.prepare(sql); 1899 stmt.get.bind(":id", id.get); 1900 stmt.get.bind(":base_prio", basePrio); 1901 stmt.get.execute; 1902 } 1903 1904 /// Remove a mutant from the worklist. 1905 void removeFromWorklist(const MutationStatusId id) @trusted { 1906 static immutable sql = format!"DELETE FROM %1$s WHERE id = :id"(mutantWorklistTable); 1907 auto stmt = db.prepare(sql); 1908 stmt.get.bind(":id", id.get); 1909 stmt.get.execute; 1910 } 1911 1912 void clearWorklist() @trusted { 1913 static immutable sql = format!"DELETE FROM %1$s"(mutantWorklistTable); 1914 auto stmt = db.prepare(sql); 1915 stmt.get.execute; 1916 } 1917 1918 long getWorklistCount() @trusted { 1919 static immutable sql = format!"SELECT count(*) FROM %1$s"(mutantWorklistTable); 1920 auto stmt = db.prepare(sql); 1921 auto res = stmt.get.execute; 1922 return res.oneValue!long; 1923 } 1924 } 1925 1926 struct DbMarkMutant { 1927 private Miniorm* db; 1928 private Database* wrapperDb; 1929 1930 bool isMarked(MutationId id) @trusted { 1931 static immutable s = format!"SELECT COUNT(*) FROM %s WHERE st_id IN 1932 (SELECT st_id FROM %s WHERE id=:id)"( 1933 markedMutantTable, mutationTable); 1934 auto stmt = db.prepare(s); 1935 stmt.get.bind(":id", cast(long) id); 1936 auto res = stmt.get.execute; 1937 return res.oneValue!long != 0; 1938 } 1939 1940 /// All marked mutants whom have a mutation status checksum that has been removed from the database. 1941 MarkedMutant[] getLostMarkings() @trusted { 1942 static immutable sql = format!"SELECT checksum0 FROM %s 1943 WHERE 1944 checksum0 NOT IN (SELECT checksum0 FROM %s)"( 1945 markedMutantTable, mutationStatusTable); 1946 1947 auto stmt = db.prepare(sql); 1948 auto app = appender!(MarkedMutant[])(); 1949 foreach (res; stmt.get.execute) { 1950 foreach (m; db.run(select!MarkedMutantTbl.where("checksum0 = :cs0", 1951 Bind("cs0")), res.peek!long(0))) { 1952 app.put(.make(m)); 1953 } 1954 } 1955 1956 return app.data; 1957 } 1958 1959 /** Mark a mutant with status and rationale (also adds metadata). 1960 */ 1961 void markMutant(const MutationId id, const Path file, const SourceLoc sloc, 1962 const MutationStatusId statusId, const Checksum cs, 1963 const Mutation.Status s, const Rationale r, string mutationTxt) @trusted { 1964 db.run(insertOrReplace!MarkedMutantTbl, MarkedMutantTbl(cs.c0, cs.c1, 1965 statusId.get, id.get, sloc.line, sloc.column, file, s, 1966 Clock.currTime.toUTC, r.get, mutationTxt)); 1967 } 1968 1969 void removeMarkedMutant(const Checksum cs) @trusted { 1970 db.run(delete_!MarkedMutantTbl.where("checksum0 = :cs0", Bind("cs0")), cast(long) cs.c0); 1971 } 1972 1973 void removeMarkedMutant(const MutationStatusId id) @trusted { 1974 db.run(delete_!MarkedMutantTbl.where("st_id = :st_id", Bind("st_id")), id.get); 1975 } 1976 1977 /// Returns: All mutants with that are marked orderd by their path 1978 MarkedMutant[] getMarkedMutants() @trusted { 1979 import miniorm : OrderingTermSort; 1980 1981 auto app = appender!(MarkedMutant[])(); 1982 foreach (m; db.run(select!MarkedMutantTbl.orderBy(OrderingTermSort.ASC, [ 1983 "path" 1984 ]))) { 1985 app.put(.make(m)); 1986 } 1987 1988 return app.data; 1989 } 1990 } 1991 1992 struct DbTimeout { 1993 private Miniorm* db; 1994 private Database* wrapperDb; 1995 1996 /// Returns: the context for the timeout algorithm. 1997 MutantTimeoutCtx getMutantTimeoutCtx() @trusted { 1998 foreach (res; db.run(select!MutantTimeoutCtx)) 1999 return res; 2000 return MutantTimeoutCtx.init; 2001 } 2002 2003 void putMutantTimeoutCtx(const MutantTimeoutCtx ctx) @trusted { 2004 db.run(delete_!MutantTimeoutCtx); 2005 db.run(insert!MutantTimeoutCtx.insert, ctx); 2006 } 2007 2008 void putMutantInTimeoutWorklist(const MutationStatusId id) @trusted { 2009 const sql = format!"INSERT OR IGNORE INTO %s (id) VALUES (:id)"(mutantTimeoutWorklistTable); 2010 auto stmt = db.prepare(sql); 2011 stmt.get.bind(":id", id.get); 2012 stmt.get.execute; 2013 } 2014 2015 /** Remove all mutants that are in the worklist that do NOT have the 2016 * mutation status timeout. 2017 */ 2018 void reduceMutantTimeoutWorklist() @trusted { 2019 static immutable sql = format!"DELETE FROM %1$s 2020 WHERE 2021 id IN (SELECT id FROM %2$s WHERE status != :status)"( 2022 mutantTimeoutWorklistTable, mutationStatusTable); 2023 auto stmt = db.prepare(sql); 2024 stmt.get.bind(":status", cast(ubyte) Mutation.Status.timeout); 2025 stmt.get.execute; 2026 } 2027 2028 /// Remove all mutants from the worklist. 2029 void clearMutantTimeoutWorklist() @trusted { 2030 static immutable sql = format!"DELETE FROM %1$s"(mutantTimeoutWorklistTable); 2031 db.run(sql); 2032 } 2033 2034 /// Returns: the number of mutants in the worklist. 2035 long countMutantTimeoutWorklist() @trusted { 2036 static immutable sql = format!"SELECT count(*) FROM %1$s"(mutantTimeoutWorklistTable); 2037 auto stmt = db.prepare(sql); 2038 auto res = stmt.get.execute(); 2039 return res.oneValue!long; 2040 } 2041 2042 /// Changes the status of mutants in the timeout worklist to unknown. 2043 void resetMutantTimeoutWorklist(Mutation.Status toStatus) @trusted { 2044 static immutable sql = format!"UPDATE %1$s SET status=:st WHERE id IN (SELECT id FROM %2$s)"( 2045 mutationStatusTable, mutantTimeoutWorklistTable); 2046 auto stmt = db.prepare(sql); 2047 stmt.get.bind(":st", cast(ubyte) toStatus); 2048 stmt.get.execute; 2049 } 2050 2051 /// Copy the timeout mutants to the worklist of mutants to test. 2052 void copyMutantTimeoutWorklist(const long prio = 100) @trusted { 2053 immutable sql = format!"INSERT OR IGNORE INTO %1$s (id,prio) 2054 SELECT id,%3$s FROM %2$s"(mutantWorklistTable, 2055 mutantTimeoutWorklistTable, prio); 2056 auto stmt = db.prepare(sql); 2057 stmt.get.execute; 2058 } 2059 2060 } 2061 2062 struct DbCoverage { 2063 private Miniorm* db; 2064 private Database* wrapperDb; 2065 2066 /// Add coverage regions. 2067 void putCoverageMap(const FileId id, const Offset[] region) @trusted { 2068 static immutable sql = format!"INSERT OR IGNORE INTO %1$s (file_id, begin, end) 2069 VALUES(:fid, :begin, :end)"(srcCovTable); 2070 auto stmt = db.prepare(sql); 2071 2072 foreach (a; region) { 2073 stmt.get.bind(":fid", id.get); 2074 stmt.get.bind(":begin", a.begin); 2075 stmt.get.bind(":end", a.end); 2076 stmt.get.execute; 2077 stmt.get.reset; 2078 } 2079 } 2080 2081 CovRegion[][FileId] getCoverageMap() @trusted { 2082 static immutable sql = format!"SELECT file_id,begin,end,id FROM %s"(srcCovTable); 2083 auto stmt = db.prepare(sql); 2084 2085 typeof(return) rval; 2086 foreach (ref r; stmt.get.execute) { 2087 auto region = CovRegion(r.peek!long(3).CoverageRegionId, 2088 Offset(r.peek!uint(1), r.peek!uint(2))); 2089 if (auto v = FileId(r.peek!long(0)) in rval) { 2090 *v ~= region; 2091 } else { 2092 rval[FileId(r.peek!long(0))] = [region]; 2093 } 2094 } 2095 2096 return rval; 2097 } 2098 2099 long getCoverageMapCount() @trusted { 2100 static immutable sql = format!"SELECT count(*) FROM %s"(srcCovTable); 2101 auto stmt = db.prepare(sql); 2102 foreach (ref r; stmt.get.execute) 2103 return r.peek!long(0); 2104 return 0; 2105 } 2106 2107 void clearCoverageMap(const FileId id) @trusted { 2108 static immutable sql = format!"DELETE FROM %1$s WHERE file_id = :id"(srcCovTable); 2109 auto stmt = db.prepare(sql); 2110 stmt.get.bind(":id", id.get); 2111 stmt.get.execute; 2112 } 2113 2114 void putCoverageInfo(const CoverageRegionId regionId, bool status) { 2115 static immutable sql = format!"INSERT OR REPLACE INTO %1$s (id, status) VALUES(:id, :status)"( 2116 srcCovInfoTable); 2117 auto stmt = db.prepare(sql); 2118 stmt.get.bind(":id", regionId.get); 2119 stmt.get.bind(":status", status); 2120 stmt.get.execute; 2121 } 2122 2123 Optional!SysTime getCoverageTimeStamp() @trusted { 2124 static immutable sql = format!"SELECT timeStamp FROM %s"(srcCovTimeStampTable); 2125 auto stmt = db.prepare(sql); 2126 2127 foreach (ref r; stmt.get.execute) { 2128 return some(r.peek!string(0).fromSqLiteDateTime); 2129 } 2130 return none!SysTime; 2131 } 2132 2133 /// Set the timestamp to the current UTC time. 2134 void updateCoverageTimeStamp() @trusted { 2135 static immutable sql = format!"INSERT OR REPLACE INTO %s (id, timestamp) VALUES(0, :time)"( 2136 srcCovTimeStampTable); 2137 auto stmt = db.prepare(sql); 2138 stmt.get.bind(":time", Clock.currTime.toSqliteDateTime); 2139 stmt.get.execute; 2140 } 2141 2142 MutationStatusId[] getNotCoveredMutants() @trusted { 2143 static immutable sql = format!"SELECT DISTINCT t3.st_id FROM %1$s t0, %2$s t1, %3$s t2, %4$s t3 2144 WHERE t0.status = 0 AND 2145 t0.id = t1.id AND 2146 t1.file_id = t2.file_id AND 2147 (t2.offset_begin BETWEEN t1.begin AND t1.end) AND 2148 (t2.offset_end BETWEEN t1.begin AND t1.end) AND 2149 t2.id = t3.mp_id"(srcCovInfoTable, 2150 srcCovTable, mutationPointTable, mutationTable); 2151 2152 auto app = appender!(MutationStatusId[])(); 2153 auto stmt = db.prepare(sql); 2154 foreach (ref r; stmt.get.execute) { 2155 app.put(MutationStatusId(r.peek!long(0))); 2156 } 2157 2158 return app.data; 2159 } 2160 } 2161 2162 struct DbSchema { 2163 import my.hash : Checksum64; 2164 2165 private Miniorm* db; 2166 private Database* wrapperDb; 2167 2168 /// Returns: all schematas excluding those that are known to not be 2169 /// possible to compile. 2170 SchemataId[] getSchematas(const SchemaStatus exclude) @trusted { 2171 static immutable sql = format!"SELECT t0.id 2172 FROM %1$s t0 2173 WHERE 2174 t0.id NOT IN (SELECT id FROM %2$s WHERE status = :status)"( 2175 schemataTable, schemataUsedTable); 2176 auto stmt = db.prepare(sql); 2177 stmt.get.bind(":status", cast(long) exclude); 2178 auto app = appender!(SchemataId[])(); 2179 foreach (a; stmt.get.execute) { 2180 app.put(SchemataId(a.peek!long(0))); 2181 } 2182 return app.data; 2183 } 2184 2185 Nullable!Schemata getSchemata(SchemataId id) @trusted { 2186 import std.zlib : uncompress; 2187 2188 static immutable sql = format!"SELECT 2189 t1.path, t0.text, t0.offset_begin, t0.offset_end 2190 FROM %1$s t0, %2$s t1 2191 WHERE 2192 t0.schem_id = :id AND 2193 t0.file_id = t1.id 2194 ORDER BY t0.order_ ASC 2195 "(schemataFragmentTable, filesTable); 2196 2197 typeof(return) rval; 2198 auto stmt = db.prepare(sql); 2199 stmt.get.bind(":id", cast(long) id); 2200 2201 auto app = appender!(SchemataFragment[])(); 2202 foreach (a; stmt.get.execute) { 2203 auto raw = a.peek!(ubyte[])(1); 2204 auto offset = Offset(a.peek!uint(2), a.peek!uint(3)); 2205 app.put(SchemataFragment(a.peek!string(0).Path, offset, 2206 cast(const(ubyte)[]) uncompress(raw, offset.end - offset.begin))); 2207 } 2208 2209 if (!app.data.empty) { 2210 rval = Schemata(SchemataId(id), app.data); 2211 } 2212 2213 return rval; 2214 } 2215 2216 /// Returns: number of mutants in a schema that are marked for testing. 2217 long schemataMutantsCount(const SchemataId id, const Mutation.Kind[] kinds) @trusted { 2218 const sql = format!"SELECT count(*) 2219 FROM %s t1, %s t2, %s t3, %s t4 2220 WHERE 2221 t1.schem_id = :id AND 2222 t1.st_id = t2.id AND 2223 t3.st_id = t1.st_id AND 2224 t2.id = t4.id AND 2225 t3.kind IN (%(%s,%)) 2226 "(schemataMutantTable, mutationStatusTable, 2227 mutationTable, mutantWorklistTable, kinds.map!(a => cast(int) a)); 2228 2229 auto stmt = db.prepare(sql); 2230 stmt.get.bind(":id", id.get); 2231 return stmt.get.execute.oneValue!long; 2232 } 2233 2234 MutationStatusId[] getSchemataMutants(const SchemataId id, const Mutation.Kind[] kinds) @trusted { 2235 // TODO: DISTINCT should not be needed. Instead use e.g. a constraint on the table or something 2236 immutable sql = format!"SELECT DISTINCT t1.st_id 2237 FROM %s t1, %s t2, %s t3, %s t4 2238 WHERE 2239 t1.schem_id = :id AND 2240 t1.st_id = t2.id AND 2241 t3.st_id = t1.st_id AND 2242 t2.id = t4.id AND 2243 t3.kind IN (%(%s,%)) 2244 "(schemataMutantTable, mutationStatusTable, 2245 mutationTable, mutantWorklistTable, kinds.map!(a => cast(int) a)); 2246 auto stmt = db.prepare(sql); 2247 stmt.get.bind(":id", id.get); 2248 2249 auto app = appender!(MutationStatusId[])(); 2250 foreach (a; stmt.get.execute) { 2251 app.put(a.peek!long(0).MutationStatusId); 2252 } 2253 2254 return app.data; 2255 } 2256 2257 /// Returns: the kind of mutants a schemata contains. 2258 Mutation.Kind[] getSchemataKinds(const SchemataId id) @trusted { 2259 static immutable sql = format!"SELECT DISTINCT t1.kind 2260 FROM %1$s t0, %2$s t1 2261 WHERE 2262 t0.schem_id = :id AND 2263 t0.st_id = t1.st_id 2264 "(schemataMutantTable, mutationTable); 2265 auto stmt = db.prepare(sql); 2266 stmt.get.bind(":id", cast(long) id); 2267 2268 auto app = appender!(Mutation.Kind[])(); 2269 foreach (a; stmt.get.execute) { 2270 app.put(a.peek!long(0).to!(Mutation.Kind)); 2271 } 2272 2273 return app.data; 2274 } 2275 2276 /// Mark a schemata as used. 2277 void markUsed(const SchemataId id, const SchemaStatus status) @trusted { 2278 static immutable sql = format!"INSERT OR IGNORE INTO %1$s VALUES(:id, :status)"( 2279 schemataUsedTable); 2280 auto stmt = db.prepare(sql); 2281 stmt.get.bind(":id", cast(long) id); 2282 stmt.get.bind(":status", status); 2283 stmt.get.execute; 2284 } 2285 2286 /// Create a schemata from a bundle of fragments. 2287 Nullable!SchemataId putSchemata(SchemataChecksum cs, 2288 const SchemataFragment[] fragments, MutationStatusId[] mutants) @trusted { 2289 import std.zlib : compress; 2290 2291 const schemId = cast(long) cs.value.c0; 2292 2293 const exists = () { 2294 static immutable sql = format!"SELECT count(*) FROM %1$s WHERE id=:id"(schemataTable); 2295 auto stmt = db.prepare(sql); 2296 stmt.get.bind(":id", schemId); 2297 return stmt.get.execute.oneValue!long != 0; 2298 2299 }(); 2300 2301 if (exists) 2302 return typeof(return)(); 2303 2304 { 2305 static immutable sql = format!"INSERT INTO %1$s VALUES(:id, :nr)"(schemataTable); 2306 auto stmt = db.prepare(sql); 2307 stmt.get.bind(":id", cast(long) cs.value.c0); 2308 stmt.get.bind(":nr", cast(long) fragments.length); 2309 stmt.get.execute; 2310 } 2311 2312 foreach (f; fragments.enumerate) { 2313 const fileId = wrapperDb.getFileId(f.value.file); 2314 if (fileId.isNull) { 2315 logger.warningf("Unable to add schemata fragment for file %s because it doesn't exist", 2316 f.value.file); 2317 continue; 2318 } 2319 2320 db.run(insert!SchemataFragmentTable, SchemataFragmentTable(0, 2321 schemId, cast(long) fileId.get, f.index, 2322 compress(f.value.text), f.value.offset.begin, f.value.offset.end)); 2323 } 2324 2325 // relate mutants to this schemata. 2326 db.run(insertOrIgnore!SchemataMutantTable, 2327 mutants.map!(a => SchemataMutantTable(cast(long) a, schemId))); 2328 2329 return typeof(return)(schemId.SchemataId); 2330 } 2331 2332 /// Prunes the database of schemas that where created by an older version. 2333 void deleteAllSchemas() @trusted { 2334 db.run(delete_!SchemataTable); 2335 } 2336 2337 /// Prunes the database of schemas that are unusable. 2338 void pruneSchemas() @trusted { 2339 auto remove = () { 2340 auto remove = appender!(long[])(); 2341 2342 // remove those that have lost some fragments 2343 static immutable sqlFragment = format!"SELECT t0.id 2344 FROM 2345 %1$s t0, 2346 (SELECT schem_id id,count(*) fragments FROM %2$s GROUP BY schem_id) t1 2347 WHERE 2348 t0.id = t1.id AND 2349 t0.fragments != t1.fragments 2350 "(schemataTable, 2351 schemataFragmentTable); 2352 auto stmt = db.prepare(sqlFragment); 2353 foreach (a; stmt.get.execute) { 2354 remove.put(a.peek!long(0)); 2355 } 2356 2357 // remove those that have lost all fragments 2358 static immutable sqlNoFragment = format!"SELECT t0.id FROM %1$s t0 WHERE t0.id NOT IN (SELECT schem_id FROM %2$s)"( 2359 schemataTable, schemataFragmentTable); 2360 stmt = db.prepare(sqlNoFragment); 2361 foreach (a; stmt.get.execute) { 2362 remove.put(a.peek!long(0)); 2363 } 2364 2365 return remove.data; 2366 }(); 2367 2368 static immutable sql = format!"DELETE FROM %1$s WHERE id=:id"(schemataTable); 2369 auto stmt = db.prepare(sql); 2370 foreach (a; remove) { 2371 stmt.get.bind(":id", a); 2372 stmt.get.execute; 2373 stmt.get.reset; 2374 } 2375 } 2376 2377 /** Removes all schemas that either do not compile or have zero mutants. 2378 * 2379 * Returns: number of schemas removed. 2380 */ 2381 long pruneUsedSchemas(const SchemaStatus[] status) @trusted { 2382 auto remove = () { 2383 auto remove = appender!(long[])(); 2384 2385 auto sqlUsed = format!"SELECT id FROM %s WHERE status IN (%(%s,%))"(schemataUsedTable, 2386 status.map!(a => cast(long) a)); 2387 auto stmt = db.prepare(sqlUsed); 2388 foreach (a; stmt.get.execute) { 2389 remove.put(a.peek!long(0)); 2390 } 2391 return remove.data; 2392 }(); 2393 2394 static immutable sql = format!"DELETE FROM %1$s WHERE id=:id"(schemataTable); 2395 auto stmt = db.prepare(sql); 2396 foreach (a; remove) { 2397 stmt.get.bind(":id", a); 2398 stmt.get.execute; 2399 stmt.get.reset; 2400 } 2401 2402 return remove.length; 2403 } 2404 2405 int[Mutation.Kind][Checksum64] getMutantProbability() @trusted { 2406 typeof(return) rval; 2407 2408 auto stmt = db.prepare( 2409 format!"SELECT kind,probability,path FROM %1$s"(schemaMutantQTable)); 2410 foreach (ref r; stmt.get.execute) { 2411 const ch = Checksum64(cast(ulong) r.peek!long(2)); 2412 rval.require(ch, (int[Mutation.Kind]).init); 2413 rval[ch][r.peek!long(0).to!(Mutation.Kind)] = r.peek!int(1); 2414 } 2415 return rval; 2416 } 2417 2418 void removeMutantProbability(const Checksum64 p) @trusted { 2419 static immutable sql = format!"DELETE FROM %1$s WHERE path=:path"(schemaMutantQTable); 2420 auto stmt = db.prepare(sql); 2421 stmt.get.bind(":path", cast(long) p.c0); 2422 stmt.get.execute; 2423 } 2424 2425 /** Save the probability state for a path. 2426 * 2427 * Only states other than 100 are saved. 2428 * 2429 * Params: 2430 * p = checksum of the path. 2431 */ 2432 void saveMutantProbability(const Checksum64 p, int[Mutation.Kind] state, const int skipMax) @trusted { 2433 auto stmt = db.prepare( 2434 format!"INSERT OR REPLACE INTO %1$s (kind,probability,path) VALUES(:kind,:q,:path)"( 2435 schemaMutantQTable)); 2436 foreach (a; state.byKeyValue) { 2437 if (a.value != skipMax) { 2438 stmt.get.bind(":kind", cast(long) a.key); 2439 stmt.get.bind(":q", cast(long) a.value); 2440 stmt.get.bind(":path", cast(long) p.c0); 2441 stmt.get.execute; 2442 stmt.get.reset; 2443 } 2444 } 2445 } 2446 2447 /// Returns: all mutant subtypes that has `status`, can occur multiple times. 2448 Mutation.Kind[] getSchemaUsedKinds(const Path p, const SchemaStatus status) @trusted { 2449 static immutable sql = format!"SELECT DISTINCT t1.kind 2450 FROM %1$s t0, %2$s t1, %3$s t2, %4$s t3, %5$s t4 2451 WHERE 2452 t4.status = :status AND 2453 t4.id = t0.schem_id AND 2454 t0.st_id = t1.st_id AND 2455 t1.mp_id = t2.id AND 2456 t2.file_id = t3.id AND 2457 t3.path = :path 2458 "(schemataMutantTable, 2459 mutationTable, mutationPointTable, filesTable, schemataUsedTable); 2460 2461 auto stmt = db.prepare(sql); 2462 stmt.get.bind(":status", cast(long) status); 2463 stmt.get.bind(":path", p.toString); 2464 2465 auto app = appender!(Mutation.Kind[])(); 2466 foreach (ref r; stmt.get.execute) { 2467 auto k = r.peek!long(0).to!(Mutation.Kind); 2468 app.put(k); 2469 } 2470 2471 return app.data; 2472 } 2473 2474 /// Returns: number of scheman matching the condition. 2475 long schemaCount(const SchemaStatus status, const long value, string condition) @trusted { 2476 const sql = format!"SELECT count(*) FROM %1$s t0 2477 WHERE status=:status AND 2478 (SELECT count(*) FROM %2$s WHERE schem_id=t0.id) %3$s :value"( 2479 schemataUsedTable, schemataMutantTable, condition); 2480 auto stmt = db.prepare(sql); 2481 stmt.get.bind(":status", cast(long) status); 2482 stmt.get.bind(":value", value); 2483 foreach (ref r; stmt.get.execute) 2484 return r.peek!long(0); 2485 return 0; 2486 } 2487 2488 /// Returns: an array of the mutants that are in schemas with the specific status 2489 long[] schemaMutantCount(const SchemaStatus status) @trusted { 2490 static immutable sql = format!"SELECT (SELECT count(*) FROM %2$s WHERE schem_id=t0.id) 2491 FROM %1$s t0 WHERE status=:status"( 2492 schemataUsedTable, schemataMutantTable); 2493 auto stmt = db.prepare(sql); 2494 stmt.get.bind(":status", cast(long) status); 2495 auto app = appender!(long[])(); 2496 foreach (ref r; stmt.get.execute) 2497 app.put(r.peek!long(0)); 2498 return app.data; 2499 } 2500 2501 long getSchemaSize(const long defaultValue) @trusted { 2502 static immutable sql = "SELECT size FROM " ~ schemaSizeQTable ~ " WHERE id=0"; 2503 auto stmt = db.prepare(sql); 2504 foreach (ref r; stmt.get.execute) 2505 return r.peek!long(0); 2506 return defaultValue; 2507 } 2508 2509 void saveSchemaSize(const long v) @trusted { 2510 static immutable sql = "INSERT OR REPLACE INTO " ~ schemaSizeQTable 2511 ~ " (id,size) VALUES(0,:size)"; 2512 auto stmt = db.prepare(sql); 2513 stmt.get.bind(":size", v); 2514 stmt.get.execute; 2515 } 2516 } 2517 2518 struct DbMetaData { 2519 private Miniorm* db; 2520 private Database* wrapperDb; 2521 2522 LineMetadata getLineMetadata(const FileId fid, const SourceLoc sloc) @trusted { 2523 // TODO: change this select to using microrm 2524 static immutable sql = format("SELECT nomut,tag,comment FROM %s 2525 WHERE 2526 file_id = :fid AND 2527 line = :line", rawSrcMetadataTable); 2528 auto stmt = db.prepare(sql); 2529 stmt.get.bind(":fid", cast(long) fid); 2530 stmt.get.bind(":line", sloc.line); 2531 2532 auto rval = typeof(return)(fid, sloc.line); 2533 foreach (res; stmt.get.execute) { 2534 if (res.peek!long(0) != 0) 2535 rval.set(NoMut(res.peek!string(1), res.peek!string(2))); 2536 } 2537 2538 return rval; 2539 } 2540 2541 /// Remove all metadata. 2542 void clearMetadata() { 2543 static immutable sql = format!"DELETE FROM %s"(rawSrcMetadataTable); 2544 db.run(sql); 2545 } 2546 2547 /** Save line metadata to the database which is used to associate line 2548 * metadata with mutants. 2549 */ 2550 void put(const LineMetadata[] mdata) { 2551 import sumtype; 2552 2553 if (mdata.empty) 2554 return; 2555 2556 // TODO: convert to microrm 2557 static immutable sql = format!"INSERT OR IGNORE INTO %s 2558 (file_id, line, nomut, tag, comment) 2559 VALUES(:fid, :line, :nomut, :tag, :comment)"( 2560 rawSrcMetadataTable); 2561 2562 auto stmt = db.prepare(sql); 2563 foreach (meta; mdata) { 2564 auto nomut = meta.attr.match!((NoMetadata a) => NoMut.init, (NoMut a) => a); 2565 stmt.get.bindAll(cast(long) meta.id, meta.line, meta.isNoMut, 2566 nomut.tag, nomut.comment); 2567 stmt.get.execute; 2568 stmt.get.reset; 2569 } 2570 } 2571 2572 /** Update the content of metadata tables with what has been added to the 2573 * raw table data. 2574 */ 2575 void updateMetadata() @trusted { 2576 db.run(format!"DELETE FROM %s"(srcMetadataTable)); 2577 db.run(format!"DELETE FROM %s"(nomutTable)); 2578 db.run(format!"DELETE FROM %s"(nomutDataTable)); 2579 2580 static immutable nomut_tbl = "INSERT INTO %s 2581 SELECT 2582 t0.id mp_id, 2583 t1.line line, 2584 count(*) status 2585 FROM %s t0, %s t1 2586 WHERE 2587 t0.file_id = t1.file_id AND 2588 (t1.line BETWEEN t0.line AND t0.line_end) 2589 GROUP BY 2590 t0.id"; 2591 db.run(format!nomut_tbl(nomutTable, mutationPointTable, rawSrcMetadataTable)); 2592 2593 static immutable src_metadata_sql = "INSERT INTO %s 2594 SELECT DISTINCT 2595 t0.id AS mut_id, 2596 t1.id AS st_id, 2597 t2.id AS mp_id, 2598 t3.id AS file_id, 2599 (SELECT count(*) FROM %s WHERE nomut.mp_id = t2.id) as nomut 2600 FROM %s t0, %s t1, %s t2, %s t3 2601 WHERE 2602 t0.mp_id = t2.id AND 2603 t0.st_id = t1.id AND 2604 t2.file_id = t3.id"; 2605 db.run(format!src_metadata_sql(srcMetadataTable, nomutTable, 2606 mutationTable, mutationStatusTable, mutationPointTable, filesTable)); 2607 2608 static immutable nomut_data_tbl = "INSERT INTO %s 2609 SELECT 2610 t0.id as mut_id, 2611 t0.mp_id as mp_id, 2612 t1.line as line, 2613 t1.tag as tag, 2614 t1.comment as comment 2615 FROM %s t0, %s t1, %s t2 2616 WHERE 2617 t0.mp_id = t2.mp_id AND 2618 t1.line = t2.line"; 2619 db.run(format!nomut_data_tbl(nomutDataTable, mutationTable, 2620 rawSrcMetadataTable, nomutTable)); 2621 } 2622 } 2623 2624 struct DbTestFile { 2625 private Miniorm* db; 2626 private Database* wrapperDb; 2627 2628 void put(const TestFile tfile) @trusted { 2629 static immutable sql = format!"INSERT OR IGNORE INTO %s (path, checksum0, checksum1, timestamp) 2630 VALUES (:path, :checksum0, :checksum1, :timestamp)"( 2631 testFilesTable); 2632 auto stmt = db.prepare(sql); 2633 stmt.get.bind(":path", tfile.file.get.toString); 2634 stmt.get.bind(":checksum0", cast(long) tfile.checksum.get.c0); 2635 stmt.get.bind(":checksum1", cast(long) tfile.checksum.get.c1); 2636 stmt.get.bind(":timestamp", tfile.timeStamp.toSqliteDateTime); 2637 stmt.get.execute; 2638 } 2639 2640 TestFile[] getTestFiles() @trusted { 2641 static immutable sql = format!"SELECT path,checksum0,checksum1,timestamp FROM %s"( 2642 testFilesTable); 2643 auto stmt = db.prepare(sql); 2644 auto res = stmt.get.execute; 2645 2646 auto app = appender!(TestFile[]); 2647 foreach (ref r; res) { 2648 app.put(TestFile(TestFilePath(Path(r.peek!string(0))), 2649 TestFileChecksum(Checksum(r.peek!long(1), r.peek!long(2))), 2650 r.peek!string(3).fromSqLiteDateTime)); 2651 } 2652 2653 return app.data; 2654 } 2655 2656 /// Returns: the oldest test file, if it exists. 2657 Optional!TestFile getNewestTestFile() @trusted { 2658 auto stmt = db.prepare(format!"SELECT path,checksum0,checksum1,timestamp 2659 FROM %s ORDER BY datetime(timestamp) DESC LIMIT 1"( 2660 testFilesTable)); 2661 auto res = stmt.get.execute; 2662 2663 foreach (ref r; res) { 2664 return some(TestFile(TestFilePath(Path(r.peek!string(0))), 2665 TestFileChecksum(Checksum(r.peek!long(1), r.peek!long(2))), 2666 r.peek!string(3).fromSqLiteDateTime)); 2667 } 2668 2669 return none!TestFile; 2670 } 2671 2672 /// Remove the file with all mutations that are coupled to it. 2673 void removeFile(const TestFilePath p) @trusted { 2674 auto stmt = db.prepare(format!"DELETE FROM %s WHERE path=:path"(testFilesTable)); 2675 stmt.get.bind(":path", p.get.toString); 2676 stmt.get.execute; 2677 } 2678 } 2679 2680 private: 2681 2682 MarkedMutant make(MarkedMutantTbl m) { 2683 import dextool.plugin.mutate.backend.type; 2684 2685 return MarkedMutant(m.mutationStatusId.MutationStatusId, Checksum(m.checksum0, 2686 m.checksum1), m.mutationId.MutationId, SourceLoc(m.line, m.column), 2687 m.path.Path, m.toStatus.to!(Mutation.Status), m.time, 2688 m.rationale.Rationale, m.mutText); 2689 } 2690 2691 string fromOrder(const MutationOrder userOrder) { 2692 final switch (userOrder) { 2693 case MutationOrder.random: 2694 return ":base_prio + t1.prio + abs(random() % 100)"; 2695 case MutationOrder.consecutive: 2696 return ":base_prio"; 2697 case MutationOrder.bySize: 2698 return ":base_prio + t1.prio"; 2699 } 2700 }