| Index: sync/syncable/directory_backing_store.cc
|
| diff --git a/sync/syncable/directory_backing_store.cc b/sync/syncable/directory_backing_store.cc
|
| deleted file mode 100644
|
| index b3209f2ee7f18ae00947beb460eb39b2e42f04b4..0000000000000000000000000000000000000000
|
| --- a/sync/syncable/directory_backing_store.cc
|
| +++ /dev/null
|
| @@ -1,1770 +0,0 @@
|
| -// Copyright 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 "sync/syncable/directory_backing_store.h"
|
| -
|
| -#include <stddef.h>
|
| -#include <stdint.h>
|
| -
|
| -#include <limits>
|
| -#include <unordered_set>
|
| -
|
| -#include "base/base64.h"
|
| -#include "base/location.h"
|
| -#include "base/logging.h"
|
| -#include "base/macros.h"
|
| -#include "base/metrics/histogram_macros.h"
|
| -#include "base/rand_util.h"
|
| -#include "base/single_thread_task_runner.h"
|
| -#include "base/strings/stringprintf.h"
|
| -#include "base/threading/thread_task_runner_handle.h"
|
| -#include "base/time/time.h"
|
| -#include "base/trace_event/trace_event.h"
|
| -#include "build/build_config.h"
|
| -#include "sql/connection.h"
|
| -#include "sql/error_delegate_util.h"
|
| -#include "sql/statement.h"
|
| -#include "sql/transaction.h"
|
| -#include "sync/internal_api/public/base/node_ordinal.h"
|
| -#include "sync/protocol/bookmark_specifics.pb.h"
|
| -#include "sync/protocol/sync.pb.h"
|
| -#include "sync/syncable/syncable-inl.h"
|
| -#include "sync/syncable/syncable_columns.h"
|
| -#include "sync/syncable/syncable_util.h"
|
| -#include "sync/util/time.h"
|
| -
|
| -using std::string;
|
| -
|
| -namespace syncer {
|
| -namespace syncable {
|
| -
|
| -// Increment this version whenever updating DB tables.
|
| -const int32_t kCurrentDBVersion = 90;
|
| -
|
| -// The current database page size in Kilobytes.
|
| -const int32_t kCurrentPageSizeKB = 32768;
|
| -
|
| -// Iterate over the fields of |entry| and bind each to |statement| for
|
| -// updating. Returns the number of args bound.
|
| -void BindFields(const EntryKernel& entry,
|
| - sql::Statement* statement) {
|
| - int index = 0;
|
| - int i = 0;
|
| - for (i = BEGIN_FIELDS; i < INT64_FIELDS_END; ++i) {
|
| - statement->BindInt64(index++, entry.ref(static_cast<Int64Field>(i)));
|
| - }
|
| - for ( ; i < TIME_FIELDS_END; ++i) {
|
| - statement->BindInt64(index++,
|
| - TimeToProtoTime(
|
| - entry.ref(static_cast<TimeField>(i))));
|
| - }
|
| - for ( ; i < ID_FIELDS_END; ++i) {
|
| - statement->BindString(index++, entry.ref(static_cast<IdField>(i)).s_);
|
| - }
|
| - for ( ; i < BIT_FIELDS_END; ++i) {
|
| - statement->BindInt(index++, entry.ref(static_cast<BitField>(i)));
|
| - }
|
| - for ( ; i < STRING_FIELDS_END; ++i) {
|
| - statement->BindString(index++, entry.ref(static_cast<StringField>(i)));
|
| - }
|
| - for ( ; i < PROTO_FIELDS_END; ++i) {
|
| - std::string temp;
|
| - entry.ref(static_cast<ProtoField>(i)).SerializeToString(&temp);
|
| - statement->BindBlob(index++, temp.data(), temp.length());
|
| - }
|
| - for ( ; i < UNIQUE_POSITION_FIELDS_END; ++i) {
|
| - std::string temp;
|
| - entry.ref(static_cast<UniquePositionField>(i)).SerializeToString(&temp);
|
| - statement->BindBlob(index++, temp.data(), temp.length());
|
| - }
|
| - for (; i < ATTACHMENT_METADATA_FIELDS_END; ++i) {
|
| - std::string temp;
|
| - entry.ref(static_cast<AttachmentMetadataField>(i)).SerializeToString(&temp);
|
| - statement->BindBlob(index++, temp.data(), temp.length());
|
| - }
|
| -}
|
| -
|
| -// Helper function that loads a number of shareable fields of the
|
| -// same type. The sharing criteria is based on comparison of
|
| -// the serialized data. Only consecutive DB columns need to compared
|
| -// to cover all possible sharing combinations.
|
| -template <typename TValue, typename TField>
|
| -void UnpackProtoFields(sql::Statement* statement,
|
| - EntryKernel* kernel,
|
| - int* index,
|
| - int end_index,
|
| - int* total_entry_copies) {
|
| - const void* prev_blob = nullptr;
|
| - int prev_length = -1;
|
| - int prev_index = -1;
|
| -
|
| - for (; *index < end_index; ++(*index)) {
|
| - int length = statement->ColumnByteLength(*index);
|
| - if (length == 0) {
|
| - // Skip this column and keep the default value in the kernel field.
|
| - continue;
|
| - }
|
| -
|
| - const void* blob = statement->ColumnBlob(*index);
|
| - // According to sqlite3 documentation, the prev_blob pointer should remain
|
| - // valid until moving to the next row.
|
| - if (length == prev_length && memcmp(blob, prev_blob, length) == 0) {
|
| - // Serialized values are the same - share the value from |prev_index|
|
| - // field with the current field.
|
| - kernel->copy(static_cast<TField>(prev_index),
|
| - static_cast<TField>(*index));
|
| - } else {
|
| - // Regular case - deserialize and copy the value to the field.
|
| - kernel->load(static_cast<TField>(*index), blob, length);
|
| - prev_blob = blob;
|
| - prev_length = length;
|
| - prev_index = *index;
|
| - ++(*total_entry_copies);
|
| - }
|
| - }
|
| -}
|
| -
|
| -// The caller owns the returned EntryKernel*. Assumes the statement currently
|
| -// points to a valid row in the metas table. Returns NULL to indicate that
|
| -// it detected a corruption in the data on unpacking.
|
| -std::unique_ptr<EntryKernel> UnpackEntry(sql::Statement* statement,
|
| - int* total_specifics_copies) {
|
| - std::unique_ptr<EntryKernel> kernel(new EntryKernel());
|
| - DCHECK_EQ(statement->ColumnCount(), static_cast<int>(FIELD_COUNT));
|
| - int i = 0;
|
| - for (i = BEGIN_FIELDS; i < INT64_FIELDS_END; ++i) {
|
| - kernel->put(static_cast<Int64Field>(i), statement->ColumnInt64(i));
|
| - }
|
| - for ( ; i < TIME_FIELDS_END; ++i) {
|
| - kernel->put(static_cast<TimeField>(i),
|
| - ProtoTimeToTime(statement->ColumnInt64(i)));
|
| - }
|
| - for ( ; i < ID_FIELDS_END; ++i) {
|
| - kernel->mutable_ref(static_cast<IdField>(i)).s_ =
|
| - statement->ColumnString(i);
|
| - }
|
| - for ( ; i < BIT_FIELDS_END; ++i) {
|
| - kernel->put(static_cast<BitField>(i), (0 != statement->ColumnInt(i)));
|
| - }
|
| - for ( ; i < STRING_FIELDS_END; ++i) {
|
| - kernel->put(static_cast<StringField>(i),
|
| - statement->ColumnString(i));
|
| - }
|
| - UnpackProtoFields<sync_pb::EntitySpecifics, ProtoField>(
|
| - statement, kernel.get(), &i, PROTO_FIELDS_END, total_specifics_copies);
|
| - for ( ; i < UNIQUE_POSITION_FIELDS_END; ++i) {
|
| - std::string temp;
|
| - statement->ColumnBlobAsString(i, &temp);
|
| -
|
| - sync_pb::UniquePosition proto;
|
| - if (!proto.ParseFromString(temp)) {
|
| - DVLOG(1) << "Unpacked invalid position. Assuming the DB is corrupt";
|
| - return std::unique_ptr<EntryKernel>();
|
| - }
|
| -
|
| - kernel->mutable_ref(static_cast<UniquePositionField>(i)) =
|
| - UniquePosition::FromProto(proto);
|
| - }
|
| - int attachemnt_specifics_counts = 0;
|
| - UnpackProtoFields<sync_pb::AttachmentMetadata, AttachmentMetadataField>(
|
| - statement, kernel.get(), &i, ATTACHMENT_METADATA_FIELDS_END,
|
| - &attachemnt_specifics_counts);
|
| -
|
| - // Sanity check on positions. We risk strange and rare crashes if our
|
| - // assumptions about unique position values are broken.
|
| - if (kernel->ShouldMaintainPosition() &&
|
| - !kernel->ref(UNIQUE_POSITION).IsValid()) {
|
| - DVLOG(1) << "Unpacked invalid position on an entity that should have a "
|
| - << "valid position. Assuming the DB is corrupt.";
|
| - return std::unique_ptr<EntryKernel>();
|
| - }
|
| -
|
| - return kernel;
|
| -}
|
| -
|
| -namespace {
|
| -
|
| -// This just has to be big enough to hold an UPDATE or INSERT statement that
|
| -// modifies all the columns in the entry table.
|
| -static const string::size_type kUpdateStatementBufferSize = 2048;
|
| -
|
| -void OnSqliteError(const base::Closure& catastrophic_error_handler,
|
| - int err,
|
| - sql::Statement* statement) {
|
| - // An error has been detected. Ignore unless it is catastrophic.
|
| - if (sql::IsErrorCatastrophic(err)) {
|
| - // At this point sql::* and DirectoryBackingStore may be on the callstack so
|
| - // don't invoke the error handler directly. Instead, PostTask to this thread
|
| - // to avoid potential reentrancy issues.
|
| - base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
|
| - catastrophic_error_handler);
|
| - }
|
| -}
|
| -
|
| -string ComposeCreateTableColumnSpecs() {
|
| - const ColumnSpec* begin = g_metas_columns;
|
| - const ColumnSpec* end = g_metas_columns + arraysize(g_metas_columns);
|
| - string query;
|
| - query.reserve(kUpdateStatementBufferSize);
|
| - char separator = '(';
|
| - for (const ColumnSpec* column = begin; column != end; ++column) {
|
| - query.push_back(separator);
|
| - separator = ',';
|
| - query.append(column->name);
|
| - query.push_back(' ');
|
| - query.append(column->spec);
|
| - }
|
| - query.push_back(')');
|
| - return query;
|
| -}
|
| -
|
| -void AppendColumnList(std::string* output) {
|
| - const char* joiner = " ";
|
| - // Be explicit in SELECT order to match up with UnpackEntry.
|
| - for (int i = BEGIN_FIELDS; i < FIELD_COUNT; ++i) {
|
| - output->append(joiner);
|
| - output->append(ColumnName(i));
|
| - joiner = ", ";
|
| - }
|
| -}
|
| -
|
| -bool SaveEntryToDB(sql::Statement* save_statement, const EntryKernel& entry) {
|
| - save_statement->Reset(true);
|
| - BindFields(entry, save_statement);
|
| - return save_statement->Run();
|
| -}
|
| -
|
| -// total_specifics_copies : Total copies of entries in memory, include extra
|
| -// copy for some entries which create by copy-on-write mechanism.
|
| -// entries_counts : entry counts for each model type.
|
| -void UploadModelTypeEntryCount(const int total_specifics_copies,
|
| - const int(&entries_counts)[MODEL_TYPE_COUNT]) {
|
| - int total_entry_counts = 0;
|
| - for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) {
|
| - std::string model_type;
|
| - if (RealModelTypeToNotificationType((ModelType)i, &model_type)) {
|
| - std::string full_histogram_name = "Sync.ModelTypeCount." + model_type;
|
| - base::HistogramBase* histogram = base::Histogram::FactoryGet(
|
| - full_histogram_name, 1, 1000000, 50,
|
| - base::HistogramBase::kUmaTargetedHistogramFlag);
|
| - if (histogram)
|
| - histogram->Add(entries_counts[i]);
|
| - total_entry_counts += entries_counts[i];
|
| - }
|
| - }
|
| - UMA_HISTOGRAM_COUNTS("Sync.ModelTypeCount", total_entry_counts);
|
| - UMA_HISTOGRAM_COUNTS("Sync.ExtraSyncDataCount",
|
| - total_specifics_copies - total_entry_counts);
|
| -}
|
| -
|
| -} // namespace
|
| -
|
| -///////////////////////////////////////////////////////////////////////////////
|
| -// DirectoryBackingStore implementation.
|
| -
|
| -DirectoryBackingStore::DirectoryBackingStore(const string& dir_name)
|
| - : dir_name_(dir_name),
|
| - database_page_size_(kCurrentPageSizeKB),
|
| - needs_metas_column_refresh_(false),
|
| - needs_share_info_column_refresh_(false) {
|
| - DCHECK(base::ThreadTaskRunnerHandle::IsSet());
|
| - ResetAndCreateConnection();
|
| -}
|
| -
|
| -DirectoryBackingStore::DirectoryBackingStore(const string& dir_name,
|
| - sql::Connection* db)
|
| - : dir_name_(dir_name),
|
| - database_page_size_(kCurrentPageSizeKB),
|
| - db_(db),
|
| - needs_metas_column_refresh_(false),
|
| - needs_share_info_column_refresh_(false) {
|
| - DCHECK(base::ThreadTaskRunnerHandle::IsSet());
|
| -}
|
| -
|
| -DirectoryBackingStore::~DirectoryBackingStore() {
|
| -}
|
| -
|
| -bool DirectoryBackingStore::DeleteEntries(EntryTable from,
|
| - const MetahandleSet& handles) {
|
| - if (handles.empty())
|
| - return true;
|
| -
|
| - sql::Statement statement;
|
| - // Call GetCachedStatement() separately to get different statements for
|
| - // different tables.
|
| - switch (from) {
|
| - case METAS_TABLE:
|
| - statement.Assign(db_->GetCachedStatement(
|
| - SQL_FROM_HERE, "DELETE FROM metas WHERE metahandle = ?"));
|
| - break;
|
| - case DELETE_JOURNAL_TABLE:
|
| - statement.Assign(db_->GetCachedStatement(
|
| - SQL_FROM_HERE, "DELETE FROM deleted_metas WHERE metahandle = ?"));
|
| - break;
|
| - }
|
| -
|
| - for (MetahandleSet::const_iterator i = handles.begin(); i != handles.end();
|
| - ++i) {
|
| - statement.BindInt64(0, *i);
|
| - if (!statement.Run())
|
| - return false;
|
| - statement.Reset(true);
|
| - }
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::SaveChanges(
|
| - const Directory::SaveChangesSnapshot& snapshot) {
|
| - DCHECK(CalledOnValidThread());
|
| - DCHECK(db_->is_open());
|
| -
|
| - // Back out early if there is nothing to write.
|
| - bool save_info =
|
| - (Directory::KERNEL_SHARE_INFO_DIRTY == snapshot.kernel_info_status);
|
| - if (!snapshot.HasUnsavedMetahandleChanges() && !save_info) {
|
| - return true;
|
| - }
|
| -
|
| - sql::Transaction transaction(db_.get());
|
| - if (!transaction.Begin())
|
| - return false;
|
| -
|
| - PrepareSaveEntryStatement(METAS_TABLE, &save_meta_statement_);
|
| - for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin();
|
| - i != snapshot.dirty_metas.end(); ++i) {
|
| - DCHECK((*i)->is_dirty());
|
| - if (!SaveEntryToDB(&save_meta_statement_, **i))
|
| - return false;
|
| - }
|
| -
|
| - if (!DeleteEntries(METAS_TABLE, snapshot.metahandles_to_purge))
|
| - return false;
|
| -
|
| - PrepareSaveEntryStatement(DELETE_JOURNAL_TABLE,
|
| - &save_delete_journal_statement_);
|
| - for (EntryKernelSet::const_iterator i = snapshot.delete_journals.begin();
|
| - i != snapshot.delete_journals.end(); ++i) {
|
| - if (!SaveEntryToDB(&save_delete_journal_statement_, **i))
|
| - return false;
|
| - }
|
| -
|
| - if (!DeleteEntries(DELETE_JOURNAL_TABLE, snapshot.delete_journals_to_purge))
|
| - return false;
|
| -
|
| - if (save_info) {
|
| - const Directory::PersistedKernelInfo& info = snapshot.kernel_info;
|
| - sql::Statement s1(db_->GetCachedStatement(
|
| - SQL_FROM_HERE,
|
| - "UPDATE share_info "
|
| - "SET store_birthday = ?, "
|
| - "bag_of_chips = ?"));
|
| - s1.BindString(0, info.store_birthday);
|
| - s1.BindBlob(1, info.bag_of_chips.data(), info.bag_of_chips.size());
|
| -
|
| - if (!s1.Run())
|
| - return false;
|
| - DCHECK_EQ(db_->GetLastChangeCount(), 1);
|
| -
|
| - sql::Statement s2(db_->GetCachedStatement(
|
| - SQL_FROM_HERE,
|
| - "INSERT OR REPLACE "
|
| - "INTO models (model_id, "
|
| - "progress_marker, "
|
| - "transaction_version, "
|
| - "context) "
|
| - "VALUES (?, ?, ?, ?)"));
|
| -
|
| - ModelTypeSet protocol_types = ProtocolTypes();
|
| - for (ModelTypeSet::Iterator iter = protocol_types.First(); iter.Good();
|
| - iter.Inc()) {
|
| - ModelType type = iter.Get();
|
| - // We persist not ModelType but rather a protobuf-derived ID.
|
| - string model_id = ModelTypeEnumToModelId(type);
|
| - string progress_marker;
|
| - info.download_progress[type].SerializeToString(&progress_marker);
|
| - s2.BindBlob(0, model_id.data(), model_id.length());
|
| - s2.BindBlob(1, progress_marker.data(), progress_marker.length());
|
| - s2.BindInt64(2, info.transaction_version[type]);
|
| - string context;
|
| - info.datatype_context[type].SerializeToString(&context);
|
| - s2.BindBlob(3, context.data(), context.length());
|
| - if (!s2.Run())
|
| - return false;
|
| - DCHECK_EQ(db_->GetLastChangeCount(), 1);
|
| - s2.Reset(true);
|
| - }
|
| - }
|
| -
|
| - return transaction.Commit();
|
| -}
|
| -
|
| -sql::Connection* DirectoryBackingStore::db() {
|
| - return db_.get();
|
| -}
|
| -
|
| -bool DirectoryBackingStore::IsOpen() const {
|
| - return db_->is_open();
|
| -}
|
| -
|
| -bool DirectoryBackingStore::Open(const base::FilePath& path) {
|
| - DCHECK(!db_->is_open());
|
| - return db_->Open(path);
|
| -}
|
| -
|
| -bool DirectoryBackingStore::OpenInMemory() {
|
| - DCHECK(!db_->is_open());
|
| - return db_->OpenInMemory();
|
| -}
|
| -
|
| -bool DirectoryBackingStore::InitializeTables() {
|
| - if (!UpdatePageSizeIfNecessary())
|
| - return false;
|
| -
|
| - sql::Transaction transaction(db_.get());
|
| - if (!transaction.Begin())
|
| - return false;
|
| -
|
| - if (!db_->DoesTableExist("share_version")) {
|
| - // Delete the existing database (if any), and create a fresh one.
|
| - DropAllTables();
|
| - if (!CreateTables())
|
| - return false;
|
| - }
|
| -
|
| - int version_on_disk = GetVersion();
|
| -
|
| - // Upgrade from version 67. Version 67 was widely distributed as the original
|
| - // Bookmark Sync release. Version 68 removed unique naming.
|
| - if (version_on_disk == 67) {
|
| - if (MigrateVersion67To68())
|
| - version_on_disk = 68;
|
| - }
|
| - // Version 69 introduced additional datatypes.
|
| - if (version_on_disk == 68) {
|
| - if (MigrateVersion68To69())
|
| - version_on_disk = 69;
|
| - }
|
| -
|
| - if (version_on_disk == 69) {
|
| - if (MigrateVersion69To70())
|
| - version_on_disk = 70;
|
| - }
|
| -
|
| - // Version 71 changed the sync progress information to be per-datatype.
|
| - if (version_on_disk == 70) {
|
| - if (MigrateVersion70To71())
|
| - version_on_disk = 71;
|
| - }
|
| -
|
| - // Version 72 removed extended attributes, a legacy way to do extensible
|
| - // key/value information, stored in their own table.
|
| - if (version_on_disk == 71) {
|
| - if (MigrateVersion71To72())
|
| - version_on_disk = 72;
|
| - }
|
| -
|
| - // Version 73 added a field for notification state.
|
| - if (version_on_disk == 72) {
|
| - if (MigrateVersion72To73())
|
| - version_on_disk = 73;
|
| - }
|
| -
|
| - // Version 74 added state for the autofill migration.
|
| - if (version_on_disk == 73) {
|
| - if (MigrateVersion73To74())
|
| - version_on_disk = 74;
|
| - }
|
| -
|
| - // Version 75 migrated from int64_t-based timestamps to per-datatype tokens.
|
| - if (version_on_disk == 74) {
|
| - if (MigrateVersion74To75())
|
| - version_on_disk = 75;
|
| - }
|
| -
|
| - // Version 76 removed all (5) autofill migration related columns.
|
| - if (version_on_disk == 75) {
|
| - if (MigrateVersion75To76())
|
| - version_on_disk = 76;
|
| - }
|
| -
|
| - // Version 77 standardized all time fields to ms since the Unix
|
| - // epoch.
|
| - if (version_on_disk == 76) {
|
| - if (MigrateVersion76To77())
|
| - version_on_disk = 77;
|
| - }
|
| -
|
| - // Version 78 added the column base_server_specifics to the metas table.
|
| - if (version_on_disk == 77) {
|
| - if (MigrateVersion77To78())
|
| - version_on_disk = 78;
|
| - }
|
| -
|
| - // Version 79 migration is a one-time fix for some users in a bad state.
|
| - if (version_on_disk == 78) {
|
| - if (MigrateVersion78To79())
|
| - version_on_disk = 79;
|
| - }
|
| -
|
| - // Version 80 migration is adding the bag_of_chips column.
|
| - if (version_on_disk == 79) {
|
| - if (MigrateVersion79To80())
|
| - version_on_disk = 80;
|
| - }
|
| -
|
| - // Version 81 replaces the int64_t server_position_in_parent_field
|
| - // with a blob server_ordinal_in_parent field.
|
| - if (version_on_disk == 80) {
|
| - if (MigrateVersion80To81())
|
| - version_on_disk = 81;
|
| - }
|
| -
|
| - // Version 82 migration added transaction_version column per data type.
|
| - if (version_on_disk == 81) {
|
| - if (MigrateVersion81To82())
|
| - version_on_disk = 82;
|
| - }
|
| -
|
| - // Version 83 migration added transaction_version column per sync entry.
|
| - if (version_on_disk == 82) {
|
| - if (MigrateVersion82To83())
|
| - version_on_disk = 83;
|
| - }
|
| -
|
| - // Version 84 migration added deleted_metas table.
|
| - if (version_on_disk == 83) {
|
| - if (MigrateVersion83To84())
|
| - version_on_disk = 84;
|
| - }
|
| -
|
| - // Version 85 migration removes the initial_sync_ended bits.
|
| - if (version_on_disk == 84) {
|
| - if (MigrateVersion84To85())
|
| - version_on_disk = 85;
|
| - }
|
| -
|
| - // Version 86 migration converts bookmarks to the unique positioning system.
|
| - // It also introduces a new field to store a unique ID for each bookmark.
|
| - if (version_on_disk == 85) {
|
| - if (MigrateVersion85To86())
|
| - version_on_disk = 86;
|
| - }
|
| -
|
| - // Version 87 migration adds a collection of attachment ids per sync entry.
|
| - if (version_on_disk == 86) {
|
| - if (MigrateVersion86To87())
|
| - version_on_disk = 87;
|
| - }
|
| -
|
| - // Version 88 migration adds datatype contexts to the models table.
|
| - if (version_on_disk == 87) {
|
| - if (MigrateVersion87To88())
|
| - version_on_disk = 88;
|
| - }
|
| -
|
| - // Version 89 migration adds server attachment metadata to the metas table.
|
| - if (version_on_disk == 88) {
|
| - if (MigrateVersion88To89())
|
| - version_on_disk = 89;
|
| - }
|
| -
|
| - // Version 90 migration removes several columns from share_info table.
|
| - if (version_on_disk == 89) {
|
| - if (MigrateVersion89To90())
|
| - version_on_disk = 90;
|
| - }
|
| -
|
| - // If one of the migrations requested it, drop columns that aren't current.
|
| - // It's only safe to do this after migrating all the way to the current
|
| - // version.
|
| - if (version_on_disk == kCurrentDBVersion && needs_column_refresh()) {
|
| - if (!RefreshColumns())
|
| - return false;
|
| - }
|
| -
|
| - // In case of error, let the caller decide whether to re-sync from scratch
|
| - // with a new database.
|
| - if (version_on_disk != kCurrentDBVersion)
|
| - return false;
|
| -
|
| - return transaction.Commit();
|
| -}
|
| -
|
| -// This function drops unused columns by creating a new table that contains only
|
| -// the currently used columns then copying all rows from the old tables into
|
| -// this new one. The tables are then rearranged so the new replaces the old.
|
| -bool DirectoryBackingStore::RefreshColumns() {
|
| - DCHECK(needs_metas_column_refresh_ || needs_share_info_column_refresh_);
|
| -
|
| - if (needs_metas_column_refresh_) {
|
| - // Create a new table named temp_metas.
|
| - SafeDropTable("temp_metas");
|
| - if (!CreateMetasTable(true))
|
| - return false;
|
| -
|
| - // Populate temp_metas from metas.
|
| - //
|
| - // At this point, the metas table may contain columns belonging to obsolete
|
| - // schema versions. This statement explicitly lists only the columns that
|
| - // belong to the current schema version, so the obsolete columns will be
|
| - // effectively dropped once we rename temp_metas over top of metas.
|
| - std::string query = "INSERT INTO temp_metas (";
|
| - AppendColumnList(&query);
|
| - query.append(") SELECT ");
|
| - AppendColumnList(&query);
|
| - query.append(" FROM metas");
|
| - if (!db_->Execute(query.c_str()))
|
| - return false;
|
| -
|
| - // Drop metas.
|
| - SafeDropTable("metas");
|
| -
|
| - // Rename temp_metas -> metas.
|
| - if (!db_->Execute("ALTER TABLE temp_metas RENAME TO metas"))
|
| - return false;
|
| -
|
| - needs_metas_column_refresh_ = false;
|
| - }
|
| -
|
| - if (needs_share_info_column_refresh_) {
|
| - // Repeat the process for share_info.
|
| - SafeDropTable("temp_share_info");
|
| - if (!CreateShareInfoTable(true))
|
| - return false;
|
| -
|
| - if (!db_->Execute(
|
| - "INSERT INTO temp_share_info (id, name, store_birthday, "
|
| - "cache_guid, bag_of_chips) "
|
| - "SELECT id, name, store_birthday, cache_guid, bag_of_chips "
|
| - "FROM share_info"))
|
| - return false;
|
| -
|
| - SafeDropTable("share_info");
|
| - if (!db_->Execute("ALTER TABLE temp_share_info RENAME TO share_info"))
|
| - return false;
|
| -
|
| - needs_share_info_column_refresh_ = false;
|
| - }
|
| -
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::LoadEntries(Directory::MetahandlesMap* handles_map,
|
| - MetahandleSet* metahandles_to_purge) {
|
| - string select;
|
| - select.reserve(kUpdateStatementBufferSize);
|
| - select.append("SELECT ");
|
| - AppendColumnList(&select);
|
| - select.append(" FROM metas");
|
| - int total_specifics_copies = 0;
|
| - int model_type_entry_count[MODEL_TYPE_COUNT];
|
| - for (int i = 0; i < MODEL_TYPE_COUNT; ++i) {
|
| - model_type_entry_count[i] = 0;
|
| - }
|
| -
|
| - sql::Statement s(db_->GetUniqueStatement(select.c_str()));
|
| -
|
| - while (s.Step()) {
|
| - std::unique_ptr<EntryKernel> kernel =
|
| - UnpackEntry(&s, &total_specifics_copies);
|
| - // A null kernel is evidence of external data corruption.
|
| - if (!kernel)
|
| - return false;
|
| -
|
| - int64_t handle = kernel->ref(META_HANDLE);
|
| - if (SafeToPurgeOnLoading(*kernel)) {
|
| - metahandles_to_purge->insert(handle);
|
| - } else {
|
| - ModelType model_type = kernel->GetModelType();
|
| - if (!IsRealDataType(model_type)) {
|
| - model_type = kernel->GetServerModelType();
|
| - }
|
| - ++model_type_entry_count[model_type];
|
| - (*handles_map)[handle] = kernel.release();
|
| - }
|
| - }
|
| -
|
| - UploadModelTypeEntryCount(total_specifics_copies, model_type_entry_count);
|
| -
|
| - return s.Succeeded();
|
| -}
|
| -
|
| -bool DirectoryBackingStore::SafeToPurgeOnLoading(
|
| - const EntryKernel& entry) const {
|
| - if (entry.ref(IS_DEL)) {
|
| - if (!entry.ref(IS_UNSYNCED) && !entry.ref(IS_UNAPPLIED_UPDATE))
|
| - return true;
|
| - else if (!entry.ref(ID).ServerKnows())
|
| - return true;
|
| - }
|
| - return false;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::LoadDeleteJournals(
|
| - JournalIndex* delete_journals) {
|
| - string select;
|
| - select.reserve(kUpdateStatementBufferSize);
|
| - select.append("SELECT ");
|
| - AppendColumnList(&select);
|
| - select.append(" FROM deleted_metas");
|
| -
|
| - sql::Statement s(db_->GetUniqueStatement(select.c_str()));
|
| -
|
| - while (s.Step()) {
|
| - int total_entry_copies;
|
| - std::unique_ptr<EntryKernel> kernel = UnpackEntry(&s, &total_entry_copies);
|
| - // A null kernel is evidence of external data corruption.
|
| - if (!kernel)
|
| - return false;
|
| - delete_journals->insert(kernel.release());
|
| - }
|
| - return s.Succeeded();
|
| -}
|
| -
|
| -bool DirectoryBackingStore::LoadInfo(Directory::KernelLoadInfo* info) {
|
| - {
|
| - sql::Statement s(db_->GetUniqueStatement(
|
| - "SELECT store_birthday, cache_guid, bag_of_chips "
|
| - "FROM share_info"));
|
| - if (!s.Step())
|
| - return false;
|
| -
|
| - info->kernel_info.store_birthday = s.ColumnString(0);
|
| - info->cache_guid = s.ColumnString(1);
|
| - s.ColumnBlobAsString(2, &(info->kernel_info.bag_of_chips));
|
| -
|
| - // Verify there was only one row returned.
|
| - DCHECK(!s.Step());
|
| - DCHECK(s.Succeeded());
|
| - }
|
| -
|
| - {
|
| - sql::Statement s(
|
| - db_->GetUniqueStatement(
|
| - "SELECT model_id, progress_marker, "
|
| - "transaction_version, context FROM models"));
|
| -
|
| - while (s.Step()) {
|
| - ModelType type = ModelIdToModelTypeEnum(s.ColumnBlob(0),
|
| - s.ColumnByteLength(0));
|
| - if (type != UNSPECIFIED && type != TOP_LEVEL_FOLDER) {
|
| - info->kernel_info.download_progress[type].ParseFromArray(
|
| - s.ColumnBlob(1), s.ColumnByteLength(1));
|
| - info->kernel_info.transaction_version[type] = s.ColumnInt64(2);
|
| - info->kernel_info.datatype_context[type].ParseFromArray(
|
| - s.ColumnBlob(3), s.ColumnByteLength(3));
|
| - }
|
| - }
|
| - if (!s.Succeeded())
|
| - return false;
|
| - }
|
| - {
|
| - sql::Statement s(
|
| - db_->GetUniqueStatement(
|
| - "SELECT MAX(metahandle) FROM metas"));
|
| - if (!s.Step())
|
| - return false;
|
| -
|
| - info->max_metahandle = s.ColumnInt64(0);
|
| -
|
| - // Verify only one row was returned.
|
| - DCHECK(!s.Step());
|
| - DCHECK(s.Succeeded());
|
| - }
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::SafeDropTable(const char* table_name) {
|
| - string query = "DROP TABLE IF EXISTS ";
|
| - query.append(table_name);
|
| - return db_->Execute(query.c_str());
|
| -}
|
| -
|
| -void DirectoryBackingStore::DropAllTables() {
|
| - SafeDropTable("metas");
|
| - SafeDropTable("temp_metas");
|
| - SafeDropTable("share_info");
|
| - SafeDropTable("temp_share_info");
|
| - SafeDropTable("share_version");
|
| - SafeDropTable("extended_attributes");
|
| - SafeDropTable("models");
|
| - SafeDropTable("temp_models");
|
| - needs_metas_column_refresh_ = false;
|
| - needs_share_info_column_refresh_ = false;
|
| -}
|
| -
|
| -// static
|
| -ModelType DirectoryBackingStore::ModelIdToModelTypeEnum(
|
| - const void* data, int size) {
|
| - sync_pb::EntitySpecifics specifics;
|
| - if (!specifics.ParseFromArray(data, size))
|
| - return UNSPECIFIED;
|
| - return GetModelTypeFromSpecifics(specifics);
|
| -}
|
| -
|
| -// static
|
| -string DirectoryBackingStore::ModelTypeEnumToModelId(ModelType model_type) {
|
| - sync_pb::EntitySpecifics specifics;
|
| - AddDefaultFieldValue(model_type, &specifics);
|
| - return specifics.SerializeAsString();
|
| -}
|
| -
|
| -// static
|
| -std::string DirectoryBackingStore::GenerateCacheGUID() {
|
| - // Generate a GUID with 128 bits of randomness.
|
| - const int kGuidBytes = 128 / 8;
|
| - std::string guid;
|
| - base::Base64Encode(base::RandBytesAsString(kGuidBytes), &guid);
|
| - return guid;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::MigrateToSpecifics(
|
| - const char* old_columns,
|
| - const char* specifics_column,
|
| - void (*handler_function)(sql::Statement* old_value_query,
|
| - int old_value_column,
|
| - sync_pb::EntitySpecifics* mutable_new_value)) {
|
| - std::string query_sql = base::StringPrintf(
|
| - "SELECT metahandle, %s, %s FROM metas", specifics_column, old_columns);
|
| - std::string update_sql = base::StringPrintf(
|
| - "UPDATE metas SET %s = ? WHERE metahandle = ?", specifics_column);
|
| -
|
| - sql::Statement query(db_->GetUniqueStatement(query_sql.c_str()));
|
| - sql::Statement update(db_->GetUniqueStatement(update_sql.c_str()));
|
| -
|
| - while (query.Step()) {
|
| - int64_t metahandle = query.ColumnInt64(0);
|
| - std::string new_value_bytes;
|
| - query.ColumnBlobAsString(1, &new_value_bytes);
|
| - sync_pb::EntitySpecifics new_value;
|
| - new_value.ParseFromString(new_value_bytes);
|
| - handler_function(&query, 2, &new_value);
|
| - new_value.SerializeToString(&new_value_bytes);
|
| -
|
| - update.BindBlob(0, new_value_bytes.data(), new_value_bytes.length());
|
| - update.BindInt64(1, metahandle);
|
| - if (!update.Run())
|
| - return false;
|
| - update.Reset(true);
|
| - }
|
| - return query.Succeeded();
|
| -}
|
| -
|
| -bool DirectoryBackingStore::SetVersion(int version) {
|
| - sql::Statement s(db_->GetCachedStatement(
|
| - SQL_FROM_HERE, "UPDATE share_version SET data = ?"));
|
| - s.BindInt(0, version);
|
| -
|
| - return s.Run();
|
| -}
|
| -
|
| -int DirectoryBackingStore::GetVersion() {
|
| - if (!db_->DoesTableExist("share_version"))
|
| - return 0;
|
| -
|
| - sql::Statement statement(db_->GetUniqueStatement(
|
| - "SELECT data FROM share_version"));
|
| - if (statement.Step()) {
|
| - return statement.ColumnInt(0);
|
| - } else {
|
| - return 0;
|
| - }
|
| -}
|
| -
|
| -bool DirectoryBackingStore::MigrateVersion67To68() {
|
| - // This change simply removed three columns:
|
| - // string NAME
|
| - // string UNSANITIZED_NAME
|
| - // string SERVER_NAME
|
| - // No data migration is necessary, but we should do a column refresh.
|
| - SetVersion(68);
|
| - needs_metas_column_refresh_ = true;
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::MigrateVersion69To70() {
|
| - // Added "unique_client_tag", renamed "singleton_tag" to unique_server_tag
|
| - SetVersion(70);
|
| - if (!db_->Execute(
|
| - "ALTER TABLE metas ADD COLUMN unique_server_tag varchar"))
|
| - return false;
|
| - if (!db_->Execute(
|
| - "ALTER TABLE metas ADD COLUMN unique_client_tag varchar"))
|
| - return false;
|
| - needs_metas_column_refresh_ = true;
|
| -
|
| - if (!db_->Execute(
|
| - "UPDATE metas SET unique_server_tag = singleton_tag"))
|
| - return false;
|
| -
|
| - return true;
|
| -}
|
| -
|
| -namespace {
|
| -
|
| -// Callback passed to MigrateToSpecifics for the v68->v69 migration. See
|
| -// MigrateVersion68To69().
|
| -void EncodeBookmarkURLAndFavicon(sql::Statement* old_value_query,
|
| - int old_value_column,
|
| - sync_pb::EntitySpecifics* mutable_new_value) {
|
| - // Extract data from the column trio we expect.
|
| - bool old_is_bookmark_object = old_value_query->ColumnBool(old_value_column);
|
| - std::string old_url = old_value_query->ColumnString(old_value_column + 1);
|
| - std::string old_favicon;
|
| - old_value_query->ColumnBlobAsString(old_value_column + 2, &old_favicon);
|
| - bool old_is_dir = old_value_query->ColumnBool(old_value_column + 3);
|
| -
|
| - if (old_is_bookmark_object) {
|
| - sync_pb::BookmarkSpecifics* bookmark_data =
|
| - mutable_new_value->mutable_bookmark();
|
| - if (!old_is_dir) {
|
| - bookmark_data->set_url(old_url);
|
| - bookmark_data->set_favicon(old_favicon);
|
| - }
|
| - }
|
| -}
|
| -
|
| -} // namespace
|
| -
|
| -bool DirectoryBackingStore::MigrateVersion68To69() {
|
| - // In Version 68, there were columns on table 'metas':
|
| - // string BOOKMARK_URL
|
| - // string SERVER_BOOKMARK_URL
|
| - // blob BOOKMARK_FAVICON
|
| - // blob SERVER_BOOKMARK_FAVICON
|
| - // In version 69, these columns went away in favor of storing
|
| - // a serialized EntrySpecifics protobuf in the columns:
|
| - // protobuf blob SPECIFICS
|
| - // protobuf blob SERVER_SPECIFICS
|
| - // For bookmarks, EntrySpecifics is extended as per
|
| - // bookmark_specifics.proto. This migration converts bookmarks from the
|
| - // former scheme to the latter scheme.
|
| -
|
| - // First, add the two new columns to the schema.
|
| - if (!db_->Execute(
|
| - "ALTER TABLE metas ADD COLUMN specifics blob"))
|
| - return false;
|
| - if (!db_->Execute(
|
| - "ALTER TABLE metas ADD COLUMN server_specifics blob"))
|
| - return false;
|
| -
|
| - // Next, fold data from the old columns into the new protobuf columns.
|
| - if (!MigrateToSpecifics(("is_bookmark_object, bookmark_url, "
|
| - "bookmark_favicon, is_dir"),
|
| - "specifics",
|
| - &EncodeBookmarkURLAndFavicon)) {
|
| - return false;
|
| - }
|
| - if (!MigrateToSpecifics(("server_is_bookmark_object, "
|
| - "server_bookmark_url, "
|
| - "server_bookmark_favicon, "
|
| - "server_is_dir"),
|
| - "server_specifics",
|
| - &EncodeBookmarkURLAndFavicon)) {
|
| - return false;
|
| - }
|
| -
|
| - // Lastly, fix up the "Google Chrome" folder, which is of the TOP_LEVEL_FOLDER
|
| - // ModelType: it shouldn't have BookmarkSpecifics.
|
| - if (!db_->Execute(
|
| - "UPDATE metas SET specifics = NULL, server_specifics = NULL WHERE "
|
| - "singleton_tag IN ('google_chrome')"))
|
| - return false;
|
| -
|
| - SetVersion(69);
|
| - needs_metas_column_refresh_ = true; // Trigger deletion of old columns.
|
| - return true;
|
| -}
|
| -
|
| -// Version 71, the columns 'initial_sync_ended' and 'last_sync_timestamp'
|
| -// were removed from the share_info table. They were replaced by
|
| -// the 'models' table, which has these values on a per-datatype basis.
|
| -bool DirectoryBackingStore::MigrateVersion70To71() {
|
| - if (!CreateV71ModelsTable())
|
| - return false;
|
| -
|
| - // Move data from the old share_info columns to the new models table.
|
| - {
|
| - sql::Statement fetch(db_->GetUniqueStatement(
|
| - "SELECT last_sync_timestamp, initial_sync_ended FROM share_info"));
|
| - if (!fetch.Step())
|
| - return false;
|
| -
|
| - int64_t last_sync_timestamp = fetch.ColumnInt64(0);
|
| - bool initial_sync_ended = fetch.ColumnBool(1);
|
| -
|
| - // Verify there were no additional rows returned.
|
| - DCHECK(!fetch.Step());
|
| - DCHECK(fetch.Succeeded());
|
| -
|
| - sql::Statement update(db_->GetUniqueStatement(
|
| - "INSERT INTO models (model_id, "
|
| - "last_download_timestamp, initial_sync_ended) VALUES (?, ?, ?)"));
|
| - string bookmark_model_id = ModelTypeEnumToModelId(BOOKMARKS);
|
| - update.BindBlob(0, bookmark_model_id.data(), bookmark_model_id.size());
|
| - update.BindInt64(1, last_sync_timestamp);
|
| - update.BindBool(2, initial_sync_ended);
|
| -
|
| - if (!update.Run())
|
| - return false;
|
| - }
|
| -
|
| - // Drop the columns from the old share_info table via a temp table.
|
| - const bool kCreateAsTempShareInfo = true;
|
| -
|
| - if (!CreateShareInfoTableVersion71(kCreateAsTempShareInfo))
|
| - return false;
|
| - if (!db_->Execute(
|
| - "INSERT INTO temp_share_info (id, name, store_birthday, "
|
| - "db_create_version, db_create_time, next_id, cache_guid) "
|
| - "SELECT id, name, store_birthday, db_create_version, "
|
| - "db_create_time, next_id, cache_guid FROM share_info"))
|
| - return false;
|
| - SafeDropTable("share_info");
|
| - if (!db_->Execute(
|
| - "ALTER TABLE temp_share_info RENAME TO share_info"))
|
| - return false;
|
| - SetVersion(71);
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::MigrateVersion71To72() {
|
| - // Version 72 removed a table 'extended_attributes', whose
|
| - // contents didn't matter.
|
| - SafeDropTable("extended_attributes");
|
| - SetVersion(72);
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::MigrateVersion72To73() {
|
| - // Version 73 added one column to the table 'share_info': notification_state
|
| - if (!db_->Execute(
|
| - "ALTER TABLE share_info ADD COLUMN notification_state BLOB"))
|
| - return false;
|
| - SetVersion(73);
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::MigrateVersion73To74() {
|
| - // Version 74 added the following columns to the table 'share_info':
|
| - // autofill_migration_state
|
| - // bookmarks_added_during_autofill_migration
|
| - // autofill_migration_time
|
| - // autofill_entries_added_during_migration
|
| - // autofill_profiles_added_during_migration
|
| -
|
| - if (!db_->Execute(
|
| - "ALTER TABLE share_info ADD COLUMN "
|
| - "autofill_migration_state INT default 0"))
|
| - return false;
|
| -
|
| - if (!db_->Execute(
|
| - "ALTER TABLE share_info ADD COLUMN "
|
| - "bookmarks_added_during_autofill_migration "
|
| - "INT default 0"))
|
| - return false;
|
| -
|
| - if (!db_->Execute(
|
| - "ALTER TABLE share_info ADD COLUMN autofill_migration_time "
|
| - "INT default 0"))
|
| - return false;
|
| -
|
| - if (!db_->Execute(
|
| - "ALTER TABLE share_info ADD COLUMN "
|
| - "autofill_entries_added_during_migration "
|
| - "INT default 0"))
|
| - return false;
|
| -
|
| - if (!db_->Execute(
|
| - "ALTER TABLE share_info ADD COLUMN "
|
| - "autofill_profiles_added_during_migration "
|
| - "INT default 0"))
|
| - return false;
|
| -
|
| - SetVersion(74);
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::MigrateVersion74To75() {
|
| - // In version 74, there was a table 'models':
|
| - // blob model_id (entity specifics, primary key)
|
| - // int last_download_timestamp
|
| - // boolean initial_sync_ended
|
| - // In version 75, we deprecated the integer-valued last_download_timestamp,
|
| - // using insted a protobuf-valued progress_marker field:
|
| - // blob progress_marker
|
| - // The progress_marker values are initialized from the value of
|
| - // last_download_timestamp, thereby preserving the download state.
|
| -
|
| - // Move aside the old table and create a new empty one at the current schema.
|
| - if (!db_->Execute("ALTER TABLE models RENAME TO temp_models"))
|
| - return false;
|
| - if (!CreateV75ModelsTable())
|
| - return false;
|
| -
|
| - sql::Statement query(db_->GetUniqueStatement(
|
| - "SELECT model_id, last_download_timestamp, initial_sync_ended "
|
| - "FROM temp_models"));
|
| -
|
| - sql::Statement update(db_->GetUniqueStatement(
|
| - "INSERT INTO models (model_id, "
|
| - "progress_marker, initial_sync_ended) VALUES (?, ?, ?)"));
|
| -
|
| - while (query.Step()) {
|
| - ModelType type = ModelIdToModelTypeEnum(query.ColumnBlob(0),
|
| - query.ColumnByteLength(0));
|
| - if (type != UNSPECIFIED) {
|
| - // Set the |timestamp_token_for_migration| on a new
|
| - // DataTypeProgressMarker, using the old value of last_download_timestamp.
|
| - // The server will turn this into a real token on our behalf the next
|
| - // time we check for updates.
|
| - sync_pb::DataTypeProgressMarker progress_marker;
|
| - progress_marker.set_data_type_id(
|
| - GetSpecificsFieldNumberFromModelType(type));
|
| - progress_marker.set_timestamp_token_for_migration(query.ColumnInt64(1));
|
| - std::string progress_blob;
|
| - progress_marker.SerializeToString(&progress_blob);
|
| -
|
| - update.BindBlob(0, query.ColumnBlob(0), query.ColumnByteLength(0));
|
| - update.BindBlob(1, progress_blob.data(), progress_blob.length());
|
| - update.BindBool(2, query.ColumnBool(2));
|
| - if (!update.Run())
|
| - return false;
|
| - update.Reset(true);
|
| - }
|
| - }
|
| - if (!query.Succeeded())
|
| - return false;
|
| -
|
| - // Drop the old table.
|
| - SafeDropTable("temp_models");
|
| -
|
| - SetVersion(75);
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::MigrateVersion75To76() {
|
| - // This change removed five columns:
|
| - // autofill_migration_state
|
| - // bookmarks_added_during_autofill_migration
|
| - // autofill_migration_time
|
| - // autofill_entries_added_during_migration
|
| - // autofill_profiles_added_during_migration
|
| - // No data migration is necessary, but we should do a column refresh.
|
| - SetVersion(76);
|
| - needs_share_info_column_refresh_ = true;
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::MigrateVersion76To77() {
|
| - // This change changes the format of stored timestamps to ms since
|
| - // the Unix epoch.
|
| -#if defined(OS_WIN)
|
| -// On Windows, we used to store timestamps in FILETIME format (100s of
|
| -// ns since Jan 1, 1601). Magic numbers taken from
|
| -// http://stackoverflow.com/questions/5398557/
|
| -// java-library-for-dealing-with-win32-filetime
|
| -// .
|
| -#define TO_UNIX_TIME_MS(x) #x " = " #x " / 10000 - 11644473600000"
|
| -#else
|
| -// On other platforms, we used to store timestamps in time_t format (s
|
| -// since the Unix epoch).
|
| -#define TO_UNIX_TIME_MS(x) #x " = " #x " * 1000"
|
| -#endif
|
| - sql::Statement update_timestamps(db_->GetUniqueStatement(
|
| - "UPDATE metas SET "
|
| - TO_UNIX_TIME_MS(mtime) ", "
|
| - TO_UNIX_TIME_MS(server_mtime) ", "
|
| - TO_UNIX_TIME_MS(ctime) ", "
|
| - TO_UNIX_TIME_MS(server_ctime)));
|
| -#undef TO_UNIX_TIME_MS
|
| - if (!update_timestamps.Run())
|
| - return false;
|
| - SetVersion(77);
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::MigrateVersion77To78() {
|
| - // Version 78 added one column to table 'metas': base_server_specifics.
|
| - if (!db_->Execute(
|
| - "ALTER TABLE metas ADD COLUMN base_server_specifics BLOB")) {
|
| - return false;
|
| - }
|
| - SetVersion(78);
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::MigrateVersion78To79() {
|
| - // Some users are stuck with a DB that causes them to reuse existing IDs. We
|
| - // perform this one-time fixup on all users to help the few that are stuck.
|
| - // See crbug.com/142987 for details.
|
| - if (!db_->Execute(
|
| - "UPDATE share_info SET next_id = next_id - 65536")) {
|
| - return false;
|
| - }
|
| - SetVersion(79);
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::MigrateVersion79To80() {
|
| - if (!db_->Execute(
|
| - "ALTER TABLE share_info ADD COLUMN bag_of_chips BLOB"))
|
| - return false;
|
| - sql::Statement update(db_->GetUniqueStatement(
|
| - "UPDATE share_info SET bag_of_chips = ?"));
|
| - // An empty message is serialized to an empty string.
|
| - update.BindBlob(0, NULL, 0);
|
| - if (!update.Run())
|
| - return false;
|
| - SetVersion(80);
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::MigrateVersion80To81() {
|
| - if (!db_->Execute(
|
| - "ALTER TABLE metas ADD COLUMN server_ordinal_in_parent BLOB"))
|
| - return false;
|
| -
|
| - sql::Statement get_positions(db_->GetUniqueStatement(
|
| - "SELECT metahandle, server_position_in_parent FROM metas"));
|
| -
|
| - sql::Statement put_ordinals(db_->GetUniqueStatement(
|
| - "UPDATE metas SET server_ordinal_in_parent = ?"
|
| - "WHERE metahandle = ?"));
|
| -
|
| - while (get_positions.Step()) {
|
| - int64_t metahandle = get_positions.ColumnInt64(0);
|
| - int64_t position = get_positions.ColumnInt64(1);
|
| -
|
| - const std::string& ordinal = Int64ToNodeOrdinal(position).ToInternalValue();
|
| - put_ordinals.BindBlob(0, ordinal.data(), ordinal.length());
|
| - put_ordinals.BindInt64(1, metahandle);
|
| -
|
| - if (!put_ordinals.Run())
|
| - return false;
|
| - put_ordinals.Reset(true);
|
| - }
|
| -
|
| - SetVersion(81);
|
| - needs_metas_column_refresh_ = true;
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::MigrateVersion81To82() {
|
| - if (!db_->Execute(
|
| - "ALTER TABLE models ADD COLUMN transaction_version BIGINT default 0"))
|
| - return false;
|
| - sql::Statement update(db_->GetUniqueStatement(
|
| - "UPDATE models SET transaction_version = 0"));
|
| - if (!update.Run())
|
| - return false;
|
| - SetVersion(82);
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::MigrateVersion82To83() {
|
| - // Version 83 added transaction_version on sync node.
|
| - if (!db_->Execute(
|
| - "ALTER TABLE metas ADD COLUMN transaction_version BIGINT default 0"))
|
| - return false;
|
| - sql::Statement update(db_->GetUniqueStatement(
|
| - "UPDATE metas SET transaction_version = 0"));
|
| - if (!update.Run())
|
| - return false;
|
| - SetVersion(83);
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::MigrateVersion83To84() {
|
| - // Version 84 added deleted_metas table to store deleted metas until we know
|
| - // for sure that the deletions are persisted in native models.
|
| - string query = "CREATE TABLE deleted_metas ";
|
| - query.append(ComposeCreateTableColumnSpecs());
|
| - if (!db_->Execute(query.c_str()))
|
| - return false;
|
| - SetVersion(84);
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::MigrateVersion84To85() {
|
| - // Version 85 removes the initial_sync_ended flag.
|
| - if (!db_->Execute("ALTER TABLE models RENAME TO temp_models"))
|
| - return false;
|
| - if (!CreateV81ModelsTable())
|
| - return false;
|
| - if (!db_->Execute("INSERT INTO models SELECT "
|
| - "model_id, progress_marker, transaction_version "
|
| - "FROM temp_models")) {
|
| - return false;
|
| - }
|
| - SafeDropTable("temp_models");
|
| -
|
| - SetVersion(85);
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::MigrateVersion85To86() {
|
| - // Version 86 removes both server ordinals and local NEXT_ID, PREV_ID and
|
| - // SERVER_{POSITION,ORDINAL}_IN_PARENT and replaces them with UNIQUE_POSITION
|
| - // and SERVER_UNIQUE_POSITION.
|
| - if (!db_->Execute("ALTER TABLE metas ADD COLUMN "
|
| - "server_unique_position BLOB")) {
|
| - return false;
|
| - }
|
| - if (!db_->Execute("ALTER TABLE metas ADD COLUMN "
|
| - "unique_position BLOB")) {
|
| - return false;
|
| - }
|
| - if (!db_->Execute("ALTER TABLE metas ADD COLUMN "
|
| - "unique_bookmark_tag VARCHAR")) {
|
| - return false;
|
| - }
|
| -
|
| - // Fetch the cache_guid from the DB, because we don't otherwise have access to
|
| - // it from here.
|
| - sql::Statement get_cache_guid(db_->GetUniqueStatement(
|
| - "SELECT cache_guid FROM share_info"));
|
| - if (!get_cache_guid.Step()) {
|
| - return false;
|
| - }
|
| - std::string cache_guid = get_cache_guid.ColumnString(0);
|
| - DCHECK(!get_cache_guid.Step());
|
| - DCHECK(get_cache_guid.Succeeded());
|
| -
|
| - sql::Statement get(db_->GetUniqueStatement(
|
| - "SELECT "
|
| - " metahandle, "
|
| - " id, "
|
| - " specifics, "
|
| - " is_dir, "
|
| - " unique_server_tag, "
|
| - " server_ordinal_in_parent "
|
| - "FROM metas"));
|
| -
|
| - // Note that we set both the local and server position based on the server
|
| - // position. We wll lose any unsynced local position changes. Unfortunately,
|
| - // there's nothing we can do to avoid that. The NEXT_ID / PREV_ID values
|
| - // can't be translated into a UNIQUE_POSTION in a reliable way.
|
| - sql::Statement put(db_->GetCachedStatement(
|
| - SQL_FROM_HERE,
|
| - "UPDATE metas SET"
|
| - " server_unique_position = ?,"
|
| - " unique_position = ?,"
|
| - " unique_bookmark_tag = ?"
|
| - "WHERE metahandle = ?"));
|
| -
|
| - while (get.Step()) {
|
| - int64_t metahandle = get.ColumnInt64(0);
|
| -
|
| - std::string id_string;
|
| - get.ColumnBlobAsString(1, &id_string);
|
| -
|
| - sync_pb::EntitySpecifics specifics;
|
| - specifics.ParseFromArray(
|
| - get.ColumnBlob(2), get.ColumnByteLength(2));
|
| -
|
| - bool is_dir = get.ColumnBool(3);
|
| -
|
| - std::string server_unique_tag = get.ColumnString(4);
|
| -
|
| - std::string ordinal_string;
|
| - get.ColumnBlobAsString(5, &ordinal_string);
|
| - NodeOrdinal ordinal(ordinal_string);
|
| -
|
| -
|
| - std::string unique_bookmark_tag;
|
| -
|
| - // We only maintain positions for bookmarks that are not server-defined
|
| - // top-level folders.
|
| - UniquePosition position;
|
| - if (GetModelTypeFromSpecifics(specifics) == BOOKMARKS
|
| - && !(is_dir && !server_unique_tag.empty())) {
|
| - if (id_string.at(0) == 'c') {
|
| - // We found an uncommitted item. This is rare, but fortunate. This
|
| - // means we can set the bookmark tag according to the originator client
|
| - // item ID and originator cache guid, because (unlike the other case) we
|
| - // know that this client is the originator.
|
| - unique_bookmark_tag = syncable::GenerateSyncableBookmarkHash(
|
| - cache_guid,
|
| - id_string.substr(1));
|
| - } else {
|
| - // If we've already committed the item, then we don't know who the
|
| - // originator was. We do not have access to the originator client item
|
| - // ID and originator cache guid at this point.
|
| - //
|
| - // We will base our hash entirely on the server ID instead. This is
|
| - // incorrect, but at least all clients that undergo this migration step
|
| - // will be incorrect in the same way.
|
| - //
|
| - // To get everyone back into a synced state, we will update the bookmark
|
| - // tag according to the originator_cache_guid and originator_item_id
|
| - // when we see updates for this item. That should ensure that commonly
|
| - // modified items will end up with the proper tag values eventually.
|
| - unique_bookmark_tag = syncable::GenerateSyncableBookmarkHash(
|
| - std::string(), // cache_guid left intentionally blank.
|
| - id_string.substr(1));
|
| - }
|
| -
|
| - int64_t int_position = NodeOrdinalToInt64(ordinal);
|
| - position = UniquePosition::FromInt64(int_position, unique_bookmark_tag);
|
| - } else {
|
| - // Leave bookmark_tag and position at their default (invalid) values.
|
| - }
|
| -
|
| - std::string position_blob;
|
| - position.SerializeToString(&position_blob);
|
| - put.BindBlob(0, position_blob.data(), position_blob.length());
|
| - put.BindBlob(1, position_blob.data(), position_blob.length());
|
| - put.BindBlob(2, unique_bookmark_tag.data(), unique_bookmark_tag.length());
|
| - put.BindInt64(3, metahandle);
|
| -
|
| - if (!put.Run())
|
| - return false;
|
| - put.Reset(true);
|
| - }
|
| -
|
| - SetVersion(86);
|
| - needs_metas_column_refresh_ = true;
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::MigrateVersion86To87() {
|
| - // Version 87 adds AttachmentMetadata proto.
|
| - if (!db_->Execute(
|
| - "ALTER TABLE metas ADD COLUMN "
|
| - "attachment_metadata BLOB")) {
|
| - return false;
|
| - }
|
| - SetVersion(87);
|
| - needs_metas_column_refresh_ = true;
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::MigrateVersion87To88() {
|
| - // Version 88 adds the datatype context to the models table.
|
| - if (!db_->Execute("ALTER TABLE models ADD COLUMN context blob"))
|
| - return false;
|
| -
|
| - SetVersion(88);
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::MigrateVersion88To89() {
|
| - // Version 89 adds server_attachment_metadata.
|
| - if (!db_->Execute(
|
| - "ALTER TABLE metas ADD COLUMN "
|
| - "server_attachment_metadata BLOB")) {
|
| - return false;
|
| - }
|
| - SetVersion(89);
|
| - needs_metas_column_refresh_ = true;
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::MigrateVersion89To90() {
|
| - // This change removed 4 columns from meta_info:
|
| - // db_create_version
|
| - // db_create_time
|
| - // next_id
|
| - // notification_state
|
| - // No data migration is necessary, but we should do a column refresh.
|
| - SetVersion(90);
|
| - needs_share_info_column_refresh_ = true;
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::CreateTables() {
|
| - DVLOG(1) << "First run, creating tables";
|
| -
|
| - // Create two little tables share_version and share_info
|
| - if (!db_->Execute(
|
| - "CREATE TABLE share_version ("
|
| - "id VARCHAR(128) primary key, data INT)")) {
|
| - return false;
|
| - }
|
| -
|
| - {
|
| - sql::Statement s(db_->GetUniqueStatement(
|
| - "INSERT INTO share_version VALUES(?, ?)"));
|
| - s.BindString(0, dir_name_);
|
| - s.BindInt(1, kCurrentDBVersion);
|
| -
|
| - if (!s.Run())
|
| - return false;
|
| - }
|
| -
|
| - const bool kCreateAsTempShareInfo = false;
|
| - if (!CreateShareInfoTable(kCreateAsTempShareInfo)) {
|
| - return false;
|
| - }
|
| -
|
| - {
|
| - sql::Statement s(db_->GetUniqueStatement(
|
| - "INSERT INTO share_info VALUES"
|
| - "(?, " // id
|
| - "?, " // name
|
| - "?, " // store_birthday
|
| - "?, " // cache_guid
|
| - "?);")); // bag_of_chips
|
| - s.BindString(0, dir_name_); // id
|
| - s.BindString(1, dir_name_); // name
|
| - s.BindString(2, std::string()); // store_birthday
|
| - s.BindString(3, GenerateCacheGUID()); // cache_guid
|
| - s.BindBlob(4, NULL, 0); // bag_of_chips
|
| - if (!s.Run())
|
| - return false;
|
| - }
|
| -
|
| - if (!CreateModelsTable())
|
| - return false;
|
| -
|
| - // Create the big metas table.
|
| - if (!CreateMetasTable(false))
|
| - return false;
|
| -
|
| - {
|
| - // Insert the entry for the root into the metas table.
|
| - const int64_t now = TimeToProtoTime(base::Time::Now());
|
| - sql::Statement s(db_->GetUniqueStatement(
|
| - "INSERT INTO metas "
|
| - "( id, metahandle, is_dir, ctime, mtime ) "
|
| - "VALUES ( \"r\", 1, 1, ?, ? )"));
|
| - s.BindInt64(0, now);
|
| - s.BindInt64(1, now);
|
| -
|
| - if (!s.Run())
|
| - return false;
|
| - }
|
| -
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::CreateMetasTable(bool is_temporary) {
|
| - string query = "CREATE TABLE ";
|
| - query.append(is_temporary ? "temp_metas" : "metas");
|
| - query.append(ComposeCreateTableColumnSpecs());
|
| - if (!db_->Execute(query.c_str()))
|
| - return false;
|
| -
|
| - // Create a deleted_metas table to save copies of deleted metas until the
|
| - // deletions are persisted. For simplicity, don't try to migrate existing
|
| - // data because it's rarely used.
|
| - SafeDropTable("deleted_metas");
|
| - query = "CREATE TABLE deleted_metas ";
|
| - query.append(ComposeCreateTableColumnSpecs());
|
| - return db_->Execute(query.c_str());
|
| -}
|
| -
|
| -bool DirectoryBackingStore::CreateV71ModelsTable() {
|
| - // This is an old schema for the Models table, used from versions 71 to 74.
|
| - return db_->Execute(
|
| - "CREATE TABLE models ("
|
| - "model_id BLOB primary key, "
|
| - "last_download_timestamp INT, "
|
| - // Gets set if the syncer ever gets updates from the
|
| - // server and the server returns 0. Lets us detect the
|
| - // end of the initial sync.
|
| - "initial_sync_ended BOOLEAN default 0)");
|
| -}
|
| -
|
| -bool DirectoryBackingStore::CreateV75ModelsTable() {
|
| - // This is an old schema for the Models table, used from versions 75 to 80.
|
| - return db_->Execute(
|
| - "CREATE TABLE models ("
|
| - "model_id BLOB primary key, "
|
| - "progress_marker BLOB, "
|
| - // Gets set if the syncer ever gets updates from the
|
| - // server and the server returns 0. Lets us detect the
|
| - // end of the initial sync.
|
| - "initial_sync_ended BOOLEAN default 0)");
|
| -}
|
| -
|
| -bool DirectoryBackingStore::CreateV81ModelsTable() {
|
| - // This is an old schema for the Models table, used from versions 81 to 87.
|
| - return db_->Execute(
|
| - "CREATE TABLE models ("
|
| - "model_id BLOB primary key, "
|
| - "progress_marker BLOB, "
|
| - // Gets set if the syncer ever gets updates from the
|
| - // server and the server returns 0. Lets us detect the
|
| - // end of the initial sync.
|
| - "transaction_version BIGINT default 0)");
|
| -}
|
| -
|
| -bool DirectoryBackingStore::CreateModelsTable() {
|
| - // This is the current schema for the Models table, from version 88
|
| - // onward. If you change the schema, you'll probably want to double-check
|
| - // the use of this function in the v84-v85 migration.
|
| - return db_->Execute(
|
| - "CREATE TABLE models ("
|
| - "model_id BLOB primary key, "
|
| - "progress_marker BLOB, "
|
| - // Gets set if the syncer ever gets updates from the
|
| - // server and the server returns 0. Lets us detect the
|
| - // end of the initial sync.
|
| - "transaction_version BIGINT default 0,"
|
| - "context BLOB)");
|
| -}
|
| -
|
| -bool DirectoryBackingStore::CreateShareInfoTable(bool is_temporary) {
|
| - const char* name = is_temporary ? "temp_share_info" : "share_info";
|
| - string query = "CREATE TABLE ";
|
| - query.append(name);
|
| - // This is the current schema for the ShareInfo table, from version 76
|
| - // onward.
|
| - query.append(" ("
|
| - "id TEXT primary key, "
|
| - "name TEXT, "
|
| - "store_birthday TEXT, "
|
| - "cache_guid TEXT, "
|
| - "bag_of_chips BLOB"
|
| - ")");
|
| - return db_->Execute(query.c_str());
|
| -}
|
| -
|
| -bool DirectoryBackingStore::CreateShareInfoTableVersion71(
|
| - bool is_temporary) {
|
| - const char* name = is_temporary ? "temp_share_info" : "share_info";
|
| - string query = "CREATE TABLE ";
|
| - query.append(name);
|
| - // This is the schema for the ShareInfo table used from versions 71 to 72.
|
| - query.append(" ("
|
| - "id TEXT primary key, "
|
| - "name TEXT, "
|
| - "store_birthday TEXT, "
|
| - "db_create_version TEXT, "
|
| - "db_create_time INT, "
|
| - "next_id INT default -2, "
|
| - "cache_guid TEXT )");
|
| - return db_->Execute(query.c_str());
|
| -}
|
| -
|
| -// This function checks to see if the given list of Metahandles has any nodes
|
| -// whose PARENT_ID values refer to ID values that do not actually exist.
|
| -// Returns true on success.
|
| -bool DirectoryBackingStore::VerifyReferenceIntegrity(
|
| - const Directory::MetahandlesMap* handles_map) {
|
| - TRACE_EVENT0("sync", "SyncDatabaseIntegrityCheck");
|
| - typedef std::unordered_set<std::string> IdsSet;
|
| -
|
| - IdsSet ids_set;
|
| - bool is_ok = true;
|
| -
|
| - for (Directory::MetahandlesMap::const_iterator it = handles_map->begin();
|
| - it != handles_map->end(); ++it) {
|
| - EntryKernel* entry = it->second;
|
| - bool is_duplicate_id = !(ids_set.insert(entry->ref(ID).value()).second);
|
| - is_ok = is_ok && !is_duplicate_id;
|
| - }
|
| -
|
| - IdsSet::iterator end = ids_set.end();
|
| - for (Directory::MetahandlesMap::const_iterator it = handles_map->begin();
|
| - it != handles_map->end(); ++it) {
|
| - EntryKernel* entry = it->second;
|
| - if (!entry->ref(PARENT_ID).IsNull()) {
|
| - bool parent_exists = (ids_set.find(entry->ref(PARENT_ID).value()) != end);
|
| - if (!parent_exists) {
|
| - return false;
|
| - }
|
| - }
|
| - }
|
| - return is_ok;
|
| -}
|
| -
|
| -void DirectoryBackingStore::PrepareSaveEntryStatement(
|
| - EntryTable table, sql::Statement* save_statement) {
|
| - if (save_statement->is_valid())
|
| - return;
|
| -
|
| - string query;
|
| - query.reserve(kUpdateStatementBufferSize);
|
| - switch (table) {
|
| - case METAS_TABLE:
|
| - query.append("INSERT OR REPLACE INTO metas ");
|
| - break;
|
| - case DELETE_JOURNAL_TABLE:
|
| - query.append("INSERT OR REPLACE INTO deleted_metas ");
|
| - break;
|
| - }
|
| -
|
| - string values;
|
| - values.reserve(kUpdateStatementBufferSize);
|
| - values.append(" VALUES ");
|
| - const char* separator = "( ";
|
| - int i = 0;
|
| - for (i = BEGIN_FIELDS; i < FIELD_COUNT; ++i) {
|
| - query.append(separator);
|
| - values.append(separator);
|
| - separator = ", ";
|
| - query.append(ColumnName(i));
|
| - values.append("?");
|
| - }
|
| - query.append(" ) ");
|
| - values.append(" )");
|
| - query.append(values);
|
| - save_statement->Assign(db_->GetUniqueStatement(
|
| - base::StringPrintf(query.c_str(), "metas").c_str()));
|
| -}
|
| -
|
| -// Get page size for the database.
|
| -bool DirectoryBackingStore::GetDatabasePageSize(int* page_size) {
|
| - sql::Statement s(db_->GetUniqueStatement("PRAGMA page_size"));
|
| - if (!s.Step())
|
| - return false;
|
| - *page_size = s.ColumnInt(0);
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::UpdatePageSizeIfNecessary() {
|
| - int page_size;
|
| - if (!GetDatabasePageSize(&page_size))
|
| - return false;
|
| - if (page_size == kCurrentPageSizeKB)
|
| - return true;
|
| - std::string update_page_size = base::StringPrintf(
|
| - "PRAGMA page_size=%i;", kCurrentPageSizeKB);
|
| - if (!db_->Execute(update_page_size.c_str()) || !Vacuum())
|
| - return false;
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::Vacuum() {
|
| - DCHECK_EQ(db_->transaction_nesting(), 0);
|
| - if (!db_->Execute("VACUUM;")) {
|
| - return false;
|
| - }
|
| - return true;
|
| -}
|
| -
|
| -bool DirectoryBackingStore::needs_column_refresh() const {
|
| - return needs_metas_column_refresh_ || needs_share_info_column_refresh_;
|
| -}
|
| -
|
| -void DirectoryBackingStore::ResetAndCreateConnection() {
|
| - db_.reset(new sql::Connection());
|
| - db_->set_histogram_tag("SyncDirectory");
|
| - db_->set_exclusive_locking();
|
| - db_->set_cache_size(32);
|
| - db_->set_page_size(database_page_size_);
|
| -
|
| - // TODO(shess): The current mitigation for http://crbug.com/537742 stores
|
| - // state in the meta table, which this database does not use.
|
| - db_->set_mmap_disabled();
|
| -
|
| - if (!catastrophic_error_handler_.is_null())
|
| - SetCatastrophicErrorHandler(catastrophic_error_handler_);
|
| -}
|
| -
|
| -void DirectoryBackingStore::SetCatastrophicErrorHandler(
|
| - const base::Closure& catastrophic_error_handler) {
|
| - DCHECK(CalledOnValidThread());
|
| - DCHECK(!catastrophic_error_handler.is_null());
|
| - catastrophic_error_handler_ = catastrophic_error_handler;
|
| - sql::Connection::ErrorCallback error_callback =
|
| - base::Bind(&OnSqliteError, catastrophic_error_handler_);
|
| - db_->set_error_callback(error_callback);
|
| -}
|
| -
|
| -} // namespace syncable
|
| -} // namespace syncer
|
|
|