text: // 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
  