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