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 schema to initialize the database. 11 12 To ensure that the upgrade path for a database always work a database is 13 created at the "lowest supported" and upgraded to the latest. 14 15 # How to add schema change 16 17 1. add an upgrade function, upgradeVX. 18 19 The function makeUpgradeTable will then automatically find it and use it. X 20 **must** be the version upgrading FROM. 21 22 # Style 23 A database schema upgrade path shall have a comment stating what date it was added. 24 Each change to the database schema must have an equal upgrade added. 25 26 # Sqlite3 27 From the sqlite3 manual $(LINK https://www.sqlite.org/datatype3.html): 28 Each value stored in an SQLite database (or manipulated by the database 29 engine) has one of the following storage classes: 30 31 NULL. The value is a NULL value. 32 33 INTEGER. The value is a signed integer, stored in 1, 2, 3, 4, 6, or 8 bytes 34 depending on the magnitude of the value. 35 36 REAL. The value is a floating point value, stored as an 8-byte IEEE floating 37 point number. 38 39 TEXT. The value is a text string, stored using the database encoding (UTF-8, 40 UTF-16BE or UTF-16LE). 41 42 BLOB. The value is a blob of data, stored exactly as it was input. 43 44 A storage class is more general than a datatype. The INTEGER storage class, for 45 example, includes 6 different integer datatypes of different lengths. This 46 makes a difference on disk. But as soon as INTEGER values are read off of disk 47 and into memory for processing, they are converted to the most general datatype 48 (8-byte signed integer). And so for the most part, "storage class" is 49 indistinguishable from "datatype" and the two terms can be used 50 interchangeably. 51 */ 52 module dextool.plugin.mutate.backend.database.schema; 53 54 import logger = std.experimental.logger; 55 import std.array : array, empty; 56 import std.datetime : SysTime; 57 import std.exception : collectException; 58 import std.format : format; 59 60 import dextool.plugin.mutate.backend.type : Language; 61 62 import d2sqlite3 : SqlDatabase = Database; 63 import miniorm : Miniorm, TableName, buildSchema, ColumnParam, TableForeignKey, 64 TableConstraint, TablePrimaryKey, KeyRef, KeyParam, ColumnName, delete_, insert, select; 65 66 immutable allTestCaseTable = "all_test_case"; 67 immutable depFileTable = "dependency_file"; 68 immutable depRootTable = "rel_dependency_root"; 69 immutable dextoolVersionTable = "dextool_version"; 70 immutable filesTable = "files"; 71 immutable killedTestCaseTable = "killed_test_case"; 72 immutable markedMutantTable = "marked_mutant"; 73 immutable mutantTimeoutCtxTable = "mutant_timeout_ctx"; 74 immutable mutantTimeoutWorklistTable = "mutant_timeout_worklist"; 75 immutable mutantWorklistTable = "mutant_worklist"; 76 immutable mutationPointTable = "mutation_point"; 77 immutable mutationScoreHistoryTable = "mutation_score_history"; 78 immutable mutationStatusTable = "mutation_status"; 79 immutable mutationTable = "mutation"; 80 immutable nomutDataTable = "nomut_data"; 81 immutable nomutTable = "nomut"; 82 immutable rawSrcMetadataTable = "raw_src_metadata"; 83 immutable runtimeHistoryTable = "test_cmd_runtime_history"; 84 immutable schemaVersionTable = "schema_version"; 85 immutable schemataFragmentTable = "schemata_fragment"; 86 immutable schemataMutantTable = "schemata_mutant"; 87 immutable schemataTable = "schemata"; 88 immutable schemataUsedTable = "schemata_used"; 89 immutable srcCovInfoTable = "src_cov_info"; 90 immutable srcCovTable = "src_cov_instr"; 91 immutable srcCovTimeStampTable = "src_cov_timestamp"; 92 immutable srcMetadataTable = "src_metadata"; 93 immutable testFilesTable = "test_files"; 94 95 private immutable invalidSchemataTable = "invalid_schemata"; 96 private immutable schemataWorkListTable = "schemata_worklist"; 97 private immutable testCaseTableV1 = "test_case"; 98 99 /** Initialize or open an existing database. 100 * 101 * Params: 102 * p = path where to initialize a new database or open an existing 103 * 104 * Returns: an open sqlite3 database object. 105 */ 106 Miniorm initializeDB(const string p) @trusted 107 in { 108 assert(p.length != 0); 109 } 110 do { 111 import std.parallelism : totalCPUs; 112 import d2sqlite3 : SQLITE_OPEN_CREATE, SQLITE_OPEN_READWRITE; 113 114 static void setPragmas(ref SqlDatabase db) { 115 // dfmt off 116 auto pragmas = [ 117 // required for foreign keys with cascade to work 118 "PRAGMA foreign_keys=ON;", 119 ]; 120 // dfmt on 121 122 foreach (p; pragmas) { 123 db.run(p); 124 } 125 } 126 127 SqlDatabase sqliteDb; 128 129 try { 130 sqliteDb = SqlDatabase(p, SQLITE_OPEN_READWRITE); 131 } catch (Exception e) { 132 logger.trace(e.msg); 133 logger.trace("Initializing a new sqlite3 database"); 134 sqliteDb = SqlDatabase(p, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); 135 } 136 137 auto db = Miniorm(sqliteDb); 138 139 // TODO: remove all key off in upgrade schemas. 140 db.run("PRAGMA foreign_keys=OFF;"); 141 try { 142 upgrade(db); 143 } catch (Exception e) { 144 logger.error("Unable to upgrade the database to the latest schema"); 145 throw e; 146 } 147 setPragmas(sqliteDb); 148 149 return db; 150 } 151 152 package: 153 154 // metadata about mutants that occur on a line extracted from the source code. 155 // It is intended to further refined. 156 // nomut = if the line should ignore mutants. 157 // tag = a user defined tag for a NOMUT. 158 // comment = a user defined comment. 159 @TableName(rawSrcMetadataTable) 160 @TableForeignKey("file_id", KeyRef("files(id)"), KeyParam("ON DELETE CASCADE")) 161 @TableConstraint("unique_line_in_file UNIQUE (file_id, line)") 162 struct RawSrcMetadata { 163 long id; 164 165 @ColumnName("file_id") 166 long fileId; 167 168 @ColumnParam("") 169 uint line; 170 171 @ColumnParam("") 172 long nomut; 173 174 @ColumnParam("") 175 string tag; 176 177 @ColumnParam("") 178 string comment; 179 } 180 181 @TableName(srcMetadataTable) 182 @TableForeignKey("mut_id", KeyRef("mutation(id)"), KeyParam("ON DELETE CASCADE")) 183 @TableForeignKey("st_id", KeyRef("mutation_status(id)"), KeyParam("ON DELETE CASCADE")) 184 @TableForeignKey("mp_id", KeyRef("mutation_point(id)"), KeyParam("ON DELETE CASCADE")) 185 @TableForeignKey("file_id", KeyRef("files(id)"), KeyParam("ON DELETE CASCADE")) 186 struct SrcMetadataTable { 187 @ColumnName("mut_id") 188 long mutationId; 189 190 @ColumnName("st_id") 191 long mutationStatusId; 192 193 @ColumnName("mp_id") 194 long mutationPointId; 195 196 @ColumnName("file_id") 197 long fileId; 198 199 @ColumnName("nomut") 200 long nomutCount; 201 } 202 203 // Reconstruct the nomut table in Miniorm. 204 @TableName(nomutTable) 205 @TableForeignKey("mp_id", KeyRef("mutation_point(id)"), KeyParam("ON DELETE CASCADE")) 206 struct NomutTbl { 207 @ColumnName("mp_id") 208 long mutationPointId; 209 210 long line; 211 212 /// != 0 when a nomut is tagged on the line. 213 long status; 214 } 215 216 @TableName(nomutDataTable) 217 @TableForeignKey("mut_id", KeyRef("mutation(id)"), KeyParam("ON DELETE CASCADE")) 218 @TableForeignKey("mp_id", KeyRef("mutation_point(id)"), KeyParam("ON DELETE CASCADE")) 219 struct NomutDataTbl { 220 @ColumnName("mut_id") 221 long mutationId; 222 223 @ColumnName("mp_id") 224 long mutationPointId; 225 226 long line; 227 228 @ColumnParam("") 229 string tag; 230 231 @ColumnParam("") 232 string comment; 233 } 234 235 @TableName(schemaVersionTable) 236 struct VersionTbl { 237 @ColumnName("version") 238 long version_; 239 } 240 241 @TableName(dextoolVersionTable) 242 struct DextoolVersionTable { 243 /// checksum is 64bit. 244 long checksum; 245 } 246 247 @TableName(filesTable) 248 @TableConstraint("unique_ UNIQUE (path)") 249 struct FilesTbl { 250 long id; 251 252 string path; 253 254 /// checksum is 128bit. 255 long checksum0; 256 long checksum1; 257 Language lang; 258 259 @ColumnName("timestamp") 260 SysTime timeStamp; 261 262 /// True if the file is a root. 263 bool root; 264 } 265 266 @TableName(testFilesTable) 267 @TableConstraint("unique_ UNIQUE (path)") 268 struct TestFilesTable { 269 long id; 270 271 string path; 272 273 /// checksum is 128bit. 274 long checksum0; 275 long checksum1; 276 277 /// Last time a change to the test file where detected. 278 @ColumnName("timestamp") 279 SysTime timeStamp; 280 } 281 282 /// there shall never exist two mutations points for the same file+offset. 283 @TableName(mutationPointTable) 284 @TableConstraint("file_offset UNIQUE (file_id, offset_begin, offset_end)") 285 @TableForeignKey("file_id", KeyRef("files(id)"), KeyParam("ON DELETE CASCADE")) 286 struct MutationPointTbl { 287 long id; 288 long file_id; 289 uint offset_begin; 290 uint offset_end; 291 292 /// line start from zero 293 @ColumnParam("") 294 uint line; 295 @ColumnParam("") 296 uint column; 297 298 @ColumnParam("") 299 uint line_end; 300 301 @ColumnParam("") 302 uint column_end; 303 } 304 305 @TableName(mutationTable) 306 @TableForeignKey("mp_id", KeyRef("mutation_point(id)"), KeyParam("ON DELETE CASCADE")) 307 @TableForeignKey("st_id", KeyRef("mutation_status(id)")) 308 @TableConstraint("unique_ UNIQUE (mp_id, kind)") 309 struct MutationTbl { 310 long id; 311 312 long mp_id; 313 314 @ColumnParam("") 315 long st_id; 316 317 long kind; 318 } 319 320 /** 321 * This could use an intermediate adapter table to normalise the test_case data 322 * but I chose not to do that because it makes it harder to add test cases and 323 * do a cleanup. 324 */ 325 @TableName(killedTestCaseTable) 326 @TableForeignKey("st_id", KeyRef("mutation_status(id)"), KeyParam("ON DELETE CASCADE")) 327 @TableForeignKey("tc_id", KeyRef("all_test_case(id)"), KeyParam("ON DELETE CASCADE")) 328 @TableConstraint("unique_ UNIQUE (st_id, tc_id)") 329 struct TestCaseKilledTbl { 330 long id; 331 332 @ColumnName("st_id") 333 long mutationStatusId; 334 @ColumnName("tc_id") 335 long testCaseId; 336 337 // location is a filesystem location or other suitable helper for a user to 338 // locate the test. 339 @ColumnParam("") 340 string location; 341 } 342 343 /** 344 * Track all test cases that has been found by the test suite output analyzer. 345 * Useful to find test cases that has never killed any mutant. 346 * name should match test_case_killed_v2_tbl 347 * TODO: name should be the primary key. on a conflict a counter should be updated. 348 */ 349 @TableName(allTestCaseTable) 350 @TableConstraint("unique_ UNIQUE (name)") 351 struct AllTestCaseTbl { 352 long id; 353 string name; 354 } 355 356 /** 357 * the status of a mutant. if it is killed or otherwise. 358 * multiple mutation operators can result in the same change of the source 359 * code. By coupling the mutant status to the checksum of the source code 360 * change it means that two mutations that have the same checksum will 361 * "cooperate". 362 * TODO: change the checksum to being NOT NULL in the future. Can't for now 363 * when migrating to schema version 5->6. 364 * compile_time_ms = time it took to compile the program for the mutant 365 * test_time_ms = time it took to run the test suite 366 * updated_ts = is when the status where last updated. Seconds at UTC+0. 367 * added_ts = when the mutant where added to the system. UTC+0. 368 */ 369 @TableName(mutationStatusTable) 370 @TableConstraint("checksum UNIQUE (checksum0, checksum1)") 371 struct MutationStatusTbl { 372 long id; 373 long status; 374 @ColumnName("exit_code") 375 int exitCode; 376 377 @ColumnName("compile_time_ms") 378 long compileTimeMs; 379 380 @ColumnName("test_time_ms") 381 long testTimeMs; 382 383 @ColumnParam("") 384 @ColumnName("update_ts") 385 SysTime updated; 386 387 @ColumnParam("") 388 @ColumnName("added_ts") 389 SysTime added; 390 391 long checksum0; 392 long checksum1; 393 394 /// Priority of the mutant used when testing. 395 long prio; 396 } 397 398 /** Mutants that should be tested. 399 */ 400 @TableName(mutantWorklistTable) 401 @TableForeignKey("id", KeyRef("mutation_status(id)"), KeyParam("ON DELETE CASCADE")) 402 struct MutantWorklistTbl { 403 long id; 404 long prio; 405 } 406 407 /** Timeout mutants that are re-tested until none of them change status from 408 * timeout. 409 */ 410 @TableName(mutantTimeoutWorklistTable) 411 @TableForeignKey("id", KeyRef("mutation_status(id)"), KeyParam("ON DELETE CASCADE")) 412 struct MutantTimeoutWorklistTbl { 413 long id; 414 } 415 416 /** The defaults for the schema is the state that the state machine start in. 417 * 418 * This mean that if there are nothing in the database then `.init` is the correct starting point. 419 */ 420 @TableName(mutantTimeoutCtxTable) 421 struct MutantTimeoutCtxTbl { 422 /// What iteration the timeout testing is at. 423 long iter; 424 425 /// Last count of the mutants in the worklist that where in the timeout state. 426 long worklistCount; 427 428 enum State { 429 init_, 430 running, 431 done 432 } 433 434 /// State of the timeout algorithm. 435 State state; 436 } 437 438 /** The lower 64bit of the checksum should be good enough as the primary key. 439 * By doing it this way it is easier to update a marked mutant without 440 * "peeking" in the database ("insert or update"). 441 * 442 * Both `st_id` and `mut_id` are values that sqlite can reuse between analyzes 443 * if they have been previously removed thus the only assured connection 444 * between a marked mutant and future code changes is the checksum. 445 */ 446 @TableName(markedMutantTable) 447 @TablePrimaryKey("checksum0") 448 struct MarkedMutantTbl { 449 /// Checksum of the mutant status the marking is related to. 450 long checksum0; 451 long checksum1; 452 453 /// updated each analyze. 454 @ColumnName("st_id") 455 long mutationStatusId; 456 457 /// updated each analyze. 458 @ColumnName("mut_id") 459 long mutationId; 460 461 uint line; 462 uint column; 463 string path; 464 465 /// The status it should always be changed to. 466 long toStatus; 467 468 /// Time when the mutant where marked. 469 SysTime time; 470 471 string rationale; 472 473 string mutText; 474 } 475 476 @TableName(schemataMutantTable) 477 @TableForeignKey("st_id", KeyRef("mutation_status(id)"), KeyParam("ON DELETE CASCADE")) 478 @TableForeignKey("schem_id", KeyRef("schemata(id)"), KeyParam("ON DELETE CASCADE")) 479 @TableConstraint("unique_ UNIQUE (st_id, schem_id)") 480 struct SchemataMutantTable { 481 @ColumnName("st_id") 482 long statusId; 483 @ColumnName("schem_id") 484 long schemaId; 485 } 486 487 @TableName(schemataTable) 488 struct SchemataTable { 489 long id; 490 491 // number of fragments the schemata consist of. 492 // used to detect if a fragment has been removed because its related file 493 // was changed. 494 long fragments; 495 } 496 497 @TableName(schemataUsedTable) 498 @TableForeignKey("id", KeyRef("schemata(id)"), KeyParam("ON DELETE CASCADE")) 499 struct SchemataUsedTable { 500 long id; 501 } 502 503 @TableName(schemataFragmentTable) 504 @TableForeignKey("schem_id", KeyRef("schemata(id)"), KeyParam("ON DELETE CASCADE")) 505 @TableForeignKey("file_id", KeyRef("files(id)"), KeyParam("ON DELETE CASCADE")) 506 struct SchemataFragmentTable { 507 long id; 508 509 @ColumnName("schem_id") 510 long schemataId; 511 512 @ColumnName("file_id") 513 long fileId; 514 515 @ColumnName("order_") 516 long order; 517 518 @ColumnParam("") 519 const(ubyte)[] text; 520 521 @ColumnName("offset_begin") 522 uint offsetBegin; 523 @ColumnName("offset_end") 524 uint offsetEnd; 525 } 526 527 /** The runtime of the test commands. 528 * 529 * By storing the data it reduces the need to run the test suite multiple times 530 * to get the minimum. 531 */ 532 @TableName(runtimeHistoryTable) 533 struct RuntimeHistoryTable { 534 long id; 535 536 /// when the measurement was taken. 537 @ColumnName("time") 538 SysTime timeStamp; 539 540 @ColumnName("time_ms") 541 long timeMs; 542 } 543 544 @TableName(mutationScoreHistoryTable) 545 struct MutationScoreHistoryTable { 546 long id; 547 548 /// when the measurement was taken. 549 @ColumnName("time") 550 SysTime timeStamp; 551 552 double score; 553 } 554 555 /** All functions that has been discovered in the source code. 556 * The `offset_begin` is where instrumentation points can be injected. 557 */ 558 @TableName(srcCovTable) 559 @TableForeignKey("file_id", KeyRef("files(id)"), KeyParam("ON DELETE CASCADE")) 560 @TableConstraint("file_offset UNIQUE (file_id, begin, end)") 561 struct CoverageCodeRegionTable { 562 long id; 563 564 @ColumnName("file_id") 565 long fileId; 566 567 /// the region in the files, in bytes. 568 uint begin; 569 uint end; 570 } 571 572 /** Each coverage region that has a valid status has an entry in this table. 573 * It do mean that there can be region that do not exist in this table. That 574 * mean that something went wrong when gathering the data. 575 */ 576 @TableName(srcCovInfoTable) 577 @TableForeignKey("id", KeyRef("src_cov_info(id)"), KeyParam("ON DELETE CASCADE")) 578 struct CoverageInfoTable { 579 long id; 580 581 /// True if the region has been visited. 582 bool status; 583 } 584 585 /// When the coverage information was gathered. 586 @TableName(srcCovTimeStampTable) 587 struct CoverageTimeTtampTable { 588 long id; 589 590 @ColumnName("timestamp") 591 SysTime timeStamp; 592 } 593 594 /** Files that roots are dependent on. They do not need to contain mutants. 595 */ 596 @TableName(depFileTable) 597 @TableConstraint("unique_ UNIQUE (file)") 598 struct DependencyFileTable { 599 long id; 600 601 string file; 602 603 /// checksum is 128bit. 604 long checksum0; 605 long checksum1; 606 } 607 608 @TableName(depRootTable) 609 @TableForeignKey("dep_id", KeyRef("dependency_file(id)"), KeyParam("ON DELETE CASCADE")) 610 @TableForeignKey("file_id", KeyRef("files(id)"), KeyParam("ON DELETE CASCADE")) 611 @TableConstraint("unique_ UNIQUE (dep_id, file_id)") 612 struct DependencyRootTable { 613 @ColumnName("dep_id") 614 long depFileId; 615 616 @ColumnName("file_id") 617 long fileId; 618 } 619 620 void updateSchemaVersion(ref Miniorm db, long ver) nothrow { 621 try { 622 db.run(delete_!VersionTbl); 623 db.run(insert!VersionTbl.insert, VersionTbl(ver)); 624 } catch (Exception e) { 625 logger.error(e.msg).collectException; 626 } 627 } 628 629 long getSchemaVersion(ref Miniorm db) nothrow { 630 try { 631 auto v = db.run(select!VersionTbl); 632 return v.empty ? 0 : v.front.version_; 633 } catch (Exception e) { 634 } 635 return 0; 636 } 637 638 void upgrade(ref Miniorm db) { 639 import d2sqlite3; 640 641 immutable maxIndex = 5; 642 643 alias upgradeFunc = void function(ref Miniorm db); 644 auto tbl = makeUpgradeTable; 645 646 bool hasUpdated; 647 648 bool running = true; 649 while (running) { 650 long version_ = 0; 651 652 try { 653 version_ = getSchemaVersion(db); 654 } catch (Exception e) { 655 logger.trace(e.msg).collectException; 656 // try again 657 continue; 658 } 659 660 if (version_ >= tbl.latestSchemaVersion) { 661 running = false; 662 continue; 663 } 664 665 logger.infof("Upgrading database from %s", version_).collectException; 666 667 if (!hasUpdated) 668 try { 669 // only do this once and always before any changes to the database. 670 foreach (i; 0 .. maxIndex) { 671 db.run(format!"DROP INDEX IF EXISTS i%s"(i)); 672 } 673 } catch (Exception e) { 674 logger.warning(e.msg).collectException; 675 logger.warning("Unable to drop database indexes").collectException; 676 } 677 678 if (auto f = version_ in tbl) { 679 try { 680 auto trans = db.transaction; 681 hasUpdated = true; 682 683 (*f)(db); 684 if (version_ != 0) 685 updateSchemaVersion(db, version_ + 1); 686 trans.commit; 687 } catch (Exception e) { 688 logger.trace(e).collectException; 689 logger.error(e.msg).collectException; 690 logger.warningf("Unable to upgrade a database of version %s", 691 version_).collectException; 692 logger.warning("This might impact the functionality. It is unwise to continue") 693 .collectException; 694 return; 695 } 696 } else { 697 logger.info("Upgrade successful").collectException; 698 running = false; 699 } 700 } 701 702 // add indexes assuming the lastest database schema 703 if (hasUpdated) 704 try { 705 auto trans = db.transaction; 706 int i; 707 db.run(format!"CREATE INDEX i%s ON %s(file_id)"(i++, mutationPointTable)); 708 db.run(format!"CREATE INDEX i%s ON %s(path)"(i++, filesTable)); 709 db.run(format!"CREATE INDEX i%s ON %s(path)"(i++, testFilesTable)); 710 711 // improve getTestCaseMutantKills by 10x 712 db.run(format!"CREATE INDEX i%s ON %s(tc_id,st_id)"(i++, killedTestCaseTable)); 713 db.run(format!"CREATE INDEX i%s ON %s(st_id)"(i++, mutationTable)); 714 715 assert(i <= maxIndex); 716 trans.commit; 717 } catch (Exception e) { 718 logger.warning(e.msg).collectException; 719 logger.warning("Unable to create database indexes").collectException; 720 } 721 } 722 723 /** If the database start it version 0, not initialized, then initialize to the 724 * latest schema version. 725 */ 726 void upgradeV0(ref Miniorm db) { 727 auto tbl = makeUpgradeTable; 728 729 db.run(buildSchema!(VersionTbl, RawSrcMetadata, FilesTbl, 730 MutationPointTbl, MutationTbl, TestCaseKilledTbl, AllTestCaseTbl, 731 MutationStatusTbl, MutantTimeoutCtxTbl, MutantTimeoutWorklistTbl, 732 MarkedMutantTbl, SrcMetadataTable, NomutTbl, NomutDataTbl, 733 NomutDataTbl, SchemataTable, SchemataFragmentTable, 734 SchemataMutantTable, 735 SchemataUsedTable, MutantWorklistTbl, RuntimeHistoryTable, 736 MutationScoreHistoryTable, TestFilesTable, CoverageCodeRegionTable, CoverageInfoTable, 737 CoverageTimeTtampTable, DependencyFileTable, DependencyRootTable, DextoolVersionTable)); 738 739 updateSchemaVersion(db, tbl.latestSchemaVersion); 740 } 741 742 /// 2018-04-08 743 void upgradeV1(ref Miniorm db) { 744 @TableName(allTestCaseTable) 745 struct AllTestCaseTbl { 746 long id; 747 748 @ColumnParam("") 749 string name; 750 } 751 752 @TableName(testCaseTableV1) 753 @TableForeignKey("mut_id", KeyRef("mutation(id)"), KeyParam("ON DELETE CASCADE")) 754 static struct TestCaseKilledTblV1 { 755 ulong id; 756 757 @ColumnName("mut_id") 758 ulong mutantId; 759 760 /// test_case is whatever identifier the user choose. 761 @ColumnName("test_case") 762 string testCase; 763 } 764 765 db.run(buildSchema!(TestCaseKilledTblV1, AllTestCaseTbl)); 766 } 767 768 /// 2018-04-22 769 void upgradeV2(ref Miniorm db) { 770 @TableName(filesTable) 771 static struct FilesTbl { 772 ulong id; 773 774 @ColumnParam("") 775 string path; 776 777 ulong checksum0; 778 ulong checksum1; 779 Language lang; 780 } 781 782 immutable newTbl = "new_" ~ filesTable; 783 784 db.run(buildSchema!FilesTbl("new_")); 785 db.run(format("INSERT INTO %s (id,path,checksum0,checksum1) SELECT * FROM %s", 786 newTbl, filesTable)); 787 db.replaceTbl(newTbl, filesTable); 788 } 789 790 /// 2018-09-01 791 void upgradeV3(ref Miniorm db) { 792 @TableName(killedTestCaseTable) 793 @TableForeignKey("mut_id", KeyRef("mutation(id)"), KeyParam("ON DELETE CASCADE")) 794 struct TestCaseKilledTblV2 { 795 ulong id; 796 797 @ColumnName("mut_id") 798 ulong mutantId; 799 800 @ColumnParam("") 801 string name; 802 803 // location is a filesystem location or other suitable helper for a user to 804 // locate the test. 805 @ColumnParam("") 806 string location; 807 } 808 809 db.run(buildSchema!TestCaseKilledTblV2); 810 db.run(format("INSERT INTO %s (id,mut_id,name) SELECT * FROM %s", 811 killedTestCaseTable, testCaseTableV1)); 812 db.run(format("DROP TABLE %s", testCaseTableV1)); 813 814 db.run(buildSchema!AllTestCaseTbl); 815 } 816 817 /// 2018-09-24 818 void upgradeV4(ref Miniorm db) { 819 @TableName(killedTestCaseTable) 820 @TableForeignKey("mut_id", KeyRef("mutation(id)"), KeyParam("ON DELETE CASCADE")) 821 @TableForeignKey("tc_id", KeyRef("all_test_case(id)"), KeyParam("ON DELETE CASCADE")) 822 static struct TestCaseKilledTblV3 { 823 ulong id; 824 825 @ColumnName("mut_id") 826 ulong mutantId; 827 @ColumnName("tc_id") 828 ulong testCaseId; 829 830 // location is a filesystem location or other suitable helper for a user to 831 // locate the test. 832 @ColumnParam("") 833 string location; 834 } 835 836 immutable newTbl = "new_" ~ killedTestCaseTable; 837 838 db.run(buildSchema!TestCaseKilledTblV3("new_")); 839 840 // add all missing test cases to all_test_case 841 db.run(format("INSERT INTO %s (name) SELECT DISTINCT t1.name FROM %s t1 LEFT JOIN %s t2 ON t2.name = t1.name WHERE t2.name IS NULL", 842 allTestCaseTable, killedTestCaseTable, allTestCaseTable)); 843 // https://stackoverflow.com/questions/2686254/how-to-select-all-records-from-one-table-that-do-not-exist-in-another-table 844 //Q: What is happening here? 845 // 846 //A: Conceptually, we select all rows from table1 and for each row we 847 //attempt to find a row in table2 with the same value for the name column. 848 //If there is no such row, we just leave the table2 portion of our result 849 //empty for that row. Then we constrain our selection by picking only those 850 //rows in the result where the matching row does not exist. Finally, We 851 //ignore all fields from our result except for the name column (the one we 852 //are sure that exists, from table1). 853 // 854 //While it may not be the most performant method possible in all cases, it 855 //should work in basically every database engine ever that attempts to 856 //implement ANSI 92 SQL 857 858 // This do NOT WORK. The result is that that this upgrade is broken because 859 // it drops all maps between killed_test_case and mutation. 860 //db.run(format("INSERT INTO %s (id,mut_id,tc_id,location) SELECT t1.id,t1.mut_id,t2.id,t1.location FROM %s t1 INNER JOIN %s t2 ON t1.name = t2.name", 861 // newTbl, killedTestCaseTable, allTestCaseTable)); 862 863 db.replaceTbl(newTbl, killedTestCaseTable); 864 } 865 866 /** 2018-09-30 867 * 868 * This upgrade will drop all existing mutations and thus all results. 869 * It is too complex trying to upgrade and keep the results. 870 * 871 * When removing this function also remove the status field in mutation_v2_tbl. 872 */ 873 void upgradeV5(ref Miniorm db) { 874 @TableName(filesTable) 875 @TableConstraint("unique_ UNIQUE (path)") 876 struct FilesTbl { 877 long id; 878 879 @ColumnParam("") 880 string path; 881 882 /// checksum is 128bit. 883 long checksum0; 884 long checksum1; 885 Language lang; 886 } 887 888 @TableName(mutationTable) 889 @TableForeignKey("mp_id", KeyRef("mutation_point(id)"), KeyParam("ON DELETE CASCADE")) 890 @TableForeignKey("st_id", KeyRef("mutation_status(id)")) 891 @TableConstraint("unique_ UNIQUE (mp_id, kind)") 892 static struct MutationTbl { 893 ulong id; 894 895 ulong mp_id; 896 897 @ColumnParam("") 898 ulong st_id; 899 900 ulong kind; 901 902 @ColumnParam("") 903 ulong status; 904 905 /// time in ms spent on verifying the mutant 906 @ColumnParam("") 907 ulong time; 908 } 909 910 @TableName(mutationStatusTable) 911 @TableConstraint("checksum UNIQUE (checksum0, checksum1)") 912 static struct MutationStatusTbl { 913 ulong id; 914 ulong status; 915 ulong checksum0; 916 ulong checksum1; 917 } 918 919 immutable new_mut_tbl = "new_" ~ mutationTable; 920 db.run(buildSchema!MutationStatusTbl); 921 922 db.run(format("DROP TABLE %s", mutationTable)); 923 db.run(buildSchema!MutationTbl); 924 925 immutable newFilesTbl = "new_" ~ filesTable; 926 db.run(buildSchema!FilesTbl("new_")); 927 db.run(format("INSERT OR IGNORE INTO %s (id,path,checksum0,checksum1,lang) SELECT * FROM %s", 928 newFilesTbl, filesTable)); 929 db.replaceTbl(newFilesTbl, filesTable); 930 } 931 932 /// 2018-10-11 933 void upgradeV6(ref Miniorm db) { 934 @TableName(mutationStatusTable) 935 @TableConstraint("checksum UNIQUE (checksum0, checksum1)") 936 static struct MutationStatusTbl { 937 ulong id; 938 ulong status; 939 ulong time; 940 SysTime timestamp; 941 ulong checksum0; 942 ulong checksum1; 943 } 944 945 immutable new_mut_tbl = "new_" ~ mutationTable; 946 947 db.run(buildSchema!MutationTbl("new_")); 948 949 db.run(format("INSERT INTO %s (id,mp_id,st_id,kind) SELECT id,mp_id,st_id,kind FROM %s", 950 new_mut_tbl, mutationTable)); 951 db.replaceTbl(new_mut_tbl, mutationTable); 952 953 immutable new_muts_tbl = "new_" ~ mutationStatusTable; 954 db.run(buildSchema!MutationStatusTbl("new_")); 955 db.run(format("INSERT INTO %s (id,status,checksum0,checksum1) SELECT id,status,checksum0,checksum1 FROM %s", 956 new_muts_tbl, mutationStatusTable)); 957 db.replaceTbl(new_muts_tbl, mutationStatusTable); 958 } 959 960 /// 2018-10-15 961 void upgradeV7(ref Miniorm db) { 962 @TableName(killedTestCaseTable) 963 @TableForeignKey("st_id", KeyRef("mutation_status(id)"), KeyParam("ON DELETE CASCADE")) 964 @TableForeignKey("tc_id", KeyRef("all_test_case(id)"), KeyParam("ON DELETE CASCADE")) 965 struct TestCaseKilledTbl { 966 long id; 967 968 @ColumnName("st_id") 969 long mutationStatusId; 970 @ColumnName("tc_id") 971 long testCaseId; 972 973 // location is a filesystem location or other suitable helper for a user to 974 // locate the test. 975 @ColumnParam("") 976 string location; 977 } 978 979 immutable newTbl = "new_" ~ killedTestCaseTable; 980 981 db.run(buildSchema!TestCaseKilledTbl("new_")); 982 983 db.run(format("INSERT INTO %s (id,st_id,tc_id,location) 984 SELECT t0.id,t1.st_id,t0.tc_id,t0.location 985 FROM %s t0, %s t1 986 WHERE 987 t0.mut_id = t1.id", newTbl, 988 killedTestCaseTable, mutationTable)); 989 990 db.replaceTbl(newTbl, killedTestCaseTable); 991 } 992 993 /// 2018-10-20 994 void upgradeV8(ref Miniorm db) { 995 immutable newTbl = "new_" ~ mutationPointTable; 996 db.run(buildSchema!MutationPointTbl("new_")); 997 db.run(format("INSERT INTO %s (id,file_id,offset_begin,offset_end,line,column) 998 SELECT t0.id,t0.file_id,t0.offset_begin,t0.offset_end,t0.line,t0.column 999 FROM %s t0", 1000 newTbl, mutationPointTable)); 1001 1002 db.replaceTbl(newTbl, mutationPointTable); 1003 } 1004 1005 /// 2018-11-10 1006 void upgradeV9(ref Miniorm db) { 1007 @TableName(mutationStatusTable) 1008 @TableConstraint("checksum UNIQUE (checksum0, checksum1)") 1009 struct MutationStatusTbl { 1010 long id; 1011 long status; 1012 1013 @ColumnParam("") 1014 long time; 1015 1016 @ColumnName("test_cnt") 1017 long testCnt; 1018 1019 @ColumnParam("") 1020 @ColumnName("update_ts") 1021 SysTime updated; 1022 1023 @ColumnParam("") 1024 @ColumnName("added_ts") 1025 SysTime added; 1026 1027 long checksum0; 1028 long checksum1; 1029 } 1030 1031 immutable newTbl = "new_" ~ mutationStatusTable; 1032 db.run(buildSchema!MutationStatusTbl("new_")); 1033 db.run(format("INSERT INTO %s (id,status,time,test_cnt,update_ts,checksum0,checksum1) 1034 SELECT t0.id,t0.status,t0.time,0,t0.timestamp,t0.checksum0,t0.checksum1 1035 FROM %s t0", 1036 newTbl, mutationStatusTable)); 1037 1038 replaceTbl(db, newTbl, mutationStatusTable); 1039 } 1040 1041 /// 2018-11-25 1042 void upgradeV10(ref Miniorm db) { 1043 @TableName(rawSrcMetadataTable) 1044 @TableForeignKey("file_id", KeyRef("files(id)"), KeyParam("ON DELETE CASCADE")) 1045 @TableConstraint("unique_line_in_file UNIQUE (file_id, line)") 1046 struct RawSrcMetadata { 1047 ulong id; 1048 1049 @ColumnName("file_id") 1050 ulong fileId; 1051 1052 @ColumnParam("") 1053 uint line; 1054 1055 @ColumnParam("") 1056 ulong nomut; 1057 } 1058 1059 db.run(buildSchema!RawSrcMetadata); 1060 void makeSrcMetadataView(ref Miniorm db) { 1061 // check if a NOMUT is on or between the start and end of a mutant. 1062 immutable src_metadata_v1_tbl = "CREATE VIEW %s 1063 AS 1064 SELECT 1065 t0.id AS mut_id, 1066 t1.id AS st_id, 1067 t2.id AS mp_id, 1068 t3.id AS file_id, 1069 (SELECT count(*) FROM %s in_t0, %s in_t1 1070 WHERE 1071 in_t0.file_id = in_t1.file_id AND 1072 t0.mp_id = in_t0.id AND 1073 (in_t1.line BETWEEN in_t0.line AND in_t0.line_end)) AS nomut 1074 FROM %s t0, %s t1, %s t2, %s t3 1075 WHERE 1076 t0.mp_id = t2.id AND 1077 t0.st_id = t1.id AND 1078 t2.file_id = t3.id 1079 "; 1080 1081 db.run(format(src_metadata_v1_tbl, srcMetadataTable, mutationPointTable, rawSrcMetadataTable, 1082 mutationTable, mutationStatusTable, mutationPointTable, filesTable)); 1083 } 1084 1085 makeSrcMetadataView(db); 1086 } 1087 1088 /// 2019-04-06 1089 void upgradeV11(ref Miniorm db) { 1090 immutable newTbl = "new_" ~ rawSrcMetadataTable; 1091 db.run(buildSchema!RawSrcMetadata("new_")); 1092 db.run(format!"INSERT INTO %s (id,file_id,line,nomut) SELECT t.id,t.file_id,t.line,t.nomut FROM %s t"(newTbl, 1093 rawSrcMetadataTable)); 1094 replaceTbl(db, newTbl, rawSrcMetadataTable); 1095 1096 db.run(format("DROP VIEW %s", srcMetadataTable)).collectException; 1097 1098 // Associate metadata from lines with the mutation status. 1099 void makeSrcMetadataView(ref Miniorm db) { 1100 // check if a NOMUT is on or between the start and end of a mutant. 1101 immutable src_metadata_tbl = "CREATE VIEW %s 1102 AS 1103 SELECT DISTINCT 1104 t0.id AS mut_id, 1105 t1.id AS st_id, 1106 t2.id AS mp_id, 1107 t3.id AS file_id, 1108 (SELECT count(*) FROM %s WHERE nomut.mp_id = t2.id) as nomut 1109 FROM %s t0, %s t1, %s t2, %s t3 1110 WHERE 1111 t0.mp_id = t2.id AND 1112 t0.st_id = t1.id AND 1113 t2.file_id = t3.id"; 1114 db.run(format(src_metadata_tbl, srcMetadataTable, nomutTable, 1115 mutationTable, mutationStatusTable, mutationPointTable, filesTable)); 1116 1117 immutable nomut_tbl = "CREATE VIEW %s 1118 AS 1119 SELECT 1120 t0.id mp_id, 1121 t1.line line, 1122 count(*) status 1123 FROM %s t0, %s t1 1124 WHERE 1125 t0.file_id = t1.file_id AND 1126 (t1.line BETWEEN t0.line AND t0.line_end) 1127 GROUP BY 1128 t0.id"; 1129 db.run(format(nomut_tbl, nomutTable, mutationPointTable, rawSrcMetadataTable)); 1130 1131 immutable nomut_data_tbl = "CREATE VIEW %s 1132 AS 1133 SELECT 1134 t0.id as mut_id, 1135 t0.mp_id as mp_id, 1136 t1.line as line, 1137 t1.tag as tag, 1138 t1.comment as comment 1139 FROM %s t0, %s t1, %s t2 1140 WHERE 1141 t0.mp_id = t2.mp_id AND 1142 t1.line = t2.line"; 1143 db.run(format(nomut_data_tbl, nomutDataTable, mutationTable, 1144 rawSrcMetadataTable, nomutTable)); 1145 } 1146 1147 makeSrcMetadataView(db); 1148 } 1149 1150 /// 2019-08-28 1151 void upgradeV12(ref Miniorm db) { 1152 db.run(buildSchema!(MutantTimeoutCtxTbl, MutantTimeoutWorklistTbl)); 1153 } 1154 1155 /// 2019-11-12 1156 void upgradeV13(ref Miniorm db) { 1157 @TableName(markedMutantTable) 1158 @TablePrimaryKey("st_id") 1159 struct MarkedMutantTbl { 1160 @ColumnName("st_id") 1161 long mutationStatusId; 1162 1163 @ColumnName("mut_id") 1164 long mutationId; 1165 1166 uint line; 1167 1168 uint column; 1169 1170 string path; 1171 1172 @ColumnName("to_status") 1173 ulong toStatus; 1174 1175 SysTime time; 1176 1177 string rationale; 1178 1179 @ColumnName("mut_text") 1180 string mutText; 1181 } 1182 1183 db.run(buildSchema!(MarkedMutantTbl)); 1184 } 1185 1186 /// 2020-01-12 1187 void upgradeV14(ref Miniorm db) { 1188 db.run(format!"DROP VIEW %s"(srcMetadataTable)); 1189 db.run(format!"DROP VIEW %s"(nomutTable)); 1190 db.run(format!"DROP VIEW %s"(nomutDataTable)); 1191 1192 db.run(buildSchema!(SrcMetadataTable, NomutTbl, NomutDataTbl)); 1193 logger.info("Re-execute analyze to update the NOMUT data"); 1194 } 1195 1196 /// 2020-01-21 1197 void upgradeV15(ref Miniorm db) { 1198 // fix bug in the marked mutant table 1199 db.run(format!"DROP TABLE %s"(markedMutantTable)); 1200 db.run(buildSchema!MarkedMutantTbl); 1201 logger.info("Dropping all marked mutants because of database changes"); 1202 } 1203 1204 /// 2020-02-12 1205 void upgradeV16(ref Miniorm db) { 1206 @TableName(schemataWorkListTable) 1207 @TableForeignKey("id", KeyRef("schemata(id)"), KeyParam("ON DELETE CASCADE")) 1208 static struct SchemataWorkListTable { 1209 long id; 1210 } 1211 1212 @TableName(schemataMutantTable) 1213 @TableForeignKey("st_id", KeyRef("mutation_status(id)"), KeyParam("ON DELETE CASCADE")) 1214 @TableForeignKey("schem_id", KeyRef("schemata_fragment(id)"), KeyParam("ON DELETE CASCADE")) 1215 static struct SchemataMutantTable { 1216 @ColumnName("st_id") 1217 long statusId; 1218 @ColumnName("schem_id") 1219 long schemaId; 1220 } 1221 1222 db.run(buildSchema!(SchemataFragmentTable, SchemataWorkListTable, SchemataMutantTable)); 1223 } 1224 1225 /// 2020-02-12 1226 void upgradeV17(ref Miniorm db) { 1227 @TableName(schemataTable) 1228 static struct SchemataTable { 1229 long id; 1230 } 1231 1232 db.run(buildSchema!(SchemataTable)); 1233 } 1234 1235 /// 2020-03-21 1236 void upgradeV18(ref Miniorm db) { 1237 // this force an old database to add indexes 1238 } 1239 1240 /// 2020-04-01 1241 void upgradeV19(ref Miniorm db) { 1242 db.run("DROP TABLE " ~ schemataWorkListTable); 1243 db.run("DROP TABLE " ~ schemataTable); 1244 db.run("DROP TABLE " ~ schemataMutantTable); 1245 1246 @TableName(invalidSchemataTable) 1247 @TableForeignKey("id", KeyRef("schemata(id)"), KeyParam("ON DELETE CASCADE")) 1248 struct InvalidSchemataTable { 1249 long id; 1250 } 1251 1252 @TableName(schemataTable) 1253 struct SchemataTable { 1254 long id; 1255 1256 // number of fragments the schemata consist of. 1257 // used to detect if a fragment has been removed because its related file 1258 // was changed. 1259 long fragments; 1260 1261 // runtime generated constant that make it possible to "prune" old 1262 // schematas automatically. it assumes that each new version of dextool may 1263 // contain updates to the schematas thus the old schemats should be 1264 // removed. 1265 @ColumnName("version") 1266 long version_; 1267 } 1268 1269 db.run(buildSchema!(SchemataTable, SchemataMutantTable, InvalidSchemataTable)); 1270 } 1271 1272 /// 2020-06-01 1273 void upgradeV20(ref Miniorm db) { 1274 db.run("DROP TABLE " ~ schemataMutantTable); 1275 db.run(buildSchema!(SchemataMutantTable)); 1276 } 1277 1278 /// 2020-11-28 1279 void upgradeV21(ref Miniorm db) { 1280 db.run("DROP TABLE " ~ invalidSchemataTable); 1281 db.run(buildSchema!(SchemataUsedTable)); 1282 } 1283 1284 /// 2020-11-28 1285 void upgradeV22(ref Miniorm db) { 1286 @TableName(mutantWorklistTable) 1287 @TableForeignKey("id", KeyRef("mutation_status(id)"), KeyParam("ON DELETE CASCADE")) 1288 struct MutantWorklistTbl { 1289 long id; 1290 } 1291 1292 db.run(buildSchema!(MutantWorklistTbl)); 1293 } 1294 1295 /// 2020-12-06 1296 void upgradeV23(ref Miniorm db) { 1297 db.run(buildSchema!(RuntimeHistoryTable)); 1298 } 1299 1300 /// 2020-12-06 1301 void upgradeV24(ref Miniorm db) { 1302 db.run(buildSchema!(MutationScoreHistoryTable)); 1303 } 1304 1305 /// 2020-12-25 1306 void upgradeV25(ref Miniorm db) { 1307 import std.traits : EnumMembers; 1308 import dextool.plugin.mutate.backend.type : Mutation; 1309 1310 @TableName(mutationStatusTable) 1311 @TableConstraint("checksum UNIQUE (checksum0, checksum1)") 1312 struct MutationStatusTbl { 1313 long id; 1314 long status; 1315 @ColumnName("exit_code") 1316 int exitCode; 1317 1318 @ColumnParam("") 1319 long time; 1320 1321 @ColumnName("test_cnt") 1322 long testCnt; 1323 1324 @ColumnParam("") 1325 @ColumnName("update_ts") 1326 SysTime updated; 1327 1328 @ColumnParam("") 1329 @ColumnName("added_ts") 1330 SysTime added; 1331 1332 long checksum0; 1333 long checksum1; 1334 } 1335 1336 immutable newTbl = "new_" ~ mutationStatusTable; 1337 db.run(buildSchema!MutationStatusTbl("new_")); 1338 1339 auto stmt = db.prepare(format( 1340 "INSERT INTO %s (id,status,exit_code,time,test_cnt,update_ts,added_ts,checksum0,checksum1) 1341 SELECT t.id,t.status,:ecode,t.time,t.test_cnt,t.update_ts,t.added_ts,t.checksum0,t.checksum1 1342 FROM %s t WHERE t.status = :status", 1343 newTbl, mutationStatusTable)); 1344 1345 foreach (st; [EnumMembers!(Mutation.Status)]) { 1346 stmt.get.bind(":ecode", (st == Mutation.Status.killed) ? 1 : 0); 1347 stmt.get.bind(":status", cast(long) st); 1348 stmt.get.execute; 1349 stmt.get.reset; 1350 } 1351 1352 replaceTbl(db, newTbl, mutationStatusTable); 1353 } 1354 1355 /// 2020-12-25 1356 void upgradeV26(ref Miniorm db) { 1357 immutable newTbl = "new_" ~ killedTestCaseTable; 1358 db.run(buildSchema!TestCaseKilledTbl("new_")); 1359 1360 db.run(format("INSERT OR IGNORE INTO %s (id, st_id, tc_id, location) 1361 SELECT t.id, t.st_id, t.tc_id, t.location 1362 FROM %s t", 1363 newTbl, killedTestCaseTable)); 1364 replaceTbl(db, newTbl, killedTestCaseTable); 1365 } 1366 1367 /// 2020-12-25 1368 void upgradeV27(ref Miniorm db) { 1369 immutable newTbl = "new_" ~ allTestCaseTable; 1370 db.run(buildSchema!AllTestCaseTbl("new_")); 1371 1372 db.run(format("INSERT OR IGNORE INTO %s (id, name) 1373 SELECT t.id, t.name 1374 FROM %s t", newTbl, allTestCaseTable)); 1375 replaceTbl(db, newTbl, allTestCaseTable); 1376 } 1377 1378 /// 2020-12-25 1379 void upgradeV28(ref Miniorm db) { 1380 import dextool.plugin.mutate.backend.type : Mutation; 1381 1382 @TableName(mutationStatusTable) 1383 @TableConstraint("checksum UNIQUE (checksum0, checksum1)") 1384 struct MutationStatusTbl { 1385 long id; 1386 long status; 1387 @ColumnName("exit_code") 1388 int exitCode; 1389 1390 @ColumnName("compile_time_ms") 1391 long compileTimeMs; 1392 1393 @ColumnName("test_time_ms") 1394 long testTimeMs; 1395 1396 @ColumnName("test_cnt") 1397 long testCnt; 1398 1399 @ColumnParam("") 1400 @ColumnName("update_ts") 1401 SysTime updated; 1402 1403 @ColumnParam("") 1404 @ColumnName("added_ts") 1405 SysTime added; 1406 1407 long checksum0; 1408 long checksum1; 1409 } 1410 1411 immutable newTbl = "new_" ~ mutationStatusTable; 1412 db.run(buildSchema!MutationStatusTbl("new_")); 1413 1414 db.run(format("INSERT INTO %s (id,status,exit_code,compile_time_ms,test_time_ms,test_cnt,update_ts,added_ts,checksum0,checksum1) 1415 SELECT t.id,t.status,t.exit_code,0,t.time,t.test_cnt,t.update_ts,t.added_ts,t.checksum0,t.checksum1 1416 FROM %s t WHERE t.time NOT NULL", newTbl, mutationStatusTable)); 1417 1418 db.run(format("INSERT INTO %s (id,status,exit_code,compile_time_ms,test_time_ms,test_cnt,update_ts,added_ts,checksum0,checksum1) 1419 SELECT t.id,t.status,t.exit_code,0,0,t.test_cnt,t.update_ts,t.added_ts,t.checksum0,t.checksum1 1420 FROM %s t WHERE t.time IS NULL", newTbl, mutationStatusTable)); 1421 1422 replaceTbl(db, newTbl, mutationStatusTable); 1423 } 1424 1425 /// 2020-12-27 1426 void upgradeV29(ref Miniorm db) { 1427 db.run(buildSchema!(TestFilesTable)); 1428 } 1429 1430 /// 2020-12-29 1431 void upgradeV30(ref Miniorm db) { 1432 import std.datetime : Clock; 1433 import miniorm : toSqliteDateTime; 1434 1435 @TableName(filesTable) 1436 @TableConstraint("unique_ UNIQUE (path)") 1437 struct FilesTbl { 1438 long id; 1439 1440 string path; 1441 1442 /// checksum is 128bit. 1443 long checksum0; 1444 long checksum1; 1445 Language lang; 1446 1447 @ColumnName("timestamp") 1448 SysTime timeStamp; 1449 } 1450 1451 immutable newFilesTbl = "new_" ~ filesTable; 1452 db.run(buildSchema!FilesTbl("new_")); 1453 auto stmt = db.prepare(format("INSERT OR IGNORE INTO %s (id,path,checksum0,checksum1,lang,timestamp) 1454 SELECT id,path,checksum0,checksum1,lang,:time FROM %s", 1455 newFilesTbl, filesTable)); 1456 stmt.get.bind(":time", Clock.currTime.toSqliteDateTime); 1457 stmt.get.execute; 1458 db.replaceTbl(newFilesTbl, filesTable); 1459 } 1460 1461 /// 2020-12-29 1462 void upgradeV31(ref Miniorm db) { 1463 db.run(buildSchema!(CoverageCodeRegionTable, CoverageInfoTable, CoverageTimeTtampTable)); 1464 } 1465 1466 /// 2021-01-02 1467 void upgradeV32(ref Miniorm db) { 1468 immutable newFilesTbl = "new_" ~ filesTable; 1469 db.run(buildSchema!FilesTbl("new_")); 1470 db.run(format("INSERT OR IGNORE INTO %s (id,path,checksum0,checksum1,lang,timestamp,root) 1471 SELECT id,path,checksum0,checksum1,lang,timestamp,0 FROM %s", 1472 newFilesTbl, filesTable)); 1473 db.replaceTbl(newFilesTbl, filesTable); 1474 } 1475 1476 /// 2021-01-15 1477 void upgradeV33(ref Miniorm db) { 1478 db.run(buildSchema!(DependencyFileTable, DependencyRootTable)); 1479 1480 // add all existing files as being dependent on each other. 1481 // this forces, if any one of them changes, all to be re-analyzed. 1482 db.run(format("INSERT OR IGNORE INTO %1$s (file,checksum0,checksum1) 1483 SELECT path,0,0 FROM %2$s", 1484 depFileTable, filesTable)); 1485 db.run(format("INSERT OR IGNORE INTO %1$s (dep_id,file_id) 1486 SELECT t0.id,t1.id FROM %2$s t0, %3$s t1", depRootTable, 1487 depFileTable, filesTable)); 1488 } 1489 1490 /// 2021-03-14 1491 void upgradeV34(ref Miniorm db) { 1492 immutable newTbl = "new_" ~ mutantWorklistTable; 1493 db.run(buildSchema!MutantWorklistTbl("new_")); 1494 db.run(format("INSERT INTO %1$s (id,prio) SELECT id,0 FROM %2$s", newTbl, mutantWorklistTable)); 1495 db.replaceTbl(newTbl, mutantWorklistTable); 1496 } 1497 1498 /// 2021-03-28 1499 void upgradeV35(ref Miniorm db) { 1500 @TableName(mutationStatusTable) 1501 @TableConstraint("checksum UNIQUE (checksum0, checksum1)") 1502 struct MutationStatusTbl { 1503 long id; 1504 long status; 1505 @ColumnName("exit_code") 1506 int exitCode; 1507 1508 @ColumnName("compile_time_ms") 1509 long compileTimeMs; 1510 1511 @ColumnName("test_time_ms") 1512 long testTimeMs; 1513 1514 @ColumnName("test_cnt") 1515 long testCnt; 1516 1517 @ColumnParam("") 1518 @ColumnName("update_ts") 1519 SysTime updated; 1520 1521 @ColumnParam("") 1522 @ColumnName("added_ts") 1523 SysTime added; 1524 1525 long checksum0; 1526 long checksum1; 1527 1528 /// Priority of the mutant used when testing. 1529 long prio; 1530 } 1531 1532 immutable newTbl = "new_" ~ mutationStatusTable; 1533 db.run(buildSchema!MutationStatusTbl("new_")); 1534 1535 db.run(format("INSERT INTO %s (id,status,exit_code,compile_time_ms,test_time_ms,test_cnt,update_ts,added_ts,checksum0,checksum1,prio) 1536 SELECT t.id,t.status,t.exit_code,t.compile_time_ms,t.test_time_ms,t.test_cnt,t.update_ts,t.added_ts,t.checksum0,t.checksum1,0 1537 FROM %s t", newTbl, mutationStatusTable)); 1538 replaceTbl(db, newTbl, mutationStatusTable); 1539 } 1540 1541 /// 2021-03-29 1542 void upgradeV36(ref Miniorm db) { 1543 import dextool.plugin.mutate.backend.type : Mutation; 1544 1545 immutable newTbl = "new_" ~ mutationStatusTable; 1546 db.run(buildSchema!MutationStatusTbl("new_")); 1547 1548 db.run(format( 1549 "INSERT INTO %s (id,status,exit_code,compile_time_ms,test_time_ms,update_ts,added_ts,checksum0,checksum1,prio) 1550 SELECT t.id,t.status,t.exit_code,t.compile_time_ms,t.test_time_ms,t.update_ts,t.added_ts,t.checksum0,t.checksum1,t.prio 1551 FROM %s t", 1552 newTbl, mutationStatusTable)); 1553 replaceTbl(db, newTbl, mutationStatusTable); 1554 } 1555 1556 // 2021-04-18 1557 void upgradeV37(ref Miniorm db) { 1558 db.run(format("DELETE FROM %s", schemataTable)); 1559 db.run(buildSchema!(DextoolVersionTable, SchemataTable)); 1560 } 1561 1562 // 2021-04-19 1563 void upgradeV38(ref Miniorm db) { 1564 db.run(format("DROP TABLE %s", schemataTable)); 1565 db.run(buildSchema!(SchemataTable)); 1566 } 1567 1568 // 2021-04-23 1569 void upgradeV39(ref Miniorm db) { 1570 // this is just to force a re-measure of the test suite. 1571 db.run(format("DELETE FROM %s", runtimeHistoryTable)); 1572 } 1573 1574 // 2021-05-06 1575 void upgradeV40(ref Miniorm db) { 1576 // force an upgrade because after this release all scheman will be zipped. 1577 db.run(format("DELETE FROM %s", schemataFragmentTable)); 1578 db.run(format("DELETE FROM %s", schemataMutantTable)); 1579 db.run(format("DELETE FROM %s", schemataTable)); 1580 db.run(format("DELETE FROM %s", schemataUsedTable)); 1581 } 1582 1583 void replaceTbl(ref Miniorm db, string src, string dst) { 1584 db.run("DROP TABLE " ~ dst); 1585 db.run(format("ALTER TABLE %s RENAME TO %s", src, dst)); 1586 } 1587 1588 struct UpgradeTable { 1589 alias UpgradeFunc = void function(ref Miniorm db); 1590 UpgradeFunc[long] tbl; 1591 alias tbl this; 1592 1593 immutable long latestSchemaVersion; 1594 } 1595 1596 /** Inspects a module for functions starting with upgradeV to create a table of 1597 * functions that can be used to upgrade a database. 1598 */ 1599 UpgradeTable makeUpgradeTable() { 1600 import std.algorithm : sort, startsWith; 1601 import std.conv : to; 1602 import std.typecons : Tuple; 1603 1604 immutable prefix = "upgradeV"; 1605 1606 alias Module = dextool.plugin.mutate.backend.database.schema; 1607 1608 // the second parameter is the database version to upgrade FROM. 1609 alias UpgradeFx = Tuple!(UpgradeTable.UpgradeFunc, long); 1610 1611 UpgradeFx[] upgradeFx; 1612 long last_from; 1613 1614 static foreach (member; __traits(allMembers, Module)) { 1615 static if (member.startsWith(prefix)) 1616 upgradeFx ~= UpgradeFx(&__traits(getMember, Module, member), 1617 member[prefix.length .. $].to!long); 1618 } 1619 1620 typeof(UpgradeTable.tbl) tbl; 1621 foreach (fn; upgradeFx.sort!((a, b) => a[1] < b[1])) { 1622 last_from = fn[1]; 1623 tbl[last_from] = fn[0]; 1624 } 1625 1626 return UpgradeTable(tbl, last_from + 1); 1627 }