OLD | NEW |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "base/bind.h" | 5 #include "base/bind.h" |
6 #include "base/file_util.h" | 6 #include "base/file_util.h" |
7 #include "base/files/scoped_temp_dir.h" | 7 #include "base/files/scoped_temp_dir.h" |
8 #include "base/logging.h" | 8 #include "base/logging.h" |
| 9 #include "base/strings/string_number_conversions.h" |
9 #include "base/strings/stringprintf.h" | 10 #include "base/strings/stringprintf.h" |
10 #include "sql/connection.h" | 11 #include "sql/connection.h" |
11 #include "sql/meta_table.h" | 12 #include "sql/meta_table.h" |
12 #include "sql/recovery.h" | 13 #include "sql/recovery.h" |
13 #include "sql/statement.h" | 14 #include "sql/statement.h" |
14 #include "sql/test/scoped_error_ignorer.h" | 15 #include "sql/test/scoped_error_ignorer.h" |
15 #include "testing/gtest/include/gtest/gtest.h" | 16 #include "testing/gtest/include/gtest/gtest.h" |
16 #include "third_party/sqlite/sqlite3.h" | 17 #include "third_party/sqlite/sqlite3.h" |
17 | 18 |
18 namespace { | 19 namespace { |
19 | 20 |
20 // Execute |sql|, and stringify the results with |column_sep| between | 21 // Execute |sql|, and stringify the results with |column_sep| between |
21 // columns and |row_sep| between rows. | 22 // columns and |row_sep| between rows. |
22 // TODO(shess): Promote this to a central testing helper. | 23 // TODO(shess): Promote this to a central testing helper. |
23 std::string ExecuteWithResults(sql::Connection* db, | 24 std::string ExecuteWithResults(sql::Connection* db, |
24 const char* sql, | 25 const char* sql, |
25 const char* column_sep, | 26 const char* column_sep, |
26 const char* row_sep) { | 27 const char* row_sep) { |
27 sql::Statement s(db->GetUniqueStatement(sql)); | 28 sql::Statement s(db->GetUniqueStatement(sql)); |
28 std::string ret; | 29 std::string ret; |
29 while (s.Step()) { | 30 while (s.Step()) { |
30 if (!ret.empty()) | 31 if (!ret.empty()) |
31 ret += row_sep; | 32 ret += row_sep; |
32 for (int i = 0; i < s.ColumnCount(); ++i) { | 33 for (int i = 0; i < s.ColumnCount(); ++i) { |
33 if (i > 0) | 34 if (i > 0) |
34 ret += column_sep; | 35 ret += column_sep; |
35 ret += s.ColumnString(i); | 36 if (s.ColumnType(i) == sql::COLUMN_TYPE_NULL) { |
| 37 ret += "<null>"; |
| 38 } else if (s.ColumnType(i) == sql::COLUMN_TYPE_BLOB) { |
| 39 ret += "<x'"; |
| 40 ret += base::HexEncode(s.ColumnBlob(i), s.ColumnByteLength(i)); |
| 41 ret += "'>"; |
| 42 } else { |
| 43 ret += s.ColumnString(i); |
| 44 } |
36 } | 45 } |
37 } | 46 } |
38 return ret; | 47 return ret; |
39 } | 48 } |
40 | 49 |
41 // Dump consistent human-readable representation of the database | 50 // Dump consistent human-readable representation of the database |
42 // schema. For tables or indices, this will contain the sql command | 51 // schema. For tables or indices, this will contain the sql command |
43 // to create the table or index. For certain automatic SQLite | 52 // to create the table or index. For certain automatic SQLite |
44 // structures with no sql, the name is used. | 53 // structures with no sql, the name is used. |
45 std::string GetSchema(sql::Connection* db) { | 54 std::string GetSchema(sql::Connection* db) { |
(...skipping 367 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
413 ASSERT_TRUE(Reopen()); | 422 ASSERT_TRUE(Reopen()); |
414 | 423 |
415 // The recovered table has consistency between the index and the table. | 424 // The recovered table has consistency between the index and the table. |
416 EXPECT_EQ("10", ExecuteWithResults(&db(), kCountSql, "|", ",")); | 425 EXPECT_EQ("10", ExecuteWithResults(&db(), kCountSql, "|", ",")); |
417 EXPECT_EQ("10", ExecuteWithResults(&db(), kDistinctSql, "|", ",")); | 426 EXPECT_EQ("10", ExecuteWithResults(&db(), kDistinctSql, "|", ",")); |
418 | 427 |
419 // The expected value was retained. | 428 // The expected value was retained. |
420 const char kSelectSql[] = "SELECT v FROM x WHERE id = 0"; | 429 const char kSelectSql[] = "SELECT v FROM x WHERE id = 0"; |
421 EXPECT_EQ("100", ExecuteWithResults(&db(), kSelectSql, "|", ",")); | 430 EXPECT_EQ("100", ExecuteWithResults(&db(), kSelectSql, "|", ",")); |
422 } | 431 } |
| 432 |
| 433 TEST_F(SQLRecoveryTest, Meta) { |
| 434 const int kVersion = 3; |
| 435 const int kCompatibleVersion = 2; |
| 436 |
| 437 { |
| 438 sql::MetaTable meta; |
| 439 EXPECT_TRUE(meta.Init(&db(), kVersion, kCompatibleVersion)); |
| 440 EXPECT_EQ(kVersion, meta.GetVersionNumber()); |
| 441 } |
| 442 |
| 443 // Test expected case where everything works. |
| 444 { |
| 445 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 446 EXPECT_TRUE(recovery->SetupMeta()); |
| 447 int version = 0; |
| 448 EXPECT_TRUE(recovery->GetMetaVersionNumber(&version)); |
| 449 EXPECT_EQ(kVersion, version); |
| 450 |
| 451 sql::Recovery::Rollback(recovery.Pass()); |
| 452 } |
| 453 ASSERT_TRUE(Reopen()); // Handle was poisoned. |
| 454 |
| 455 // Test version row missing. |
| 456 EXPECT_TRUE(db().Execute("DELETE FROM meta WHERE key = 'version'")); |
| 457 { |
| 458 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 459 EXPECT_TRUE(recovery->SetupMeta()); |
| 460 int version = 0; |
| 461 EXPECT_FALSE(recovery->GetMetaVersionNumber(&version)); |
| 462 EXPECT_EQ(0, version); |
| 463 |
| 464 sql::Recovery::Rollback(recovery.Pass()); |
| 465 } |
| 466 ASSERT_TRUE(Reopen()); // Handle was poisoned. |
| 467 |
| 468 // Test meta table missing. |
| 469 EXPECT_TRUE(db().Execute("DROP TABLE meta")); |
| 470 { |
| 471 sql::ScopedErrorIgnorer ignore_errors; |
| 472 ignore_errors.IgnoreError(SQLITE_CORRUPT); // From virtual table. |
| 473 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 474 EXPECT_FALSE(recovery->SetupMeta()); |
| 475 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); |
| 476 } |
| 477 } |
| 478 |
| 479 // Baseline AutoRecoverTable() test. |
| 480 TEST_F(SQLRecoveryTest, AutoRecoverTable) { |
| 481 // BIGINT and VARCHAR to test type affinity. |
| 482 const char kCreateSql[] = "CREATE TABLE x (id BIGINT, t TEXT, v VARCHAR)"; |
| 483 ASSERT_TRUE(db().Execute(kCreateSql)); |
| 484 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (11, 'This is', 'a test')")); |
| 485 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (5, 'That was', 'a test')")); |
| 486 |
| 487 // Save aside a copy of the original schema and data. |
| 488 const std::string orig_schema(GetSchema(&db())); |
| 489 const char kXSql[] = "SELECT * FROM x ORDER BY 1"; |
| 490 const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 491 |
| 492 // Create a lame-duck table which will not be propagated by recovery to |
| 493 // detect that the recovery code actually ran. |
| 494 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)")); |
| 495 ASSERT_NE(orig_schema, GetSchema(&db())); |
| 496 |
| 497 { |
| 498 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 499 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); |
| 500 |
| 501 // Save a copy of the temp db's schema before recovering the table. |
| 502 const char kTempSchemaSql[] = "SELECT name, sql FROM sqlite_temp_master"; |
| 503 const std::string temp_schema( |
| 504 ExecuteWithResults(recovery->db(), kTempSchemaSql, "|", "\n")); |
| 505 |
| 506 EXPECT_TRUE(recovery->AutoRecoverTable("x", 0)); |
| 507 |
| 508 // Test that any additional temp tables were cleaned up. |
| 509 EXPECT_EQ(temp_schema, |
| 510 ExecuteWithResults(recovery->db(), kTempSchemaSql, "|", "\n")); |
| 511 |
| 512 ASSERT_TRUE(sql::Recovery::Recovered(recovery.Pass())); |
| 513 } |
| 514 |
| 515 // Since the database was not corrupt, the entire schema and all |
| 516 // data should be recovered. |
| 517 ASSERT_TRUE(Reopen()); |
| 518 ASSERT_EQ(orig_schema, GetSchema(&db())); |
| 519 ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 520 |
| 521 // Recovery fails if the target table doesn't exist. |
| 522 { |
| 523 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 524 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); |
| 525 |
| 526 // TODO(shess): Should this failure implicitly lead to Raze()? |
| 527 EXPECT_FALSE(recovery->AutoRecoverTable("y", 0)); |
| 528 |
| 529 sql::Recovery::Unrecoverable(recovery.Pass()); |
| 530 } |
| 531 } |
| 532 |
| 533 // Test that default values correctly replace nulls. The recovery |
| 534 // virtual table reads directly from the database, so DEFAULT is not |
| 535 // interpretted at that level. |
| 536 TEST_F(SQLRecoveryTest, AutoRecoverTableWithDefault) { |
| 537 ASSERT_TRUE(db().Execute("CREATE TABLE x (id INTEGER)")); |
| 538 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (5)")); |
| 539 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (15)")); |
| 540 |
| 541 // ALTER effectively leaves the new columns NULL in the first two |
| 542 // rows. The row with 17 will get the default injected at insert |
| 543 // time, while the row with 42 will get the actual value provided. |
| 544 // Embedded "'" to make sure default-handling continues to be quoted |
| 545 // correctly. |
| 546 ASSERT_TRUE(db().Execute("ALTER TABLE x ADD COLUMN t TEXT DEFAULT 'a''a'")); |
| 547 ASSERT_TRUE(db().Execute("ALTER TABLE x ADD COLUMN b BLOB DEFAULT x'AA55'")); |
| 548 ASSERT_TRUE(db().Execute("ALTER TABLE x ADD COLUMN i INT DEFAULT 93")); |
| 549 ASSERT_TRUE(db().Execute("INSERT INTO x (id) VALUES (17)")); |
| 550 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (42, 'b', x'1234', 12)")); |
| 551 |
| 552 // Save aside a copy of the original schema and data. |
| 553 const std::string orig_schema(GetSchema(&db())); |
| 554 const char kXSql[] = "SELECT * FROM x ORDER BY 1"; |
| 555 const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 556 |
| 557 // Create a lame-duck table which will not be propagated by recovery to |
| 558 // detect that the recovery code actually ran. |
| 559 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)")); |
| 560 ASSERT_NE(orig_schema, GetSchema(&db())); |
| 561 |
| 562 // Mechanically adjust the stored schema and data to allow detecting |
| 563 // where the default value is coming from. The target table is just |
| 564 // like the original with the default for [t] changed, to signal |
| 565 // defaults coming from the recovery system. The two %5 rows should |
| 566 // get the target-table default for [t], while the others should get |
| 567 // the source-table default. |
| 568 std::string final_schema(orig_schema); |
| 569 std::string final_data(orig_data); |
| 570 size_t pos; |
| 571 while ((pos = final_schema.find("'a''a'")) != std::string::npos) { |
| 572 final_schema.replace(pos, 6, "'c''c'"); |
| 573 } |
| 574 while ((pos = final_data.find("5|a'a")) != std::string::npos) { |
| 575 final_data.replace(pos, 5, "5|c'c"); |
| 576 } |
| 577 |
| 578 { |
| 579 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 580 // Different default to detect which table provides the default. |
| 581 ASSERT_TRUE(recovery->db()->Execute(final_schema.c_str())); |
| 582 EXPECT_TRUE(recovery->AutoRecoverTable("x", 0)); |
| 583 ASSERT_TRUE(sql::Recovery::Recovered(recovery.Pass())); |
| 584 } |
| 585 |
| 586 // Since the database was not corrupt, the entire schema and all |
| 587 // data should be recovered. |
| 588 ASSERT_TRUE(Reopen()); |
| 589 ASSERT_EQ(final_schema, GetSchema(&db())); |
| 590 ASSERT_EQ(final_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 591 } |
| 592 |
| 593 // Test that rows with NULL in a NOT NULL column are filtered |
| 594 // correctly. In the wild, this would probably happen due to |
| 595 // corruption, but here it is simulated by recovering a table which |
| 596 // allowed nulls into a table which does not. |
| 597 TEST_F(SQLRecoveryTest, AutoRecoverTableNullFilter) { |
| 598 const char kOrigSchema[] = "CREATE TABLE x (id INTEGER, t TEXT)"; |
| 599 const char kFinalSchema[] = "CREATE TABLE x (id INTEGER, t TEXT NOT NULL)"; |
| 600 |
| 601 ASSERT_TRUE(db().Execute(kOrigSchema)); |
| 602 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (5, null)")); |
| 603 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (15, 'this is a test')")); |
| 604 |
| 605 // Create a lame-duck table which will not be propagated by recovery to |
| 606 // detect that the recovery code actually ran. |
| 607 ASSERT_EQ(kOrigSchema, GetSchema(&db())); |
| 608 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)")); |
| 609 ASSERT_NE(kOrigSchema, GetSchema(&db())); |
| 610 |
| 611 { |
| 612 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 613 ASSERT_TRUE(recovery->db()->Execute(kFinalSchema)); |
| 614 EXPECT_TRUE(recovery->AutoRecoverTable("x", 0)); |
| 615 ASSERT_TRUE(sql::Recovery::Recovered(recovery.Pass())); |
| 616 } |
| 617 |
| 618 // The schema should be the same, but only one row of data should |
| 619 // have been recovered. |
| 620 ASSERT_TRUE(Reopen()); |
| 621 ASSERT_EQ(kFinalSchema, GetSchema(&db())); |
| 622 const char kXSql[] = "SELECT * FROM x ORDER BY 1"; |
| 623 ASSERT_EQ("15|this is a test", ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 624 } |
| 625 |
| 626 // Test AutoRecoverTable with a ROWID alias. |
| 627 TEST_F(SQLRecoveryTest, AutoRecoverTableWithRowid) { |
| 628 // The rowid alias is almost always the first column, intentionally |
| 629 // put it later. |
| 630 const char kCreateSql[] = |
| 631 "CREATE TABLE x (t TEXT, id INTEGER PRIMARY KEY NOT NULL)"; |
| 632 ASSERT_TRUE(db().Execute(kCreateSql)); |
| 633 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('This is a test', null)")); |
| 634 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('That was a test', null)")); |
| 635 |
| 636 // Save aside a copy of the original schema and data. |
| 637 const std::string orig_schema(GetSchema(&db())); |
| 638 const char kXSql[] = "SELECT * FROM x ORDER BY 1"; |
| 639 const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 640 |
| 641 // Create a lame-duck table which will not be propagated by recovery to |
| 642 // detect that the recovery code actually ran. |
| 643 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)")); |
| 644 ASSERT_NE(orig_schema, GetSchema(&db())); |
| 645 |
| 646 { |
| 647 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 648 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); |
| 649 EXPECT_TRUE(recovery->AutoRecoverTable("x", 0)); |
| 650 ASSERT_TRUE(sql::Recovery::Recovered(recovery.Pass())); |
| 651 } |
| 652 |
| 653 // Since the database was not corrupt, the entire schema and all |
| 654 // data should be recovered. |
| 655 ASSERT_TRUE(Reopen()); |
| 656 ASSERT_EQ(orig_schema, GetSchema(&db())); |
| 657 ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 658 } |
| 659 |
| 660 // Test that a compound primary key doesn't fire the ROWID code. |
| 661 TEST_F(SQLRecoveryTest, AutoRecoverTableWithCompoundKey) { |
| 662 const char kCreateSql[] = |
| 663 "CREATE TABLE x (" |
| 664 "id INTEGER NOT NULL," |
| 665 "id2 TEXT NOT NULL," |
| 666 "t TEXT," |
| 667 "PRIMARY KEY (id, id2)" |
| 668 ")"; |
| 669 ASSERT_TRUE(db().Execute(kCreateSql)); |
| 670 |
| 671 // NOTE(shess): Do not accidentally use [id] 1, 2, 3, as those will |
| 672 // be the ROWID values. |
| 673 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (1, 'a', 'This is a test')")); |
| 674 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (1, 'b', 'That was a test')")); |
| 675 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (2, 'a', 'Another test')")); |
| 676 |
| 677 // Save aside a copy of the original schema and data. |
| 678 const std::string orig_schema(GetSchema(&db())); |
| 679 const char kXSql[] = "SELECT * FROM x ORDER BY 1"; |
| 680 const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 681 |
| 682 // Create a lame-duck table which will not be propagated by recovery to |
| 683 // detect that the recovery code actually ran. |
| 684 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)")); |
| 685 ASSERT_NE(orig_schema, GetSchema(&db())); |
| 686 |
| 687 { |
| 688 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 689 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); |
| 690 EXPECT_TRUE(recovery->AutoRecoverTable("x", 0)); |
| 691 ASSERT_TRUE(sql::Recovery::Recovered(recovery.Pass())); |
| 692 } |
| 693 |
| 694 // Since the database was not corrupt, the entire schema and all |
| 695 // data should be recovered. |
| 696 ASSERT_TRUE(Reopen()); |
| 697 ASSERT_EQ(orig_schema, GetSchema(&db())); |
| 698 ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 699 } |
| 700 |
| 701 // Test |extend_columns| support. |
| 702 TEST_F(SQLRecoveryTest, AutoRecoverTableExtendColumns) { |
| 703 const char kCreateSql[] = "CREATE TABLE x (id INTEGER PRIMARY KEY, t0 TEXT)"; |
| 704 ASSERT_TRUE(db().Execute(kCreateSql)); |
| 705 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (1, 'This is')")); |
| 706 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (2, 'That was')")); |
| 707 |
| 708 // Save aside a copy of the original schema and data. |
| 709 const std::string orig_schema(GetSchema(&db())); |
| 710 const char kXSql[] = "SELECT * FROM x ORDER BY 1"; |
| 711 const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 712 |
| 713 // Modify the table to add a column, and add data to that column. |
| 714 ASSERT_TRUE(db().Execute("ALTER TABLE x ADD COLUMN t1 TEXT")); |
| 715 ASSERT_TRUE(db().Execute("UPDATE x SET t1 = 'a test'")); |
| 716 ASSERT_NE(orig_schema, GetSchema(&db())); |
| 717 ASSERT_NE(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 718 |
| 719 { |
| 720 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 721 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); |
| 722 EXPECT_TRUE(recovery->AutoRecoverTable("x", 1)); |
| 723 ASSERT_TRUE(sql::Recovery::Recovered(recovery.Pass())); |
| 724 } |
| 725 |
| 726 // Since the database was not corrupt, the entire schema and all |
| 727 // data should be recovered. |
| 728 ASSERT_TRUE(Reopen()); |
| 729 ASSERT_EQ(orig_schema, GetSchema(&db())); |
| 730 ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 731 } |
423 #endif // !defined(USE_SYSTEM_SQLITE) | 732 #endif // !defined(USE_SYSTEM_SQLITE) |
424 | 733 |
425 } // namespace | 734 } // namespace |
OLD | NEW |