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"
11 #include "base/base64.h"
12 #include "base/debug/trace_event.h"
13 #include "base/logging.h"
14 #include "base/rand_util.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/time/time.h"
17 #include "sql/connection.h"
18 #include "sql/statement.h"
19 #include "sql/transaction.h"
20 #include "sync/internal_api/public/base/node_ordinal.h"
21 #include "sync/protocol/bookmark_specifics.pb.h"
22 #include "sync/protocol/sync.pb.h"
23 #include "sync/syncable/syncable-inl.h"
24 #include "sync/syncable/syncable_columns.h"
25 #include "sync/syncable/syncable_util.h"
26 #include "sync/util/time.h"
33 // This just has to be big enough to hold an UPDATE or INSERT statement that
34 // modifies all the columns in the entry table.
35 static const string::size_type kUpdateStatementBufferSize = 2048;
37 // Increment this version whenever updating DB tables.
38 const int32 kCurrentDBVersion = 88;
40 // Iterate over the fields of |entry| and bind each to |statement| for
41 // updating. Returns the number of args bound.
42 void BindFields(const EntryKernel& entry,
43 sql::Statement* statement) {
46 for (i = BEGIN_FIELDS; i < INT64_FIELDS_END; ++i) {
47 statement->BindInt64(index++, entry.ref(static_cast<Int64Field>(i)));
49 for ( ; i < TIME_FIELDS_END; ++i) {
50 statement->BindInt64(index++,
52 entry.ref(static_cast<TimeField>(i))));
54 for ( ; i < ID_FIELDS_END; ++i) {
55 statement->BindString(index++, entry.ref(static_cast<IdField>(i)).s_);
57 for ( ; i < BIT_FIELDS_END; ++i) {
58 statement->BindInt(index++, entry.ref(static_cast<BitField>(i)));
60 for ( ; i < STRING_FIELDS_END; ++i) {
61 statement->BindString(index++, entry.ref(static_cast<StringField>(i)));
63 for ( ; i < PROTO_FIELDS_END; ++i) {
65 entry.ref(static_cast<ProtoField>(i)).SerializeToString(&temp);
66 statement->BindBlob(index++, temp.data(), temp.length());
68 for ( ; i < UNIQUE_POSITION_FIELDS_END; ++i) {
70 entry.ref(static_cast<UniquePositionField>(i)).SerializeToString(&temp);
71 statement->BindBlob(index++, temp.data(), temp.length());
73 for (; i < ATTACHMENT_METADATA_FIELDS_END; ++i) {
75 entry.ref(static_cast<AttachmentMetadataField>(i)).SerializeToString(&temp);
76 statement->BindBlob(index++, temp.data(), temp.length());
80 // The caller owns the returned EntryKernel*. Assumes the statement currently
81 // points to a valid row in the metas table. Returns NULL to indicate that
82 // it detected a corruption in the data on unpacking.
83 scoped_ptr<EntryKernel> UnpackEntry(sql::Statement* statement) {
84 scoped_ptr<EntryKernel> kernel(new EntryKernel());
85 DCHECK_EQ(statement->ColumnCount(), static_cast<int>(FIELD_COUNT));
87 for (i = BEGIN_FIELDS; i < INT64_FIELDS_END; ++i) {
88 kernel->put(static_cast<Int64Field>(i), statement->ColumnInt64(i));
90 for ( ; i < TIME_FIELDS_END; ++i) {
91 kernel->put(static_cast<TimeField>(i),
92 ProtoTimeToTime(statement->ColumnInt64(i)));
94 for ( ; i < ID_FIELDS_END; ++i) {
95 kernel->mutable_ref(static_cast<IdField>(i)).s_ =
96 statement->ColumnString(i);
98 for ( ; i < BIT_FIELDS_END; ++i) {
99 kernel->put(static_cast<BitField>(i), (0 != statement->ColumnInt(i)));
101 for ( ; i < STRING_FIELDS_END; ++i) {
102 kernel->put(static_cast<StringField>(i),
103 statement->ColumnString(i));
105 for ( ; i < PROTO_FIELDS_END; ++i) {
106 kernel->mutable_ref(static_cast<ProtoField>(i)).ParseFromArray(
107 statement->ColumnBlob(i), statement->ColumnByteLength(i));
109 for ( ; i < UNIQUE_POSITION_FIELDS_END; ++i) {
111 statement->ColumnBlobAsString(i, &temp);
113 sync_pb::UniquePosition proto;
114 if (!proto.ParseFromString(temp)) {
115 DVLOG(1) << "Unpacked invalid position. Assuming the DB is corrupt";
116 return scoped_ptr<EntryKernel>();
119 kernel->mutable_ref(static_cast<UniquePositionField>(i)) =
120 UniquePosition::FromProto(proto);
122 for (; i < ATTACHMENT_METADATA_FIELDS_END; ++i) {
123 kernel->mutable_ref(static_cast<AttachmentMetadataField>(i)).ParseFromArray(
124 statement->ColumnBlob(i), statement->ColumnByteLength(i));
126 return kernel.Pass();
131 string ComposeCreateTableColumnSpecs() {
132 const ColumnSpec* begin = g_metas_columns;
133 const ColumnSpec* end = g_metas_columns + arraysize(g_metas_columns);
135 query.reserve(kUpdateStatementBufferSize);
136 char separator = '(';
137 for (const ColumnSpec* column = begin; column != end; ++column) {
138 query.push_back(separator);
140 query.append(column->name);
141 query.push_back(' ');
142 query.append(column->spec);
144 query.push_back(')');
148 void AppendColumnList(std::string* output) {
149 const char* joiner = " ";
150 // Be explicit in SELECT order to match up with UnpackEntry.
151 for (int i = BEGIN_FIELDS; i < FIELD_COUNT; ++i) {
152 output->append(joiner);
153 output->append(ColumnName(i));
160 ///////////////////////////////////////////////////////////////////////////////
161 // DirectoryBackingStore implementation.
163 DirectoryBackingStore::DirectoryBackingStore(const string& dir_name)
164 : db_(new sql::Connection()),
166 needs_column_refresh_(false) {
167 db_->set_histogram_tag("SyncDirectory");
168 db_->set_page_size(4096);
169 db_->set_cache_size(32);
172 DirectoryBackingStore::DirectoryBackingStore(const string& dir_name,
176 needs_column_refresh_(false) {
179 DirectoryBackingStore::~DirectoryBackingStore() {
182 bool DirectoryBackingStore::DeleteEntries(EntryTable from,
183 const MetahandleSet& handles) {
187 sql::Statement statement;
188 // Call GetCachedStatement() separately to get different statements for
192 statement.Assign(db_->GetCachedStatement(
193 SQL_FROM_HERE, "DELETE FROM metas WHERE metahandle = ?"));
195 case DELETE_JOURNAL_TABLE:
196 statement.Assign(db_->GetCachedStatement(
197 SQL_FROM_HERE, "DELETE FROM deleted_metas WHERE metahandle = ?"));
201 for (MetahandleSet::const_iterator i = handles.begin(); i != handles.end();
203 statement.BindInt64(0, *i);
204 if (!statement.Run())
206 statement.Reset(true);
211 bool DirectoryBackingStore::SaveChanges(
212 const Directory::SaveChangesSnapshot& snapshot) {
213 DCHECK(CalledOnValidThread());
214 DCHECK(db_->is_open());
216 // Back out early if there is nothing to write.
218 (Directory::KERNEL_SHARE_INFO_DIRTY == snapshot.kernel_info_status);
219 if (snapshot.dirty_metas.empty() && snapshot.metahandles_to_purge.empty() &&
220 snapshot.delete_journals.empty() &&
221 snapshot.delete_journals_to_purge.empty() && !save_info) {
225 sql::Transaction transaction(db_.get());
226 if (!transaction.Begin())
229 PrepareSaveEntryStatement(METAS_TABLE, &save_meta_statment_);
230 for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin();
231 i != snapshot.dirty_metas.end(); ++i) {
232 DCHECK((*i)->is_dirty());
233 if (!SaveEntryToDB(&save_meta_statment_, **i))
237 if (!DeleteEntries(METAS_TABLE, snapshot.metahandles_to_purge))
240 PrepareSaveEntryStatement(DELETE_JOURNAL_TABLE,
241 &save_delete_journal_statment_);
242 for (EntryKernelSet::const_iterator i = snapshot.delete_journals.begin();
243 i != snapshot.delete_journals.end(); ++i) {
244 if (!SaveEntryToDB(&save_delete_journal_statment_, **i))
248 if (!DeleteEntries(DELETE_JOURNAL_TABLE, snapshot.delete_journals_to_purge))
252 const Directory::PersistedKernelInfo& info = snapshot.kernel_info;
253 sql::Statement s1(db_->GetCachedStatement(
256 "SET store_birthday = ?, "
258 "bag_of_chips = ?"));
259 s1.BindString(0, info.store_birthday);
260 s1.BindInt64(1, info.next_id);
261 s1.BindBlob(2, info.bag_of_chips.data(), info.bag_of_chips.size());
265 DCHECK_EQ(db_->GetLastChangeCount(), 1);
267 sql::Statement s2(db_->GetCachedStatement(
270 "INTO models (model_id, "
272 "transaction_version, "
274 "VALUES (?, ?, ?, ?)"));
276 ModelTypeSet protocol_types = ProtocolTypes();
277 for (ModelTypeSet::Iterator iter = protocol_types.First(); iter.Good();
279 ModelType type = iter.Get();
280 // We persist not ModelType but rather a protobuf-derived ID.
281 string model_id = ModelTypeEnumToModelId(type);
282 string progress_marker;
283 info.download_progress[type].SerializeToString(&progress_marker);
284 s2.BindBlob(0, model_id.data(), model_id.length());
285 s2.BindBlob(1, progress_marker.data(), progress_marker.length());
286 s2.BindInt64(2, info.transaction_version[type]);
288 info.datatype_context[type].SerializeToString(&context);
289 s2.BindBlob(3, context.data(), context.length());
292 DCHECK_EQ(db_->GetLastChangeCount(), 1);
297 return transaction.Commit();
300 bool DirectoryBackingStore::InitializeTables() {
301 sql::Transaction transaction(db_.get());
302 if (!transaction.Begin())
305 int version_on_disk = GetVersion();
307 // Upgrade from version 67. Version 67 was widely distributed as the original
308 // Bookmark Sync release. Version 68 removed unique naming.
309 if (version_on_disk == 67) {
310 if (MigrateVersion67To68())
311 version_on_disk = 68;
313 // Version 69 introduced additional datatypes.
314 if (version_on_disk == 68) {
315 if (MigrateVersion68To69())
316 version_on_disk = 69;
319 if (version_on_disk == 69) {
320 if (MigrateVersion69To70())
321 version_on_disk = 70;
324 // Version 71 changed the sync progress information to be per-datatype.
325 if (version_on_disk == 70) {
326 if (MigrateVersion70To71())
327 version_on_disk = 71;
330 // Version 72 removed extended attributes, a legacy way to do extensible
331 // key/value information, stored in their own table.
332 if (version_on_disk == 71) {
333 if (MigrateVersion71To72())
334 version_on_disk = 72;
337 // Version 73 added a field for notification state.
338 if (version_on_disk == 72) {
339 if (MigrateVersion72To73())
340 version_on_disk = 73;
343 // Version 74 added state for the autofill migration.
344 if (version_on_disk == 73) {
345 if (MigrateVersion73To74())
346 version_on_disk = 74;
349 // Version 75 migrated from int64-based timestamps to per-datatype tokens.
350 if (version_on_disk == 74) {
351 if (MigrateVersion74To75())
352 version_on_disk = 75;
355 // Version 76 removed all (5) autofill migration related columns.
356 if (version_on_disk == 75) {
357 if (MigrateVersion75To76())
358 version_on_disk = 76;
361 // Version 77 standardized all time fields to ms since the Unix
363 if (version_on_disk == 76) {
364 if (MigrateVersion76To77())
365 version_on_disk = 77;
368 // Version 78 added the column base_server_specifics to the metas table.
369 if (version_on_disk == 77) {
370 if (MigrateVersion77To78())
371 version_on_disk = 78;
374 // Version 79 migration is a one-time fix for some users in a bad state.
375 if (version_on_disk == 78) {
376 if (MigrateVersion78To79())
377 version_on_disk = 79;
380 // Version 80 migration is adding the bag_of_chips column.
381 if (version_on_disk == 79) {
382 if (MigrateVersion79To80())
383 version_on_disk = 80;
386 // Version 81 replaces the int64 server_position_in_parent_field
387 // with a blob server_ordinal_in_parent field.
388 if (version_on_disk == 80) {
389 if (MigrateVersion80To81())
390 version_on_disk = 81;
393 // Version 82 migration added transaction_version column per data type.
394 if (version_on_disk == 81) {
395 if (MigrateVersion81To82())
396 version_on_disk = 82;
399 // Version 83 migration added transaction_version column per sync entry.
400 if (version_on_disk == 82) {
401 if (MigrateVersion82To83())
402 version_on_disk = 83;
405 // Version 84 migration added deleted_metas table.
406 if (version_on_disk == 83) {
407 if (MigrateVersion83To84())
408 version_on_disk = 84;
411 // Version 85 migration removes the initial_sync_ended bits.
412 if (version_on_disk == 84) {
413 if (MigrateVersion84To85())
414 version_on_disk = 85;
417 // Version 86 migration converts bookmarks to the unique positioning system.
418 // It also introduces a new field to store a unique ID for each bookmark.
419 if (version_on_disk == 85) {
420 if (MigrateVersion85To86())
421 version_on_disk = 86;
424 // Version 87 migration adds a collection of attachment ids per sync entry.
425 if (version_on_disk == 86) {
426 if (MigrateVersion86To87())
427 version_on_disk = 87;
430 // Version 88 migration adds datatype contexts to the models table.
431 if (version_on_disk == 87) {
432 if (MigrateVersion87To88())
433 version_on_disk = 88;
436 // If one of the migrations requested it, drop columns that aren't current.
437 // It's only safe to do this after migrating all the way to the current
439 if (version_on_disk == kCurrentDBVersion && needs_column_refresh_) {
440 if (!RefreshColumns())
444 // A final, alternative catch-all migration to simply re-sync everything.
445 if (version_on_disk != kCurrentDBVersion) {
446 if (version_on_disk > kCurrentDBVersion)
449 // Fallback (re-sync everything) migration path.
450 DVLOG(1) << "Old/null sync database, version " << version_on_disk;
451 // Delete the existing database (if any), and create a fresh one.
457 sql::Statement s(db_->GetUniqueStatement(
458 "SELECT db_create_version, db_create_time FROM share_info"));
461 string db_create_version = s.ColumnString(0);
462 int db_create_time = s.ColumnInt(1);
463 DVLOG(1) << "DB created at " << db_create_time << " by version " <<
466 return transaction.Commit();
469 // This function drops unused columns by creating a new table that contains only
470 // the currently used columns then copying all rows from the old tables into
471 // this new one. The tables are then rearranged so the new replaces the old.
472 bool DirectoryBackingStore::RefreshColumns() {
473 DCHECK(needs_column_refresh_);
475 // Create a new table named temp_metas.
476 SafeDropTable("temp_metas");
477 if (!CreateMetasTable(true))
480 // Populate temp_metas from metas.
482 // At this point, the metas table may contain columns belonging to obsolete
483 // schema versions. This statement explicitly lists only the columns that
484 // belong to the current schema version, so the obsolete columns will be
485 // effectively dropped once we rename temp_metas over top of metas.
486 std::string query = "INSERT INTO temp_metas (";
487 AppendColumnList(&query);
488 query.append(") SELECT ");
489 AppendColumnList(&query);
490 query.append(" FROM metas");
491 if (!db_->Execute(query.c_str()))
495 SafeDropTable("metas");
497 // Rename temp_metas -> metas.
498 if (!db_->Execute("ALTER TABLE temp_metas RENAME TO metas"))
501 // Repeat the process for share_info.
502 SafeDropTable("temp_share_info");
503 if (!CreateShareInfoTable(true))
506 // TODO(rlarocque, 124140): Remove notification_state.
508 "INSERT INTO temp_share_info (id, name, store_birthday, "
509 "db_create_version, db_create_time, next_id, cache_guid,"
510 "notification_state, bag_of_chips) "
511 "SELECT id, name, store_birthday, db_create_version, "
512 "db_create_time, next_id, cache_guid, notification_state, "
517 SafeDropTable("share_info");
518 if (!db_->Execute("ALTER TABLE temp_share_info RENAME TO share_info"))
521 needs_column_refresh_ = false;
525 bool DirectoryBackingStore::LoadEntries(
526 Directory::MetahandlesMap* handles_map) {
528 select.reserve(kUpdateStatementBufferSize);
529 select.append("SELECT ");
530 AppendColumnList(&select);
531 select.append(" FROM metas");
533 sql::Statement s(db_->GetUniqueStatement(select.c_str()));
536 scoped_ptr<EntryKernel> kernel = UnpackEntry(&s);
537 // A null kernel is evidence of external data corruption.
541 int64 handle = kernel->ref(META_HANDLE);
542 (*handles_map)[handle] = kernel.release();
544 return s.Succeeded();
547 bool DirectoryBackingStore::LoadDeleteJournals(
548 JournalIndex* delete_journals) {
550 select.reserve(kUpdateStatementBufferSize);
551 select.append("SELECT ");
552 AppendColumnList(&select);
553 select.append(" FROM deleted_metas");
555 sql::Statement s(db_->GetUniqueStatement(select.c_str()));
558 scoped_ptr<EntryKernel> kernel = UnpackEntry(&s);
559 // A null kernel is evidence of external data corruption.
562 delete_journals->insert(kernel.release());
564 return s.Succeeded();
567 bool DirectoryBackingStore::LoadInfo(Directory::KernelLoadInfo* info) {
570 db_->GetUniqueStatement(
571 "SELECT store_birthday, next_id, cache_guid, bag_of_chips "
576 info->kernel_info.store_birthday = s.ColumnString(0);
577 info->kernel_info.next_id = s.ColumnInt64(1);
578 info->cache_guid = s.ColumnString(2);
579 s.ColumnBlobAsString(3, &(info->kernel_info.bag_of_chips));
581 // Verify there was only one row returned.
583 DCHECK(s.Succeeded());
588 db_->GetUniqueStatement(
589 "SELECT model_id, progress_marker, "
590 "transaction_version, context FROM models"));
593 ModelType type = ModelIdToModelTypeEnum(s.ColumnBlob(0),
594 s.ColumnByteLength(0));
595 if (type != UNSPECIFIED && type != TOP_LEVEL_FOLDER) {
596 info->kernel_info.download_progress[type].ParseFromArray(
597 s.ColumnBlob(1), s.ColumnByteLength(1));
598 info->kernel_info.transaction_version[type] = s.ColumnInt64(2);
599 info->kernel_info.datatype_context[type].ParseFromArray(
600 s.ColumnBlob(3), s.ColumnByteLength(3));
608 db_->GetUniqueStatement(
609 "SELECT MAX(metahandle) FROM metas"));
613 info->max_metahandle = s.ColumnInt64(0);
615 // Verify only one row was returned.
617 DCHECK(s.Succeeded());
623 bool DirectoryBackingStore::SaveEntryToDB(sql::Statement* save_statement,
624 const EntryKernel& entry) {
625 save_statement->Reset(true);
626 BindFields(entry, save_statement);
627 return save_statement->Run();
630 bool DirectoryBackingStore::DropDeletedEntries() {
631 if (!db_->Execute("DELETE FROM metas "
633 "AND is_unsynced < 1 "
634 "AND is_unapplied_update < 1")) {
637 if (!db_->Execute("DELETE FROM metas "
639 "AND id LIKE 'c%'")) {
645 bool DirectoryBackingStore::SafeDropTable(const char* table_name) {
646 string query = "DROP TABLE IF EXISTS ";
647 query.append(table_name);
648 return db_->Execute(query.c_str());
651 void DirectoryBackingStore::DropAllTables() {
652 SafeDropTable("metas");
653 SafeDropTable("temp_metas");
654 SafeDropTable("share_info");
655 SafeDropTable("temp_share_info");
656 SafeDropTable("share_version");
657 SafeDropTable("extended_attributes");
658 SafeDropTable("models");
659 SafeDropTable("temp_models");
660 needs_column_refresh_ = false;
664 ModelType DirectoryBackingStore::ModelIdToModelTypeEnum(
665 const void* data, int size) {
666 sync_pb::EntitySpecifics specifics;
667 if (!specifics.ParseFromArray(data, size))
669 return GetModelTypeFromSpecifics(specifics);
673 string DirectoryBackingStore::ModelTypeEnumToModelId(ModelType model_type) {
674 sync_pb::EntitySpecifics specifics;
675 AddDefaultFieldValue(model_type, &specifics);
676 return specifics.SerializeAsString();
680 std::string DirectoryBackingStore::GenerateCacheGUID() {
681 // Generate a GUID with 128 bits of randomness.
682 const int kGuidBytes = 128 / 8;
684 base::Base64Encode(base::RandBytesAsString(kGuidBytes), &guid);
688 bool DirectoryBackingStore::MigrateToSpecifics(
689 const char* old_columns,
690 const char* specifics_column,
691 void (*handler_function)(sql::Statement* old_value_query,
692 int old_value_column,
693 sync_pb::EntitySpecifics* mutable_new_value)) {
694 std::string query_sql = base::StringPrintf(
695 "SELECT metahandle, %s, %s FROM metas", specifics_column, old_columns);
696 std::string update_sql = base::StringPrintf(
697 "UPDATE metas SET %s = ? WHERE metahandle = ?", specifics_column);
699 sql::Statement query(db_->GetUniqueStatement(query_sql.c_str()));
700 sql::Statement update(db_->GetUniqueStatement(update_sql.c_str()));
702 while (query.Step()) {
703 int64 metahandle = query.ColumnInt64(0);
704 std::string new_value_bytes;
705 query.ColumnBlobAsString(1, &new_value_bytes);
706 sync_pb::EntitySpecifics new_value;
707 new_value.ParseFromString(new_value_bytes);
708 handler_function(&query, 2, &new_value);
709 new_value.SerializeToString(&new_value_bytes);
711 update.BindBlob(0, new_value_bytes.data(), new_value_bytes.length());
712 update.BindInt64(1, metahandle);
717 return query.Succeeded();
720 bool DirectoryBackingStore::SetVersion(int version) {
721 sql::Statement s(db_->GetCachedStatement(
722 SQL_FROM_HERE, "UPDATE share_version SET data = ?"));
723 s.BindInt(0, version);
728 int DirectoryBackingStore::GetVersion() {
729 if (!db_->DoesTableExist("share_version"))
732 sql::Statement statement(db_->GetUniqueStatement(
733 "SELECT data FROM share_version"));
734 if (statement.Step()) {
735 return statement.ColumnInt(0);
741 bool DirectoryBackingStore::MigrateVersion67To68() {
742 // This change simply removed three columns:
744 // string UNSANITIZED_NAME
745 // string SERVER_NAME
746 // No data migration is necessary, but we should do a column refresh.
748 needs_column_refresh_ = true;
752 bool DirectoryBackingStore::MigrateVersion69To70() {
753 // Added "unique_client_tag", renamed "singleton_tag" to unique_server_tag
756 "ALTER TABLE metas ADD COLUMN unique_server_tag varchar"))
759 "ALTER TABLE metas ADD COLUMN unique_client_tag varchar"))
761 needs_column_refresh_ = true;
764 "UPDATE metas SET unique_server_tag = singleton_tag"))
772 // Callback passed to MigrateToSpecifics for the v68->v69 migration. See
773 // MigrateVersion68To69().
774 void EncodeBookmarkURLAndFavicon(sql::Statement* old_value_query,
775 int old_value_column,
776 sync_pb::EntitySpecifics* mutable_new_value) {
777 // Extract data from the column trio we expect.
778 bool old_is_bookmark_object = old_value_query->ColumnBool(old_value_column);
779 std::string old_url = old_value_query->ColumnString(old_value_column + 1);
780 std::string old_favicon;
781 old_value_query->ColumnBlobAsString(old_value_column + 2, &old_favicon);
782 bool old_is_dir = old_value_query->ColumnBool(old_value_column + 3);
784 if (old_is_bookmark_object) {
785 sync_pb::BookmarkSpecifics* bookmark_data =
786 mutable_new_value->mutable_bookmark();
788 bookmark_data->set_url(old_url);
789 bookmark_data->set_favicon(old_favicon);
796 bool DirectoryBackingStore::MigrateVersion68To69() {
797 // In Version 68, there were columns on table 'metas':
798 // string BOOKMARK_URL
799 // string SERVER_BOOKMARK_URL
800 // blob BOOKMARK_FAVICON
801 // blob SERVER_BOOKMARK_FAVICON
802 // In version 69, these columns went away in favor of storing
803 // a serialized EntrySpecifics protobuf in the columns:
804 // protobuf blob SPECIFICS
805 // protobuf blob SERVER_SPECIFICS
806 // For bookmarks, EntrySpecifics is extended as per
807 // bookmark_specifics.proto. This migration converts bookmarks from the
808 // former scheme to the latter scheme.
810 // First, add the two new columns to the schema.
812 "ALTER TABLE metas ADD COLUMN specifics blob"))
815 "ALTER TABLE metas ADD COLUMN server_specifics blob"))
818 // Next, fold data from the old columns into the new protobuf columns.
819 if (!MigrateToSpecifics(("is_bookmark_object, bookmark_url, "
820 "bookmark_favicon, is_dir"),
822 &EncodeBookmarkURLAndFavicon)) {
825 if (!MigrateToSpecifics(("server_is_bookmark_object, "
826 "server_bookmark_url, "
827 "server_bookmark_favicon, "
830 &EncodeBookmarkURLAndFavicon)) {
834 // Lastly, fix up the "Google Chrome" folder, which is of the TOP_LEVEL_FOLDER
835 // ModelType: it shouldn't have BookmarkSpecifics.
837 "UPDATE metas SET specifics = NULL, server_specifics = NULL WHERE "
838 "singleton_tag IN ('google_chrome')"))
842 needs_column_refresh_ = true; // Trigger deletion of old columns.
846 // Version 71, the columns 'initial_sync_ended' and 'last_sync_timestamp'
847 // were removed from the share_info table. They were replaced by
848 // the 'models' table, which has these values on a per-datatype basis.
849 bool DirectoryBackingStore::MigrateVersion70To71() {
850 if (!CreateV71ModelsTable())
853 // Move data from the old share_info columns to the new models table.
855 sql::Statement fetch(db_->GetUniqueStatement(
856 "SELECT last_sync_timestamp, initial_sync_ended FROM share_info"));
860 int64 last_sync_timestamp = fetch.ColumnInt64(0);
861 bool initial_sync_ended = fetch.ColumnBool(1);
863 // Verify there were no additional rows returned.
864 DCHECK(!fetch.Step());
865 DCHECK(fetch.Succeeded());
867 sql::Statement update(db_->GetUniqueStatement(
868 "INSERT INTO models (model_id, "
869 "last_download_timestamp, initial_sync_ended) VALUES (?, ?, ?)"));
870 string bookmark_model_id = ModelTypeEnumToModelId(BOOKMARKS);
871 update.BindBlob(0, bookmark_model_id.data(), bookmark_model_id.size());
872 update.BindInt64(1, last_sync_timestamp);
873 update.BindBool(2, initial_sync_ended);
879 // Drop the columns from the old share_info table via a temp table.
880 const bool kCreateAsTempShareInfo = true;
882 if (!CreateShareInfoTableVersion71(kCreateAsTempShareInfo))
885 "INSERT INTO temp_share_info (id, name, store_birthday, "
886 "db_create_version, db_create_time, next_id, cache_guid) "
887 "SELECT id, name, store_birthday, db_create_version, "
888 "db_create_time, next_id, cache_guid FROM share_info"))
890 SafeDropTable("share_info");
892 "ALTER TABLE temp_share_info RENAME TO share_info"))
898 bool DirectoryBackingStore::MigrateVersion71To72() {
899 // Version 72 removed a table 'extended_attributes', whose
900 // contents didn't matter.
901 SafeDropTable("extended_attributes");
906 bool DirectoryBackingStore::MigrateVersion72To73() {
907 // Version 73 added one column to the table 'share_info': notification_state
909 "ALTER TABLE share_info ADD COLUMN notification_state BLOB"))
915 bool DirectoryBackingStore::MigrateVersion73To74() {
916 // Version 74 added the following columns to the table 'share_info':
917 // autofill_migration_state
918 // bookmarks_added_during_autofill_migration
919 // autofill_migration_time
920 // autofill_entries_added_during_migration
921 // autofill_profiles_added_during_migration
924 "ALTER TABLE share_info ADD COLUMN "
925 "autofill_migration_state INT default 0"))
929 "ALTER TABLE share_info ADD COLUMN "
930 "bookmarks_added_during_autofill_migration "
935 "ALTER TABLE share_info ADD COLUMN autofill_migration_time "
940 "ALTER TABLE share_info ADD COLUMN "
941 "autofill_entries_added_during_migration "
946 "ALTER TABLE share_info ADD COLUMN "
947 "autofill_profiles_added_during_migration "
955 bool DirectoryBackingStore::MigrateVersion74To75() {
956 // In version 74, there was a table 'models':
957 // blob model_id (entity specifics, primary key)
958 // int last_download_timestamp
959 // boolean initial_sync_ended
960 // In version 75, we deprecated the integer-valued last_download_timestamp,
961 // using insted a protobuf-valued progress_marker field:
962 // blob progress_marker
963 // The progress_marker values are initialized from the value of
964 // last_download_timestamp, thereby preserving the download state.
966 // Move aside the old table and create a new empty one at the current schema.
967 if (!db_->Execute("ALTER TABLE models RENAME TO temp_models"))
969 if (!CreateV75ModelsTable())
972 sql::Statement query(db_->GetUniqueStatement(
973 "SELECT model_id, last_download_timestamp, initial_sync_ended "
974 "FROM temp_models"));
976 sql::Statement update(db_->GetUniqueStatement(
977 "INSERT INTO models (model_id, "
978 "progress_marker, initial_sync_ended) VALUES (?, ?, ?)"));
980 while (query.Step()) {
981 ModelType type = ModelIdToModelTypeEnum(query.ColumnBlob(0),
982 query.ColumnByteLength(0));
983 if (type != UNSPECIFIED) {
984 // Set the |timestamp_token_for_migration| on a new
985 // DataTypeProgressMarker, using the old value of last_download_timestamp.
986 // The server will turn this into a real token on our behalf the next
987 // time we check for updates.
988 sync_pb::DataTypeProgressMarker progress_marker;
989 progress_marker.set_data_type_id(
990 GetSpecificsFieldNumberFromModelType(type));
991 progress_marker.set_timestamp_token_for_migration(query.ColumnInt64(1));
992 std::string progress_blob;
993 progress_marker.SerializeToString(&progress_blob);
995 update.BindBlob(0, query.ColumnBlob(0), query.ColumnByteLength(0));
996 update.BindBlob(1, progress_blob.data(), progress_blob.length());
997 update.BindBool(2, query.ColumnBool(2));
1003 if (!query.Succeeded())
1006 // Drop the old table.
1007 SafeDropTable("temp_models");
1013 bool DirectoryBackingStore::MigrateVersion75To76() {
1014 // This change removed five columns:
1015 // autofill_migration_state
1016 // bookmarks_added_during_autofill_migration
1017 // autofill_migration_time
1018 // autofill_entries_added_during_migration
1019 // autofill_profiles_added_during_migration
1020 // No data migration is necessary, but we should do a column refresh.
1022 needs_column_refresh_ = true;
1026 bool DirectoryBackingStore::MigrateVersion76To77() {
1027 // This change changes the format of stored timestamps to ms since
1030 // On Windows, we used to store timestamps in FILETIME format (100s of
1031 // ns since Jan 1, 1601). Magic numbers taken from
1032 // http://stackoverflow.com/questions/5398557/
1033 // java-library-for-dealing-with-win32-filetime
1035 #define TO_UNIX_TIME_MS(x) #x " = " #x " / 10000 - 11644473600000"
1037 // On other platforms, we used to store timestamps in time_t format (s
1038 // since the Unix epoch).
1039 #define TO_UNIX_TIME_MS(x) #x " = " #x " * 1000"
1041 sql::Statement update_timestamps(db_->GetUniqueStatement(
1043 TO_UNIX_TIME_MS(mtime) ", "
1044 TO_UNIX_TIME_MS(server_mtime) ", "
1045 TO_UNIX_TIME_MS(ctime) ", "
1046 TO_UNIX_TIME_MS(server_ctime)));
1047 #undef TO_UNIX_TIME_MS
1048 if (!update_timestamps.Run())
1054 bool DirectoryBackingStore::MigrateVersion77To78() {
1055 // Version 78 added one column to table 'metas': base_server_specifics.
1057 "ALTER TABLE metas ADD COLUMN base_server_specifics BLOB")) {
1064 bool DirectoryBackingStore::MigrateVersion78To79() {
1065 // Some users are stuck with a DB that causes them to reuse existing IDs. We
1066 // perform this one-time fixup on all users to help the few that are stuck.
1067 // See crbug.com/142987 for details.
1069 "UPDATE share_info SET next_id = next_id - 65536")) {
1076 bool DirectoryBackingStore::MigrateVersion79To80() {
1078 "ALTER TABLE share_info ADD COLUMN bag_of_chips BLOB"))
1080 sql::Statement update(db_->GetUniqueStatement(
1081 "UPDATE share_info SET bag_of_chips = ?"));
1082 // An empty message is serialized to an empty string.
1083 update.BindBlob(0, NULL, 0);
1090 bool DirectoryBackingStore::MigrateVersion80To81() {
1092 "ALTER TABLE metas ADD COLUMN server_ordinal_in_parent BLOB"))
1095 sql::Statement get_positions(db_->GetUniqueStatement(
1096 "SELECT metahandle, server_position_in_parent FROM metas"));
1098 sql::Statement put_ordinals(db_->GetUniqueStatement(
1099 "UPDATE metas SET server_ordinal_in_parent = ?"
1100 "WHERE metahandle = ?"));
1102 while(get_positions.Step()) {
1103 int64 metahandle = get_positions.ColumnInt64(0);
1104 int64 position = get_positions.ColumnInt64(1);
1106 const std::string& ordinal = Int64ToNodeOrdinal(position).ToInternalValue();
1107 put_ordinals.BindBlob(0, ordinal.data(), ordinal.length());
1108 put_ordinals.BindInt64(1, metahandle);
1110 if(!put_ordinals.Run())
1112 put_ordinals.Reset(true);
1116 needs_column_refresh_ = true;
1120 bool DirectoryBackingStore::MigrateVersion81To82() {
1122 "ALTER TABLE models ADD COLUMN transaction_version BIGINT default 0"))
1124 sql::Statement update(db_->GetUniqueStatement(
1125 "UPDATE models SET transaction_version = 0"));
1132 bool DirectoryBackingStore::MigrateVersion82To83() {
1133 // Version 83 added transaction_version on sync node.
1135 "ALTER TABLE metas ADD COLUMN transaction_version BIGINT default 0"))
1137 sql::Statement update(db_->GetUniqueStatement(
1138 "UPDATE metas SET transaction_version = 0"));
1145 bool DirectoryBackingStore::MigrateVersion83To84() {
1146 // Version 84 added deleted_metas table to store deleted metas until we know
1147 // for sure that the deletions are persisted in native models.
1148 string query = "CREATE TABLE deleted_metas ";
1149 query.append(ComposeCreateTableColumnSpecs());
1150 if (!db_->Execute(query.c_str()))
1156 bool DirectoryBackingStore::MigrateVersion84To85() {
1157 // Version 85 removes the initial_sync_ended flag.
1158 if (!db_->Execute("ALTER TABLE models RENAME TO temp_models"))
1160 if (!CreateV81ModelsTable())
1162 if (!db_->Execute("INSERT INTO models SELECT "
1163 "model_id, progress_marker, transaction_version "
1164 "FROM temp_models")) {
1167 SafeDropTable("temp_models");
1173 bool DirectoryBackingStore::MigrateVersion85To86() {
1174 // Version 86 removes both server ordinals and local NEXT_ID, PREV_ID and
1175 // SERVER_{POSITION,ORDINAL}_IN_PARENT and replaces them with UNIQUE_POSITION
1176 // and SERVER_UNIQUE_POSITION.
1177 if (!db_->Execute("ALTER TABLE metas ADD COLUMN "
1178 "server_unique_position BLOB")) {
1181 if (!db_->Execute("ALTER TABLE metas ADD COLUMN "
1182 "unique_position BLOB")) {
1185 if (!db_->Execute("ALTER TABLE metas ADD COLUMN "
1186 "unique_bookmark_tag VARCHAR")) {
1190 // Fetch the cache_guid from the DB, because we don't otherwise have access to
1192 sql::Statement get_cache_guid(db_->GetUniqueStatement(
1193 "SELECT cache_guid FROM share_info"));
1194 if (!get_cache_guid.Step()) {
1197 std::string cache_guid = get_cache_guid.ColumnString(0);
1198 DCHECK(!get_cache_guid.Step());
1199 DCHECK(get_cache_guid.Succeeded());
1201 sql::Statement get(db_->GetUniqueStatement(
1207 " unique_server_tag, "
1208 " server_ordinal_in_parent "
1211 // Note that we set both the local and server position based on the server
1212 // position. We wll lose any unsynced local position changes. Unfortunately,
1213 // there's nothing we can do to avoid that. The NEXT_ID / PREV_ID values
1214 // can't be translated into a UNIQUE_POSTION in a reliable way.
1215 sql::Statement put(db_->GetCachedStatement(
1218 " server_unique_position = ?,"
1219 " unique_position = ?,"
1220 " unique_bookmark_tag = ?"
1221 "WHERE metahandle = ?"));
1223 while (get.Step()) {
1224 int64 metahandle = get.ColumnInt64(0);
1226 std::string id_string;
1227 get.ColumnBlobAsString(1, &id_string);
1229 sync_pb::EntitySpecifics specifics;
1230 specifics.ParseFromArray(
1231 get.ColumnBlob(2), get.ColumnByteLength(2));
1233 bool is_dir = get.ColumnBool(3);
1235 std::string server_unique_tag = get.ColumnString(4);
1237 std::string ordinal_string;
1238 get.ColumnBlobAsString(5, &ordinal_string);
1239 NodeOrdinal ordinal(ordinal_string);
1242 std::string unique_bookmark_tag;
1244 // We only maintain positions for bookmarks that are not server-defined
1245 // top-level folders.
1246 UniquePosition position;
1247 if (GetModelTypeFromSpecifics(specifics) == BOOKMARKS
1248 && !(is_dir && !server_unique_tag.empty())) {
1249 if (id_string.at(0) == 'c') {
1250 // We found an uncommitted item. This is rare, but fortunate. This
1251 // means we can set the bookmark tag according to the originator client
1252 // item ID and originator cache guid, because (unlike the other case) we
1253 // know that this client is the originator.
1254 unique_bookmark_tag = syncable::GenerateSyncableBookmarkHash(
1256 id_string.substr(1));
1258 // If we've already committed the item, then we don't know who the
1259 // originator was. We do not have access to the originator client item
1260 // ID and originator cache guid at this point.
1262 // We will base our hash entirely on the server ID instead. This is
1263 // incorrect, but at least all clients that undergo this migration step
1264 // will be incorrect in the same way.
1266 // To get everyone back into a synced state, we will update the bookmark
1267 // tag according to the originator_cache_guid and originator_item_id
1268 // when we see updates for this item. That should ensure that commonly
1269 // modified items will end up with the proper tag values eventually.
1270 unique_bookmark_tag = syncable::GenerateSyncableBookmarkHash(
1271 std::string(), // cache_guid left intentionally blank.
1272 id_string.substr(1));
1275 int64 int_position = NodeOrdinalToInt64(ordinal);
1276 position = UniquePosition::FromInt64(int_position, unique_bookmark_tag);
1278 // Leave bookmark_tag and position at their default (invalid) values.
1281 std::string position_blob;
1282 position.SerializeToString(&position_blob);
1283 put.BindBlob(0, position_blob.data(), position_blob.length());
1284 put.BindBlob(1, position_blob.data(), position_blob.length());
1285 put.BindBlob(2, unique_bookmark_tag.data(), unique_bookmark_tag.length());
1286 put.BindInt64(3, metahandle);
1294 needs_column_refresh_ = true;
1298 bool DirectoryBackingStore::MigrateVersion86To87() {
1299 // Version 87 adds AttachmentMetadata proto.
1301 "ALTER TABLE metas ADD COLUMN "
1302 "attachment_metadata BLOB")) {
1306 needs_column_refresh_ = true;
1310 bool DirectoryBackingStore::MigrateVersion87To88() {
1311 // Version 88 adds the datatype context to the models table.
1312 if (!db_->Execute("ALTER TABLE models ADD COLUMN context blob"))
1319 bool DirectoryBackingStore::CreateTables() {
1320 DVLOG(1) << "First run, creating tables";
1321 // Create two little tables share_version and share_info
1323 "CREATE TABLE share_version ("
1324 "id VARCHAR(128) primary key, data INT)")) {
1329 sql::Statement s(db_->GetUniqueStatement(
1330 "INSERT INTO share_version VALUES(?, ?)"));
1331 s.BindString(0, dir_name_);
1332 s.BindInt(1, kCurrentDBVersion);
1338 const bool kCreateAsTempShareInfo = false;
1339 if (!CreateShareInfoTable(kCreateAsTempShareInfo)) {
1344 sql::Statement s(db_->GetUniqueStatement(
1345 "INSERT INTO share_info VALUES"
1348 "?, " // store_birthday
1349 "?, " // db_create_version
1350 "?, " // db_create_time
1353 // TODO(rlarocque, 124140): Remove notification_state field.
1354 "?, " // notification_state
1355 "?);")); // bag_of_chips
1356 s.BindString(0, dir_name_); // id
1357 s.BindString(1, dir_name_); // name
1358 s.BindString(2, std::string()); // store_birthday
1359 // TODO(akalin): Remove this unused db_create_version field. (Or
1360 // actually use it for something.) http://crbug.com/118356
1361 s.BindString(3, "Unknown"); // db_create_version
1362 s.BindInt(4, static_cast<int32>(time(0))); // db_create_time
1363 s.BindString(5, GenerateCacheGUID()); // cache_guid
1364 // TODO(rlarocque, 124140): Remove this unused notification-state field.
1365 s.BindBlob(6, NULL, 0); // notification_state
1366 s.BindBlob(7, NULL, 0); // bag_of_chips
1371 if (!CreateModelsTable())
1374 // Create the big metas table.
1375 if (!CreateMetasTable(false))
1379 // Insert the entry for the root into the metas table.
1380 const int64 now = TimeToProtoTime(base::Time::Now());
1381 sql::Statement s(db_->GetUniqueStatement(
1382 "INSERT INTO metas "
1383 "( id, metahandle, is_dir, ctime, mtime ) "
1384 "VALUES ( \"r\", 1, 1, ?, ? )"));
1385 s.BindInt64(0, now);
1386 s.BindInt64(1, now);
1395 bool DirectoryBackingStore::CreateMetasTable(bool is_temporary) {
1396 string query = "CREATE TABLE ";
1397 query.append(is_temporary ? "temp_metas" : "metas");
1398 query.append(ComposeCreateTableColumnSpecs());
1399 if (!db_->Execute(query.c_str()))
1402 // Create a deleted_metas table to save copies of deleted metas until the
1403 // deletions are persisted. For simplicity, don't try to migrate existing
1404 // data because it's rarely used.
1405 SafeDropTable("deleted_metas");
1406 query = "CREATE TABLE deleted_metas ";
1407 query.append(ComposeCreateTableColumnSpecs());
1408 return db_->Execute(query.c_str());
1411 bool DirectoryBackingStore::CreateV71ModelsTable() {
1412 // This is an old schema for the Models table, used from versions 71 to 74.
1413 return db_->Execute(
1414 "CREATE TABLE models ("
1415 "model_id BLOB primary key, "
1416 "last_download_timestamp INT, "
1417 // Gets set if the syncer ever gets updates from the
1418 // server and the server returns 0. Lets us detect the
1419 // end of the initial sync.
1420 "initial_sync_ended BOOLEAN default 0)");
1423 bool DirectoryBackingStore::CreateV75ModelsTable() {
1424 // This is an old schema for the Models table, used from versions 75 to 80.
1425 return db_->Execute(
1426 "CREATE TABLE models ("
1427 "model_id BLOB primary key, "
1428 "progress_marker BLOB, "
1429 // Gets set if the syncer ever gets updates from the
1430 // server and the server returns 0. Lets us detect the
1431 // end of the initial sync.
1432 "initial_sync_ended BOOLEAN default 0)");
1435 bool DirectoryBackingStore::CreateV81ModelsTable() {
1436 // This is an old schema for the Models table, used from versions 81 to 87.
1437 return db_->Execute(
1438 "CREATE TABLE models ("
1439 "model_id BLOB primary key, "
1440 "progress_marker BLOB, "
1441 // Gets set if the syncer ever gets updates from the
1442 // server and the server returns 0. Lets us detect the
1443 // end of the initial sync.
1444 "transaction_version BIGINT default 0)");
1447 bool DirectoryBackingStore::CreateModelsTable() {
1448 // This is the current schema for the Models table, from version 88
1449 // onward. If you change the schema, you'll probably want to double-check
1450 // the use of this function in the v84-v85 migration.
1451 return db_->Execute(
1452 "CREATE TABLE models ("
1453 "model_id BLOB primary key, "
1454 "progress_marker BLOB, "
1455 // Gets set if the syncer ever gets updates from the
1456 // server and the server returns 0. Lets us detect the
1457 // end of the initial sync.
1458 "transaction_version BIGINT default 0,"
1462 bool DirectoryBackingStore::CreateShareInfoTable(bool is_temporary) {
1463 const char* name = is_temporary ? "temp_share_info" : "share_info";
1464 string query = "CREATE TABLE ";
1466 // This is the current schema for the ShareInfo table, from version 76
1469 "id TEXT primary key, "
1471 "store_birthday TEXT, "
1472 "db_create_version TEXT, "
1473 "db_create_time INT, "
1474 "next_id INT default -2, "
1476 // TODO(rlarocque, 124140): Remove notification_state field.
1477 "notification_state BLOB, "
1480 return db_->Execute(query.c_str());
1483 bool DirectoryBackingStore::CreateShareInfoTableVersion71(
1484 bool is_temporary) {
1485 const char* name = is_temporary ? "temp_share_info" : "share_info";
1486 string query = "CREATE TABLE ";
1488 // This is the schema for the ShareInfo table used from versions 71 to 72.
1490 "id TEXT primary key, "
1492 "store_birthday TEXT, "
1493 "db_create_version TEXT, "
1494 "db_create_time INT, "
1495 "next_id INT default -2, "
1496 "cache_guid TEXT )");
1497 return db_->Execute(query.c_str());
1500 // This function checks to see if the given list of Metahandles has any nodes
1501 // whose PARENT_ID values refer to ID values that do not actually exist.
1502 // Returns true on success.
1503 bool DirectoryBackingStore::VerifyReferenceIntegrity(
1504 const Directory::MetahandlesMap* handles_map) {
1505 TRACE_EVENT0("sync", "SyncDatabaseIntegrityCheck");
1506 using namespace syncable;
1507 typedef base::hash_set<std::string> IdsSet;
1512 for (Directory::MetahandlesMap::const_iterator it = handles_map->begin();
1513 it != handles_map->end(); ++it) {
1514 EntryKernel* entry = it->second;
1515 bool is_duplicate_id = !(ids_set.insert(entry->ref(ID).value()).second);
1516 is_ok = is_ok && !is_duplicate_id;
1519 IdsSet::iterator end = ids_set.end();
1520 for (Directory::MetahandlesMap::const_iterator it = handles_map->begin();
1521 it != handles_map->end(); ++it) {
1522 EntryKernel* entry = it->second;
1523 bool parent_exists = (ids_set.find(entry->ref(PARENT_ID).value()) != end);
1524 if (!parent_exists) {
1531 void DirectoryBackingStore::PrepareSaveEntryStatement(
1532 EntryTable table, sql::Statement* save_statement) {
1533 if (save_statement->is_valid())
1537 query.reserve(kUpdateStatementBufferSize);
1540 query.append("INSERT OR REPLACE INTO metas ");
1542 case DELETE_JOURNAL_TABLE:
1543 query.append("INSERT OR REPLACE INTO deleted_metas ");
1548 values.reserve(kUpdateStatementBufferSize);
1549 values.append(" VALUES ");
1550 const char* separator = "( ";
1552 for (i = BEGIN_FIELDS; i < FIELD_COUNT; ++i) {
1553 query.append(separator);
1554 values.append(separator);
1556 query.append(ColumnName(i));
1559 query.append(" ) ");
1560 values.append(" )");
1561 query.append(values);
1562 save_statement->Assign(db_->GetUniqueStatement(
1563 base::StringPrintf(query.c_str(), "metas").c_str()));
1566 } // namespace syncable
1567 } // namespace syncer