Add an UMA stat to be able to see if the User pods are show on start screen,
[chromium-blink-merge.git] / sync / syncable / directory_backing_store.cc
blob514ed8dcf002ab351263536155e5e4ad32b364a2
1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "sync/syncable/directory_backing_store.h"
7 #include "build/build_config.h"
9 #include <limits>
11 #include "base/base64.h"
12 #include "base/logging.h"
13 #include "base/metrics/field_trial.h"
14 #include "base/rand_util.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/time/time.h"
17 #include "base/trace_event/trace_event.h"
18 #include "sql/connection.h"
19 #include "sql/statement.h"
20 #include "sql/transaction.h"
21 #include "sync/internal_api/public/base/node_ordinal.h"
22 #include "sync/protocol/bookmark_specifics.pb.h"
23 #include "sync/protocol/sync.pb.h"
24 #include "sync/syncable/syncable-inl.h"
25 #include "sync/syncable/syncable_columns.h"
26 #include "sync/syncable/syncable_util.h"
27 #include "sync/util/time.h"
29 using std::string;
31 namespace {
33 bool IsSyncBackingDatabase32KEnabled() {
34 const std::string group_name =
35 base::FieldTrialList::FindFullName("SyncBackingDatabase32K");
36 return group_name == "Enabled";
39 } // namespace
41 namespace syncer {
42 namespace syncable {
44 // This just has to be big enough to hold an UPDATE or INSERT statement that
45 // modifies all the columns in the entry table.
46 static const string::size_type kUpdateStatementBufferSize = 2048;
48 // Increment this version whenever updating DB tables.
49 const int32 kCurrentDBVersion = 89;
51 // Iterate over the fields of |entry| and bind each to |statement| for
52 // updating. Returns the number of args bound.
53 void BindFields(const EntryKernel& entry,
54 sql::Statement* statement) {
55 int index = 0;
56 int i = 0;
57 for (i = BEGIN_FIELDS; i < INT64_FIELDS_END; ++i) {
58 statement->BindInt64(index++, entry.ref(static_cast<Int64Field>(i)));
60 for ( ; i < TIME_FIELDS_END; ++i) {
61 statement->BindInt64(index++,
62 TimeToProtoTime(
63 entry.ref(static_cast<TimeField>(i))));
65 for ( ; i < ID_FIELDS_END; ++i) {
66 statement->BindString(index++, entry.ref(static_cast<IdField>(i)).s_);
68 for ( ; i < BIT_FIELDS_END; ++i) {
69 statement->BindInt(index++, entry.ref(static_cast<BitField>(i)));
71 for ( ; i < STRING_FIELDS_END; ++i) {
72 statement->BindString(index++, entry.ref(static_cast<StringField>(i)));
74 for ( ; i < PROTO_FIELDS_END; ++i) {
75 std::string temp;
76 entry.ref(static_cast<ProtoField>(i)).SerializeToString(&temp);
77 statement->BindBlob(index++, temp.data(), temp.length());
79 for ( ; i < UNIQUE_POSITION_FIELDS_END; ++i) {
80 std::string temp;
81 entry.ref(static_cast<UniquePositionField>(i)).SerializeToString(&temp);
82 statement->BindBlob(index++, temp.data(), temp.length());
84 for (; i < ATTACHMENT_METADATA_FIELDS_END; ++i) {
85 std::string temp;
86 entry.ref(static_cast<AttachmentMetadataField>(i)).SerializeToString(&temp);
87 statement->BindBlob(index++, temp.data(), temp.length());
91 // The caller owns the returned EntryKernel*. Assumes the statement currently
92 // points to a valid row in the metas table. Returns NULL to indicate that
93 // it detected a corruption in the data on unpacking.
94 scoped_ptr<EntryKernel> UnpackEntry(sql::Statement* statement) {
95 scoped_ptr<EntryKernel> kernel(new EntryKernel());
96 DCHECK_EQ(statement->ColumnCount(), static_cast<int>(FIELD_COUNT));
97 int i = 0;
98 for (i = BEGIN_FIELDS; i < INT64_FIELDS_END; ++i) {
99 kernel->put(static_cast<Int64Field>(i), statement->ColumnInt64(i));
101 for ( ; i < TIME_FIELDS_END; ++i) {
102 kernel->put(static_cast<TimeField>(i),
103 ProtoTimeToTime(statement->ColumnInt64(i)));
105 for ( ; i < ID_FIELDS_END; ++i) {
106 kernel->mutable_ref(static_cast<IdField>(i)).s_ =
107 statement->ColumnString(i);
109 for ( ; i < BIT_FIELDS_END; ++i) {
110 kernel->put(static_cast<BitField>(i), (0 != statement->ColumnInt(i)));
112 for ( ; i < STRING_FIELDS_END; ++i) {
113 kernel->put(static_cast<StringField>(i),
114 statement->ColumnString(i));
116 for ( ; i < PROTO_FIELDS_END; ++i) {
117 kernel->mutable_ref(static_cast<ProtoField>(i)).ParseFromArray(
118 statement->ColumnBlob(i), statement->ColumnByteLength(i));
120 for ( ; i < UNIQUE_POSITION_FIELDS_END; ++i) {
121 std::string temp;
122 statement->ColumnBlobAsString(i, &temp);
124 sync_pb::UniquePosition proto;
125 if (!proto.ParseFromString(temp)) {
126 DVLOG(1) << "Unpacked invalid position. Assuming the DB is corrupt";
127 return scoped_ptr<EntryKernel>();
130 kernel->mutable_ref(static_cast<UniquePositionField>(i)) =
131 UniquePosition::FromProto(proto);
133 for (; i < ATTACHMENT_METADATA_FIELDS_END; ++i) {
134 kernel->mutable_ref(static_cast<AttachmentMetadataField>(i)).ParseFromArray(
135 statement->ColumnBlob(i), statement->ColumnByteLength(i));
138 // Sanity check on positions. We risk strange and rare crashes if our
139 // assumptions about unique position values are broken.
140 if (kernel->ShouldMaintainPosition() &&
141 !kernel->ref(UNIQUE_POSITION).IsValid()) {
142 DVLOG(1) << "Unpacked invalid position on an entity that should have a "
143 << "valid position. Assuming the DB is corrupt.";
144 return scoped_ptr<EntryKernel>();
147 return kernel.Pass();
150 namespace {
152 string ComposeCreateTableColumnSpecs() {
153 const ColumnSpec* begin = g_metas_columns;
154 const ColumnSpec* end = g_metas_columns + arraysize(g_metas_columns);
155 string query;
156 query.reserve(kUpdateStatementBufferSize);
157 char separator = '(';
158 for (const ColumnSpec* column = begin; column != end; ++column) {
159 query.push_back(separator);
160 separator = ',';
161 query.append(column->name);
162 query.push_back(' ');
163 query.append(column->spec);
165 query.push_back(')');
166 return query;
169 void AppendColumnList(std::string* output) {
170 const char* joiner = " ";
171 // Be explicit in SELECT order to match up with UnpackEntry.
172 for (int i = BEGIN_FIELDS; i < FIELD_COUNT; ++i) {
173 output->append(joiner);
174 output->append(ColumnName(i));
175 joiner = ", ";
179 } // namespace
181 ///////////////////////////////////////////////////////////////////////////////
182 // DirectoryBackingStore implementation.
184 DirectoryBackingStore::DirectoryBackingStore(const string& dir_name)
185 : dir_name_(dir_name),
186 database_page_size_(IsSyncBackingDatabase32KEnabled() ? 32768 : 4096),
187 needs_column_refresh_(false) {
188 ResetAndCreateConnection();
191 DirectoryBackingStore::DirectoryBackingStore(const string& dir_name,
192 sql::Connection* db)
193 : db_(db),
194 dir_name_(dir_name),
195 database_page_size_(IsSyncBackingDatabase32KEnabled() ? 32768 : 4096),
196 needs_column_refresh_(false) {
199 DirectoryBackingStore::~DirectoryBackingStore() {
202 bool DirectoryBackingStore::DeleteEntries(EntryTable from,
203 const MetahandleSet& handles) {
204 if (handles.empty())
205 return true;
207 sql::Statement statement;
208 // Call GetCachedStatement() separately to get different statements for
209 // different tables.
210 switch (from) {
211 case METAS_TABLE:
212 statement.Assign(db_->GetCachedStatement(
213 SQL_FROM_HERE, "DELETE FROM metas WHERE metahandle = ?"));
214 break;
215 case DELETE_JOURNAL_TABLE:
216 statement.Assign(db_->GetCachedStatement(
217 SQL_FROM_HERE, "DELETE FROM deleted_metas WHERE metahandle = ?"));
218 break;
221 for (MetahandleSet::const_iterator i = handles.begin(); i != handles.end();
222 ++i) {
223 statement.BindInt64(0, *i);
224 if (!statement.Run())
225 return false;
226 statement.Reset(true);
228 return true;
231 bool DirectoryBackingStore::SaveChanges(
232 const Directory::SaveChangesSnapshot& snapshot) {
233 DCHECK(CalledOnValidThread());
234 DCHECK(db_->is_open());
236 // Back out early if there is nothing to write.
237 bool save_info =
238 (Directory::KERNEL_SHARE_INFO_DIRTY == snapshot.kernel_info_status);
239 if (snapshot.dirty_metas.empty() && snapshot.metahandles_to_purge.empty() &&
240 snapshot.delete_journals.empty() &&
241 snapshot.delete_journals_to_purge.empty() && !save_info) {
242 return true;
245 sql::Transaction transaction(db_.get());
246 if (!transaction.Begin())
247 return false;
249 PrepareSaveEntryStatement(METAS_TABLE, &save_meta_statement_);
250 for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin();
251 i != snapshot.dirty_metas.end(); ++i) {
252 DCHECK((*i)->is_dirty());
253 if (!SaveEntryToDB(&save_meta_statement_, **i))
254 return false;
257 if (!DeleteEntries(METAS_TABLE, snapshot.metahandles_to_purge))
258 return false;
260 PrepareSaveEntryStatement(DELETE_JOURNAL_TABLE,
261 &save_delete_journal_statement_);
262 for (EntryKernelSet::const_iterator i = snapshot.delete_journals.begin();
263 i != snapshot.delete_journals.end(); ++i) {
264 if (!SaveEntryToDB(&save_delete_journal_statement_, **i))
265 return false;
268 if (!DeleteEntries(DELETE_JOURNAL_TABLE, snapshot.delete_journals_to_purge))
269 return false;
271 if (save_info) {
272 const Directory::PersistedKernelInfo& info = snapshot.kernel_info;
273 sql::Statement s1(db_->GetCachedStatement(
274 SQL_FROM_HERE,
275 "UPDATE share_info "
276 "SET store_birthday = ?, "
277 "next_id = ?, "
278 "bag_of_chips = ?"));
279 s1.BindString(0, info.store_birthday);
280 s1.BindInt64(1, info.next_id);
281 s1.BindBlob(2, info.bag_of_chips.data(), info.bag_of_chips.size());
283 if (!s1.Run())
284 return false;
285 DCHECK_EQ(db_->GetLastChangeCount(), 1);
287 sql::Statement s2(db_->GetCachedStatement(
288 SQL_FROM_HERE,
289 "INSERT OR REPLACE "
290 "INTO models (model_id, "
291 "progress_marker, "
292 "transaction_version, "
293 "context) "
294 "VALUES (?, ?, ?, ?)"));
296 ModelTypeSet protocol_types = ProtocolTypes();
297 for (ModelTypeSet::Iterator iter = protocol_types.First(); iter.Good();
298 iter.Inc()) {
299 ModelType type = iter.Get();
300 // We persist not ModelType but rather a protobuf-derived ID.
301 string model_id = ModelTypeEnumToModelId(type);
302 string progress_marker;
303 info.download_progress[type].SerializeToString(&progress_marker);
304 s2.BindBlob(0, model_id.data(), model_id.length());
305 s2.BindBlob(1, progress_marker.data(), progress_marker.length());
306 s2.BindInt64(2, info.transaction_version[type]);
307 string context;
308 info.datatype_context[type].SerializeToString(&context);
309 s2.BindBlob(3, context.data(), context.length());
310 if (!s2.Run())
311 return false;
312 DCHECK_EQ(db_->GetLastChangeCount(), 1);
313 s2.Reset(true);
317 return transaction.Commit();
320 bool DirectoryBackingStore::InitializeTables() {
321 int page_size = 0;
322 if (IsSyncBackingDatabase32KEnabled() && GetDatabasePageSize(&page_size) &&
323 page_size == 4096) {
324 IncreasePageSizeTo32K();
326 sql::Transaction transaction(db_.get());
327 if (!transaction.Begin())
328 return false;
330 int version_on_disk = GetVersion();
332 // Upgrade from version 67. Version 67 was widely distributed as the original
333 // Bookmark Sync release. Version 68 removed unique naming.
334 if (version_on_disk == 67) {
335 if (MigrateVersion67To68())
336 version_on_disk = 68;
338 // Version 69 introduced additional datatypes.
339 if (version_on_disk == 68) {
340 if (MigrateVersion68To69())
341 version_on_disk = 69;
344 if (version_on_disk == 69) {
345 if (MigrateVersion69To70())
346 version_on_disk = 70;
349 // Version 71 changed the sync progress information to be per-datatype.
350 if (version_on_disk == 70) {
351 if (MigrateVersion70To71())
352 version_on_disk = 71;
355 // Version 72 removed extended attributes, a legacy way to do extensible
356 // key/value information, stored in their own table.
357 if (version_on_disk == 71) {
358 if (MigrateVersion71To72())
359 version_on_disk = 72;
362 // Version 73 added a field for notification state.
363 if (version_on_disk == 72) {
364 if (MigrateVersion72To73())
365 version_on_disk = 73;
368 // Version 74 added state for the autofill migration.
369 if (version_on_disk == 73) {
370 if (MigrateVersion73To74())
371 version_on_disk = 74;
374 // Version 75 migrated from int64-based timestamps to per-datatype tokens.
375 if (version_on_disk == 74) {
376 if (MigrateVersion74To75())
377 version_on_disk = 75;
380 // Version 76 removed all (5) autofill migration related columns.
381 if (version_on_disk == 75) {
382 if (MigrateVersion75To76())
383 version_on_disk = 76;
386 // Version 77 standardized all time fields to ms since the Unix
387 // epoch.
388 if (version_on_disk == 76) {
389 if (MigrateVersion76To77())
390 version_on_disk = 77;
393 // Version 78 added the column base_server_specifics to the metas table.
394 if (version_on_disk == 77) {
395 if (MigrateVersion77To78())
396 version_on_disk = 78;
399 // Version 79 migration is a one-time fix for some users in a bad state.
400 if (version_on_disk == 78) {
401 if (MigrateVersion78To79())
402 version_on_disk = 79;
405 // Version 80 migration is adding the bag_of_chips column.
406 if (version_on_disk == 79) {
407 if (MigrateVersion79To80())
408 version_on_disk = 80;
411 // Version 81 replaces the int64 server_position_in_parent_field
412 // with a blob server_ordinal_in_parent field.
413 if (version_on_disk == 80) {
414 if (MigrateVersion80To81())
415 version_on_disk = 81;
418 // Version 82 migration added transaction_version column per data type.
419 if (version_on_disk == 81) {
420 if (MigrateVersion81To82())
421 version_on_disk = 82;
424 // Version 83 migration added transaction_version column per sync entry.
425 if (version_on_disk == 82) {
426 if (MigrateVersion82To83())
427 version_on_disk = 83;
430 // Version 84 migration added deleted_metas table.
431 if (version_on_disk == 83) {
432 if (MigrateVersion83To84())
433 version_on_disk = 84;
436 // Version 85 migration removes the initial_sync_ended bits.
437 if (version_on_disk == 84) {
438 if (MigrateVersion84To85())
439 version_on_disk = 85;
442 // Version 86 migration converts bookmarks to the unique positioning system.
443 // It also introduces a new field to store a unique ID for each bookmark.
444 if (version_on_disk == 85) {
445 if (MigrateVersion85To86())
446 version_on_disk = 86;
449 // Version 87 migration adds a collection of attachment ids per sync entry.
450 if (version_on_disk == 86) {
451 if (MigrateVersion86To87())
452 version_on_disk = 87;
455 // Version 88 migration adds datatype contexts to the models table.
456 if (version_on_disk == 87) {
457 if (MigrateVersion87To88())
458 version_on_disk = 88;
461 // Version 89 migration adds server attachment metadata to the metas table.
462 if (version_on_disk == 88) {
463 if (MigrateVersion88To89())
464 version_on_disk = 89;
467 // If one of the migrations requested it, drop columns that aren't current.
468 // It's only safe to do this after migrating all the way to the current
469 // version.
470 if (version_on_disk == kCurrentDBVersion && needs_column_refresh_) {
471 if (!RefreshColumns())
472 version_on_disk = 0;
475 // A final, alternative catch-all migration to simply re-sync everything.
476 if (version_on_disk != kCurrentDBVersion) {
477 if (version_on_disk > kCurrentDBVersion)
478 return false;
480 // Fallback (re-sync everything) migration path.
481 DVLOG(1) << "Old/null sync database, version " << version_on_disk;
482 // Delete the existing database (if any), and create a fresh one.
483 DropAllTables();
484 if (!CreateTables())
485 return false;
488 sql::Statement s(db_->GetUniqueStatement(
489 "SELECT db_create_version, db_create_time FROM share_info"));
490 if (!s.Step())
491 return false;
492 string db_create_version = s.ColumnString(0);
493 int db_create_time = s.ColumnInt(1);
494 DVLOG(1) << "DB created at " << db_create_time << " by version " <<
495 db_create_version;
497 return transaction.Commit();
500 // This function drops unused columns by creating a new table that contains only
501 // the currently used columns then copying all rows from the old tables into
502 // this new one. The tables are then rearranged so the new replaces the old.
503 bool DirectoryBackingStore::RefreshColumns() {
504 DCHECK(needs_column_refresh_);
506 // Create a new table named temp_metas.
507 SafeDropTable("temp_metas");
508 if (!CreateMetasTable(true))
509 return false;
511 // Populate temp_metas from metas.
513 // At this point, the metas table may contain columns belonging to obsolete
514 // schema versions. This statement explicitly lists only the columns that
515 // belong to the current schema version, so the obsolete columns will be
516 // effectively dropped once we rename temp_metas over top of metas.
517 std::string query = "INSERT INTO temp_metas (";
518 AppendColumnList(&query);
519 query.append(") SELECT ");
520 AppendColumnList(&query);
521 query.append(" FROM metas");
522 if (!db_->Execute(query.c_str()))
523 return false;
525 // Drop metas.
526 SafeDropTable("metas");
528 // Rename temp_metas -> metas.
529 if (!db_->Execute("ALTER TABLE temp_metas RENAME TO metas"))
530 return false;
532 // Repeat the process for share_info.
533 SafeDropTable("temp_share_info");
534 if (!CreateShareInfoTable(true))
535 return false;
537 // TODO(rlarocque, 124140): Remove notification_state.
538 if (!db_->Execute(
539 "INSERT INTO temp_share_info (id, name, store_birthday, "
540 "db_create_version, db_create_time, next_id, cache_guid,"
541 "notification_state, bag_of_chips) "
542 "SELECT id, name, store_birthday, db_create_version, "
543 "db_create_time, next_id, cache_guid, notification_state, "
544 "bag_of_chips "
545 "FROM share_info"))
546 return false;
548 SafeDropTable("share_info");
549 if (!db_->Execute("ALTER TABLE temp_share_info RENAME TO share_info"))
550 return false;
552 needs_column_refresh_ = false;
553 return true;
556 bool DirectoryBackingStore::LoadEntries(Directory::MetahandlesMap* handles_map,
557 MetahandleSet* metahandles_to_purge) {
558 string select;
559 select.reserve(kUpdateStatementBufferSize);
560 select.append("SELECT ");
561 AppendColumnList(&select);
562 select.append(" FROM metas");
564 sql::Statement s(db_->GetUniqueStatement(select.c_str()));
566 while (s.Step()) {
567 scoped_ptr<EntryKernel> kernel = UnpackEntry(&s);
568 // A null kernel is evidence of external data corruption.
569 if (!kernel)
570 return false;
572 int64 handle = kernel->ref(META_HANDLE);
573 if (SafeToPurgeOnLoading(*kernel))
574 metahandles_to_purge->insert(handle);
575 else
576 (*handles_map)[handle] = kernel.release();
578 return s.Succeeded();
581 bool DirectoryBackingStore::SafeToPurgeOnLoading(
582 const EntryKernel& entry) const {
583 if (entry.ref(IS_DEL)) {
584 if (!entry.ref(IS_UNSYNCED) && !entry.ref(IS_UNAPPLIED_UPDATE))
585 return true;
586 else if (!entry.ref(ID).ServerKnows())
587 return true;
589 return false;
592 bool DirectoryBackingStore::LoadDeleteJournals(
593 JournalIndex* delete_journals) {
594 string select;
595 select.reserve(kUpdateStatementBufferSize);
596 select.append("SELECT ");
597 AppendColumnList(&select);
598 select.append(" FROM deleted_metas");
600 sql::Statement s(db_->GetUniqueStatement(select.c_str()));
602 while (s.Step()) {
603 scoped_ptr<EntryKernel> kernel = UnpackEntry(&s);
604 // A null kernel is evidence of external data corruption.
605 if (!kernel)
606 return false;
607 delete_journals->insert(kernel.release());
609 return s.Succeeded();
612 bool DirectoryBackingStore::LoadInfo(Directory::KernelLoadInfo* info) {
614 sql::Statement s(
615 db_->GetUniqueStatement(
616 "SELECT store_birthday, next_id, cache_guid, bag_of_chips "
617 "FROM share_info"));
618 if (!s.Step())
619 return false;
621 info->kernel_info.store_birthday = s.ColumnString(0);
622 info->kernel_info.next_id = s.ColumnInt64(1);
623 info->cache_guid = s.ColumnString(2);
624 s.ColumnBlobAsString(3, &(info->kernel_info.bag_of_chips));
626 // Verify there was only one row returned.
627 DCHECK(!s.Step());
628 DCHECK(s.Succeeded());
632 sql::Statement s(
633 db_->GetUniqueStatement(
634 "SELECT model_id, progress_marker, "
635 "transaction_version, context FROM models"));
637 while (s.Step()) {
638 ModelType type = ModelIdToModelTypeEnum(s.ColumnBlob(0),
639 s.ColumnByteLength(0));
640 if (type != UNSPECIFIED && type != TOP_LEVEL_FOLDER) {
641 info->kernel_info.download_progress[type].ParseFromArray(
642 s.ColumnBlob(1), s.ColumnByteLength(1));
643 info->kernel_info.transaction_version[type] = s.ColumnInt64(2);
644 info->kernel_info.datatype_context[type].ParseFromArray(
645 s.ColumnBlob(3), s.ColumnByteLength(3));
648 if (!s.Succeeded())
649 return false;
652 sql::Statement s(
653 db_->GetUniqueStatement(
654 "SELECT MAX(metahandle) FROM metas"));
655 if (!s.Step())
656 return false;
658 info->max_metahandle = s.ColumnInt64(0);
660 // Verify only one row was returned.
661 DCHECK(!s.Step());
662 DCHECK(s.Succeeded());
664 return true;
667 /* static */
668 bool DirectoryBackingStore::SaveEntryToDB(sql::Statement* save_statement,
669 const EntryKernel& entry) {
670 save_statement->Reset(true);
671 BindFields(entry, save_statement);
672 return save_statement->Run();
675 bool DirectoryBackingStore::SafeDropTable(const char* table_name) {
676 string query = "DROP TABLE IF EXISTS ";
677 query.append(table_name);
678 return db_->Execute(query.c_str());
681 void DirectoryBackingStore::DropAllTables() {
682 SafeDropTable("metas");
683 SafeDropTable("temp_metas");
684 SafeDropTable("share_info");
685 SafeDropTable("temp_share_info");
686 SafeDropTable("share_version");
687 SafeDropTable("extended_attributes");
688 SafeDropTable("models");
689 SafeDropTable("temp_models");
690 needs_column_refresh_ = false;
693 // static
694 ModelType DirectoryBackingStore::ModelIdToModelTypeEnum(
695 const void* data, int size) {
696 sync_pb::EntitySpecifics specifics;
697 if (!specifics.ParseFromArray(data, size))
698 return UNSPECIFIED;
699 return GetModelTypeFromSpecifics(specifics);
702 // static
703 string DirectoryBackingStore::ModelTypeEnumToModelId(ModelType model_type) {
704 sync_pb::EntitySpecifics specifics;
705 AddDefaultFieldValue(model_type, &specifics);
706 return specifics.SerializeAsString();
709 // static
710 std::string DirectoryBackingStore::GenerateCacheGUID() {
711 // Generate a GUID with 128 bits of randomness.
712 const int kGuidBytes = 128 / 8;
713 std::string guid;
714 base::Base64Encode(base::RandBytesAsString(kGuidBytes), &guid);
715 return guid;
718 bool DirectoryBackingStore::MigrateToSpecifics(
719 const char* old_columns,
720 const char* specifics_column,
721 void (*handler_function)(sql::Statement* old_value_query,
722 int old_value_column,
723 sync_pb::EntitySpecifics* mutable_new_value)) {
724 std::string query_sql = base::StringPrintf(
725 "SELECT metahandle, %s, %s FROM metas", specifics_column, old_columns);
726 std::string update_sql = base::StringPrintf(
727 "UPDATE metas SET %s = ? WHERE metahandle = ?", specifics_column);
729 sql::Statement query(db_->GetUniqueStatement(query_sql.c_str()));
730 sql::Statement update(db_->GetUniqueStatement(update_sql.c_str()));
732 while (query.Step()) {
733 int64 metahandle = query.ColumnInt64(0);
734 std::string new_value_bytes;
735 query.ColumnBlobAsString(1, &new_value_bytes);
736 sync_pb::EntitySpecifics new_value;
737 new_value.ParseFromString(new_value_bytes);
738 handler_function(&query, 2, &new_value);
739 new_value.SerializeToString(&new_value_bytes);
741 update.BindBlob(0, new_value_bytes.data(), new_value_bytes.length());
742 update.BindInt64(1, metahandle);
743 if (!update.Run())
744 return false;
745 update.Reset(true);
747 return query.Succeeded();
750 bool DirectoryBackingStore::SetVersion(int version) {
751 sql::Statement s(db_->GetCachedStatement(
752 SQL_FROM_HERE, "UPDATE share_version SET data = ?"));
753 s.BindInt(0, version);
755 return s.Run();
758 int DirectoryBackingStore::GetVersion() {
759 if (!db_->DoesTableExist("share_version"))
760 return 0;
762 sql::Statement statement(db_->GetUniqueStatement(
763 "SELECT data FROM share_version"));
764 if (statement.Step()) {
765 return statement.ColumnInt(0);
766 } else {
767 return 0;
771 bool DirectoryBackingStore::MigrateVersion67To68() {
772 // This change simply removed three columns:
773 // string NAME
774 // string UNSANITIZED_NAME
775 // string SERVER_NAME
776 // No data migration is necessary, but we should do a column refresh.
777 SetVersion(68);
778 needs_column_refresh_ = true;
779 return true;
782 bool DirectoryBackingStore::MigrateVersion69To70() {
783 // Added "unique_client_tag", renamed "singleton_tag" to unique_server_tag
784 SetVersion(70);
785 if (!db_->Execute(
786 "ALTER TABLE metas ADD COLUMN unique_server_tag varchar"))
787 return false;
788 if (!db_->Execute(
789 "ALTER TABLE metas ADD COLUMN unique_client_tag varchar"))
790 return false;
791 needs_column_refresh_ = true;
793 if (!db_->Execute(
794 "UPDATE metas SET unique_server_tag = singleton_tag"))
795 return false;
797 return true;
800 namespace {
802 // Callback passed to MigrateToSpecifics for the v68->v69 migration. See
803 // MigrateVersion68To69().
804 void EncodeBookmarkURLAndFavicon(sql::Statement* old_value_query,
805 int old_value_column,
806 sync_pb::EntitySpecifics* mutable_new_value) {
807 // Extract data from the column trio we expect.
808 bool old_is_bookmark_object = old_value_query->ColumnBool(old_value_column);
809 std::string old_url = old_value_query->ColumnString(old_value_column + 1);
810 std::string old_favicon;
811 old_value_query->ColumnBlobAsString(old_value_column + 2, &old_favicon);
812 bool old_is_dir = old_value_query->ColumnBool(old_value_column + 3);
814 if (old_is_bookmark_object) {
815 sync_pb::BookmarkSpecifics* bookmark_data =
816 mutable_new_value->mutable_bookmark();
817 if (!old_is_dir) {
818 bookmark_data->set_url(old_url);
819 bookmark_data->set_favicon(old_favicon);
824 } // namespace
826 bool DirectoryBackingStore::MigrateVersion68To69() {
827 // In Version 68, there were columns on table 'metas':
828 // string BOOKMARK_URL
829 // string SERVER_BOOKMARK_URL
830 // blob BOOKMARK_FAVICON
831 // blob SERVER_BOOKMARK_FAVICON
832 // In version 69, these columns went away in favor of storing
833 // a serialized EntrySpecifics protobuf in the columns:
834 // protobuf blob SPECIFICS
835 // protobuf blob SERVER_SPECIFICS
836 // For bookmarks, EntrySpecifics is extended as per
837 // bookmark_specifics.proto. This migration converts bookmarks from the
838 // former scheme to the latter scheme.
840 // First, add the two new columns to the schema.
841 if (!db_->Execute(
842 "ALTER TABLE metas ADD COLUMN specifics blob"))
843 return false;
844 if (!db_->Execute(
845 "ALTER TABLE metas ADD COLUMN server_specifics blob"))
846 return false;
848 // Next, fold data from the old columns into the new protobuf columns.
849 if (!MigrateToSpecifics(("is_bookmark_object, bookmark_url, "
850 "bookmark_favicon, is_dir"),
851 "specifics",
852 &EncodeBookmarkURLAndFavicon)) {
853 return false;
855 if (!MigrateToSpecifics(("server_is_bookmark_object, "
856 "server_bookmark_url, "
857 "server_bookmark_favicon, "
858 "server_is_dir"),
859 "server_specifics",
860 &EncodeBookmarkURLAndFavicon)) {
861 return false;
864 // Lastly, fix up the "Google Chrome" folder, which is of the TOP_LEVEL_FOLDER
865 // ModelType: it shouldn't have BookmarkSpecifics.
866 if (!db_->Execute(
867 "UPDATE metas SET specifics = NULL, server_specifics = NULL WHERE "
868 "singleton_tag IN ('google_chrome')"))
869 return false;
871 SetVersion(69);
872 needs_column_refresh_ = true; // Trigger deletion of old columns.
873 return true;
876 // Version 71, the columns 'initial_sync_ended' and 'last_sync_timestamp'
877 // were removed from the share_info table. They were replaced by
878 // the 'models' table, which has these values on a per-datatype basis.
879 bool DirectoryBackingStore::MigrateVersion70To71() {
880 if (!CreateV71ModelsTable())
881 return false;
883 // Move data from the old share_info columns to the new models table.
885 sql::Statement fetch(db_->GetUniqueStatement(
886 "SELECT last_sync_timestamp, initial_sync_ended FROM share_info"));
887 if (!fetch.Step())
888 return false;
890 int64 last_sync_timestamp = fetch.ColumnInt64(0);
891 bool initial_sync_ended = fetch.ColumnBool(1);
893 // Verify there were no additional rows returned.
894 DCHECK(!fetch.Step());
895 DCHECK(fetch.Succeeded());
897 sql::Statement update(db_->GetUniqueStatement(
898 "INSERT INTO models (model_id, "
899 "last_download_timestamp, initial_sync_ended) VALUES (?, ?, ?)"));
900 string bookmark_model_id = ModelTypeEnumToModelId(BOOKMARKS);
901 update.BindBlob(0, bookmark_model_id.data(), bookmark_model_id.size());
902 update.BindInt64(1, last_sync_timestamp);
903 update.BindBool(2, initial_sync_ended);
905 if (!update.Run())
906 return false;
909 // Drop the columns from the old share_info table via a temp table.
910 const bool kCreateAsTempShareInfo = true;
912 if (!CreateShareInfoTableVersion71(kCreateAsTempShareInfo))
913 return false;
914 if (!db_->Execute(
915 "INSERT INTO temp_share_info (id, name, store_birthday, "
916 "db_create_version, db_create_time, next_id, cache_guid) "
917 "SELECT id, name, store_birthday, db_create_version, "
918 "db_create_time, next_id, cache_guid FROM share_info"))
919 return false;
920 SafeDropTable("share_info");
921 if (!db_->Execute(
922 "ALTER TABLE temp_share_info RENAME TO share_info"))
923 return false;
924 SetVersion(71);
925 return true;
928 bool DirectoryBackingStore::MigrateVersion71To72() {
929 // Version 72 removed a table 'extended_attributes', whose
930 // contents didn't matter.
931 SafeDropTable("extended_attributes");
932 SetVersion(72);
933 return true;
936 bool DirectoryBackingStore::MigrateVersion72To73() {
937 // Version 73 added one column to the table 'share_info': notification_state
938 if (!db_->Execute(
939 "ALTER TABLE share_info ADD COLUMN notification_state BLOB"))
940 return false;
941 SetVersion(73);
942 return true;
945 bool DirectoryBackingStore::MigrateVersion73To74() {
946 // Version 74 added the following columns to the table 'share_info':
947 // autofill_migration_state
948 // bookmarks_added_during_autofill_migration
949 // autofill_migration_time
950 // autofill_entries_added_during_migration
951 // autofill_profiles_added_during_migration
953 if (!db_->Execute(
954 "ALTER TABLE share_info ADD COLUMN "
955 "autofill_migration_state INT default 0"))
956 return false;
958 if (!db_->Execute(
959 "ALTER TABLE share_info ADD COLUMN "
960 "bookmarks_added_during_autofill_migration "
961 "INT default 0"))
962 return false;
964 if (!db_->Execute(
965 "ALTER TABLE share_info ADD COLUMN autofill_migration_time "
966 "INT default 0"))
967 return false;
969 if (!db_->Execute(
970 "ALTER TABLE share_info ADD COLUMN "
971 "autofill_entries_added_during_migration "
972 "INT default 0"))
973 return false;
975 if (!db_->Execute(
976 "ALTER TABLE share_info ADD COLUMN "
977 "autofill_profiles_added_during_migration "
978 "INT default 0"))
979 return false;
981 SetVersion(74);
982 return true;
985 bool DirectoryBackingStore::MigrateVersion74To75() {
986 // In version 74, there was a table 'models':
987 // blob model_id (entity specifics, primary key)
988 // int last_download_timestamp
989 // boolean initial_sync_ended
990 // In version 75, we deprecated the integer-valued last_download_timestamp,
991 // using insted a protobuf-valued progress_marker field:
992 // blob progress_marker
993 // The progress_marker values are initialized from the value of
994 // last_download_timestamp, thereby preserving the download state.
996 // Move aside the old table and create a new empty one at the current schema.
997 if (!db_->Execute("ALTER TABLE models RENAME TO temp_models"))
998 return false;
999 if (!CreateV75ModelsTable())
1000 return false;
1002 sql::Statement query(db_->GetUniqueStatement(
1003 "SELECT model_id, last_download_timestamp, initial_sync_ended "
1004 "FROM temp_models"));
1006 sql::Statement update(db_->GetUniqueStatement(
1007 "INSERT INTO models (model_id, "
1008 "progress_marker, initial_sync_ended) VALUES (?, ?, ?)"));
1010 while (query.Step()) {
1011 ModelType type = ModelIdToModelTypeEnum(query.ColumnBlob(0),
1012 query.ColumnByteLength(0));
1013 if (type != UNSPECIFIED) {
1014 // Set the |timestamp_token_for_migration| on a new
1015 // DataTypeProgressMarker, using the old value of last_download_timestamp.
1016 // The server will turn this into a real token on our behalf the next
1017 // time we check for updates.
1018 sync_pb::DataTypeProgressMarker progress_marker;
1019 progress_marker.set_data_type_id(
1020 GetSpecificsFieldNumberFromModelType(type));
1021 progress_marker.set_timestamp_token_for_migration(query.ColumnInt64(1));
1022 std::string progress_blob;
1023 progress_marker.SerializeToString(&progress_blob);
1025 update.BindBlob(0, query.ColumnBlob(0), query.ColumnByteLength(0));
1026 update.BindBlob(1, progress_blob.data(), progress_blob.length());
1027 update.BindBool(2, query.ColumnBool(2));
1028 if (!update.Run())
1029 return false;
1030 update.Reset(true);
1033 if (!query.Succeeded())
1034 return false;
1036 // Drop the old table.
1037 SafeDropTable("temp_models");
1039 SetVersion(75);
1040 return true;
1043 bool DirectoryBackingStore::MigrateVersion75To76() {
1044 // This change removed five columns:
1045 // autofill_migration_state
1046 // bookmarks_added_during_autofill_migration
1047 // autofill_migration_time
1048 // autofill_entries_added_during_migration
1049 // autofill_profiles_added_during_migration
1050 // No data migration is necessary, but we should do a column refresh.
1051 SetVersion(76);
1052 needs_column_refresh_ = true;
1053 return true;
1056 bool DirectoryBackingStore::MigrateVersion76To77() {
1057 // This change changes the format of stored timestamps to ms since
1058 // the Unix epoch.
1059 #if defined(OS_WIN)
1060 // On Windows, we used to store timestamps in FILETIME format (100s of
1061 // ns since Jan 1, 1601). Magic numbers taken from
1062 // http://stackoverflow.com/questions/5398557/
1063 // java-library-for-dealing-with-win32-filetime
1064 // .
1065 #define TO_UNIX_TIME_MS(x) #x " = " #x " / 10000 - 11644473600000"
1066 #else
1067 // On other platforms, we used to store timestamps in time_t format (s
1068 // since the Unix epoch).
1069 #define TO_UNIX_TIME_MS(x) #x " = " #x " * 1000"
1070 #endif
1071 sql::Statement update_timestamps(db_->GetUniqueStatement(
1072 "UPDATE metas SET "
1073 TO_UNIX_TIME_MS(mtime) ", "
1074 TO_UNIX_TIME_MS(server_mtime) ", "
1075 TO_UNIX_TIME_MS(ctime) ", "
1076 TO_UNIX_TIME_MS(server_ctime)));
1077 #undef TO_UNIX_TIME_MS
1078 if (!update_timestamps.Run())
1079 return false;
1080 SetVersion(77);
1081 return true;
1084 bool DirectoryBackingStore::MigrateVersion77To78() {
1085 // Version 78 added one column to table 'metas': base_server_specifics.
1086 if (!db_->Execute(
1087 "ALTER TABLE metas ADD COLUMN base_server_specifics BLOB")) {
1088 return false;
1090 SetVersion(78);
1091 return true;
1094 bool DirectoryBackingStore::MigrateVersion78To79() {
1095 // Some users are stuck with a DB that causes them to reuse existing IDs. We
1096 // perform this one-time fixup on all users to help the few that are stuck.
1097 // See crbug.com/142987 for details.
1098 if (!db_->Execute(
1099 "UPDATE share_info SET next_id = next_id - 65536")) {
1100 return false;
1102 SetVersion(79);
1103 return true;
1106 bool DirectoryBackingStore::MigrateVersion79To80() {
1107 if (!db_->Execute(
1108 "ALTER TABLE share_info ADD COLUMN bag_of_chips BLOB"))
1109 return false;
1110 sql::Statement update(db_->GetUniqueStatement(
1111 "UPDATE share_info SET bag_of_chips = ?"));
1112 // An empty message is serialized to an empty string.
1113 update.BindBlob(0, NULL, 0);
1114 if (!update.Run())
1115 return false;
1116 SetVersion(80);
1117 return true;
1120 bool DirectoryBackingStore::MigrateVersion80To81() {
1121 if(!db_->Execute(
1122 "ALTER TABLE metas ADD COLUMN server_ordinal_in_parent BLOB"))
1123 return false;
1125 sql::Statement get_positions(db_->GetUniqueStatement(
1126 "SELECT metahandle, server_position_in_parent FROM metas"));
1128 sql::Statement put_ordinals(db_->GetUniqueStatement(
1129 "UPDATE metas SET server_ordinal_in_parent = ?"
1130 "WHERE metahandle = ?"));
1132 while(get_positions.Step()) {
1133 int64 metahandle = get_positions.ColumnInt64(0);
1134 int64 position = get_positions.ColumnInt64(1);
1136 const std::string& ordinal = Int64ToNodeOrdinal(position).ToInternalValue();
1137 put_ordinals.BindBlob(0, ordinal.data(), ordinal.length());
1138 put_ordinals.BindInt64(1, metahandle);
1140 if(!put_ordinals.Run())
1141 return false;
1142 put_ordinals.Reset(true);
1145 SetVersion(81);
1146 needs_column_refresh_ = true;
1147 return true;
1150 bool DirectoryBackingStore::MigrateVersion81To82() {
1151 if (!db_->Execute(
1152 "ALTER TABLE models ADD COLUMN transaction_version BIGINT default 0"))
1153 return false;
1154 sql::Statement update(db_->GetUniqueStatement(
1155 "UPDATE models SET transaction_version = 0"));
1156 if (!update.Run())
1157 return false;
1158 SetVersion(82);
1159 return true;
1162 bool DirectoryBackingStore::MigrateVersion82To83() {
1163 // Version 83 added transaction_version on sync node.
1164 if (!db_->Execute(
1165 "ALTER TABLE metas ADD COLUMN transaction_version BIGINT default 0"))
1166 return false;
1167 sql::Statement update(db_->GetUniqueStatement(
1168 "UPDATE metas SET transaction_version = 0"));
1169 if (!update.Run())
1170 return false;
1171 SetVersion(83);
1172 return true;
1175 bool DirectoryBackingStore::MigrateVersion83To84() {
1176 // Version 84 added deleted_metas table to store deleted metas until we know
1177 // for sure that the deletions are persisted in native models.
1178 string query = "CREATE TABLE deleted_metas ";
1179 query.append(ComposeCreateTableColumnSpecs());
1180 if (!db_->Execute(query.c_str()))
1181 return false;
1182 SetVersion(84);
1183 return true;
1186 bool DirectoryBackingStore::MigrateVersion84To85() {
1187 // Version 85 removes the initial_sync_ended flag.
1188 if (!db_->Execute("ALTER TABLE models RENAME TO temp_models"))
1189 return false;
1190 if (!CreateV81ModelsTable())
1191 return false;
1192 if (!db_->Execute("INSERT INTO models SELECT "
1193 "model_id, progress_marker, transaction_version "
1194 "FROM temp_models")) {
1195 return false;
1197 SafeDropTable("temp_models");
1199 SetVersion(85);
1200 return true;
1203 bool DirectoryBackingStore::MigrateVersion85To86() {
1204 // Version 86 removes both server ordinals and local NEXT_ID, PREV_ID and
1205 // SERVER_{POSITION,ORDINAL}_IN_PARENT and replaces them with UNIQUE_POSITION
1206 // and SERVER_UNIQUE_POSITION.
1207 if (!db_->Execute("ALTER TABLE metas ADD COLUMN "
1208 "server_unique_position BLOB")) {
1209 return false;
1211 if (!db_->Execute("ALTER TABLE metas ADD COLUMN "
1212 "unique_position BLOB")) {
1213 return false;
1215 if (!db_->Execute("ALTER TABLE metas ADD COLUMN "
1216 "unique_bookmark_tag VARCHAR")) {
1217 return false;
1220 // Fetch the cache_guid from the DB, because we don't otherwise have access to
1221 // it from here.
1222 sql::Statement get_cache_guid(db_->GetUniqueStatement(
1223 "SELECT cache_guid FROM share_info"));
1224 if (!get_cache_guid.Step()) {
1225 return false;
1227 std::string cache_guid = get_cache_guid.ColumnString(0);
1228 DCHECK(!get_cache_guid.Step());
1229 DCHECK(get_cache_guid.Succeeded());
1231 sql::Statement get(db_->GetUniqueStatement(
1232 "SELECT "
1233 " metahandle, "
1234 " id, "
1235 " specifics, "
1236 " is_dir, "
1237 " unique_server_tag, "
1238 " server_ordinal_in_parent "
1239 "FROM metas"));
1241 // Note that we set both the local and server position based on the server
1242 // position. We wll lose any unsynced local position changes. Unfortunately,
1243 // there's nothing we can do to avoid that. The NEXT_ID / PREV_ID values
1244 // can't be translated into a UNIQUE_POSTION in a reliable way.
1245 sql::Statement put(db_->GetCachedStatement(
1246 SQL_FROM_HERE,
1247 "UPDATE metas SET"
1248 " server_unique_position = ?,"
1249 " unique_position = ?,"
1250 " unique_bookmark_tag = ?"
1251 "WHERE metahandle = ?"));
1253 while (get.Step()) {
1254 int64 metahandle = get.ColumnInt64(0);
1256 std::string id_string;
1257 get.ColumnBlobAsString(1, &id_string);
1259 sync_pb::EntitySpecifics specifics;
1260 specifics.ParseFromArray(
1261 get.ColumnBlob(2), get.ColumnByteLength(2));
1263 bool is_dir = get.ColumnBool(3);
1265 std::string server_unique_tag = get.ColumnString(4);
1267 std::string ordinal_string;
1268 get.ColumnBlobAsString(5, &ordinal_string);
1269 NodeOrdinal ordinal(ordinal_string);
1272 std::string unique_bookmark_tag;
1274 // We only maintain positions for bookmarks that are not server-defined
1275 // top-level folders.
1276 UniquePosition position;
1277 if (GetModelTypeFromSpecifics(specifics) == BOOKMARKS
1278 && !(is_dir && !server_unique_tag.empty())) {
1279 if (id_string.at(0) == 'c') {
1280 // We found an uncommitted item. This is rare, but fortunate. This
1281 // means we can set the bookmark tag according to the originator client
1282 // item ID and originator cache guid, because (unlike the other case) we
1283 // know that this client is the originator.
1284 unique_bookmark_tag = syncable::GenerateSyncableBookmarkHash(
1285 cache_guid,
1286 id_string.substr(1));
1287 } else {
1288 // If we've already committed the item, then we don't know who the
1289 // originator was. We do not have access to the originator client item
1290 // ID and originator cache guid at this point.
1292 // We will base our hash entirely on the server ID instead. This is
1293 // incorrect, but at least all clients that undergo this migration step
1294 // will be incorrect in the same way.
1296 // To get everyone back into a synced state, we will update the bookmark
1297 // tag according to the originator_cache_guid and originator_item_id
1298 // when we see updates for this item. That should ensure that commonly
1299 // modified items will end up with the proper tag values eventually.
1300 unique_bookmark_tag = syncable::GenerateSyncableBookmarkHash(
1301 std::string(), // cache_guid left intentionally blank.
1302 id_string.substr(1));
1305 int64 int_position = NodeOrdinalToInt64(ordinal);
1306 position = UniquePosition::FromInt64(int_position, unique_bookmark_tag);
1307 } else {
1308 // Leave bookmark_tag and position at their default (invalid) values.
1311 std::string position_blob;
1312 position.SerializeToString(&position_blob);
1313 put.BindBlob(0, position_blob.data(), position_blob.length());
1314 put.BindBlob(1, position_blob.data(), position_blob.length());
1315 put.BindBlob(2, unique_bookmark_tag.data(), unique_bookmark_tag.length());
1316 put.BindInt64(3, metahandle);
1318 if (!put.Run())
1319 return false;
1320 put.Reset(true);
1323 SetVersion(86);
1324 needs_column_refresh_ = true;
1325 return true;
1328 bool DirectoryBackingStore::MigrateVersion86To87() {
1329 // Version 87 adds AttachmentMetadata proto.
1330 if (!db_->Execute(
1331 "ALTER TABLE metas ADD COLUMN "
1332 "attachment_metadata BLOB")) {
1333 return false;
1335 SetVersion(87);
1336 needs_column_refresh_ = true;
1337 return true;
1340 bool DirectoryBackingStore::MigrateVersion87To88() {
1341 // Version 88 adds the datatype context to the models table.
1342 if (!db_->Execute("ALTER TABLE models ADD COLUMN context blob"))
1343 return false;
1345 SetVersion(88);
1346 return true;
1349 bool DirectoryBackingStore::MigrateVersion88To89() {
1350 // Version 89 adds server_attachment_metadata.
1351 if (!db_->Execute(
1352 "ALTER TABLE metas ADD COLUMN "
1353 "server_attachment_metadata BLOB")) {
1354 return false;
1356 SetVersion(89);
1357 needs_column_refresh_ = true;
1358 return true;
1361 bool DirectoryBackingStore::CreateTables() {
1362 DVLOG(1) << "First run, creating tables";
1363 // Create two little tables share_version and share_info
1364 if (!db_->Execute(
1365 "CREATE TABLE share_version ("
1366 "id VARCHAR(128) primary key, data INT)")) {
1367 return false;
1371 sql::Statement s(db_->GetUniqueStatement(
1372 "INSERT INTO share_version VALUES(?, ?)"));
1373 s.BindString(0, dir_name_);
1374 s.BindInt(1, kCurrentDBVersion);
1376 if (!s.Run())
1377 return false;
1380 const bool kCreateAsTempShareInfo = false;
1381 if (!CreateShareInfoTable(kCreateAsTempShareInfo)) {
1382 return false;
1386 sql::Statement s(db_->GetUniqueStatement(
1387 "INSERT INTO share_info VALUES"
1388 "(?, " // id
1389 "?, " // name
1390 "?, " // store_birthday
1391 "?, " // db_create_version
1392 "?, " // db_create_time
1393 "-2, " // next_id
1394 "?, " // cache_guid
1395 // TODO(rlarocque, 124140): Remove notification_state field.
1396 "?, " // notification_state
1397 "?);")); // bag_of_chips
1398 s.BindString(0, dir_name_); // id
1399 s.BindString(1, dir_name_); // name
1400 s.BindString(2, std::string()); // store_birthday
1401 // TODO(akalin): Remove this unused db_create_version field. (Or
1402 // actually use it for something.) http://crbug.com/118356
1403 s.BindString(3, "Unknown"); // db_create_version
1404 s.BindInt(4, static_cast<int32>(time(0))); // db_create_time
1405 s.BindString(5, GenerateCacheGUID()); // cache_guid
1406 // TODO(rlarocque, 124140): Remove this unused notification-state field.
1407 s.BindBlob(6, NULL, 0); // notification_state
1408 s.BindBlob(7, NULL, 0); // bag_of_chips
1409 if (!s.Run())
1410 return false;
1413 if (!CreateModelsTable())
1414 return false;
1416 // Create the big metas table.
1417 if (!CreateMetasTable(false))
1418 return false;
1421 // Insert the entry for the root into the metas table.
1422 const int64 now = TimeToProtoTime(base::Time::Now());
1423 sql::Statement s(db_->GetUniqueStatement(
1424 "INSERT INTO metas "
1425 "( id, metahandle, is_dir, ctime, mtime ) "
1426 "VALUES ( \"r\", 1, 1, ?, ? )"));
1427 s.BindInt64(0, now);
1428 s.BindInt64(1, now);
1430 if (!s.Run())
1431 return false;
1434 return true;
1437 bool DirectoryBackingStore::CreateMetasTable(bool is_temporary) {
1438 string query = "CREATE TABLE ";
1439 query.append(is_temporary ? "temp_metas" : "metas");
1440 query.append(ComposeCreateTableColumnSpecs());
1441 if (!db_->Execute(query.c_str()))
1442 return false;
1444 // Create a deleted_metas table to save copies of deleted metas until the
1445 // deletions are persisted. For simplicity, don't try to migrate existing
1446 // data because it's rarely used.
1447 SafeDropTable("deleted_metas");
1448 query = "CREATE TABLE deleted_metas ";
1449 query.append(ComposeCreateTableColumnSpecs());
1450 return db_->Execute(query.c_str());
1453 bool DirectoryBackingStore::CreateV71ModelsTable() {
1454 // This is an old schema for the Models table, used from versions 71 to 74.
1455 return db_->Execute(
1456 "CREATE TABLE models ("
1457 "model_id BLOB primary key, "
1458 "last_download_timestamp INT, "
1459 // Gets set if the syncer ever gets updates from the
1460 // server and the server returns 0. Lets us detect the
1461 // end of the initial sync.
1462 "initial_sync_ended BOOLEAN default 0)");
1465 bool DirectoryBackingStore::CreateV75ModelsTable() {
1466 // This is an old schema for the Models table, used from versions 75 to 80.
1467 return db_->Execute(
1468 "CREATE TABLE models ("
1469 "model_id BLOB primary key, "
1470 "progress_marker BLOB, "
1471 // Gets set if the syncer ever gets updates from the
1472 // server and the server returns 0. Lets us detect the
1473 // end of the initial sync.
1474 "initial_sync_ended BOOLEAN default 0)");
1477 bool DirectoryBackingStore::CreateV81ModelsTable() {
1478 // This is an old schema for the Models table, used from versions 81 to 87.
1479 return db_->Execute(
1480 "CREATE TABLE models ("
1481 "model_id BLOB primary key, "
1482 "progress_marker BLOB, "
1483 // Gets set if the syncer ever gets updates from the
1484 // server and the server returns 0. Lets us detect the
1485 // end of the initial sync.
1486 "transaction_version BIGINT default 0)");
1489 bool DirectoryBackingStore::CreateModelsTable() {
1490 // This is the current schema for the Models table, from version 88
1491 // onward. If you change the schema, you'll probably want to double-check
1492 // the use of this function in the v84-v85 migration.
1493 return db_->Execute(
1494 "CREATE TABLE models ("
1495 "model_id BLOB primary key, "
1496 "progress_marker BLOB, "
1497 // Gets set if the syncer ever gets updates from the
1498 // server and the server returns 0. Lets us detect the
1499 // end of the initial sync.
1500 "transaction_version BIGINT default 0,"
1501 "context BLOB)");
1504 bool DirectoryBackingStore::CreateShareInfoTable(bool is_temporary) {
1505 const char* name = is_temporary ? "temp_share_info" : "share_info";
1506 string query = "CREATE TABLE ";
1507 query.append(name);
1508 // This is the current schema for the ShareInfo table, from version 76
1509 // onward.
1510 query.append(" ("
1511 "id TEXT primary key, "
1512 "name TEXT, "
1513 "store_birthday TEXT, "
1514 "db_create_version TEXT, "
1515 "db_create_time INT, "
1516 "next_id INT default -2, "
1517 "cache_guid TEXT, "
1518 // TODO(rlarocque, 124140): Remove notification_state field.
1519 "notification_state BLOB, "
1520 "bag_of_chips BLOB"
1521 ")");
1522 return db_->Execute(query.c_str());
1525 bool DirectoryBackingStore::CreateShareInfoTableVersion71(
1526 bool is_temporary) {
1527 const char* name = is_temporary ? "temp_share_info" : "share_info";
1528 string query = "CREATE TABLE ";
1529 query.append(name);
1530 // This is the schema for the ShareInfo table used from versions 71 to 72.
1531 query.append(" ("
1532 "id TEXT primary key, "
1533 "name TEXT, "
1534 "store_birthday TEXT, "
1535 "db_create_version TEXT, "
1536 "db_create_time INT, "
1537 "next_id INT default -2, "
1538 "cache_guid TEXT )");
1539 return db_->Execute(query.c_str());
1542 // This function checks to see if the given list of Metahandles has any nodes
1543 // whose PARENT_ID values refer to ID values that do not actually exist.
1544 // Returns true on success.
1545 bool DirectoryBackingStore::VerifyReferenceIntegrity(
1546 const Directory::MetahandlesMap* handles_map) {
1547 TRACE_EVENT0("sync", "SyncDatabaseIntegrityCheck");
1548 using namespace syncable;
1549 typedef base::hash_set<std::string> IdsSet;
1551 IdsSet ids_set;
1552 bool is_ok = true;
1554 for (Directory::MetahandlesMap::const_iterator it = handles_map->begin();
1555 it != handles_map->end(); ++it) {
1556 EntryKernel* entry = it->second;
1557 bool is_duplicate_id = !(ids_set.insert(entry->ref(ID).value()).second);
1558 is_ok = is_ok && !is_duplicate_id;
1561 IdsSet::iterator end = ids_set.end();
1562 for (Directory::MetahandlesMap::const_iterator it = handles_map->begin();
1563 it != handles_map->end(); ++it) {
1564 EntryKernel* entry = it->second;
1565 if (!entry->ref(PARENT_ID).IsNull()) {
1566 bool parent_exists = (ids_set.find(entry->ref(PARENT_ID).value()) != end);
1567 if (!parent_exists) {
1568 return false;
1572 return is_ok;
1575 void DirectoryBackingStore::PrepareSaveEntryStatement(
1576 EntryTable table, sql::Statement* save_statement) {
1577 if (save_statement->is_valid())
1578 return;
1580 string query;
1581 query.reserve(kUpdateStatementBufferSize);
1582 switch (table) {
1583 case METAS_TABLE:
1584 query.append("INSERT OR REPLACE INTO metas ");
1585 break;
1586 case DELETE_JOURNAL_TABLE:
1587 query.append("INSERT OR REPLACE INTO deleted_metas ");
1588 break;
1591 string values;
1592 values.reserve(kUpdateStatementBufferSize);
1593 values.append(" VALUES ");
1594 const char* separator = "( ";
1595 int i = 0;
1596 for (i = BEGIN_FIELDS; i < FIELD_COUNT; ++i) {
1597 query.append(separator);
1598 values.append(separator);
1599 separator = ", ";
1600 query.append(ColumnName(i));
1601 values.append("?");
1603 query.append(" ) ");
1604 values.append(" )");
1605 query.append(values);
1606 save_statement->Assign(db_->GetUniqueStatement(
1607 base::StringPrintf(query.c_str(), "metas").c_str()));
1610 // Get page size for the database.
1611 bool DirectoryBackingStore::GetDatabasePageSize(int* page_size) {
1612 sql::Statement s(db_->GetUniqueStatement("PRAGMA page_size"));
1613 if (!s.Step())
1614 return false;
1615 *page_size = s.ColumnInt(0);
1616 return true;
1619 bool DirectoryBackingStore::IncreasePageSizeTo32K() {
1620 if (!db_->Execute("PRAGMA page_size=32768;") || !Vacuum()) {
1621 return false;
1623 return true;
1626 bool DirectoryBackingStore::Vacuum() {
1627 DCHECK_EQ(db_->transaction_nesting(), 0);
1628 if (!db_->Execute("VACUUM;")) {
1629 return false;
1631 return true;
1634 bool DirectoryBackingStore::needs_column_refresh() const {
1635 return needs_column_refresh_;
1638 void DirectoryBackingStore::ResetAndCreateConnection() {
1639 db_.reset(new sql::Connection());
1640 db_->set_histogram_tag("SyncDirectory");
1641 db_->set_exclusive_locking();
1642 db_->set_cache_size(32);
1643 db_->set_page_size(database_page_size_);
1646 } // namespace syncable
1647 } // namespace syncer