blob: d2555411a2f74db90388a18815e2a8252df15928 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sql/meta_table.h"
#include <stdint.h>
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "sql/database.h"
#include "sql/statement.h"
#include "sql/transaction.h"
namespace {
// Keys understood directly by sql:MetaTable.
const char kVersionKey[] = "version";
const char kCompatibleVersionKey[] = "last_compatible_version";
const char kMmapStatusKey[] = "mmap_status";
// Used to track success/failure of deprecation checks.
enum DeprecationEventType {
// Database has info, but no meta table. This is probably bad.
DEPRECATION_DATABASE_NOT_EMPTY = 0,
// No meta, unable to query sqlite_master. This is probably bad.
DEPRECATION_DATABASE_UNKNOWN,
// Failure querying meta table, corruption or similar problem likely.
DEPRECATION_FAILED_VERSION,
// Version key not found in meta table. Some sort of update error likely.
DEPRECATION_NO_VERSION,
// Version was out-dated, database successfully razed. Should only
// happen once per long-idle user, low volume expected.
DEPRECATION_RAZED,
// Version was out-dated, database raze failed. This user's
// database will be stuck.
DEPRECATION_RAZE_FAILED,
// Always keep this at the end.
DEPRECATION_EVENT_MAX,
};
void RecordDeprecationEvent(DeprecationEventType deprecation_event) {
UMA_HISTOGRAM_ENUMERATION("Sqlite.DeprecationVersionResult",
deprecation_event, DEPRECATION_EVENT_MAX);
}
} // namespace
namespace sql {
MetaTable::MetaTable() : db_(nullptr) {}
MetaTable::~MetaTable() = default;
// static
constexpr int64_t MetaTable::kMmapFailure;
constexpr int64_t MetaTable::kMmapSuccess;
// static
bool MetaTable::DoesTableExist(sql::Database* db) {
DCHECK(db);
return db->DoesTableExist("meta");
}
// static
bool MetaTable::GetMmapStatus(Database* db, int64_t* status) {
const char* kMmapStatusSql = "SELECT value FROM meta WHERE key = ?";
Statement s(db->GetUniqueStatement(kMmapStatusSql));
if (!s.is_valid())
return false;
// It is fine for the status to be missing entirely, but any error prevents
// memory-mapping.
s.BindString(0, kMmapStatusKey);
*status = s.Step() ? s.ColumnInt64(0) : 0;
return s.Succeeded();
}
// static
bool MetaTable::SetMmapStatus(Database* db, int64_t status) {
DCHECK(status == kMmapFailure || status == kMmapSuccess || status >= 0);
const char* kMmapUpdateStatusSql = "REPLACE INTO meta VALUES (?, ?)";
Statement s(db->GetUniqueStatement(kMmapUpdateStatusSql));
s.BindString(0, kMmapStatusKey);
s.BindInt64(1, status);
return s.Run();
}
// static
void MetaTable::RazeIfDeprecated(Database* db, int deprecated_version) {
DCHECK_GT(deprecated_version, 0);
DCHECK_EQ(0, db->transaction_nesting());
if (!DoesTableExist(db)) {
sql::Statement s(db->GetUniqueStatement(
"SELECT COUNT(*) FROM sqlite_master"));
if (s.Step()) {
if (s.ColumnInt(0) != 0) {
RecordDeprecationEvent(DEPRECATION_DATABASE_NOT_EMPTY);
}
// NOTE(shess): Empty database at first run is expected, so
// don't histogram that case.
} else {
RecordDeprecationEvent(DEPRECATION_DATABASE_UNKNOWN);
}
return;
}
// TODO(shess): Share sql with PrepareGetStatement().
sql::Statement s(db->GetUniqueStatement(
"SELECT value FROM meta WHERE key=?"));
s.BindCString(0, kVersionKey);
if (!s.Step()) {
if (!s.Succeeded()) {
RecordDeprecationEvent(DEPRECATION_FAILED_VERSION);
} else {
RecordDeprecationEvent(DEPRECATION_NO_VERSION);
}
return;
}
int version = s.ColumnInt(0);
s.Clear(); // Clear potential automatic transaction for Raze().
if (version <= deprecated_version) {
if (db->Raze()) {
RecordDeprecationEvent(DEPRECATION_RAZED);
} else {
RecordDeprecationEvent(DEPRECATION_RAZE_FAILED);
}
return;
}
// NOTE(shess): Successfully getting a version which is not
// deprecated is expected, so don't histogram that case.
}
bool MetaTable::Init(Database* db, int version, int compatible_version) {
DCHECK(!db_ && db);
db_ = db;
// If values stored are nullptr or missing entirely, 0 will be reported.
// Require new clients to start with a greater initial version.
DCHECK_GT(version, 0);
DCHECK_GT(compatible_version, 0);
// Make sure the table is created an populated atomically.
sql::Transaction transaction(db_);
if (!transaction.Begin())
return false;
if (!DoesTableExist(db)) {
if (!db_->Execute("CREATE TABLE meta"
"(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY, value LONGVARCHAR)"))
return false;
// Newly-created databases start out with mmap'ed I/O, but have no place to
// store the setting. Set here so that later opens don't need to validate.
SetMmapStatus(db_, kMmapSuccess);
// Note: there is no index over the meta table. We currently only have a
// couple of keys, so it doesn't matter. If we start storing more stuff in
// there, we should create an index.
SetVersionNumber(version);
SetCompatibleVersionNumber(compatible_version);
} else {
db_->AddTaggedHistogram("Sqlite.Version", GetVersionNumber());
}
return transaction.Commit();
}
void MetaTable::Reset() {
db_ = nullptr;
}
void MetaTable::SetVersionNumber(int version) {
DCHECK_GT(version, 0);
SetValue(kVersionKey, version);
}
int MetaTable::GetVersionNumber() {
int version = 0;
return GetValue(kVersionKey, &version) ? version : 0;
}
void MetaTable::SetCompatibleVersionNumber(int version) {
DCHECK_GT(version, 0);
SetValue(kCompatibleVersionKey, version);
}
int MetaTable::GetCompatibleVersionNumber() {
int version = 0;
return GetValue(kCompatibleVersionKey, &version) ? version : 0;
}
bool MetaTable::SetValue(const char* key, const std::string& value) {
Statement s;
PrepareSetStatement(&s, key);
s.BindString(1, value);
return s.Run();
}
bool MetaTable::SetValue(const char* key, int value) {
Statement s;
PrepareSetStatement(&s, key);
s.BindInt(1, value);
return s.Run();
}
bool MetaTable::SetValue(const char* key, int64_t value) {
Statement s;
PrepareSetStatement(&s, key);
s.BindInt64(1, value);
return s.Run();
}
bool MetaTable::GetValue(const char* key, std::string* value) {
Statement s;
if (!PrepareGetStatement(&s, key))
return false;
*value = s.ColumnString(0);
return true;
}
bool MetaTable::GetValue(const char* key, int* value) {
Statement s;
if (!PrepareGetStatement(&s, key))
return false;
*value = s.ColumnInt(0);
return true;
}
bool MetaTable::GetValue(const char* key, int64_t* value) {
Statement s;
if (!PrepareGetStatement(&s, key))
return false;
*value = s.ColumnInt64(0);
return true;
}
bool MetaTable::DeleteKey(const char* key) {
DCHECK(db_);
Statement s(db_->GetUniqueStatement("DELETE FROM meta WHERE key=?"));
s.BindCString(0, key);
return s.Run();
}
void MetaTable::PrepareSetStatement(Statement* statement, const char* key) {
DCHECK(db_ && statement);
statement->Assign(db_->GetCachedStatement(SQL_FROM_HERE,
"INSERT OR REPLACE INTO meta (key,value) VALUES (?,?)"));
statement->BindCString(0, key);
}
bool MetaTable::PrepareGetStatement(Statement* statement, const char* key) {
DCHECK(db_ && statement);
statement->Assign(db_->GetCachedStatement(SQL_FROM_HERE,
"SELECT value FROM meta WHERE key=?"));
statement->BindCString(0, key);
return statement->Step();
}
} // namespace sql