summaryrefslogtreecommitdiffstats
path: root/sql/recovery.cc
diff options
context:
space:
mode:
authorkinuko@chromium.org <kinuko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-11-18 04:18:47 +0000
committerkinuko@chromium.org <kinuko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-11-18 04:18:47 +0000
commita8848a7685d5f673b30bc1f5336e6f9eba44fc41 (patch)
treedc1888152353c034340059cb229810ba3f4aab54 /sql/recovery.cc
parent2a4ee00d34865b2486e84eaf001c0e18358c901d (diff)
downloadchromium_src-a8848a7685d5f673b30bc1f5336e6f9eba44fc41.zip
chromium_src-a8848a7685d5f673b30bc1f5336e6f9eba44fc41.tar.gz
chromium_src-a8848a7685d5f673b30bc1f5336e6f9eba44fc41.tar.bz2
Revert 235595 "Revert 235492 "[sql] Recover Favicons v5 database..."
> Revert 235492 "[sql] Recover Favicons v5 databases, with more re..." > > Speculative revert to find the cause for Mac size regression > (will revert this revert later) > > > [sql] Recover Favicons v5 databases, with more recovery automation. > > > > An entirely automated recovery system runs afoul of questions about > > whether the corrupt database's schema can be trusted. > > sql::Recovery::AutoRecoverTable() uses a schema created by the caller > > to construct the recovery virtual table and then copies the data over. > > > > sql::Recovery::SetupMeta() and GetMetaVersionNumber() simplify > > accessing meta-table info in the corrupt database. > > > > sql::test::IntegrityCheck() and CorruptSizeInHeader() helpers to > > simplify common testing operations. > > > > Rewrite ThumbnailDatabase v6 and v7 recovery code and tests using > > these changes, and add a v5 recovery path. Additionally handle > > deprecated versions. > > > > BUG=240396,109482 > > > > Review URL: https://codereview.chromium.org/50493012 > > TBR=shess@chromium.org > > Review URL: https://codereview.chromium.org/74933002 TBR=kinuko@chromium.org Review URL: https://codereview.chromium.org/74953002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@235604 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'sql/recovery.cc')
-rw-r--r--sql/recovery.cc174
1 files changed, 174 insertions, 0 deletions
diff --git a/sql/recovery.cc b/sql/recovery.cc
index c750fd0..46f609b 100644
--- a/sql/recovery.cc
+++ b/sql/recovery.cc
@@ -5,9 +5,13 @@
#include "sql/recovery.h"
#include "base/files/file_path.h"
+#include "base/format_macros.h"
#include "base/logging.h"
#include "base/metrics/sparse_histogram.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
#include "sql/connection.h"
+#include "sql/statement.h"
#include "third_party/sqlite/sqlite3.h"
namespace sql {
@@ -233,4 +237,174 @@ void Recovery::Shutdown(Recovery::Disposition raze) {
db_ = NULL;
}
+bool Recovery::AutoRecoverTable(const char* table_name,
+ size_t extend_columns,
+ size_t* rows_recovered) {
+ // Query the info for the recovered table in database [main].
+ std::string query(
+ base::StringPrintf("PRAGMA main.table_info(%s)", table_name));
+ Statement s(db()->GetUniqueStatement(query.c_str()));
+
+ // The columns of the recover virtual table.
+ std::vector<std::string> create_column_decls;
+
+ // The columns to select from the recover virtual table when copying
+ // to the recovered table.
+ std::vector<std::string> insert_columns;
+
+ // If PRIMARY KEY is a single INTEGER column, then it is an alias
+ // for ROWID. The primary key can be compound, so this can only be
+ // determined after processing all column data and tracking what is
+ // seen. |pk_column_count| counts the columns in the primary key.
+ // |rowid_decl| stores the ROWID version of the last INTEGER column
+ // seen, which is at |rowid_ofs| in |create_column_decls|.
+ size_t pk_column_count = 0;
+ size_t rowid_ofs; // Only valid if rowid_decl is set.
+ std::string rowid_decl; // ROWID version of column |rowid_ofs|.
+
+ while (s.Step()) {
+ const std::string column_name(s.ColumnString(1));
+ const std::string column_type(s.ColumnString(2));
+ const bool not_null = s.ColumnBool(3);
+ const int default_type = s.ColumnType(4);
+ const bool default_is_null = (default_type == COLUMN_TYPE_NULL);
+ const int pk_column = s.ColumnInt(5);
+
+ if (pk_column > 0) {
+ // TODO(shess): http://www.sqlite.org/pragma.html#pragma_table_info
+ // documents column 5 as the index of the column in the primary key
+ // (zero for not in primary key). I find that it is always 1 for
+ // columns in the primary key. Since this code is very dependent on
+ // that pragma, review if the implementation changes.
+ DCHECK_EQ(pk_column, 1);
+ ++pk_column_count;
+ }
+
+ // Construct column declaration as "name type [optional constraint]".
+ std::string column_decl = column_name;
+
+ // SQLite's affinity detection is documented at:
+ // http://www.sqlite.org/datatype3.html#affname
+ // The gist of it is that CHAR, TEXT, and INT use substring matches.
+ if (column_type.find("INT") != std::string::npos) {
+ if (pk_column == 1) {
+ rowid_ofs = create_column_decls.size();
+ rowid_decl = column_name + " ROWID";
+ }
+ column_decl += " INTEGER";
+ } else if (column_type.find("CHAR") != std::string::npos ||
+ column_type.find("TEXT") != std::string::npos) {
+ column_decl += " TEXT";
+ } else if (column_type == "BLOB") {
+ column_decl += " BLOB";
+ } else {
+ // TODO(shess): AFAICT, there remain:
+ // - contains("CLOB") -> TEXT
+ // - contains("REAL") -> REAL
+ // - contains("FLOA") -> REAL
+ // - contains("DOUB") -> REAL
+ // - other -> "NUMERIC"
+ // Just code those in as they come up.
+ NOTREACHED() << " Unsupported type " << column_type;
+ return false;
+ }
+
+ // If column has constraint "NOT NULL", then inserting NULL into
+ // that column will fail. If the column has a non-NULL DEFAULT
+ // specified, the INSERT will handle it (see below). If the
+ // DEFAULT is also NULL, the row must be filtered out.
+ // TODO(shess): The above scenario applies to INSERT OR REPLACE,
+ // whereas INSERT OR IGNORE drops such rows.
+ // http://www.sqlite.org/lang_conflict.html
+ if (not_null && default_is_null)
+ column_decl += " NOT NULL";
+
+ create_column_decls.push_back(column_decl);
+
+ // Per the NOTE in the header file, convert NULL values to the
+ // DEFAULT. All columns could be IFNULL(column_name,default), but
+ // the NULL case would require special handling either way.
+ if (default_is_null) {
+ insert_columns.push_back(column_name);
+ } else {
+ // The default value appears to be pre-quoted, as if it is
+ // literally from the sqlite_master CREATE statement.
+ std::string default_value = s.ColumnString(4);
+ insert_columns.push_back(base::StringPrintf(
+ "IFNULL(%s,%s)", column_name.c_str(), default_value.c_str()));
+ }
+ }
+
+ // Receiving no column information implies that the table doesn't exist.
+ if (create_column_decls.empty())
+ return false;
+
+ // If the PRIMARY KEY was a single INTEGER column, convert it to ROWID.
+ if (pk_column_count == 1 && !rowid_decl.empty())
+ create_column_decls[rowid_ofs] = rowid_decl;
+
+ // Additional columns accept anything.
+ // TODO(shess): ignoreN isn't well namespaced. But it will fail to
+ // execute in case of conflicts.
+ for (size_t i = 0; i < extend_columns; ++i) {
+ create_column_decls.push_back(
+ base::StringPrintf("ignore%" PRIuS " ANY", i));
+ }
+
+ std::string recover_create(base::StringPrintf(
+ "CREATE VIRTUAL TABLE temp.recover_%s USING recover(corrupt.%s, %s)",
+ table_name,
+ table_name,
+ JoinString(create_column_decls, ',').c_str()));
+
+ std::string recover_insert(base::StringPrintf(
+ "INSERT OR REPLACE INTO main.%s SELECT %s FROM temp.recover_%s",
+ table_name,
+ JoinString(insert_columns, ',').c_str(),
+ table_name));
+
+ std::string recover_drop(base::StringPrintf(
+ "DROP TABLE temp.recover_%s", table_name));
+
+ if (!db()->Execute(recover_create.c_str()))
+ return false;
+
+ if (!db()->Execute(recover_insert.c_str())) {
+ ignore_result(db()->Execute(recover_drop.c_str()));
+ return false;
+ }
+
+ *rows_recovered = db()->GetLastChangeCount();
+
+ // TODO(shess): Is leaving the recover table around a breaker?
+ return db()->Execute(recover_drop.c_str());
+}
+
+bool Recovery::SetupMeta() {
+ const char kCreateSql[] =
+ "CREATE VIRTUAL TABLE temp.recover_meta USING recover"
+ "("
+ "corrupt.meta,"
+ "key TEXT NOT NULL,"
+ "value ANY" // Whatever is stored.
+ ")";
+ return db()->Execute(kCreateSql);
+}
+
+bool Recovery::GetMetaVersionNumber(int* version) {
+ DCHECK(version);
+ // TODO(shess): DCHECK(db()->DoesTableExist("temp.recover_meta"));
+ // Unfortunately, DoesTableExist() queries sqlite_master, not
+ // sqlite_temp_master.
+
+ const char kVersionSql[] =
+ "SELECT value FROM temp.recover_meta WHERE key = 'version'";
+ sql::Statement recovery_version(db()->GetUniqueStatement(kVersionSql));
+ if (!recovery_version.Step())
+ return false;
+
+ *version = recovery_version.ColumnInt(0);
+ return true;
+}
+
} // namespace sql