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