Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(426)

Side by Side Diff: sql/recovery.cc

Issue 1832173002: [sql] Database recovery system for Shortcuts. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: grammar Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « sql/recovery.h ('k') | sql/recovery_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 "sql/recovery.h" 5 #include "sql/recovery.h"
6 6
7 #include <stddef.h> 7 #include <stddef.h>
8 8
9 #include "base/files/file_path.h" 9 #include "base/files/file_path.h"
10 #include "base/format_macros.h" 10 #include "base/format_macros.h"
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
77 77
78 // GetMetaVersionNumber() successfully completed. 78 // GetMetaVersionNumber() successfully completed.
79 RECOVERY_SUCCESS_META_VERSION, 79 RECOVERY_SUCCESS_META_VERSION,
80 80
81 // Failed in querying recovery meta table. 81 // Failed in querying recovery meta table.
82 RECOVERY_FAILED_META_QUERY, 82 RECOVERY_FAILED_META_QUERY,
83 83
84 // No version key in recovery meta table. 84 // No version key in recovery meta table.
85 RECOVERY_FAILED_META_NO_VERSION, 85 RECOVERY_FAILED_META_NO_VERSION,
86 86
87 // Automatically recovered entire database successfully.
88 RECOVERY_SUCCESS_AUTORECOVERDB,
89
90 // Database was so broken recovery couldn't be entered.
91 RECOVERY_FAILED_AUTORECOVERDB_BEGIN,
92
93 // Failed to schema from corrupt database.
94 RECOVERY_FAILED_AUTORECOVERDB_SCHEMASELECT,
95
96 // Failed to create copy of schema in recovery database.
97 RECOVERY_FAILED_AUTORECOVERDB_SCHEMACREATE,
98
99 // Failed querying tables to recover. Should be impossible.
100 RECOVERY_FAILED_AUTORECOVERDB_NAMESELECT,
101
102 // Failed to recover an individual table.
103 RECOVERY_FAILED_AUTORECOVERDB_TABLE,
104
105 // Failed to recover [sqlite_sequence] table.
106 RECOVERY_FAILED_AUTORECOVERDB_SEQUENCE,
107
108 // Failed to recover triggers or views or virtual tables.
109 RECOVERY_FAILED_AUTORECOVERDB_AUX,
110
87 // Always keep this at the end. 111 // Always keep this at the end.
88 RECOVERY_EVENT_MAX, 112 RECOVERY_EVENT_MAX,
89 }; 113 };
90 114
91 void RecordRecoveryEvent(RecoveryEventType recovery_event) { 115 void RecordRecoveryEvent(RecoveryEventType recovery_event) {
92 UMA_HISTOGRAM_ENUMERATION("Sqlite.RecoveryEvents", 116 UMA_HISTOGRAM_ENUMERATION("Sqlite.RecoveryEvents",
93 recovery_event, RECOVERY_EVENT_MAX); 117 recovery_event, RECOVERY_EVENT_MAX);
94 } 118 }
95 119
96 } // namespace 120 } // namespace
(...skipping 404 matching lines...) Expand 10 before | Expand all | Expand 10 after
501 RecordRecoveryEvent(RECOVERY_FAILED_META_NO_VERSION); 525 RecordRecoveryEvent(RECOVERY_FAILED_META_NO_VERSION);
502 } 526 }
503 return false; 527 return false;
504 } 528 }
505 529
506 RecordRecoveryEvent(RECOVERY_SUCCESS_META_VERSION); 530 RecordRecoveryEvent(RECOVERY_SUCCESS_META_VERSION);
507 *version = recovery_version.ColumnInt(0); 531 *version = recovery_version.ColumnInt(0);
508 return true; 532 return true;
509 } 533 }
510 534
535 namespace {
536
537 // Collect statements from [corrupt.sqlite_master.sql] which start with |prefix|
538 // (which should be a valid SQL string ending with the space before a table
539 // name), then apply the statements to [main]. Skip any table named
540 // 'sqlite_sequence', as that table is created on demand by SQLite if any tables
541 // use AUTOINCREMENT.
542 //
543 // Returns |true| if all of the matching items were created in the main
544 // database. Returns |false| if an item fails on creation, or if the corrupt
545 // database schema cannot be queried.
546 bool SchemaCopyHelper(Connection* db, const char* prefix) {
547 const size_t prefix_len = strlen(prefix);
548 DCHECK_EQ(' ', prefix[prefix_len-1]);
549
550 sql::Statement s(db->GetUniqueStatement(
551 "SELECT DISTINCT sql FROM corrupt.sqlite_master "
552 "WHERE name<>'sqlite_sequence'"));
553 while (s.Step()) {
554 std::string sql = s.ColumnString(0);
555
556 // Skip statements that don't start with |prefix|.
557 if (sql.compare(0, prefix_len, prefix) != 0)
558 continue;
559
560 sql.insert(prefix_len, "main.");
561 if (!db->Execute(sql.c_str())) {
562 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_SCHEMACREATE);
563 return false;
564 }
565 }
566 if (!s.Succeeded()) {
567 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_SCHEMASELECT);
568 return false;
569 }
570 return true;
571 }
572
573 } // namespace
574
575 // This method is derived from SQLite's vacuum.c. VACUUM operates very
576 // similarily, creating a new database, populating the schema, then copying the
577 // data.
578 //
579 // TODO(shess): This conservatively uses Rollback() rather than Unrecoverable().
580 // With Rollback(), it is expected that the database will continue to generate
581 // errors. Change the failure cases to Unrecoverable() if/when histogram
582 // results indicate that everything is working reasonably.
583 //
584 // static
585 void Recovery::RecoverDatabase(Connection* db,
586 const base::FilePath& db_path) {
587 std::unique_ptr<sql::Recovery> recovery = sql::Recovery::Begin(db, db_path);
588 if (!recovery) {
589 // TODO(shess): If recovery can't even get started, Raze() or Delete().
590 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_BEGIN);
591 db->Poison();
592 return;
593 }
594
595 #if DCHECK_IS_ON()
596 // This code silently fails to recover fts3 virtual tables. At this time no
597 // browser database contain fts3 tables. Just to be safe, complain loudly if
598 // the database contains virtual tables.
599 //
600 // fts3 has an [x_segdir] table containing a column [end_block INTEGER]. But
601 // it actually stores either an integer or a text containing a pair of
602 // integers separated by a space. AutoRecoverTable() trusts the INTEGER tag
603 // when setting up the recover vtable, so those rows get dropped. Setting
604 // that column to ANY may work.
605 if (db->is_open()) {
606 sql::Statement s(db->GetUniqueStatement(
607 "SELECT 1 FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE %'"));
608 DCHECK(!s.Step()) << "Recovery of virtual tables not supported";
609 }
610 #endif
611
612 // TODO(shess): vacuum.c turns off checks and foreign keys.
613
614 // TODO(shess): vacuum.c turns synchronous=OFF for the target. I do not fully
615 // understand this, as the temporary db should not have a journal file at all.
616 // Perhaps it does in case of cache spill?
617
618 // Copy table schema from [corrupt] to [main].
619 if (!SchemaCopyHelper(recovery->db(), "CREATE TABLE ") ||
620 !SchemaCopyHelper(recovery->db(), "CREATE INDEX ") ||
621 !SchemaCopyHelper(recovery->db(), "CREATE UNIQUE INDEX ")) {
622 // No RecordRecoveryEvent() here because SchemaCopyHelper() already did.
623 Recovery::Rollback(std::move(recovery));
624 return;
625 }
626
627 // Run auto-recover against each table, skipping the sequence table. This is
628 // necessary because table recovery can create the sequence table as a side
629 // effect, so recovering that table inline could lead to duplicate data.
630 {
631 sql::Statement s(recovery->db()->GetUniqueStatement(
632 "SELECT name FROM sqlite_master WHERE sql LIKE 'CREATE TABLE %' "
633 "AND name!='sqlite_sequence'"));
634 while (s.Step()) {
635 const std::string name = s.ColumnString(0);
636 size_t rows_recovered;
637 if (!recovery->AutoRecoverTable(name.c_str(), &rows_recovered)) {
638 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_TABLE);
639 Recovery::Rollback(std::move(recovery));
640 return;
641 }
642 }
643 if (!s.Succeeded()) {
644 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_NAMESELECT);
645 Recovery::Rollback(std::move(recovery));
646 return;
647 }
648 }
649
650 // Overwrite any sequences created.
651 if (recovery->db()->DoesTableExist("corrupt.sqlite_sequence")) {
652 ignore_result(recovery->db()->Execute("DELETE FROM main.sqlite_sequence"));
653 size_t rows_recovered;
654 if (!recovery->AutoRecoverTable("sqlite_sequence", &rows_recovered)) {
655 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_SEQUENCE);
656 Recovery::Rollback(std::move(recovery));
657 return;
658 }
659 }
660
661 // Copy triggers and views directly to sqlite_master. Any tables they refer
662 // to should already exist.
663 char kCreateMetaItems[] =
664 "INSERT INTO main.sqlite_master "
665 "SELECT type, name, tbl_name, rootpage, sql "
666 "FROM corrupt.sqlite_master WHERE type='view' OR type='trigger'";
667 if (!recovery->db()->Execute(kCreateMetaItems)) {
668 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_AUX);
669 Recovery::Rollback(std::move(recovery));
670 return;
671 }
672
673 RecordRecoveryEvent(RECOVERY_SUCCESS_AUTORECOVERDB);
674 ignore_result(Recovery::Recovered(std::move(recovery)));
675 }
676
677 // static
678 bool Recovery::ShouldRecover(int extended_error) {
679 // Trim extended error codes.
680 int error = extended_error & 0xFF;
681 switch (error) {
682 case SQLITE_NOTADB:
683 // SQLITE_NOTADB happens if the SQLite header is broken. Some earlier
684 // versions of SQLite return this where other versions return
685 // SQLITE_CORRUPT, which is a recoverable case. Later versions only
686 // return this error only in unrecoverable cases, in which case recovery
687 // will fail with no changes to the database, so there's no harm in
688 // attempting recovery in this case.
689 return true;
690
691 case SQLITE_CORRUPT:
692 // SQLITE_CORRUPT generally means that the database is readable as a
693 // SQLite database, but some inconsistency has been detected by SQLite.
694 // In many cases the inconsistency is relatively trivial, such as if an
695 // index refers to a row which was deleted, in which case most or even all
696 // of the data can be recovered. This can also be reported if parts of
697 // the file have been overwritten with garbage data, in which recovery
698 // should be able to recover partial data.
699 return true;
700
701 // TODO(shess): Possible future options for automated fixing:
702 // - SQLITE_CANTOPEN - delete the broken symlink or directory.
703 // - SQLITE_PERM - permissions could be fixed.
704 // - SQLITE_READONLY - permissions could be fixed.
705 // - SQLITE_IOERR - rewrite using new blocks.
706 // - SQLITE_FULL - recover in memory and rewrite subset of data.
707
708 default:
709 return false;
710 }
711 }
712
511 } // namespace sql 713 } // namespace sql
OLDNEW
« no previous file with comments | « sql/recovery.h ('k') | sql/recovery_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698