Revert "Fix broken channel icon in chrome://help on CrOS" and try again
[chromium-blink-merge.git] / sync / syncable / directory_backing_store.cc
blobd49ede50b1197a6cf9fa9212732435cc26b81883
1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "sync/syncable/directory_backing_store.h"
7 #include "build/build_config.h"
9 #include <limits>
11 #include "base/base64.h"
12 #include "base/logging.h"
13 #include "base/rand_util.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/thread_task_runner_handle.h"
16 #include "base/time/time.h"
17 #include "base/trace_event/trace_event.h"
18 #include "sql/connection.h"
19 #include "sql/error_delegate_util.h"
20 #include "sql/statement.h"
21 #include "sql/transaction.h"
22 #include "sync/internal_api/public/base/node_ordinal.h"
23 #include "sync/protocol/bookmark_specifics.pb.h"
24 #include "sync/protocol/sync.pb.h"
25 #include "sync/syncable/syncable-inl.h"
26 #include "sync/syncable/syncable_columns.h"
27 #include "sync/syncable/syncable_util.h"
28 #include "sync/util/time.h"
30 using std::string;
32 namespace syncer {
33 namespace syncable {
35 // Increment this version whenever updating DB tables.
36 const int32 kCurrentDBVersion = 89;
38 // Iterate over the fields of |entry| and bind each to |statement| for
39 // updating. Returns the number of args bound.
40 void BindFields(const EntryKernel& entry,
41 sql::Statement* statement) {
42 int index = 0;
43 int i = 0;
44 for (i = BEGIN_FIELDS; i < INT64_FIELDS_END; ++i) {
45 statement->BindInt64(index++, entry.ref(static_cast<Int64Field>(i)));
47 for ( ; i < TIME_FIELDS_END; ++i) {
48 statement->BindInt64(index++,
49 TimeToProtoTime(
50 entry.ref(static_cast<TimeField>(i))));
52 for ( ; i < ID_FIELDS_END; ++i) {
53 statement->BindString(index++, entry.ref(static_cast<IdField>(i)).s_);
55 for ( ; i < BIT_FIELDS_END; ++i) {
56 statement->BindInt(index++, entry.ref(static_cast<BitField>(i)));
58 for ( ; i < STRING_FIELDS_END; ++i) {
59 statement->BindString(index++, entry.ref(static_cast<StringField>(i)));
61 for ( ; i < PROTO_FIELDS_END; ++i) {
62 std::string temp;
63 entry.ref(static_cast<ProtoField>(i)).SerializeToString(&temp);
64 statement->BindBlob(index++, temp.data(), temp.length());
66 for ( ; i < UNIQUE_POSITION_FIELDS_END; ++i) {
67 std::string temp;
68 entry.ref(static_cast<UniquePositionField>(i)).SerializeToString(&temp);
69 statement->BindBlob(index++, temp.data(), temp.length());
71 for (; i < ATTACHMENT_METADATA_FIELDS_END; ++i) {
72 std::string temp;
73 entry.ref(static_cast<AttachmentMetadataField>(i)).SerializeToString(&temp);
74 statement->BindBlob(index++, temp.data(), temp.length());
78 // Helper function that loads a number of shareable fields of the
79 // same type. The sharing criteria is based on comparison of
80 // the serialized data. Only consecutive DB columns need to compared
81 // to cover all possible sharing combinations.
82 template <typename TValue, typename TField>
83 void UnpackProtoFields(sql::Statement* statement,
84 EntryKernel* kernel,
85 int* index,
86 int end_index) {
87 const void* prev_blob = nullptr;
88 int prev_length = -1;
89 int prev_index = -1;
91 for (; *index < end_index; ++(*index)) {
92 int length = statement->ColumnByteLength(*index);
93 if (length == 0) {
94 // Skip this column and keep the default value in the kernel field.
95 continue;
98 const void* blob = statement->ColumnBlob(*index);
99 // According to sqlite3 documentation, the prev_blob pointer should remain
100 // valid until moving to the next row.
101 if (length == prev_length && memcmp(blob, prev_blob, length) == 0) {
102 // Serialized values are the same - share the value from |prev_index|
103 // field with the current field.
104 kernel->copy(static_cast<TField>(prev_index),
105 static_cast<TField>(*index));
106 } else {
107 // Regular case - deserialize and copy the value to the field.
108 kernel->load(static_cast<TField>(*index), blob, length);
109 prev_blob = blob;
110 prev_length = length;
111 prev_index = *index;
116 // The caller owns the returned EntryKernel*. Assumes the statement currently
117 // points to a valid row in the metas table. Returns NULL to indicate that
118 // it detected a corruption in the data on unpacking.
119 scoped_ptr<EntryKernel> UnpackEntry(sql::Statement* statement) {
120 scoped_ptr<EntryKernel> kernel(new EntryKernel());
121 DCHECK_EQ(statement->ColumnCount(), static_cast<int>(FIELD_COUNT));
122 int i = 0;
123 for (i = BEGIN_FIELDS; i < INT64_FIELDS_END; ++i) {
124 kernel->put(static_cast<Int64Field>(i), statement->ColumnInt64(i));
126 for ( ; i < TIME_FIELDS_END; ++i) {
127 kernel->put(static_cast<TimeField>(i),
128 ProtoTimeToTime(statement->ColumnInt64(i)));
130 for ( ; i < ID_FIELDS_END; ++i) {
131 kernel->mutable_ref(static_cast<IdField>(i)).s_ =
132 statement->ColumnString(i);
134 for ( ; i < BIT_FIELDS_END; ++i) {
135 kernel->put(static_cast<BitField>(i), (0 != statement->ColumnInt(i)));
137 for ( ; i < STRING_FIELDS_END; ++i) {
138 kernel->put(static_cast<StringField>(i),
139 statement->ColumnString(i));
141 UnpackProtoFields<sync_pb::EntitySpecifics, ProtoField>(
142 statement, kernel.get(), &i, PROTO_FIELDS_END);
143 for ( ; i < UNIQUE_POSITION_FIELDS_END; ++i) {
144 std::string temp;
145 statement->ColumnBlobAsString(i, &temp);
147 sync_pb::UniquePosition proto;
148 if (!proto.ParseFromString(temp)) {
149 DVLOG(1) << "Unpacked invalid position. Assuming the DB is corrupt";
150 return scoped_ptr<EntryKernel>();
153 kernel->mutable_ref(static_cast<UniquePositionField>(i)) =
154 UniquePosition::FromProto(proto);
156 UnpackProtoFields<sync_pb::AttachmentMetadata, AttachmentMetadataField>(
157 statement, kernel.get(), &i, ATTACHMENT_METADATA_FIELDS_END);
159 // Sanity check on positions. We risk strange and rare crashes if our
160 // assumptions about unique position values are broken.
161 if (kernel->ShouldMaintainPosition() &&
162 !kernel->ref(UNIQUE_POSITION).IsValid()) {
163 DVLOG(1) << "Unpacked invalid position on an entity that should have a "
164 << "valid position. Assuming the DB is corrupt.";
165 return scoped_ptr<EntryKernel>();
168 return kernel.Pass();
171 namespace {
173 // This just has to be big enough to hold an UPDATE or INSERT statement that
174 // modifies all the columns in the entry table.
175 static const string::size_type kUpdateStatementBufferSize = 2048;
177 void OnSqliteError(const base::Closure& catastrophic_error_handler,
178 int err,
179 sql::Statement* statement) {
180 // An error has been detected. Ignore unless it is catastrophic.
181 if (sql::IsErrorCatastrophic(err)) {
182 // At this point sql::* and DirectoryBackingStore may be on the callstack so
183 // don't invoke the error handler directly. Instead, PostTask to this thread
184 // to avoid potential reentrancy issues.
185 base::MessageLoop::current()->PostTask(FROM_HERE,
186 catastrophic_error_handler);
190 string ComposeCreateTableColumnSpecs() {
191 const ColumnSpec* begin = g_metas_columns;
192 const ColumnSpec* end = g_metas_columns + arraysize(g_metas_columns);
193 string query;
194 query.reserve(kUpdateStatementBufferSize);
195 char separator = '(';
196 for (const ColumnSpec* column = begin; column != end; ++column) {
197 query.push_back(separator);
198 separator = ',';
199 query.append(column->name);
200 query.push_back(' ');
201 query.append(column->spec);
203 query.push_back(')');
204 return query;
207 void AppendColumnList(std::string* output) {
208 const char* joiner = " ";
209 // Be explicit in SELECT order to match up with UnpackEntry.
210 for (int i = BEGIN_FIELDS; i < FIELD_COUNT; ++i) {
211 output->append(joiner);
212 output->append(ColumnName(i));
213 joiner = ", ";
217 bool SaveEntryToDB(sql::Statement* save_statement, const EntryKernel& entry) {
218 save_statement->Reset(true);
219 BindFields(entry, save_statement);
220 return save_statement->Run();
223 } // namespace
225 ///////////////////////////////////////////////////////////////////////////////
226 // DirectoryBackingStore implementation.
228 DirectoryBackingStore::DirectoryBackingStore(const string& dir_name)
229 : dir_name_(dir_name),
230 database_page_size_(32768),
231 needs_column_refresh_(false) {
232 DCHECK(base::ThreadTaskRunnerHandle::IsSet());
233 ResetAndCreateConnection();
236 DirectoryBackingStore::DirectoryBackingStore(const string& dir_name,
237 sql::Connection* db)
238 : dir_name_(dir_name),
239 database_page_size_(32768),
240 db_(db),
241 needs_column_refresh_(false) {
242 DCHECK(base::ThreadTaskRunnerHandle::IsSet());
245 DirectoryBackingStore::~DirectoryBackingStore() {
248 bool DirectoryBackingStore::DeleteEntries(EntryTable from,
249 const MetahandleSet& handles) {
250 if (handles.empty())
251 return true;
253 sql::Statement statement;
254 // Call GetCachedStatement() separately to get different statements for
255 // different tables.
256 switch (from) {
257 case METAS_TABLE:
258 statement.Assign(db_->GetCachedStatement(
259 SQL_FROM_HERE, "DELETE FROM metas WHERE metahandle = ?"));
260 break;
261 case DELETE_JOURNAL_TABLE:
262 statement.Assign(db_->GetCachedStatement(
263 SQL_FROM_HERE, "DELETE FROM deleted_metas WHERE metahandle = ?"));
264 break;
267 for (MetahandleSet::const_iterator i = handles.begin(); i != handles.end();
268 ++i) {
269 statement.BindInt64(0, *i);
270 if (!statement.Run())
271 return false;
272 statement.Reset(true);
274 return true;
277 bool DirectoryBackingStore::SaveChanges(
278 const Directory::SaveChangesSnapshot& snapshot) {
279 DCHECK(CalledOnValidThread());
280 DCHECK(db_->is_open());
282 // Back out early if there is nothing to write.
283 bool save_info =
284 (Directory::KERNEL_SHARE_INFO_DIRTY == snapshot.kernel_info_status);
285 if (!snapshot.HasUnsavedMetahandleChanges() && !save_info) {
286 return true;
289 sql::Transaction transaction(db_.get());
290 if (!transaction.Begin())
291 return false;
293 PrepareSaveEntryStatement(METAS_TABLE, &save_meta_statement_);
294 for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin();
295 i != snapshot.dirty_metas.end(); ++i) {
296 DCHECK((*i)->is_dirty());
297 if (!SaveEntryToDB(&save_meta_statement_, **i))
298 return false;
301 if (!DeleteEntries(METAS_TABLE, snapshot.metahandles_to_purge))
302 return false;
304 PrepareSaveEntryStatement(DELETE_JOURNAL_TABLE,
305 &save_delete_journal_statement_);
306 for (EntryKernelSet::const_iterator i = snapshot.delete_journals.begin();
307 i != snapshot.delete_journals.end(); ++i) {
308 if (!SaveEntryToDB(&save_delete_journal_statement_, **i))
309 return false;
312 if (!DeleteEntries(DELETE_JOURNAL_TABLE, snapshot.delete_journals_to_purge))
313 return false;
315 if (save_info) {
316 const Directory::PersistedKernelInfo& info = snapshot.kernel_info;
317 sql::Statement s1(db_->GetCachedStatement(
318 SQL_FROM_HERE,
319 "UPDATE share_info "
320 "SET store_birthday = ?, "
321 "bag_of_chips = ?"));
322 s1.BindString(0, info.store_birthday);
323 s1.BindBlob(1, info.bag_of_chips.data(), info.bag_of_chips.size());
325 if (!s1.Run())
326 return false;
327 DCHECK_EQ(db_->GetLastChangeCount(), 1);
329 sql::Statement s2(db_->GetCachedStatement(
330 SQL_FROM_HERE,
331 "INSERT OR REPLACE "
332 "INTO models (model_id, "
333 "progress_marker, "
334 "transaction_version, "
335 "context) "
336 "VALUES (?, ?, ?, ?)"));
338 ModelTypeSet protocol_types = ProtocolTypes();
339 for (ModelTypeSet::Iterator iter = protocol_types.First(); iter.Good();
340 iter.Inc()) {
341 ModelType type = iter.Get();
342 // We persist not ModelType but rather a protobuf-derived ID.
343 string model_id = ModelTypeEnumToModelId(type);
344 string progress_marker;
345 info.download_progress[type].SerializeToString(&progress_marker);
346 s2.BindBlob(0, model_id.data(), model_id.length());
347 s2.BindBlob(1, progress_marker.data(), progress_marker.length());
348 s2.BindInt64(2, info.transaction_version[type]);
349 string context;
350 info.datatype_context[type].SerializeToString(&context);
351 s2.BindBlob(3, context.data(), context.length());
352 if (!s2.Run())
353 return false;
354 DCHECK_EQ(db_->GetLastChangeCount(), 1);
355 s2.Reset(true);
359 return transaction.Commit();
362 sql::Connection* DirectoryBackingStore::db() {
363 return db_.get();
366 bool DirectoryBackingStore::IsOpen() const {
367 return db_->is_open();
370 bool DirectoryBackingStore::Open(const base::FilePath& path) {
371 DCHECK(!db_->is_open());
372 return db_->Open(path);
375 bool DirectoryBackingStore::OpenInMemory() {
376 DCHECK(!db_->is_open());
377 return db_->OpenInMemory();
380 bool DirectoryBackingStore::InitializeTables() {
381 int page_size = 0;
382 if (GetDatabasePageSize(&page_size) && page_size == 4096) {
383 IncreasePageSizeTo32K();
385 sql::Transaction transaction(db_.get());
386 if (!transaction.Begin())
387 return false;
389 int version_on_disk = GetVersion();
391 // Upgrade from version 67. Version 67 was widely distributed as the original
392 // Bookmark Sync release. Version 68 removed unique naming.
393 if (version_on_disk == 67) {
394 if (MigrateVersion67To68())
395 version_on_disk = 68;
397 // Version 69 introduced additional datatypes.
398 if (version_on_disk == 68) {
399 if (MigrateVersion68To69())
400 version_on_disk = 69;
403 if (version_on_disk == 69) {
404 if (MigrateVersion69To70())
405 version_on_disk = 70;
408 // Version 71 changed the sync progress information to be per-datatype.
409 if (version_on_disk == 70) {
410 if (MigrateVersion70To71())
411 version_on_disk = 71;
414 // Version 72 removed extended attributes, a legacy way to do extensible
415 // key/value information, stored in their own table.
416 if (version_on_disk == 71) {
417 if (MigrateVersion71To72())
418 version_on_disk = 72;
421 // Version 73 added a field for notification state.
422 if (version_on_disk == 72) {
423 if (MigrateVersion72To73())
424 version_on_disk = 73;
427 // Version 74 added state for the autofill migration.
428 if (version_on_disk == 73) {
429 if (MigrateVersion73To74())
430 version_on_disk = 74;
433 // Version 75 migrated from int64-based timestamps to per-datatype tokens.
434 if (version_on_disk == 74) {
435 if (MigrateVersion74To75())
436 version_on_disk = 75;
439 // Version 76 removed all (5) autofill migration related columns.
440 if (version_on_disk == 75) {
441 if (MigrateVersion75To76())
442 version_on_disk = 76;
445 // Version 77 standardized all time fields to ms since the Unix
446 // epoch.
447 if (version_on_disk == 76) {
448 if (MigrateVersion76To77())
449 version_on_disk = 77;
452 // Version 78 added the column base_server_specifics to the metas table.
453 if (version_on_disk == 77) {
454 if (MigrateVersion77To78())
455 version_on_disk = 78;
458 // Version 79 migration is a one-time fix for some users in a bad state.
459 if (version_on_disk == 78) {
460 if (MigrateVersion78To79())
461 version_on_disk = 79;
464 // Version 80 migration is adding the bag_of_chips column.
465 if (version_on_disk == 79) {
466 if (MigrateVersion79To80())
467 version_on_disk = 80;
470 // Version 81 replaces the int64 server_position_in_parent_field
471 // with a blob server_ordinal_in_parent field.
472 if (version_on_disk == 80) {
473 if (MigrateVersion80To81())
474 version_on_disk = 81;
477 // Version 82 migration added transaction_version column per data type.
478 if (version_on_disk == 81) {
479 if (MigrateVersion81To82())
480 version_on_disk = 82;
483 // Version 83 migration added transaction_version column per sync entry.
484 if (version_on_disk == 82) {
485 if (MigrateVersion82To83())
486 version_on_disk = 83;
489 // Version 84 migration added deleted_metas table.
490 if (version_on_disk == 83) {
491 if (MigrateVersion83To84())
492 version_on_disk = 84;
495 // Version 85 migration removes the initial_sync_ended bits.
496 if (version_on_disk == 84) {
497 if (MigrateVersion84To85())
498 version_on_disk = 85;
501 // Version 86 migration converts bookmarks to the unique positioning system.
502 // It also introduces a new field to store a unique ID for each bookmark.
503 if (version_on_disk == 85) {
504 if (MigrateVersion85To86())
505 version_on_disk = 86;
508 // Version 87 migration adds a collection of attachment ids per sync entry.
509 if (version_on_disk == 86) {
510 if (MigrateVersion86To87())
511 version_on_disk = 87;
514 // Version 88 migration adds datatype contexts to the models table.
515 if (version_on_disk == 87) {
516 if (MigrateVersion87To88())
517 version_on_disk = 88;
520 // Version 89 migration adds server attachment metadata to the metas table.
521 if (version_on_disk == 88) {
522 if (MigrateVersion88To89())
523 version_on_disk = 89;
526 // If one of the migrations requested it, drop columns that aren't current.
527 // It's only safe to do this after migrating all the way to the current
528 // version.
529 if (version_on_disk == kCurrentDBVersion && needs_column_refresh_) {
530 if (!RefreshColumns())
531 version_on_disk = 0;
534 // A final, alternative catch-all migration to simply re-sync everything.
535 if (version_on_disk != kCurrentDBVersion) {
536 if (version_on_disk > kCurrentDBVersion)
537 return false;
539 // Fallback (re-sync everything) migration path.
540 DVLOG(1) << "Old/null sync database, version " << version_on_disk;
541 // Delete the existing database (if any), and create a fresh one.
542 DropAllTables();
543 if (!CreateTables())
544 return false;
547 sql::Statement s(db_->GetUniqueStatement(
548 "SELECT db_create_version, db_create_time FROM share_info"));
549 if (!s.Step())
550 return false;
551 string db_create_version = s.ColumnString(0);
552 int db_create_time = s.ColumnInt(1);
553 DVLOG(1) << "DB created at " << db_create_time << " by version " <<
554 db_create_version;
556 return transaction.Commit();
559 // This function drops unused columns by creating a new table that contains only
560 // the currently used columns then copying all rows from the old tables into
561 // this new one. The tables are then rearranged so the new replaces the old.
562 bool DirectoryBackingStore::RefreshColumns() {
563 DCHECK(needs_column_refresh_);
565 // Create a new table named temp_metas.
566 SafeDropTable("temp_metas");
567 if (!CreateMetasTable(true))
568 return false;
570 // Populate temp_metas from metas.
572 // At this point, the metas table may contain columns belonging to obsolete
573 // schema versions. This statement explicitly lists only the columns that
574 // belong to the current schema version, so the obsolete columns will be
575 // effectively dropped once we rename temp_metas over top of metas.
576 std::string query = "INSERT INTO temp_metas (";
577 AppendColumnList(&query);
578 query.append(") SELECT ");
579 AppendColumnList(&query);
580 query.append(" FROM metas");
581 if (!db_->Execute(query.c_str()))
582 return false;
584 // Drop metas.
585 SafeDropTable("metas");
587 // Rename temp_metas -> metas.
588 if (!db_->Execute("ALTER TABLE temp_metas RENAME TO metas"))
589 return false;
591 // Repeat the process for share_info.
592 SafeDropTable("temp_share_info");
593 if (!CreateShareInfoTable(true))
594 return false;
596 // TODO(rlarocque, 124140): Remove notification_state.
597 if (!db_->Execute(
598 "INSERT INTO temp_share_info (id, name, store_birthday, "
599 "db_create_version, db_create_time, next_id, cache_guid,"
600 "notification_state, bag_of_chips) "
601 "SELECT id, name, store_birthday, db_create_version, "
602 "db_create_time, next_id, cache_guid, notification_state, "
603 "bag_of_chips "
604 "FROM share_info"))
605 return false;
607 SafeDropTable("share_info");
608 if (!db_->Execute("ALTER TABLE temp_share_info RENAME TO share_info"))
609 return false;
611 needs_column_refresh_ = false;
612 return true;
615 bool DirectoryBackingStore::LoadEntries(Directory::MetahandlesMap* handles_map,
616 MetahandleSet* metahandles_to_purge) {
617 string select;
618 select.reserve(kUpdateStatementBufferSize);
619 select.append("SELECT ");
620 AppendColumnList(&select);
621 select.append(" FROM metas");
623 sql::Statement s(db_->GetUniqueStatement(select.c_str()));
625 while (s.Step()) {
626 scoped_ptr<EntryKernel> kernel = UnpackEntry(&s);
627 // A null kernel is evidence of external data corruption.
628 if (!kernel)
629 return false;
631 int64 handle = kernel->ref(META_HANDLE);
632 if (SafeToPurgeOnLoading(*kernel))
633 metahandles_to_purge->insert(handle);
634 else
635 (*handles_map)[handle] = kernel.release();
637 return s.Succeeded();
640 bool DirectoryBackingStore::SafeToPurgeOnLoading(
641 const EntryKernel& entry) const {
642 if (entry.ref(IS_DEL)) {
643 if (!entry.ref(IS_UNSYNCED) && !entry.ref(IS_UNAPPLIED_UPDATE))
644 return true;
645 else if (!entry.ref(ID).ServerKnows())
646 return true;
648 return false;
651 bool DirectoryBackingStore::LoadDeleteJournals(
652 JournalIndex* delete_journals) {
653 string select;
654 select.reserve(kUpdateStatementBufferSize);
655 select.append("SELECT ");
656 AppendColumnList(&select);
657 select.append(" FROM deleted_metas");
659 sql::Statement s(db_->GetUniqueStatement(select.c_str()));
661 while (s.Step()) {
662 scoped_ptr<EntryKernel> kernel = UnpackEntry(&s);
663 // A null kernel is evidence of external data corruption.
664 if (!kernel)
665 return false;
666 delete_journals->insert(kernel.release());
668 return s.Succeeded();
671 bool DirectoryBackingStore::LoadInfo(Directory::KernelLoadInfo* info) {
673 sql::Statement s(db_->GetUniqueStatement(
674 "SELECT store_birthday, cache_guid, bag_of_chips "
675 "FROM share_info"));
676 if (!s.Step())
677 return false;
679 info->kernel_info.store_birthday = s.ColumnString(0);
680 info->cache_guid = s.ColumnString(1);
681 s.ColumnBlobAsString(2, &(info->kernel_info.bag_of_chips));
683 // Verify there was only one row returned.
684 DCHECK(!s.Step());
685 DCHECK(s.Succeeded());
689 sql::Statement s(
690 db_->GetUniqueStatement(
691 "SELECT model_id, progress_marker, "
692 "transaction_version, context FROM models"));
694 while (s.Step()) {
695 ModelType type = ModelIdToModelTypeEnum(s.ColumnBlob(0),
696 s.ColumnByteLength(0));
697 if (type != UNSPECIFIED && type != TOP_LEVEL_FOLDER) {
698 info->kernel_info.download_progress[type].ParseFromArray(
699 s.ColumnBlob(1), s.ColumnByteLength(1));
700 info->kernel_info.transaction_version[type] = s.ColumnInt64(2);
701 info->kernel_info.datatype_context[type].ParseFromArray(
702 s.ColumnBlob(3), s.ColumnByteLength(3));
705 if (!s.Succeeded())
706 return false;
709 sql::Statement s(
710 db_->GetUniqueStatement(
711 "SELECT MAX(metahandle) FROM metas"));
712 if (!s.Step())
713 return false;
715 info->max_metahandle = s.ColumnInt64(0);
717 // Verify only one row was returned.
718 DCHECK(!s.Step());
719 DCHECK(s.Succeeded());
721 return true;
724 bool DirectoryBackingStore::SafeDropTable(const char* table_name) {
725 string query = "DROP TABLE IF EXISTS ";
726 query.append(table_name);
727 return db_->Execute(query.c_str());
730 void DirectoryBackingStore::DropAllTables() {
731 SafeDropTable("metas");
732 SafeDropTable("temp_metas");
733 SafeDropTable("share_info");
734 SafeDropTable("temp_share_info");
735 SafeDropTable("share_version");
736 SafeDropTable("extended_attributes");
737 SafeDropTable("models");
738 SafeDropTable("temp_models");
739 needs_column_refresh_ = false;
742 // static
743 ModelType DirectoryBackingStore::ModelIdToModelTypeEnum(
744 const void* data, int size) {
745 sync_pb::EntitySpecifics specifics;
746 if (!specifics.ParseFromArray(data, size))
747 return UNSPECIFIED;
748 return GetModelTypeFromSpecifics(specifics);
751 // static
752 string DirectoryBackingStore::ModelTypeEnumToModelId(ModelType model_type) {
753 sync_pb::EntitySpecifics specifics;
754 AddDefaultFieldValue(model_type, &specifics);
755 return specifics.SerializeAsString();
758 // static
759 std::string DirectoryBackingStore::GenerateCacheGUID() {
760 // Generate a GUID with 128 bits of randomness.
761 const int kGuidBytes = 128 / 8;
762 std::string guid;
763 base::Base64Encode(base::RandBytesAsString(kGuidBytes), &guid);
764 return guid;
767 bool DirectoryBackingStore::MigrateToSpecifics(
768 const char* old_columns,
769 const char* specifics_column,
770 void (*handler_function)(sql::Statement* old_value_query,
771 int old_value_column,
772 sync_pb::EntitySpecifics* mutable_new_value)) {
773 std::string query_sql = base::StringPrintf(
774 "SELECT metahandle, %s, %s FROM metas", specifics_column, old_columns);
775 std::string update_sql = base::StringPrintf(
776 "UPDATE metas SET %s = ? WHERE metahandle = ?", specifics_column);
778 sql::Statement query(db_->GetUniqueStatement(query_sql.c_str()));
779 sql::Statement update(db_->GetUniqueStatement(update_sql.c_str()));
781 while (query.Step()) {
782 int64 metahandle = query.ColumnInt64(0);
783 std::string new_value_bytes;
784 query.ColumnBlobAsString(1, &new_value_bytes);
785 sync_pb::EntitySpecifics new_value;
786 new_value.ParseFromString(new_value_bytes);
787 handler_function(&query, 2, &new_value);
788 new_value.SerializeToString(&new_value_bytes);
790 update.BindBlob(0, new_value_bytes.data(), new_value_bytes.length());
791 update.BindInt64(1, metahandle);
792 if (!update.Run())
793 return false;
794 update.Reset(true);
796 return query.Succeeded();
799 bool DirectoryBackingStore::SetVersion(int version) {
800 sql::Statement s(db_->GetCachedStatement(
801 SQL_FROM_HERE, "UPDATE share_version SET data = ?"));
802 s.BindInt(0, version);
804 return s.Run();
807 int DirectoryBackingStore::GetVersion() {
808 if (!db_->DoesTableExist("share_version"))
809 return 0;
811 sql::Statement statement(db_->GetUniqueStatement(
812 "SELECT data FROM share_version"));
813 if (statement.Step()) {
814 return statement.ColumnInt(0);
815 } else {
816 return 0;
820 bool DirectoryBackingStore::MigrateVersion67To68() {
821 // This change simply removed three columns:
822 // string NAME
823 // string UNSANITIZED_NAME
824 // string SERVER_NAME
825 // No data migration is necessary, but we should do a column refresh.
826 SetVersion(68);
827 needs_column_refresh_ = true;
828 return true;
831 bool DirectoryBackingStore::MigrateVersion69To70() {
832 // Added "unique_client_tag", renamed "singleton_tag" to unique_server_tag
833 SetVersion(70);
834 if (!db_->Execute(
835 "ALTER TABLE metas ADD COLUMN unique_server_tag varchar"))
836 return false;
837 if (!db_->Execute(
838 "ALTER TABLE metas ADD COLUMN unique_client_tag varchar"))
839 return false;
840 needs_column_refresh_ = true;
842 if (!db_->Execute(
843 "UPDATE metas SET unique_server_tag = singleton_tag"))
844 return false;
846 return true;
849 namespace {
851 // Callback passed to MigrateToSpecifics for the v68->v69 migration. See
852 // MigrateVersion68To69().
853 void EncodeBookmarkURLAndFavicon(sql::Statement* old_value_query,
854 int old_value_column,
855 sync_pb::EntitySpecifics* mutable_new_value) {
856 // Extract data from the column trio we expect.
857 bool old_is_bookmark_object = old_value_query->ColumnBool(old_value_column);
858 std::string old_url = old_value_query->ColumnString(old_value_column + 1);
859 std::string old_favicon;
860 old_value_query->ColumnBlobAsString(old_value_column + 2, &old_favicon);
861 bool old_is_dir = old_value_query->ColumnBool(old_value_column + 3);
863 if (old_is_bookmark_object) {
864 sync_pb::BookmarkSpecifics* bookmark_data =
865 mutable_new_value->mutable_bookmark();
866 if (!old_is_dir) {
867 bookmark_data->set_url(old_url);
868 bookmark_data->set_favicon(old_favicon);
873 } // namespace
875 bool DirectoryBackingStore::MigrateVersion68To69() {
876 // In Version 68, there were columns on table 'metas':
877 // string BOOKMARK_URL
878 // string SERVER_BOOKMARK_URL
879 // blob BOOKMARK_FAVICON
880 // blob SERVER_BOOKMARK_FAVICON
881 // In version 69, these columns went away in favor of storing
882 // a serialized EntrySpecifics protobuf in the columns:
883 // protobuf blob SPECIFICS
884 // protobuf blob SERVER_SPECIFICS
885 // For bookmarks, EntrySpecifics is extended as per
886 // bookmark_specifics.proto. This migration converts bookmarks from the
887 // former scheme to the latter scheme.
889 // First, add the two new columns to the schema.
890 if (!db_->Execute(
891 "ALTER TABLE metas ADD COLUMN specifics blob"))
892 return false;
893 if (!db_->Execute(
894 "ALTER TABLE metas ADD COLUMN server_specifics blob"))
895 return false;
897 // Next, fold data from the old columns into the new protobuf columns.
898 if (!MigrateToSpecifics(("is_bookmark_object, bookmark_url, "
899 "bookmark_favicon, is_dir"),
900 "specifics",
901 &EncodeBookmarkURLAndFavicon)) {
902 return false;
904 if (!MigrateToSpecifics(("server_is_bookmark_object, "
905 "server_bookmark_url, "
906 "server_bookmark_favicon, "
907 "server_is_dir"),
908 "server_specifics",
909 &EncodeBookmarkURLAndFavicon)) {
910 return false;
913 // Lastly, fix up the "Google Chrome" folder, which is of the TOP_LEVEL_FOLDER
914 // ModelType: it shouldn't have BookmarkSpecifics.
915 if (!db_->Execute(
916 "UPDATE metas SET specifics = NULL, server_specifics = NULL WHERE "
917 "singleton_tag IN ('google_chrome')"))
918 return false;
920 SetVersion(69);
921 needs_column_refresh_ = true; // Trigger deletion of old columns.
922 return true;
925 // Version 71, the columns 'initial_sync_ended' and 'last_sync_timestamp'
926 // were removed from the share_info table. They were replaced by
927 // the 'models' table, which has these values on a per-datatype basis.
928 bool DirectoryBackingStore::MigrateVersion70To71() {
929 if (!CreateV71ModelsTable())
930 return false;
932 // Move data from the old share_info columns to the new models table.
934 sql::Statement fetch(db_->GetUniqueStatement(
935 "SELECT last_sync_timestamp, initial_sync_ended FROM share_info"));
936 if (!fetch.Step())
937 return false;
939 int64 last_sync_timestamp = fetch.ColumnInt64(0);
940 bool initial_sync_ended = fetch.ColumnBool(1);
942 // Verify there were no additional rows returned.
943 DCHECK(!fetch.Step());
944 DCHECK(fetch.Succeeded());
946 sql::Statement update(db_->GetUniqueStatement(
947 "INSERT INTO models (model_id, "
948 "last_download_timestamp, initial_sync_ended) VALUES (?, ?, ?)"));
949 string bookmark_model_id = ModelTypeEnumToModelId(BOOKMARKS);
950 update.BindBlob(0, bookmark_model_id.data(), bookmark_model_id.size());
951 update.BindInt64(1, last_sync_timestamp);
952 update.BindBool(2, initial_sync_ended);
954 if (!update.Run())
955 return false;
958 // Drop the columns from the old share_info table via a temp table.
959 const bool kCreateAsTempShareInfo = true;
961 if (!CreateShareInfoTableVersion71(kCreateAsTempShareInfo))
962 return false;
963 if (!db_->Execute(
964 "INSERT INTO temp_share_info (id, name, store_birthday, "
965 "db_create_version, db_create_time, next_id, cache_guid) "
966 "SELECT id, name, store_birthday, db_create_version, "
967 "db_create_time, next_id, cache_guid FROM share_info"))
968 return false;
969 SafeDropTable("share_info");
970 if (!db_->Execute(
971 "ALTER TABLE temp_share_info RENAME TO share_info"))
972 return false;
973 SetVersion(71);
974 return true;
977 bool DirectoryBackingStore::MigrateVersion71To72() {
978 // Version 72 removed a table 'extended_attributes', whose
979 // contents didn't matter.
980 SafeDropTable("extended_attributes");
981 SetVersion(72);
982 return true;
985 bool DirectoryBackingStore::MigrateVersion72To73() {
986 // Version 73 added one column to the table 'share_info': notification_state
987 if (!db_->Execute(
988 "ALTER TABLE share_info ADD COLUMN notification_state BLOB"))
989 return false;
990 SetVersion(73);
991 return true;
994 bool DirectoryBackingStore::MigrateVersion73To74() {
995 // Version 74 added the following columns to the table 'share_info':
996 // autofill_migration_state
997 // bookmarks_added_during_autofill_migration
998 // autofill_migration_time
999 // autofill_entries_added_during_migration
1000 // autofill_profiles_added_during_migration
1002 if (!db_->Execute(
1003 "ALTER TABLE share_info ADD COLUMN "
1004 "autofill_migration_state INT default 0"))
1005 return false;
1007 if (!db_->Execute(
1008 "ALTER TABLE share_info ADD COLUMN "
1009 "bookmarks_added_during_autofill_migration "
1010 "INT default 0"))
1011 return false;
1013 if (!db_->Execute(
1014 "ALTER TABLE share_info ADD COLUMN autofill_migration_time "
1015 "INT default 0"))
1016 return false;
1018 if (!db_->Execute(
1019 "ALTER TABLE share_info ADD COLUMN "
1020 "autofill_entries_added_during_migration "
1021 "INT default 0"))
1022 return false;
1024 if (!db_->Execute(
1025 "ALTER TABLE share_info ADD COLUMN "
1026 "autofill_profiles_added_during_migration "
1027 "INT default 0"))
1028 return false;
1030 SetVersion(74);
1031 return true;
1034 bool DirectoryBackingStore::MigrateVersion74To75() {
1035 // In version 74, there was a table 'models':
1036 // blob model_id (entity specifics, primary key)
1037 // int last_download_timestamp
1038 // boolean initial_sync_ended
1039 // In version 75, we deprecated the integer-valued last_download_timestamp,
1040 // using insted a protobuf-valued progress_marker field:
1041 // blob progress_marker
1042 // The progress_marker values are initialized from the value of
1043 // last_download_timestamp, thereby preserving the download state.
1045 // Move aside the old table and create a new empty one at the current schema.
1046 if (!db_->Execute("ALTER TABLE models RENAME TO temp_models"))
1047 return false;
1048 if (!CreateV75ModelsTable())
1049 return false;
1051 sql::Statement query(db_->GetUniqueStatement(
1052 "SELECT model_id, last_download_timestamp, initial_sync_ended "
1053 "FROM temp_models"));
1055 sql::Statement update(db_->GetUniqueStatement(
1056 "INSERT INTO models (model_id, "
1057 "progress_marker, initial_sync_ended) VALUES (?, ?, ?)"));
1059 while (query.Step()) {
1060 ModelType type = ModelIdToModelTypeEnum(query.ColumnBlob(0),
1061 query.ColumnByteLength(0));
1062 if (type != UNSPECIFIED) {
1063 // Set the |timestamp_token_for_migration| on a new
1064 // DataTypeProgressMarker, using the old value of last_download_timestamp.
1065 // The server will turn this into a real token on our behalf the next
1066 // time we check for updates.
1067 sync_pb::DataTypeProgressMarker progress_marker;
1068 progress_marker.set_data_type_id(
1069 GetSpecificsFieldNumberFromModelType(type));
1070 progress_marker.set_timestamp_token_for_migration(query.ColumnInt64(1));
1071 std::string progress_blob;
1072 progress_marker.SerializeToString(&progress_blob);
1074 update.BindBlob(0, query.ColumnBlob(0), query.ColumnByteLength(0));
1075 update.BindBlob(1, progress_blob.data(), progress_blob.length());
1076 update.BindBool(2, query.ColumnBool(2));
1077 if (!update.Run())
1078 return false;
1079 update.Reset(true);
1082 if (!query.Succeeded())
1083 return false;
1085 // Drop the old table.
1086 SafeDropTable("temp_models");
1088 SetVersion(75);
1089 return true;
1092 bool DirectoryBackingStore::MigrateVersion75To76() {
1093 // This change removed five columns:
1094 // autofill_migration_state
1095 // bookmarks_added_during_autofill_migration
1096 // autofill_migration_time
1097 // autofill_entries_added_during_migration
1098 // autofill_profiles_added_during_migration
1099 // No data migration is necessary, but we should do a column refresh.
1100 SetVersion(76);
1101 needs_column_refresh_ = true;
1102 return true;
1105 bool DirectoryBackingStore::MigrateVersion76To77() {
1106 // This change changes the format of stored timestamps to ms since
1107 // the Unix epoch.
1108 #if defined(OS_WIN)
1109 // On Windows, we used to store timestamps in FILETIME format (100s of
1110 // ns since Jan 1, 1601). Magic numbers taken from
1111 // http://stackoverflow.com/questions/5398557/
1112 // java-library-for-dealing-with-win32-filetime
1113 // .
1114 #define TO_UNIX_TIME_MS(x) #x " = " #x " / 10000 - 11644473600000"
1115 #else
1116 // On other platforms, we used to store timestamps in time_t format (s
1117 // since the Unix epoch).
1118 #define TO_UNIX_TIME_MS(x) #x " = " #x " * 1000"
1119 #endif
1120 sql::Statement update_timestamps(db_->GetUniqueStatement(
1121 "UPDATE metas SET "
1122 TO_UNIX_TIME_MS(mtime) ", "
1123 TO_UNIX_TIME_MS(server_mtime) ", "
1124 TO_UNIX_TIME_MS(ctime) ", "
1125 TO_UNIX_TIME_MS(server_ctime)));
1126 #undef TO_UNIX_TIME_MS
1127 if (!update_timestamps.Run())
1128 return false;
1129 SetVersion(77);
1130 return true;
1133 bool DirectoryBackingStore::MigrateVersion77To78() {
1134 // Version 78 added one column to table 'metas': base_server_specifics.
1135 if (!db_->Execute(
1136 "ALTER TABLE metas ADD COLUMN base_server_specifics BLOB")) {
1137 return false;
1139 SetVersion(78);
1140 return true;
1143 bool DirectoryBackingStore::MigrateVersion78To79() {
1144 // Some users are stuck with a DB that causes them to reuse existing IDs. We
1145 // perform this one-time fixup on all users to help the few that are stuck.
1146 // See crbug.com/142987 for details.
1147 if (!db_->Execute(
1148 "UPDATE share_info SET next_id = next_id - 65536")) {
1149 return false;
1151 SetVersion(79);
1152 return true;
1155 bool DirectoryBackingStore::MigrateVersion79To80() {
1156 if (!db_->Execute(
1157 "ALTER TABLE share_info ADD COLUMN bag_of_chips BLOB"))
1158 return false;
1159 sql::Statement update(db_->GetUniqueStatement(
1160 "UPDATE share_info SET bag_of_chips = ?"));
1161 // An empty message is serialized to an empty string.
1162 update.BindBlob(0, NULL, 0);
1163 if (!update.Run())
1164 return false;
1165 SetVersion(80);
1166 return true;
1169 bool DirectoryBackingStore::MigrateVersion80To81() {
1170 if(!db_->Execute(
1171 "ALTER TABLE metas ADD COLUMN server_ordinal_in_parent BLOB"))
1172 return false;
1174 sql::Statement get_positions(db_->GetUniqueStatement(
1175 "SELECT metahandle, server_position_in_parent FROM metas"));
1177 sql::Statement put_ordinals(db_->GetUniqueStatement(
1178 "UPDATE metas SET server_ordinal_in_parent = ?"
1179 "WHERE metahandle = ?"));
1181 while(get_positions.Step()) {
1182 int64 metahandle = get_positions.ColumnInt64(0);
1183 int64 position = get_positions.ColumnInt64(1);
1185 const std::string& ordinal = Int64ToNodeOrdinal(position).ToInternalValue();
1186 put_ordinals.BindBlob(0, ordinal.data(), ordinal.length());
1187 put_ordinals.BindInt64(1, metahandle);
1189 if(!put_ordinals.Run())
1190 return false;
1191 put_ordinals.Reset(true);
1194 SetVersion(81);
1195 needs_column_refresh_ = true;
1196 return true;
1199 bool DirectoryBackingStore::MigrateVersion81To82() {
1200 if (!db_->Execute(
1201 "ALTER TABLE models ADD COLUMN transaction_version BIGINT default 0"))
1202 return false;
1203 sql::Statement update(db_->GetUniqueStatement(
1204 "UPDATE models SET transaction_version = 0"));
1205 if (!update.Run())
1206 return false;
1207 SetVersion(82);
1208 return true;
1211 bool DirectoryBackingStore::MigrateVersion82To83() {
1212 // Version 83 added transaction_version on sync node.
1213 if (!db_->Execute(
1214 "ALTER TABLE metas ADD COLUMN transaction_version BIGINT default 0"))
1215 return false;
1216 sql::Statement update(db_->GetUniqueStatement(
1217 "UPDATE metas SET transaction_version = 0"));
1218 if (!update.Run())
1219 return false;
1220 SetVersion(83);
1221 return true;
1224 bool DirectoryBackingStore::MigrateVersion83To84() {
1225 // Version 84 added deleted_metas table to store deleted metas until we know
1226 // for sure that the deletions are persisted in native models.
1227 string query = "CREATE TABLE deleted_metas ";
1228 query.append(ComposeCreateTableColumnSpecs());
1229 if (!db_->Execute(query.c_str()))
1230 return false;
1231 SetVersion(84);
1232 return true;
1235 bool DirectoryBackingStore::MigrateVersion84To85() {
1236 // Version 85 removes the initial_sync_ended flag.
1237 if (!db_->Execute("ALTER TABLE models RENAME TO temp_models"))
1238 return false;
1239 if (!CreateV81ModelsTable())
1240 return false;
1241 if (!db_->Execute("INSERT INTO models SELECT "
1242 "model_id, progress_marker, transaction_version "
1243 "FROM temp_models")) {
1244 return false;
1246 SafeDropTable("temp_models");
1248 SetVersion(85);
1249 return true;
1252 bool DirectoryBackingStore::MigrateVersion85To86() {
1253 // Version 86 removes both server ordinals and local NEXT_ID, PREV_ID and
1254 // SERVER_{POSITION,ORDINAL}_IN_PARENT and replaces them with UNIQUE_POSITION
1255 // and SERVER_UNIQUE_POSITION.
1256 if (!db_->Execute("ALTER TABLE metas ADD COLUMN "
1257 "server_unique_position BLOB")) {
1258 return false;
1260 if (!db_->Execute("ALTER TABLE metas ADD COLUMN "
1261 "unique_position BLOB")) {
1262 return false;
1264 if (!db_->Execute("ALTER TABLE metas ADD COLUMN "
1265 "unique_bookmark_tag VARCHAR")) {
1266 return false;
1269 // Fetch the cache_guid from the DB, because we don't otherwise have access to
1270 // it from here.
1271 sql::Statement get_cache_guid(db_->GetUniqueStatement(
1272 "SELECT cache_guid FROM share_info"));
1273 if (!get_cache_guid.Step()) {
1274 return false;
1276 std::string cache_guid = get_cache_guid.ColumnString(0);
1277 DCHECK(!get_cache_guid.Step());
1278 DCHECK(get_cache_guid.Succeeded());
1280 sql::Statement get(db_->GetUniqueStatement(
1281 "SELECT "
1282 " metahandle, "
1283 " id, "
1284 " specifics, "
1285 " is_dir, "
1286 " unique_server_tag, "
1287 " server_ordinal_in_parent "
1288 "FROM metas"));
1290 // Note that we set both the local and server position based on the server
1291 // position. We wll lose any unsynced local position changes. Unfortunately,
1292 // there's nothing we can do to avoid that. The NEXT_ID / PREV_ID values
1293 // can't be translated into a UNIQUE_POSTION in a reliable way.
1294 sql::Statement put(db_->GetCachedStatement(
1295 SQL_FROM_HERE,
1296 "UPDATE metas SET"
1297 " server_unique_position = ?,"
1298 " unique_position = ?,"
1299 " unique_bookmark_tag = ?"
1300 "WHERE metahandle = ?"));
1302 while (get.Step()) {
1303 int64 metahandle = get.ColumnInt64(0);
1305 std::string id_string;
1306 get.ColumnBlobAsString(1, &id_string);
1308 sync_pb::EntitySpecifics specifics;
1309 specifics.ParseFromArray(
1310 get.ColumnBlob(2), get.ColumnByteLength(2));
1312 bool is_dir = get.ColumnBool(3);
1314 std::string server_unique_tag = get.ColumnString(4);
1316 std::string ordinal_string;
1317 get.ColumnBlobAsString(5, &ordinal_string);
1318 NodeOrdinal ordinal(ordinal_string);
1321 std::string unique_bookmark_tag;
1323 // We only maintain positions for bookmarks that are not server-defined
1324 // top-level folders.
1325 UniquePosition position;
1326 if (GetModelTypeFromSpecifics(specifics) == BOOKMARKS
1327 && !(is_dir && !server_unique_tag.empty())) {
1328 if (id_string.at(0) == 'c') {
1329 // We found an uncommitted item. This is rare, but fortunate. This
1330 // means we can set the bookmark tag according to the originator client
1331 // item ID and originator cache guid, because (unlike the other case) we
1332 // know that this client is the originator.
1333 unique_bookmark_tag = syncable::GenerateSyncableBookmarkHash(
1334 cache_guid,
1335 id_string.substr(1));
1336 } else {
1337 // If we've already committed the item, then we don't know who the
1338 // originator was. We do not have access to the originator client item
1339 // ID and originator cache guid at this point.
1341 // We will base our hash entirely on the server ID instead. This is
1342 // incorrect, but at least all clients that undergo this migration step
1343 // will be incorrect in the same way.
1345 // To get everyone back into a synced state, we will update the bookmark
1346 // tag according to the originator_cache_guid and originator_item_id
1347 // when we see updates for this item. That should ensure that commonly
1348 // modified items will end up with the proper tag values eventually.
1349 unique_bookmark_tag = syncable::GenerateSyncableBookmarkHash(
1350 std::string(), // cache_guid left intentionally blank.
1351 id_string.substr(1));
1354 int64 int_position = NodeOrdinalToInt64(ordinal);
1355 position = UniquePosition::FromInt64(int_position, unique_bookmark_tag);
1356 } else {
1357 // Leave bookmark_tag and position at their default (invalid) values.
1360 std::string position_blob;
1361 position.SerializeToString(&position_blob);
1362 put.BindBlob(0, position_blob.data(), position_blob.length());
1363 put.BindBlob(1, position_blob.data(), position_blob.length());
1364 put.BindBlob(2, unique_bookmark_tag.data(), unique_bookmark_tag.length());
1365 put.BindInt64(3, metahandle);
1367 if (!put.Run())
1368 return false;
1369 put.Reset(true);
1372 SetVersion(86);
1373 needs_column_refresh_ = true;
1374 return true;
1377 bool DirectoryBackingStore::MigrateVersion86To87() {
1378 // Version 87 adds AttachmentMetadata proto.
1379 if (!db_->Execute(
1380 "ALTER TABLE metas ADD COLUMN "
1381 "attachment_metadata BLOB")) {
1382 return false;
1384 SetVersion(87);
1385 needs_column_refresh_ = true;
1386 return true;
1389 bool DirectoryBackingStore::MigrateVersion87To88() {
1390 // Version 88 adds the datatype context to the models table.
1391 if (!db_->Execute("ALTER TABLE models ADD COLUMN context blob"))
1392 return false;
1394 SetVersion(88);
1395 return true;
1398 bool DirectoryBackingStore::MigrateVersion88To89() {
1399 // Version 89 adds server_attachment_metadata.
1400 if (!db_->Execute(
1401 "ALTER TABLE metas ADD COLUMN "
1402 "server_attachment_metadata BLOB")) {
1403 return false;
1405 SetVersion(89);
1406 needs_column_refresh_ = true;
1407 return true;
1410 bool DirectoryBackingStore::CreateTables() {
1411 DVLOG(1) << "First run, creating tables";
1412 // Create two little tables share_version and share_info
1413 if (!db_->Execute(
1414 "CREATE TABLE share_version ("
1415 "id VARCHAR(128) primary key, data INT)")) {
1416 return false;
1420 sql::Statement s(db_->GetUniqueStatement(
1421 "INSERT INTO share_version VALUES(?, ?)"));
1422 s.BindString(0, dir_name_);
1423 s.BindInt(1, kCurrentDBVersion);
1425 if (!s.Run())
1426 return false;
1429 const bool kCreateAsTempShareInfo = false;
1430 if (!CreateShareInfoTable(kCreateAsTempShareInfo)) {
1431 return false;
1435 sql::Statement s(db_->GetUniqueStatement(
1436 "INSERT INTO share_info VALUES"
1437 "(?, " // id
1438 "?, " // name
1439 "?, " // store_birthday
1440 "?, " // db_create_version
1441 "?, " // db_create_time
1442 "-2, " // next_id
1443 "?, " // cache_guid
1444 // TODO(rlarocque, 124140): Remove notification_state field.
1445 "?, " // notification_state
1446 "?);")); // bag_of_chips
1447 s.BindString(0, dir_name_); // id
1448 s.BindString(1, dir_name_); // name
1449 s.BindString(2, std::string()); // store_birthday
1450 // TODO(akalin): Remove this unused db_create_version field. (Or
1451 // actually use it for something.) http://crbug.com/118356
1452 s.BindString(3, "Unknown"); // db_create_version
1453 s.BindInt(4, static_cast<int32>(time(0))); // db_create_time
1454 s.BindString(5, GenerateCacheGUID()); // cache_guid
1455 // TODO(rlarocque, 124140): Remove this unused notification-state field.
1456 s.BindBlob(6, NULL, 0); // notification_state
1457 s.BindBlob(7, NULL, 0); // bag_of_chips
1458 if (!s.Run())
1459 return false;
1462 if (!CreateModelsTable())
1463 return false;
1465 // Create the big metas table.
1466 if (!CreateMetasTable(false))
1467 return false;
1470 // Insert the entry for the root into the metas table.
1471 const int64 now = TimeToProtoTime(base::Time::Now());
1472 sql::Statement s(db_->GetUniqueStatement(
1473 "INSERT INTO metas "
1474 "( id, metahandle, is_dir, ctime, mtime ) "
1475 "VALUES ( \"r\", 1, 1, ?, ? )"));
1476 s.BindInt64(0, now);
1477 s.BindInt64(1, now);
1479 if (!s.Run())
1480 return false;
1483 return true;
1486 bool DirectoryBackingStore::CreateMetasTable(bool is_temporary) {
1487 string query = "CREATE TABLE ";
1488 query.append(is_temporary ? "temp_metas" : "metas");
1489 query.append(ComposeCreateTableColumnSpecs());
1490 if (!db_->Execute(query.c_str()))
1491 return false;
1493 // Create a deleted_metas table to save copies of deleted metas until the
1494 // deletions are persisted. For simplicity, don't try to migrate existing
1495 // data because it's rarely used.
1496 SafeDropTable("deleted_metas");
1497 query = "CREATE TABLE deleted_metas ";
1498 query.append(ComposeCreateTableColumnSpecs());
1499 return db_->Execute(query.c_str());
1502 bool DirectoryBackingStore::CreateV71ModelsTable() {
1503 // This is an old schema for the Models table, used from versions 71 to 74.
1504 return db_->Execute(
1505 "CREATE TABLE models ("
1506 "model_id BLOB primary key, "
1507 "last_download_timestamp INT, "
1508 // Gets set if the syncer ever gets updates from the
1509 // server and the server returns 0. Lets us detect the
1510 // end of the initial sync.
1511 "initial_sync_ended BOOLEAN default 0)");
1514 bool DirectoryBackingStore::CreateV75ModelsTable() {
1515 // This is an old schema for the Models table, used from versions 75 to 80.
1516 return db_->Execute(
1517 "CREATE TABLE models ("
1518 "model_id BLOB primary key, "
1519 "progress_marker BLOB, "
1520 // Gets set if the syncer ever gets updates from the
1521 // server and the server returns 0. Lets us detect the
1522 // end of the initial sync.
1523 "initial_sync_ended BOOLEAN default 0)");
1526 bool DirectoryBackingStore::CreateV81ModelsTable() {
1527 // This is an old schema for the Models table, used from versions 81 to 87.
1528 return db_->Execute(
1529 "CREATE TABLE models ("
1530 "model_id BLOB primary key, "
1531 "progress_marker BLOB, "
1532 // Gets set if the syncer ever gets updates from the
1533 // server and the server returns 0. Lets us detect the
1534 // end of the initial sync.
1535 "transaction_version BIGINT default 0)");
1538 bool DirectoryBackingStore::CreateModelsTable() {
1539 // This is the current schema for the Models table, from version 88
1540 // onward. If you change the schema, you'll probably want to double-check
1541 // the use of this function in the v84-v85 migration.
1542 return db_->Execute(
1543 "CREATE TABLE models ("
1544 "model_id BLOB primary key, "
1545 "progress_marker BLOB, "
1546 // Gets set if the syncer ever gets updates from the
1547 // server and the server returns 0. Lets us detect the
1548 // end of the initial sync.
1549 "transaction_version BIGINT default 0,"
1550 "context BLOB)");
1553 bool DirectoryBackingStore::CreateShareInfoTable(bool is_temporary) {
1554 const char* name = is_temporary ? "temp_share_info" : "share_info";
1555 string query = "CREATE TABLE ";
1556 query.append(name);
1557 // This is the current schema for the ShareInfo table, from version 76
1558 // onward.
1559 query.append(" ("
1560 "id TEXT primary key, "
1561 "name TEXT, "
1562 "store_birthday TEXT, "
1563 "db_create_version TEXT, "
1564 "db_create_time INT, "
1565 "next_id INT default -2, "
1566 "cache_guid TEXT, "
1567 // TODO(rlarocque, 124140): Remove notification_state field.
1568 "notification_state BLOB, "
1569 "bag_of_chips BLOB"
1570 ")");
1571 return db_->Execute(query.c_str());
1574 bool DirectoryBackingStore::CreateShareInfoTableVersion71(
1575 bool is_temporary) {
1576 const char* name = is_temporary ? "temp_share_info" : "share_info";
1577 string query = "CREATE TABLE ";
1578 query.append(name);
1579 // This is the schema for the ShareInfo table used from versions 71 to 72.
1580 query.append(" ("
1581 "id TEXT primary key, "
1582 "name TEXT, "
1583 "store_birthday TEXT, "
1584 "db_create_version TEXT, "
1585 "db_create_time INT, "
1586 "next_id INT default -2, "
1587 "cache_guid TEXT )");
1588 return db_->Execute(query.c_str());
1591 // This function checks to see if the given list of Metahandles has any nodes
1592 // whose PARENT_ID values refer to ID values that do not actually exist.
1593 // Returns true on success.
1594 bool DirectoryBackingStore::VerifyReferenceIntegrity(
1595 const Directory::MetahandlesMap* handles_map) {
1596 TRACE_EVENT0("sync", "SyncDatabaseIntegrityCheck");
1597 using namespace syncable;
1598 typedef base::hash_set<std::string> IdsSet;
1600 IdsSet ids_set;
1601 bool is_ok = true;
1603 for (Directory::MetahandlesMap::const_iterator it = handles_map->begin();
1604 it != handles_map->end(); ++it) {
1605 EntryKernel* entry = it->second;
1606 bool is_duplicate_id = !(ids_set.insert(entry->ref(ID).value()).second);
1607 is_ok = is_ok && !is_duplicate_id;
1610 IdsSet::iterator end = ids_set.end();
1611 for (Directory::MetahandlesMap::const_iterator it = handles_map->begin();
1612 it != handles_map->end(); ++it) {
1613 EntryKernel* entry = it->second;
1614 if (!entry->ref(PARENT_ID).IsNull()) {
1615 bool parent_exists = (ids_set.find(entry->ref(PARENT_ID).value()) != end);
1616 if (!parent_exists) {
1617 return false;
1621 return is_ok;
1624 void DirectoryBackingStore::PrepareSaveEntryStatement(
1625 EntryTable table, sql::Statement* save_statement) {
1626 if (save_statement->is_valid())
1627 return;
1629 string query;
1630 query.reserve(kUpdateStatementBufferSize);
1631 switch (table) {
1632 case METAS_TABLE:
1633 query.append("INSERT OR REPLACE INTO metas ");
1634 break;
1635 case DELETE_JOURNAL_TABLE:
1636 query.append("INSERT OR REPLACE INTO deleted_metas ");
1637 break;
1640 string values;
1641 values.reserve(kUpdateStatementBufferSize);
1642 values.append(" VALUES ");
1643 const char* separator = "( ";
1644 int i = 0;
1645 for (i = BEGIN_FIELDS; i < FIELD_COUNT; ++i) {
1646 query.append(separator);
1647 values.append(separator);
1648 separator = ", ";
1649 query.append(ColumnName(i));
1650 values.append("?");
1652 query.append(" ) ");
1653 values.append(" )");
1654 query.append(values);
1655 save_statement->Assign(db_->GetUniqueStatement(
1656 base::StringPrintf(query.c_str(), "metas").c_str()));
1659 // Get page size for the database.
1660 bool DirectoryBackingStore::GetDatabasePageSize(int* page_size) {
1661 sql::Statement s(db_->GetUniqueStatement("PRAGMA page_size"));
1662 if (!s.Step())
1663 return false;
1664 *page_size = s.ColumnInt(0);
1665 return true;
1668 bool DirectoryBackingStore::IncreasePageSizeTo32K() {
1669 if (!db_->Execute("PRAGMA page_size=32768;") || !Vacuum()) {
1670 return false;
1672 return true;
1675 bool DirectoryBackingStore::Vacuum() {
1676 DCHECK_EQ(db_->transaction_nesting(), 0);
1677 if (!db_->Execute("VACUUM;")) {
1678 return false;
1680 return true;
1683 bool DirectoryBackingStore::needs_column_refresh() const {
1684 return needs_column_refresh_;
1687 void DirectoryBackingStore::ResetAndCreateConnection() {
1688 db_.reset(new sql::Connection());
1689 db_->set_histogram_tag("SyncDirectory");
1690 db_->set_exclusive_locking();
1691 db_->set_cache_size(32);
1692 db_->set_page_size(database_page_size_);
1693 if (!catastrophic_error_handler_.is_null())
1694 SetCatastrophicErrorHandler(catastrophic_error_handler_);
1697 void DirectoryBackingStore::SetCatastrophicErrorHandler(
1698 const base::Closure& catastrophic_error_handler) {
1699 DCHECK(CalledOnValidThread());
1700 DCHECK(!catastrophic_error_handler.is_null());
1701 catastrophic_error_handler_ = catastrophic_error_handler;
1702 sql::Connection::ErrorCallback error_callback =
1703 base::Bind(&OnSqliteError, catastrophic_error_handler_);
1704 db_->set_error_callback(error_callback);
1707 } // namespace syncable
1708 } // namespace syncer