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

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

Powered by Google App Engine
This is Rietveld 408576698