Add strings for the upcoming Settings API UI on the chrome://settings page.
[chromium-blink-merge.git] / sync / syncable / directory_backing_store.cc
blob5371f43b2a62e110b6681c91cf55f4d80b5adf53
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/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"
28 using std::string;
30 namespace syncer {
31 namespace syncable {
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 = 87;
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) {
44 int index = 0;
45 int i = 0;
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++,
51 TimeToProtoTime(
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) {
64 std::string temp;
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) {
69 std::string temp;
70 entry.ref(static_cast<UniquePositionField>(i)).SerializeToString(&temp);
71 statement->BindBlob(index++, temp.data(), temp.length());
75 // The caller owns the returned EntryKernel*. Assumes the statement currently
76 // points to a valid row in the metas table. Returns NULL to indicate that
77 // it detected a corruption in the data on unpacking.
78 scoped_ptr<EntryKernel> UnpackEntry(sql::Statement* statement) {
79 scoped_ptr<EntryKernel> kernel(new EntryKernel());
80 DCHECK_EQ(statement->ColumnCount(), static_cast<int>(FIELD_COUNT));
81 int i = 0;
82 for (i = BEGIN_FIELDS; i < INT64_FIELDS_END; ++i) {
83 kernel->put(static_cast<Int64Field>(i), statement->ColumnInt64(i));
85 for ( ; i < TIME_FIELDS_END; ++i) {
86 kernel->put(static_cast<TimeField>(i),
87 ProtoTimeToTime(statement->ColumnInt64(i)));
89 for ( ; i < ID_FIELDS_END; ++i) {
90 kernel->mutable_ref(static_cast<IdField>(i)).s_ =
91 statement->ColumnString(i);
93 for ( ; i < BIT_FIELDS_END; ++i) {
94 kernel->put(static_cast<BitField>(i), (0 != statement->ColumnInt(i)));
96 for ( ; i < STRING_FIELDS_END; ++i) {
97 kernel->put(static_cast<StringField>(i),
98 statement->ColumnString(i));
100 for ( ; i < PROTO_FIELDS_END; ++i) {
101 kernel->mutable_ref(static_cast<ProtoField>(i)).ParseFromArray(
102 statement->ColumnBlob(i), statement->ColumnByteLength(i));
104 for ( ; i < UNIQUE_POSITION_FIELDS_END; ++i) {
105 std::string temp;
106 statement->ColumnBlobAsString(i, &temp);
108 sync_pb::UniquePosition proto;
109 if (!proto.ParseFromString(temp)) {
110 DVLOG(1) << "Unpacked invalid position. Assuming the DB is corrupt";
111 return scoped_ptr<EntryKernel>();
114 kernel->mutable_ref(static_cast<UniquePositionField>(i)) =
115 UniquePosition::FromProto(proto);
117 return kernel.Pass();
120 namespace {
122 string ComposeCreateTableColumnSpecs() {
123 const ColumnSpec* begin = g_metas_columns;
124 const ColumnSpec* end = g_metas_columns + arraysize(g_metas_columns);
125 string query;
126 query.reserve(kUpdateStatementBufferSize);
127 char separator = '(';
128 for (const ColumnSpec* column = begin; column != end; ++column) {
129 query.push_back(separator);
130 separator = ',';
131 query.append(column->name);
132 query.push_back(' ');
133 query.append(column->spec);
135 query.push_back(')');
136 return query;
139 void AppendColumnList(std::string* output) {
140 const char* joiner = " ";
141 // Be explicit in SELECT order to match up with UnpackEntry.
142 for (int i = BEGIN_FIELDS; i < FIELD_COUNT; ++i) {
143 output->append(joiner);
144 output->append(ColumnName(i));
145 joiner = ", ";
149 } // namespace
151 ///////////////////////////////////////////////////////////////////////////////
152 // DirectoryBackingStore implementation.
154 DirectoryBackingStore::DirectoryBackingStore(const string& dir_name)
155 : db_(new sql::Connection()),
156 dir_name_(dir_name),
157 needs_column_refresh_(false) {
158 db_->set_histogram_tag("SyncDirectory");
159 db_->set_page_size(4096);
160 db_->set_cache_size(32);
163 DirectoryBackingStore::DirectoryBackingStore(const string& dir_name,
164 sql::Connection* db)
165 : db_(db),
166 dir_name_(dir_name),
167 needs_column_refresh_(false) {
170 DirectoryBackingStore::~DirectoryBackingStore() {
173 bool DirectoryBackingStore::DeleteEntries(EntryTable from,
174 const MetahandleSet& handles) {
175 if (handles.empty())
176 return true;
178 sql::Statement statement;
179 // Call GetCachedStatement() separately to get different statements for
180 // different tables.
181 switch (from) {
182 case METAS_TABLE:
183 statement.Assign(db_->GetCachedStatement(
184 SQL_FROM_HERE, "DELETE FROM metas WHERE metahandle = ?"));
185 break;
186 case DELETE_JOURNAL_TABLE:
187 statement.Assign(db_->GetCachedStatement(
188 SQL_FROM_HERE, "DELETE FROM deleted_metas WHERE metahandle = ?"));
189 break;
192 for (MetahandleSet::const_iterator i = handles.begin(); i != handles.end();
193 ++i) {
194 statement.BindInt64(0, *i);
195 if (!statement.Run())
196 return false;
197 statement.Reset(true);
199 return true;
202 bool DirectoryBackingStore::SaveChanges(
203 const Directory::SaveChangesSnapshot& snapshot) {
204 DCHECK(CalledOnValidThread());
205 DCHECK(db_->is_open());
207 // Back out early if there is nothing to write.
208 bool save_info =
209 (Directory::KERNEL_SHARE_INFO_DIRTY == snapshot.kernel_info_status);
210 if (snapshot.dirty_metas.empty() && snapshot.metahandles_to_purge.empty() &&
211 snapshot.delete_journals.empty() &&
212 snapshot.delete_journals_to_purge.empty() && !save_info) {
213 return true;
216 sql::Transaction transaction(db_.get());
217 if (!transaction.Begin())
218 return false;
220 PrepareSaveEntryStatement(METAS_TABLE, &save_meta_statment_);
221 for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin();
222 i != snapshot.dirty_metas.end(); ++i) {
223 DCHECK((*i)->is_dirty());
224 if (!SaveEntryToDB(&save_meta_statment_, **i))
225 return false;
228 if (!DeleteEntries(METAS_TABLE, snapshot.metahandles_to_purge))
229 return false;
231 PrepareSaveEntryStatement(DELETE_JOURNAL_TABLE,
232 &save_delete_journal_statment_);
233 for (EntryKernelSet::const_iterator i = snapshot.delete_journals.begin();
234 i != snapshot.delete_journals.end(); ++i) {
235 if (!SaveEntryToDB(&save_delete_journal_statment_, **i))
236 return false;
239 if (!DeleteEntries(DELETE_JOURNAL_TABLE, snapshot.delete_journals_to_purge))
240 return false;
242 if (save_info) {
243 const Directory::PersistedKernelInfo& info = snapshot.kernel_info;
244 sql::Statement s1(db_->GetCachedStatement(
245 SQL_FROM_HERE,
246 "UPDATE share_info "
247 "SET store_birthday = ?, "
248 "next_id = ?, "
249 "bag_of_chips = ?"));
250 s1.BindString(0, info.store_birthday);
251 s1.BindInt64(1, info.next_id);
252 s1.BindBlob(2, info.bag_of_chips.data(), info.bag_of_chips.size());
254 if (!s1.Run())
255 return false;
256 DCHECK_EQ(db_->GetLastChangeCount(), 1);
258 sql::Statement s2(db_->GetCachedStatement(
259 SQL_FROM_HERE,
260 "INSERT OR REPLACE "
261 "INTO models (model_id, progress_marker, transaction_version) "
262 "VALUES (?, ?, ?)"));
264 ModelTypeSet protocol_types = ProtocolTypes();
265 for (ModelTypeSet::Iterator iter = protocol_types.First(); iter.Good();
266 iter.Inc()) {
267 ModelType type = iter.Get();
268 // We persist not ModelType but rather a protobuf-derived ID.
269 string model_id = ModelTypeEnumToModelId(type);
270 string progress_marker;
271 info.download_progress[type].SerializeToString(&progress_marker);
272 s2.BindBlob(0, model_id.data(), model_id.length());
273 s2.BindBlob(1, progress_marker.data(), progress_marker.length());
274 s2.BindInt64(2, info.transaction_version[type]);
275 if (!s2.Run())
276 return false;
277 DCHECK_EQ(db_->GetLastChangeCount(), 1);
278 s2.Reset(true);
282 return transaction.Commit();
285 bool DirectoryBackingStore::InitializeTables() {
286 sql::Transaction transaction(db_.get());
287 if (!transaction.Begin())
288 return false;
290 int version_on_disk = GetVersion();
292 // Upgrade from version 67. Version 67 was widely distributed as the original
293 // Bookmark Sync release. Version 68 removed unique naming.
294 if (version_on_disk == 67) {
295 if (MigrateVersion67To68())
296 version_on_disk = 68;
298 // Version 69 introduced additional datatypes.
299 if (version_on_disk == 68) {
300 if (MigrateVersion68To69())
301 version_on_disk = 69;
304 if (version_on_disk == 69) {
305 if (MigrateVersion69To70())
306 version_on_disk = 70;
309 // Version 71 changed the sync progress information to be per-datatype.
310 if (version_on_disk == 70) {
311 if (MigrateVersion70To71())
312 version_on_disk = 71;
315 // Version 72 removed extended attributes, a legacy way to do extensible
316 // key/value information, stored in their own table.
317 if (version_on_disk == 71) {
318 if (MigrateVersion71To72())
319 version_on_disk = 72;
322 // Version 73 added a field for notification state.
323 if (version_on_disk == 72) {
324 if (MigrateVersion72To73())
325 version_on_disk = 73;
328 // Version 74 added state for the autofill migration.
329 if (version_on_disk == 73) {
330 if (MigrateVersion73To74())
331 version_on_disk = 74;
334 // Version 75 migrated from int64-based timestamps to per-datatype tokens.
335 if (version_on_disk == 74) {
336 if (MigrateVersion74To75())
337 version_on_disk = 75;
340 // Version 76 removed all (5) autofill migration related columns.
341 if (version_on_disk == 75) {
342 if (MigrateVersion75To76())
343 version_on_disk = 76;
346 // Version 77 standardized all time fields to ms since the Unix
347 // epoch.
348 if (version_on_disk == 76) {
349 if (MigrateVersion76To77())
350 version_on_disk = 77;
353 // Version 78 added the column base_server_specifics to the metas table.
354 if (version_on_disk == 77) {
355 if (MigrateVersion77To78())
356 version_on_disk = 78;
359 // Version 79 migration is a one-time fix for some users in a bad state.
360 if (version_on_disk == 78) {
361 if (MigrateVersion78To79())
362 version_on_disk = 79;
365 // Version 80 migration is adding the bag_of_chips column.
366 if (version_on_disk == 79) {
367 if (MigrateVersion79To80())
368 version_on_disk = 80;
371 // Version 81 replaces the int64 server_position_in_parent_field
372 // with a blob server_ordinal_in_parent field.
373 if (version_on_disk == 80) {
374 if (MigrateVersion80To81())
375 version_on_disk = 81;
378 // Version 82 migration added transaction_version column per data type.
379 if (version_on_disk == 81) {
380 if (MigrateVersion81To82())
381 version_on_disk = 82;
384 // Version 83 migration added transaction_version column per sync entry.
385 if (version_on_disk == 82) {
386 if (MigrateVersion82To83())
387 version_on_disk = 83;
390 // Version 84 migration added deleted_metas table.
391 if (version_on_disk == 83) {
392 if (MigrateVersion83To84())
393 version_on_disk = 84;
396 // Version 85 migration removes the initial_sync_ended bits.
397 if (version_on_disk == 84) {
398 if (MigrateVersion84To85())
399 version_on_disk = 85;
402 // Version 86 migration converts bookmarks to the unique positioning system.
403 // It also introduces a new field to store a unique ID for each bookmark.
404 if (version_on_disk == 85) {
405 if (MigrateVersion85To86())
406 version_on_disk = 86;
409 // Version 87 migration adds a collection of attachment ids per sync entry.
410 if (version_on_disk == 86) {
411 if (MigrateVersion86To87())
412 version_on_disk = 87;
415 // If one of the migrations requested it, drop columns that aren't current.
416 // It's only safe to do this after migrating all the way to the current
417 // version.
418 if (version_on_disk == kCurrentDBVersion && needs_column_refresh_) {
419 if (!RefreshColumns())
420 version_on_disk = 0;
423 // A final, alternative catch-all migration to simply re-sync everything.
424 if (version_on_disk != kCurrentDBVersion) {
425 if (version_on_disk > kCurrentDBVersion)
426 return false;
428 // Fallback (re-sync everything) migration path.
429 DVLOG(1) << "Old/null sync database, version " << version_on_disk;
430 // Delete the existing database (if any), and create a fresh one.
431 DropAllTables();
432 if (!CreateTables())
433 return false;
436 sql::Statement s(db_->GetUniqueStatement(
437 "SELECT db_create_version, db_create_time FROM share_info"));
438 if (!s.Step())
439 return false;
440 string db_create_version = s.ColumnString(0);
441 int db_create_time = s.ColumnInt(1);
442 DVLOG(1) << "DB created at " << db_create_time << " by version " <<
443 db_create_version;
445 return transaction.Commit();
448 // This function drops unused columns by creating a new table that contains only
449 // the currently used columns then copying all rows from the old tables into
450 // this new one. The tables are then rearranged so the new replaces the old.
451 bool DirectoryBackingStore::RefreshColumns() {
452 DCHECK(needs_column_refresh_);
454 // Create a new table named temp_metas.
455 SafeDropTable("temp_metas");
456 if (!CreateMetasTable(true))
457 return false;
459 // Populate temp_metas from metas.
461 // At this point, the metas table may contain columns belonging to obsolete
462 // schema versions. This statement explicitly lists only the columns that
463 // belong to the current schema version, so the obsolete columns will be
464 // effectively dropped once we rename temp_metas over top of metas.
465 std::string query = "INSERT INTO temp_metas (";
466 AppendColumnList(&query);
467 query.append(") SELECT ");
468 AppendColumnList(&query);
469 query.append(" FROM metas");
470 if (!db_->Execute(query.c_str()))
471 return false;
473 // Drop metas.
474 SafeDropTable("metas");
476 // Rename temp_metas -> metas.
477 if (!db_->Execute("ALTER TABLE temp_metas RENAME TO metas"))
478 return false;
480 // Repeat the process for share_info.
481 SafeDropTable("temp_share_info");
482 if (!CreateShareInfoTable(true))
483 return false;
485 // TODO(rlarocque, 124140): Remove notification_state.
486 if (!db_->Execute(
487 "INSERT INTO temp_share_info (id, name, store_birthday, "
488 "db_create_version, db_create_time, next_id, cache_guid,"
489 "notification_state, bag_of_chips) "
490 "SELECT id, name, store_birthday, db_create_version, "
491 "db_create_time, next_id, cache_guid, notification_state, "
492 "bag_of_chips "
493 "FROM share_info"))
494 return false;
496 SafeDropTable("share_info");
497 if (!db_->Execute("ALTER TABLE temp_share_info RENAME TO share_info"))
498 return false;
500 needs_column_refresh_ = false;
501 return true;
504 bool DirectoryBackingStore::LoadEntries(
505 Directory::MetahandlesMap* handles_map) {
506 string select;
507 select.reserve(kUpdateStatementBufferSize);
508 select.append("SELECT ");
509 AppendColumnList(&select);
510 select.append(" FROM metas");
512 sql::Statement s(db_->GetUniqueStatement(select.c_str()));
514 while (s.Step()) {
515 scoped_ptr<EntryKernel> kernel = UnpackEntry(&s);
516 // A null kernel is evidence of external data corruption.
517 if (!kernel)
518 return false;
520 int64 handle = kernel->ref(META_HANDLE);
521 (*handles_map)[handle] = kernel.release();
523 return s.Succeeded();
526 bool DirectoryBackingStore::LoadDeleteJournals(
527 JournalIndex* delete_journals) {
528 string select;
529 select.reserve(kUpdateStatementBufferSize);
530 select.append("SELECT ");
531 AppendColumnList(&select);
532 select.append(" FROM deleted_metas");
534 sql::Statement s(db_->GetUniqueStatement(select.c_str()));
536 while (s.Step()) {
537 scoped_ptr<EntryKernel> kernel = UnpackEntry(&s);
538 // A null kernel is evidence of external data corruption.
539 if (!kernel)
540 return false;
541 delete_journals->insert(kernel.release());
543 return s.Succeeded();
546 bool DirectoryBackingStore::LoadInfo(Directory::KernelLoadInfo* info) {
548 sql::Statement s(
549 db_->GetUniqueStatement(
550 "SELECT store_birthday, next_id, cache_guid, bag_of_chips "
551 "FROM share_info"));
552 if (!s.Step())
553 return false;
555 info->kernel_info.store_birthday = s.ColumnString(0);
556 info->kernel_info.next_id = s.ColumnInt64(1);
557 info->cache_guid = s.ColumnString(2);
558 s.ColumnBlobAsString(3, &(info->kernel_info.bag_of_chips));
560 // Verify there was only one row returned.
561 DCHECK(!s.Step());
562 DCHECK(s.Succeeded());
566 sql::Statement s(
567 db_->GetUniqueStatement(
568 "SELECT model_id, progress_marker, "
569 "transaction_version FROM models"));
571 while (s.Step()) {
572 ModelType type = ModelIdToModelTypeEnum(s.ColumnBlob(0),
573 s.ColumnByteLength(0));
574 if (type != UNSPECIFIED && type != TOP_LEVEL_FOLDER) {
575 info->kernel_info.download_progress[type].ParseFromArray(
576 s.ColumnBlob(1), s.ColumnByteLength(1));
577 info->kernel_info.transaction_version[type] = s.ColumnInt64(2);
580 if (!s.Succeeded())
581 return false;
584 sql::Statement s(
585 db_->GetUniqueStatement(
586 "SELECT MAX(metahandle) FROM metas"));
587 if (!s.Step())
588 return false;
590 info->max_metahandle = s.ColumnInt64(0);
592 // Verify only one row was returned.
593 DCHECK(!s.Step());
594 DCHECK(s.Succeeded());
596 return true;
599 /* static */
600 bool DirectoryBackingStore::SaveEntryToDB(sql::Statement* save_statement,
601 const EntryKernel& entry) {
602 save_statement->Reset(true);
603 BindFields(entry, save_statement);
604 return save_statement->Run();
607 bool DirectoryBackingStore::DropDeletedEntries() {
608 if (!db_->Execute("DELETE FROM metas "
609 "WHERE is_del > 0 "
610 "AND is_unsynced < 1 "
611 "AND is_unapplied_update < 1")) {
612 return false;
614 if (!db_->Execute("DELETE FROM metas "
615 "WHERE is_del > 0 "
616 "AND id LIKE 'c%'")) {
617 return false;
619 return true;
622 bool DirectoryBackingStore::SafeDropTable(const char* table_name) {
623 string query = "DROP TABLE IF EXISTS ";
624 query.append(table_name);
625 return db_->Execute(query.c_str());
628 void DirectoryBackingStore::DropAllTables() {
629 SafeDropTable("metas");
630 SafeDropTable("temp_metas");
631 SafeDropTable("share_info");
632 SafeDropTable("temp_share_info");
633 SafeDropTable("share_version");
634 SafeDropTable("extended_attributes");
635 SafeDropTable("models");
636 SafeDropTable("temp_models");
637 needs_column_refresh_ = false;
640 // static
641 ModelType DirectoryBackingStore::ModelIdToModelTypeEnum(
642 const void* data, int size) {
643 sync_pb::EntitySpecifics specifics;
644 if (!specifics.ParseFromArray(data, size))
645 return UNSPECIFIED;
646 return GetModelTypeFromSpecifics(specifics);
649 // static
650 string DirectoryBackingStore::ModelTypeEnumToModelId(ModelType model_type) {
651 sync_pb::EntitySpecifics specifics;
652 AddDefaultFieldValue(model_type, &specifics);
653 return specifics.SerializeAsString();
656 // static
657 std::string DirectoryBackingStore::GenerateCacheGUID() {
658 // Generate a GUID with 128 bits of randomness.
659 const int kGuidBytes = 128 / 8;
660 std::string guid;
661 base::Base64Encode(base::RandBytesAsString(kGuidBytes), &guid);
662 return guid;
665 bool DirectoryBackingStore::MigrateToSpecifics(
666 const char* old_columns,
667 const char* specifics_column,
668 void (*handler_function)(sql::Statement* old_value_query,
669 int old_value_column,
670 sync_pb::EntitySpecifics* mutable_new_value)) {
671 std::string query_sql = base::StringPrintf(
672 "SELECT metahandle, %s, %s FROM metas", specifics_column, old_columns);
673 std::string update_sql = base::StringPrintf(
674 "UPDATE metas SET %s = ? WHERE metahandle = ?", specifics_column);
676 sql::Statement query(db_->GetUniqueStatement(query_sql.c_str()));
677 sql::Statement update(db_->GetUniqueStatement(update_sql.c_str()));
679 while (query.Step()) {
680 int64 metahandle = query.ColumnInt64(0);
681 std::string new_value_bytes;
682 query.ColumnBlobAsString(1, &new_value_bytes);
683 sync_pb::EntitySpecifics new_value;
684 new_value.ParseFromString(new_value_bytes);
685 handler_function(&query, 2, &new_value);
686 new_value.SerializeToString(&new_value_bytes);
688 update.BindBlob(0, new_value_bytes.data(), new_value_bytes.length());
689 update.BindInt64(1, metahandle);
690 if (!update.Run())
691 return false;
692 update.Reset(true);
694 return query.Succeeded();
697 bool DirectoryBackingStore::SetVersion(int version) {
698 sql::Statement s(db_->GetCachedStatement(
699 SQL_FROM_HERE, "UPDATE share_version SET data = ?"));
700 s.BindInt(0, version);
702 return s.Run();
705 int DirectoryBackingStore::GetVersion() {
706 if (!db_->DoesTableExist("share_version"))
707 return 0;
709 sql::Statement statement(db_->GetUniqueStatement(
710 "SELECT data FROM share_version"));
711 if (statement.Step()) {
712 return statement.ColumnInt(0);
713 } else {
714 return 0;
718 bool DirectoryBackingStore::MigrateVersion67To68() {
719 // This change simply removed three columns:
720 // string NAME
721 // string UNSANITIZED_NAME
722 // string SERVER_NAME
723 // No data migration is necessary, but we should do a column refresh.
724 SetVersion(68);
725 needs_column_refresh_ = true;
726 return true;
729 bool DirectoryBackingStore::MigrateVersion69To70() {
730 // Added "unique_client_tag", renamed "singleton_tag" to unique_server_tag
731 SetVersion(70);
732 if (!db_->Execute(
733 "ALTER TABLE metas ADD COLUMN unique_server_tag varchar"))
734 return false;
735 if (!db_->Execute(
736 "ALTER TABLE metas ADD COLUMN unique_client_tag varchar"))
737 return false;
738 needs_column_refresh_ = true;
740 if (!db_->Execute(
741 "UPDATE metas SET unique_server_tag = singleton_tag"))
742 return false;
744 return true;
747 namespace {
749 // Callback passed to MigrateToSpecifics for the v68->v69 migration. See
750 // MigrateVersion68To69().
751 void EncodeBookmarkURLAndFavicon(sql::Statement* old_value_query,
752 int old_value_column,
753 sync_pb::EntitySpecifics* mutable_new_value) {
754 // Extract data from the column trio we expect.
755 bool old_is_bookmark_object = old_value_query->ColumnBool(old_value_column);
756 std::string old_url = old_value_query->ColumnString(old_value_column + 1);
757 std::string old_favicon;
758 old_value_query->ColumnBlobAsString(old_value_column + 2, &old_favicon);
759 bool old_is_dir = old_value_query->ColumnBool(old_value_column + 3);
761 if (old_is_bookmark_object) {
762 sync_pb::BookmarkSpecifics* bookmark_data =
763 mutable_new_value->mutable_bookmark();
764 if (!old_is_dir) {
765 bookmark_data->set_url(old_url);
766 bookmark_data->set_favicon(old_favicon);
771 } // namespace
773 bool DirectoryBackingStore::MigrateVersion68To69() {
774 // In Version 68, there were columns on table 'metas':
775 // string BOOKMARK_URL
776 // string SERVER_BOOKMARK_URL
777 // blob BOOKMARK_FAVICON
778 // blob SERVER_BOOKMARK_FAVICON
779 // In version 69, these columns went away in favor of storing
780 // a serialized EntrySpecifics protobuf in the columns:
781 // protobuf blob SPECIFICS
782 // protobuf blob SERVER_SPECIFICS
783 // For bookmarks, EntrySpecifics is extended as per
784 // bookmark_specifics.proto. This migration converts bookmarks from the
785 // former scheme to the latter scheme.
787 // First, add the two new columns to the schema.
788 if (!db_->Execute(
789 "ALTER TABLE metas ADD COLUMN specifics blob"))
790 return false;
791 if (!db_->Execute(
792 "ALTER TABLE metas ADD COLUMN server_specifics blob"))
793 return false;
795 // Next, fold data from the old columns into the new protobuf columns.
796 if (!MigrateToSpecifics(("is_bookmark_object, bookmark_url, "
797 "bookmark_favicon, is_dir"),
798 "specifics",
799 &EncodeBookmarkURLAndFavicon)) {
800 return false;
802 if (!MigrateToSpecifics(("server_is_bookmark_object, "
803 "server_bookmark_url, "
804 "server_bookmark_favicon, "
805 "server_is_dir"),
806 "server_specifics",
807 &EncodeBookmarkURLAndFavicon)) {
808 return false;
811 // Lastly, fix up the "Google Chrome" folder, which is of the TOP_LEVEL_FOLDER
812 // ModelType: it shouldn't have BookmarkSpecifics.
813 if (!db_->Execute(
814 "UPDATE metas SET specifics = NULL, server_specifics = NULL WHERE "
815 "singleton_tag IN ('google_chrome')"))
816 return false;
818 SetVersion(69);
819 needs_column_refresh_ = true; // Trigger deletion of old columns.
820 return true;
823 // Version 71, the columns 'initial_sync_ended' and 'last_sync_timestamp'
824 // were removed from the share_info table. They were replaced by
825 // the 'models' table, which has these values on a per-datatype basis.
826 bool DirectoryBackingStore::MigrateVersion70To71() {
827 if (!CreateV71ModelsTable())
828 return false;
830 // Move data from the old share_info columns to the new models table.
832 sql::Statement fetch(db_->GetUniqueStatement(
833 "SELECT last_sync_timestamp, initial_sync_ended FROM share_info"));
834 if (!fetch.Step())
835 return false;
837 int64 last_sync_timestamp = fetch.ColumnInt64(0);
838 bool initial_sync_ended = fetch.ColumnBool(1);
840 // Verify there were no additional rows returned.
841 DCHECK(!fetch.Step());
842 DCHECK(fetch.Succeeded());
844 sql::Statement update(db_->GetUniqueStatement(
845 "INSERT INTO models (model_id, "
846 "last_download_timestamp, initial_sync_ended) VALUES (?, ?, ?)"));
847 string bookmark_model_id = ModelTypeEnumToModelId(BOOKMARKS);
848 update.BindBlob(0, bookmark_model_id.data(), bookmark_model_id.size());
849 update.BindInt64(1, last_sync_timestamp);
850 update.BindBool(2, initial_sync_ended);
852 if (!update.Run())
853 return false;
856 // Drop the columns from the old share_info table via a temp table.
857 const bool kCreateAsTempShareInfo = true;
859 if (!CreateShareInfoTableVersion71(kCreateAsTempShareInfo))
860 return false;
861 if (!db_->Execute(
862 "INSERT INTO temp_share_info (id, name, store_birthday, "
863 "db_create_version, db_create_time, next_id, cache_guid) "
864 "SELECT id, name, store_birthday, db_create_version, "
865 "db_create_time, next_id, cache_guid FROM share_info"))
866 return false;
867 SafeDropTable("share_info");
868 if (!db_->Execute(
869 "ALTER TABLE temp_share_info RENAME TO share_info"))
870 return false;
871 SetVersion(71);
872 return true;
875 bool DirectoryBackingStore::MigrateVersion71To72() {
876 // Version 72 removed a table 'extended_attributes', whose
877 // contents didn't matter.
878 SafeDropTable("extended_attributes");
879 SetVersion(72);
880 return true;
883 bool DirectoryBackingStore::MigrateVersion72To73() {
884 // Version 73 added one column to the table 'share_info': notification_state
885 if (!db_->Execute(
886 "ALTER TABLE share_info ADD COLUMN notification_state BLOB"))
887 return false;
888 SetVersion(73);
889 return true;
892 bool DirectoryBackingStore::MigrateVersion73To74() {
893 // Version 74 added the following columns to the table 'share_info':
894 // autofill_migration_state
895 // bookmarks_added_during_autofill_migration
896 // autofill_migration_time
897 // autofill_entries_added_during_migration
898 // autofill_profiles_added_during_migration
900 if (!db_->Execute(
901 "ALTER TABLE share_info ADD COLUMN "
902 "autofill_migration_state INT default 0"))
903 return false;
905 if (!db_->Execute(
906 "ALTER TABLE share_info ADD COLUMN "
907 "bookmarks_added_during_autofill_migration "
908 "INT default 0"))
909 return false;
911 if (!db_->Execute(
912 "ALTER TABLE share_info ADD COLUMN autofill_migration_time "
913 "INT default 0"))
914 return false;
916 if (!db_->Execute(
917 "ALTER TABLE share_info ADD COLUMN "
918 "autofill_entries_added_during_migration "
919 "INT default 0"))
920 return false;
922 if (!db_->Execute(
923 "ALTER TABLE share_info ADD COLUMN "
924 "autofill_profiles_added_during_migration "
925 "INT default 0"))
926 return false;
928 SetVersion(74);
929 return true;
932 bool DirectoryBackingStore::MigrateVersion74To75() {
933 // In version 74, there was a table 'models':
934 // blob model_id (entity specifics, primary key)
935 // int last_download_timestamp
936 // boolean initial_sync_ended
937 // In version 75, we deprecated the integer-valued last_download_timestamp,
938 // using insted a protobuf-valued progress_marker field:
939 // blob progress_marker
940 // The progress_marker values are initialized from the value of
941 // last_download_timestamp, thereby preserving the download state.
943 // Move aside the old table and create a new empty one at the current schema.
944 if (!db_->Execute("ALTER TABLE models RENAME TO temp_models"))
945 return false;
946 if (!CreateV75ModelsTable())
947 return false;
949 sql::Statement query(db_->GetUniqueStatement(
950 "SELECT model_id, last_download_timestamp, initial_sync_ended "
951 "FROM temp_models"));
953 sql::Statement update(db_->GetUniqueStatement(
954 "INSERT INTO models (model_id, "
955 "progress_marker, initial_sync_ended) VALUES (?, ?, ?)"));
957 while (query.Step()) {
958 ModelType type = ModelIdToModelTypeEnum(query.ColumnBlob(0),
959 query.ColumnByteLength(0));
960 if (type != UNSPECIFIED) {
961 // Set the |timestamp_token_for_migration| on a new
962 // DataTypeProgressMarker, using the old value of last_download_timestamp.
963 // The server will turn this into a real token on our behalf the next
964 // time we check for updates.
965 sync_pb::DataTypeProgressMarker progress_marker;
966 progress_marker.set_data_type_id(
967 GetSpecificsFieldNumberFromModelType(type));
968 progress_marker.set_timestamp_token_for_migration(query.ColumnInt64(1));
969 std::string progress_blob;
970 progress_marker.SerializeToString(&progress_blob);
972 update.BindBlob(0, query.ColumnBlob(0), query.ColumnByteLength(0));
973 update.BindBlob(1, progress_blob.data(), progress_blob.length());
974 update.BindBool(2, query.ColumnBool(2));
975 if (!update.Run())
976 return false;
977 update.Reset(true);
980 if (!query.Succeeded())
981 return false;
983 // Drop the old table.
984 SafeDropTable("temp_models");
986 SetVersion(75);
987 return true;
990 bool DirectoryBackingStore::MigrateVersion75To76() {
991 // This change removed five columns:
992 // autofill_migration_state
993 // bookmarks_added_during_autofill_migration
994 // autofill_migration_time
995 // autofill_entries_added_during_migration
996 // autofill_profiles_added_during_migration
997 // No data migration is necessary, but we should do a column refresh.
998 SetVersion(76);
999 needs_column_refresh_ = true;
1000 return true;
1003 bool DirectoryBackingStore::MigrateVersion76To77() {
1004 // This change changes the format of stored timestamps to ms since
1005 // the Unix epoch.
1006 #if defined(OS_WIN)
1007 // On Windows, we used to store timestamps in FILETIME format (100s of
1008 // ns since Jan 1, 1601). Magic numbers taken from
1009 // http://stackoverflow.com/questions/5398557/
1010 // java-library-for-dealing-with-win32-filetime
1011 // .
1012 #define TO_UNIX_TIME_MS(x) #x " = " #x " / 10000 - 11644473600000"
1013 #else
1014 // On other platforms, we used to store timestamps in time_t format (s
1015 // since the Unix epoch).
1016 #define TO_UNIX_TIME_MS(x) #x " = " #x " * 1000"
1017 #endif
1018 sql::Statement update_timestamps(db_->GetUniqueStatement(
1019 "UPDATE metas SET "
1020 TO_UNIX_TIME_MS(mtime) ", "
1021 TO_UNIX_TIME_MS(server_mtime) ", "
1022 TO_UNIX_TIME_MS(ctime) ", "
1023 TO_UNIX_TIME_MS(server_ctime)));
1024 #undef TO_UNIX_TIME_MS
1025 if (!update_timestamps.Run())
1026 return false;
1027 SetVersion(77);
1028 return true;
1031 bool DirectoryBackingStore::MigrateVersion77To78() {
1032 // Version 78 added one column to table 'metas': base_server_specifics.
1033 if (!db_->Execute(
1034 "ALTER TABLE metas ADD COLUMN base_server_specifics BLOB")) {
1035 return false;
1037 SetVersion(78);
1038 return true;
1041 bool DirectoryBackingStore::MigrateVersion78To79() {
1042 // Some users are stuck with a DB that causes them to reuse existing IDs. We
1043 // perform this one-time fixup on all users to help the few that are stuck.
1044 // See crbug.com/142987 for details.
1045 if (!db_->Execute(
1046 "UPDATE share_info SET next_id = next_id - 65536")) {
1047 return false;
1049 SetVersion(79);
1050 return true;
1053 bool DirectoryBackingStore::MigrateVersion79To80() {
1054 if (!db_->Execute(
1055 "ALTER TABLE share_info ADD COLUMN bag_of_chips BLOB"))
1056 return false;
1057 sql::Statement update(db_->GetUniqueStatement(
1058 "UPDATE share_info SET bag_of_chips = ?"));
1059 // An empty message is serialized to an empty string.
1060 update.BindBlob(0, NULL, 0);
1061 if (!update.Run())
1062 return false;
1063 SetVersion(80);
1064 return true;
1067 bool DirectoryBackingStore::MigrateVersion80To81() {
1068 if(!db_->Execute(
1069 "ALTER TABLE metas ADD COLUMN server_ordinal_in_parent BLOB"))
1070 return false;
1072 sql::Statement get_positions(db_->GetUniqueStatement(
1073 "SELECT metahandle, server_position_in_parent FROM metas"));
1075 sql::Statement put_ordinals(db_->GetUniqueStatement(
1076 "UPDATE metas SET server_ordinal_in_parent = ?"
1077 "WHERE metahandle = ?"));
1079 while(get_positions.Step()) {
1080 int64 metahandle = get_positions.ColumnInt64(0);
1081 int64 position = get_positions.ColumnInt64(1);
1083 const std::string& ordinal = Int64ToNodeOrdinal(position).ToInternalValue();
1084 put_ordinals.BindBlob(0, ordinal.data(), ordinal.length());
1085 put_ordinals.BindInt64(1, metahandle);
1087 if(!put_ordinals.Run())
1088 return false;
1089 put_ordinals.Reset(true);
1092 SetVersion(81);
1093 needs_column_refresh_ = true;
1094 return true;
1097 bool DirectoryBackingStore::MigrateVersion81To82() {
1098 if (!db_->Execute(
1099 "ALTER TABLE models ADD COLUMN transaction_version BIGINT default 0"))
1100 return false;
1101 sql::Statement update(db_->GetUniqueStatement(
1102 "UPDATE models SET transaction_version = 0"));
1103 if (!update.Run())
1104 return false;
1105 SetVersion(82);
1106 return true;
1109 bool DirectoryBackingStore::MigrateVersion82To83() {
1110 // Version 83 added transaction_version on sync node.
1111 if (!db_->Execute(
1112 "ALTER TABLE metas ADD COLUMN transaction_version BIGINT default 0"))
1113 return false;
1114 sql::Statement update(db_->GetUniqueStatement(
1115 "UPDATE metas SET transaction_version = 0"));
1116 if (!update.Run())
1117 return false;
1118 SetVersion(83);
1119 return true;
1122 bool DirectoryBackingStore::MigrateVersion83To84() {
1123 // Version 84 added deleted_metas table to store deleted metas until we know
1124 // for sure that the deletions are persisted in native models.
1125 string query = "CREATE TABLE deleted_metas ";
1126 query.append(ComposeCreateTableColumnSpecs());
1127 if (!db_->Execute(query.c_str()))
1128 return false;
1129 SetVersion(84);
1130 return true;
1133 bool DirectoryBackingStore::MigrateVersion84To85() {
1134 // Version 85 removes the initial_sync_ended flag.
1135 if (!db_->Execute("ALTER TABLE models RENAME TO temp_models"))
1136 return false;
1137 if (!CreateModelsTable())
1138 return false;
1139 if (!db_->Execute("INSERT INTO models SELECT "
1140 "model_id, progress_marker, transaction_version "
1141 "FROM temp_models")) {
1142 return false;
1144 SafeDropTable("temp_models");
1146 SetVersion(85);
1147 return true;
1150 bool DirectoryBackingStore::MigrateVersion85To86() {
1151 // Version 86 removes both server ordinals and local NEXT_ID, PREV_ID and
1152 // SERVER_{POSITION,ORDINAL}_IN_PARENT and replaces them with UNIQUE_POSITION
1153 // and SERVER_UNIQUE_POSITION.
1154 if (!db_->Execute("ALTER TABLE metas ADD COLUMN "
1155 "server_unique_position BLOB")) {
1156 return false;
1158 if (!db_->Execute("ALTER TABLE metas ADD COLUMN "
1159 "unique_position BLOB")) {
1160 return false;
1162 if (!db_->Execute("ALTER TABLE metas ADD COLUMN "
1163 "unique_bookmark_tag VARCHAR")) {
1164 return false;
1167 // Fetch the cache_guid from the DB, because we don't otherwise have access to
1168 // it from here.
1169 sql::Statement get_cache_guid(db_->GetUniqueStatement(
1170 "SELECT cache_guid FROM share_info"));
1171 if (!get_cache_guid.Step()) {
1172 return false;
1174 std::string cache_guid = get_cache_guid.ColumnString(0);
1175 DCHECK(!get_cache_guid.Step());
1176 DCHECK(get_cache_guid.Succeeded());
1178 sql::Statement get(db_->GetUniqueStatement(
1179 "SELECT "
1180 " metahandle, "
1181 " id, "
1182 " specifics, "
1183 " is_dir, "
1184 " unique_server_tag, "
1185 " server_ordinal_in_parent "
1186 "FROM metas"));
1188 // Note that we set both the local and server position based on the server
1189 // position. We wll lose any unsynced local position changes. Unfortunately,
1190 // there's nothing we can do to avoid that. The NEXT_ID / PREV_ID values
1191 // can't be translated into a UNIQUE_POSTION in a reliable way.
1192 sql::Statement put(db_->GetCachedStatement(
1193 SQL_FROM_HERE,
1194 "UPDATE metas SET"
1195 " server_unique_position = ?,"
1196 " unique_position = ?,"
1197 " unique_bookmark_tag = ?"
1198 "WHERE metahandle = ?"));
1200 while (get.Step()) {
1201 int64 metahandle = get.ColumnInt64(0);
1203 std::string id_string;
1204 get.ColumnBlobAsString(1, &id_string);
1206 sync_pb::EntitySpecifics specifics;
1207 specifics.ParseFromArray(
1208 get.ColumnBlob(2), get.ColumnByteLength(2));
1210 bool is_dir = get.ColumnBool(3);
1212 std::string server_unique_tag = get.ColumnString(4);
1214 std::string ordinal_string;
1215 get.ColumnBlobAsString(5, &ordinal_string);
1216 NodeOrdinal ordinal(ordinal_string);
1219 std::string unique_bookmark_tag;
1221 // We only maintain positions for bookmarks that are not server-defined
1222 // top-level folders.
1223 UniquePosition position;
1224 if (GetModelTypeFromSpecifics(specifics) == BOOKMARKS
1225 && !(is_dir && !server_unique_tag.empty())) {
1226 if (id_string.at(0) == 'c') {
1227 // We found an uncommitted item. This is rare, but fortunate. This
1228 // means we can set the bookmark tag according to the originator client
1229 // item ID and originator cache guid, because (unlike the other case) we
1230 // know that this client is the originator.
1231 unique_bookmark_tag = syncable::GenerateSyncableBookmarkHash(
1232 cache_guid,
1233 id_string.substr(1));
1234 } else {
1235 // If we've already committed the item, then we don't know who the
1236 // originator was. We do not have access to the originator client item
1237 // ID and originator cache guid at this point.
1239 // We will base our hash entirely on the server ID instead. This is
1240 // incorrect, but at least all clients that undergo this migration step
1241 // will be incorrect in the same way.
1243 // To get everyone back into a synced state, we will update the bookmark
1244 // tag according to the originator_cache_guid and originator_item_id
1245 // when we see updates for this item. That should ensure that commonly
1246 // modified items will end up with the proper tag values eventually.
1247 unique_bookmark_tag = syncable::GenerateSyncableBookmarkHash(
1248 std::string(), // cache_guid left intentionally blank.
1249 id_string.substr(1));
1252 int64 int_position = NodeOrdinalToInt64(ordinal);
1253 position = UniquePosition::FromInt64(int_position, unique_bookmark_tag);
1254 } else {
1255 // Leave bookmark_tag and position at their default (invalid) values.
1258 std::string position_blob;
1259 position.SerializeToString(&position_blob);
1260 put.BindBlob(0, position_blob.data(), position_blob.length());
1261 put.BindBlob(1, position_blob.data(), position_blob.length());
1262 put.BindBlob(2, unique_bookmark_tag.data(), unique_bookmark_tag.length());
1263 put.BindInt64(3, metahandle);
1265 if (!put.Run())
1266 return false;
1267 put.Reset(true);
1270 SetVersion(86);
1271 needs_column_refresh_ = true;
1272 return true;
1275 bool DirectoryBackingStore::MigrateVersion86To87() {
1276 // Version 87 adds AttachmentMetadata proto.
1277 if (!db_->Execute(
1278 "ALTER TABLE metas ADD COLUMN "
1279 "attachment_metadata BLOB")) {
1280 return false;
1282 SetVersion(87);
1283 needs_column_refresh_ = true;
1284 return true;
1287 bool DirectoryBackingStore::CreateTables() {
1288 DVLOG(1) << "First run, creating tables";
1289 // Create two little tables share_version and share_info
1290 if (!db_->Execute(
1291 "CREATE TABLE share_version ("
1292 "id VARCHAR(128) primary key, data INT)")) {
1293 return false;
1297 sql::Statement s(db_->GetUniqueStatement(
1298 "INSERT INTO share_version VALUES(?, ?)"));
1299 s.BindString(0, dir_name_);
1300 s.BindInt(1, kCurrentDBVersion);
1302 if (!s.Run())
1303 return false;
1306 const bool kCreateAsTempShareInfo = false;
1307 if (!CreateShareInfoTable(kCreateAsTempShareInfo)) {
1308 return false;
1312 sql::Statement s(db_->GetUniqueStatement(
1313 "INSERT INTO share_info VALUES"
1314 "(?, " // id
1315 "?, " // name
1316 "?, " // store_birthday
1317 "?, " // db_create_version
1318 "?, " // db_create_time
1319 "-2, " // next_id
1320 "?, " // cache_guid
1321 // TODO(rlarocque, 124140): Remove notification_state field.
1322 "?, " // notification_state
1323 "?);")); // bag_of_chips
1324 s.BindString(0, dir_name_); // id
1325 s.BindString(1, dir_name_); // name
1326 s.BindString(2, std::string()); // store_birthday
1327 // TODO(akalin): Remove this unused db_create_version field. (Or
1328 // actually use it for something.) http://crbug.com/118356
1329 s.BindString(3, "Unknown"); // db_create_version
1330 s.BindInt(4, static_cast<int32>(time(0))); // db_create_time
1331 s.BindString(5, GenerateCacheGUID()); // cache_guid
1332 // TODO(rlarocque, 124140): Remove this unused notification-state field.
1333 s.BindBlob(6, NULL, 0); // notification_state
1334 s.BindBlob(7, NULL, 0); // bag_of_chips
1335 if (!s.Run())
1336 return false;
1339 if (!CreateModelsTable())
1340 return false;
1342 // Create the big metas table.
1343 if (!CreateMetasTable(false))
1344 return false;
1347 // Insert the entry for the root into the metas table.
1348 const int64 now = TimeToProtoTime(base::Time::Now());
1349 sql::Statement s(db_->GetUniqueStatement(
1350 "INSERT INTO metas "
1351 "( id, metahandle, is_dir, ctime, mtime ) "
1352 "VALUES ( \"r\", 1, 1, ?, ? )"));
1353 s.BindInt64(0, now);
1354 s.BindInt64(1, now);
1356 if (!s.Run())
1357 return false;
1360 return true;
1363 bool DirectoryBackingStore::CreateMetasTable(bool is_temporary) {
1364 string query = "CREATE TABLE ";
1365 query.append(is_temporary ? "temp_metas" : "metas");
1366 query.append(ComposeCreateTableColumnSpecs());
1367 if (!db_->Execute(query.c_str()))
1368 return false;
1370 // Create a deleted_metas table to save copies of deleted metas until the
1371 // deletions are persisted. For simplicity, don't try to migrate existing
1372 // data because it's rarely used.
1373 SafeDropTable("deleted_metas");
1374 query = "CREATE TABLE deleted_metas ";
1375 query.append(ComposeCreateTableColumnSpecs());
1376 return db_->Execute(query.c_str());
1379 bool DirectoryBackingStore::CreateV71ModelsTable() {
1380 // This is an old schema for the Models table, used from versions 71 to 74.
1381 return db_->Execute(
1382 "CREATE TABLE models ("
1383 "model_id BLOB primary key, "
1384 "last_download_timestamp INT, "
1385 // Gets set if the syncer ever gets updates from the
1386 // server and the server returns 0. Lets us detect the
1387 // end of the initial sync.
1388 "initial_sync_ended BOOLEAN default 0)");
1391 bool DirectoryBackingStore::CreateV75ModelsTable() {
1392 // This is an old schema for the Models table, used from versions 75 to 80.
1393 return db_->Execute(
1394 "CREATE TABLE models ("
1395 "model_id BLOB primary key, "
1396 "progress_marker BLOB, "
1397 // Gets set if the syncer ever gets updates from the
1398 // server and the server returns 0. Lets us detect the
1399 // end of the initial sync.
1400 "initial_sync_ended BOOLEAN default 0)");
1403 bool DirectoryBackingStore::CreateModelsTable() {
1404 // This is the current schema for the Models table, from version 81
1405 // onward. If you change the schema, you'll probably want to double-check
1406 // the use of this function in the v84-v85 migration.
1407 return db_->Execute(
1408 "CREATE TABLE models ("
1409 "model_id BLOB primary key, "
1410 "progress_marker BLOB, "
1411 // Gets set if the syncer ever gets updates from the
1412 // server and the server returns 0. Lets us detect the
1413 // end of the initial sync.
1414 "transaction_version BIGINT default 0)");
1417 bool DirectoryBackingStore::CreateShareInfoTable(bool is_temporary) {
1418 const char* name = is_temporary ? "temp_share_info" : "share_info";
1419 string query = "CREATE TABLE ";
1420 query.append(name);
1421 // This is the current schema for the ShareInfo table, from version 76
1422 // onward.
1423 query.append(" ("
1424 "id TEXT primary key, "
1425 "name TEXT, "
1426 "store_birthday TEXT, "
1427 "db_create_version TEXT, "
1428 "db_create_time INT, "
1429 "next_id INT default -2, "
1430 "cache_guid TEXT, "
1431 // TODO(rlarocque, 124140): Remove notification_state field.
1432 "notification_state BLOB, "
1433 "bag_of_chips BLOB"
1434 ")");
1435 return db_->Execute(query.c_str());
1438 bool DirectoryBackingStore::CreateShareInfoTableVersion71(
1439 bool is_temporary) {
1440 const char* name = is_temporary ? "temp_share_info" : "share_info";
1441 string query = "CREATE TABLE ";
1442 query.append(name);
1443 // This is the schema for the ShareInfo table used from versions 71 to 72.
1444 query.append(" ("
1445 "id TEXT primary key, "
1446 "name TEXT, "
1447 "store_birthday TEXT, "
1448 "db_create_version TEXT, "
1449 "db_create_time INT, "
1450 "next_id INT default -2, "
1451 "cache_guid TEXT )");
1452 return db_->Execute(query.c_str());
1455 // This function checks to see if the given list of Metahandles has any nodes
1456 // whose PARENT_ID values refer to ID values that do not actually exist.
1457 // Returns true on success.
1458 bool DirectoryBackingStore::VerifyReferenceIntegrity(
1459 const Directory::MetahandlesMap* handles_map) {
1460 TRACE_EVENT0("sync", "SyncDatabaseIntegrityCheck");
1461 using namespace syncable;
1462 typedef base::hash_set<std::string> IdsSet;
1464 IdsSet ids_set;
1465 bool is_ok = true;
1467 for (Directory::MetahandlesMap::const_iterator it = handles_map->begin();
1468 it != handles_map->end(); ++it) {
1469 EntryKernel* entry = it->second;
1470 bool is_duplicate_id = !(ids_set.insert(entry->ref(ID).value()).second);
1471 is_ok = is_ok && !is_duplicate_id;
1474 IdsSet::iterator end = ids_set.end();
1475 for (Directory::MetahandlesMap::const_iterator it = handles_map->begin();
1476 it != handles_map->end(); ++it) {
1477 EntryKernel* entry = it->second;
1478 bool parent_exists = (ids_set.find(entry->ref(PARENT_ID).value()) != end);
1479 if (!parent_exists) {
1480 return false;
1483 return is_ok;
1486 void DirectoryBackingStore::PrepareSaveEntryStatement(
1487 EntryTable table, sql::Statement* save_statement) {
1488 if (save_statement->is_valid())
1489 return;
1491 string query;
1492 query.reserve(kUpdateStatementBufferSize);
1493 switch (table) {
1494 case METAS_TABLE:
1495 query.append("INSERT OR REPLACE INTO metas ");
1496 break;
1497 case DELETE_JOURNAL_TABLE:
1498 query.append("INSERT OR REPLACE INTO deleted_metas ");
1499 break;
1502 string values;
1503 values.reserve(kUpdateStatementBufferSize);
1504 values.append(" VALUES ");
1505 const char* separator = "( ";
1506 int i = 0;
1507 for (i = BEGIN_FIELDS; i < FIELD_COUNT; ++i) {
1508 query.append(separator);
1509 values.append(separator);
1510 separator = ", ";
1511 query.append(ColumnName(i));
1512 values.append("?");
1514 query.append(" ) ");
1515 values.append(" )");
1516 query.append(values);
1517 save_statement->Assign(db_->GetUniqueStatement(
1518 base::StringPrintf(query.c_str(), "metas").c_str()));
1521 } // namespace syncable
1522 } // namespace syncer