| // 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/check_op.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"; |
| |
| } // namespace |
| |
| namespace sql { |
| |
| MetaTable::MetaTable() = default; |
| |
| 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::DeleteTableForTesting(sql::Database* db) { |
| DCHECK(db); |
| return db->Execute("DROP TABLE IF EXISTS 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::RazeIfIncompatible(Database* db, |
| int lowest_supported_version, |
| int current_version) { |
| if (!sql::MetaTable::DoesTableExist(db)) |
| return; |
| |
| // TODO(crbug.com/1228463): Share sql with PrepareGetStatement(). |
| sql::Statement s( |
| db->GetUniqueStatement("SELECT value FROM meta WHERE key=?")); |
| s.BindCString(0, kVersionKey); |
| if (!s.Step()) |
| return; |
| int on_disk_schema_version = s.ColumnInt(0); |
| |
| s.Assign(db->GetUniqueStatement("SELECT value FROM meta WHERE key=?")); |
| s.BindCString(0, kCompatibleVersionKey); |
| if (!s.Step()) |
| return; |
| int on_disk_compatible_version = s.ColumnInt(0); |
| |
| s.Clear(); // Clear potential automatic transaction for Raze(). |
| |
| if ((lowest_supported_version != kNoLowestSupportedVersion && |
| lowest_supported_version > on_disk_schema_version) || |
| (current_version < on_disk_compatible_version)) { |
| db->Raze(); |
| return; |
| } |
| } |
| |
| 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); |
| } |
| 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 |