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