| Index: trunk/src/sql/recovery.cc
|
| ===================================================================
|
| --- trunk/src/sql/recovery.cc (revision 235603)
|
| +++ trunk/src/sql/recovery.cc (working copy)
|
| @@ -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 @@
|
| 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
|
|
|