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