Convert events_unittests to run exclusively on Swarming
[chromium-blink-merge.git] / sync / syncable / directory_backing_store.cc
blob378b396b89d168af6f0b0aa95a04ba9ccfce74d3
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/thread_task_runner_handle.h"
17 #include "base/time/time.h"
18 #include "base/trace_event/trace_event.h"
19 #include "sql/connection.h"
20 #include "sql/error_delegate_util.h"
21 #include "sql/statement.h"
22 #include "sql/transaction.h"
23 #include "sync/internal_api/public/base/node_ordinal.h"
24 #include "sync/protocol/bookmark_specifics.pb.h"
25 #include "sync/protocol/sync.pb.h"
26 #include "sync/syncable/syncable-inl.h"
27 #include "sync/syncable/syncable_columns.h"
28 #include "sync/syncable/syncable_util.h"
29 #include "sync/util/time.h"
31 using std::string;
33 namespace syncer {
34 namespace syncable {
36 // Increment this version whenever updating DB tables.
37 const int32 kCurrentDBVersion = 89;
39 // Iterate over the fields of |entry| and bind each to |statement| for
40 // updating. Returns the number of args bound.
41 void BindFields(const EntryKernel& entry,
42 sql::Statement* statement) {
43 int index = 0;
44 int i = 0;
45 for (i = BEGIN_FIELDS; i < INT64_FIELDS_END; ++i) {
46 statement->BindInt64(index++, entry.ref(static_cast<Int64Field>(i)));
48 for ( ; i < TIME_FIELDS_END; ++i) {
49 statement->BindInt64(index++,
50 TimeToProtoTime(
51 entry.ref(static_cast<TimeField>(i))));
53 for ( ; i < ID_FIELDS_END; ++i) {
54 statement->BindString(index++, entry.ref(static_cast<IdField>(i)).s_);
56 for ( ; i < BIT_FIELDS_END; ++i) {
57 statement->BindInt(index++, entry.ref(static_cast<BitField>(i)));
59 for ( ; i < STRING_FIELDS_END; ++i) {
60 statement->BindString(index++, entry.ref(static_cast<StringField>(i)));
62 for ( ; i < PROTO_FIELDS_END; ++i) {
63 std::string temp;
64 entry.ref(static_cast<ProtoField>(i)).SerializeToString(&temp);
65 statement->BindBlob(index++, temp.data(), temp.length());
67 for ( ; i < UNIQUE_POSITION_FIELDS_END; ++i) {
68 std::string temp;
69 entry.ref(static_cast<UniquePositionField>(i)).SerializeToString(&temp);
70 statement->BindBlob(index++, temp.data(), temp.length());
72 for (; i < ATTACHMENT_METADATA_FIELDS_END; ++i) {
73 std::string temp;
74 entry.ref(static_cast<AttachmentMetadataField>(i)).SerializeToString(&temp);
75 statement->BindBlob(index++, temp.data(), temp.length());
79 // Helper function that loads a number of shareable fields of the
80 // same type. The sharing criteria is based on comparison of
81 // the serialized data. Only consecutive DB columns need to compared
82 // to cover all possible sharing combinations.
83 template <typename TValue, typename TField>
84 void UnpackProtoFields(sql::Statement* statement,
85 EntryKernel* kernel,
86 int* index,
87 int end_index) {
88 const void* prev_blob = nullptr;
89 int prev_length = -1;
90 int prev_index = -1;
92 for (; *index < end_index; ++(*index)) {
93 int length = statement->ColumnByteLength(*index);
94 if (length == 0) {
95 // Skip this column and keep the default value in the kernel field.
96 continue;
99 const void* blob = statement->ColumnBlob(*index);
100 // According to sqlite3 documentation, the prev_blob pointer should remain
101 // valid until moving to the next row.
102 if (length == prev_length && memcmp(blob, prev_blob, length) == 0) {
103 // Serialized values are the same - share the value from |prev_index|
104 // field with the current field.
105 kernel->copy(static_cast<TField>(prev_index),
106 static_cast<TField>(*index));
107 } else {
108 // Regular case - deserialize and copy the value to the field.
109 TValue value;
110 value.ParseFromArray(blob, length);
111 kernel->put(static_cast<TField>(*index), value);
112 prev_blob = blob;
113 prev_length = length;
114 prev_index = *index;
119 // The caller owns the returned EntryKernel*. Assumes the statement currently
120 // points to a valid row in the metas table. Returns NULL to indicate that
121 // it detected a corruption in the data on unpacking.
122 scoped_ptr<EntryKernel> UnpackEntry(sql::Statement* statement) {
123 scoped_ptr<EntryKernel> kernel(new EntryKernel());
124 DCHECK_EQ(statement->ColumnCount(), static_cast<int>(FIELD_COUNT));
125 int i = 0;
126 for (i = BEGIN_FIELDS; i < INT64_FIELDS_END; ++i) {
127 kernel->put(static_cast<Int64Field>(i), statement->ColumnInt64(i));
129 for ( ; i < TIME_FIELDS_END; ++i) {
130 kernel->put(static_cast<TimeField>(i),
131 ProtoTimeToTime(statement->ColumnInt64(i)));
133 for ( ; i < ID_FIELDS_END; ++i) {
134 kernel->mutable_ref(static_cast<IdField>(i)).s_ =
135 statement->ColumnString(i);
137 for ( ; i < BIT_FIELDS_END; ++i) {
138 kernel->put(static_cast<BitField>(i), (0 != statement->ColumnInt(i)));
140 for ( ; i < STRING_FIELDS_END; ++i) {
141 kernel->put(static_cast<StringField>(i),
142 statement->ColumnString(i));
144 UnpackProtoFields<sync_pb::EntitySpecifics, ProtoField>(
145 statement, kernel.get(), &i, PROTO_FIELDS_END);
146 for ( ; i < UNIQUE_POSITION_FIELDS_END; ++i) {
147 std::string temp;
148 statement->ColumnBlobAsString(i, &temp);
150 sync_pb::UniquePosition proto;
151 if (!proto.ParseFromString(temp)) {
152 DVLOG(1) << "Unpacked invalid position. Assuming the DB is corrupt";
153 return scoped_ptr<EntryKernel>();
156 kernel->mutable_ref(static_cast<UniquePositionField>(i)) =
157 UniquePosition::FromProto(proto);
159 UnpackProtoFields<sync_pb::AttachmentMetadata, AttachmentMetadataField>(
160 statement, kernel.get(), &i, ATTACHMENT_METADATA_FIELDS_END);
162 // Sanity check on positions. We risk strange and rare crashes if our
163 // assumptions about unique position values are broken.
164 if (kernel->ShouldMaintainPosition() &&
165 !kernel->ref(UNIQUE_POSITION).IsValid()) {
166 DVLOG(1) << "Unpacked invalid position on an entity that should have a "
167 << "valid position. Assuming the DB is corrupt.";
168 return scoped_ptr<EntryKernel>();
171 return kernel.Pass();
174 namespace {
176 // This just has to be big enough to hold an UPDATE or INSERT statement that
177 // modifies all the columns in the entry table.
178 static const string::size_type kUpdateStatementBufferSize = 2048;
180 bool IsSyncBackingDatabase32KEnabled() {
181 const std::string group_name =
182 base::FieldTrialList::FindFullName("SyncBackingDatabase32K");
183 return group_name == "Enabled";
186 void OnSqliteError(const base::Closure& catastrophic_error_handler,
187 int err,
188 sql::Statement* statement) {
189 // An error has been detected. Ignore unless it is catastrophic.
190 if (sql::IsErrorCatastrophic(err)) {
191 // At this point sql::* and DirectoryBackingStore may be on the callstack so
192 // don't invoke the error handler directly. Instead, PostTask to this thread
193 // to avoid potential reentrancy issues.
194 base::MessageLoop::current()->PostTask(FROM_HERE,
195 catastrophic_error_handler);
199 string ComposeCreateTableColumnSpecs() {
200 const ColumnSpec* begin = g_metas_columns;
201 const ColumnSpec* end = g_metas_columns + arraysize(g_metas_columns);
202 string query;
203 query.reserve(kUpdateStatementBufferSize);
204 char separator = '(';
205 for (const ColumnSpec* column = begin; column != end; ++column) {
206 query.push_back(separator);
207 separator = ',';
208 query.append(column->name);
209 query.push_back(' ');
210 query.append(column->spec);
212 query.push_back(')');
213 return query;
216 void AppendColumnList(std::string* output) {
217 const char* joiner = " ";
218 // Be explicit in SELECT order to match up with UnpackEntry.
219 for (int i = BEGIN_FIELDS; i < FIELD_COUNT; ++i) {
220 output->append(joiner);
221 output->append(ColumnName(i));
222 joiner = ", ";
226 bool SaveEntryToDB(sql::Statement* save_statement, const EntryKernel& entry) {
227 save_statement->Reset(true);
228 BindFields(entry, save_statement);
229 return save_statement->Run();
232 } // namespace
234 ///////////////////////////////////////////////////////////////////////////////
235 // DirectoryBackingStore implementation.
237 DirectoryBackingStore::DirectoryBackingStore(const string& dir_name)
238 : dir_name_(dir_name),
239 database_page_size_(IsSyncBackingDatabase32KEnabled() ? 32768 : 4096),
240 needs_column_refresh_(false) {
241 DCHECK(base::ThreadTaskRunnerHandle::IsSet());
242 ResetAndCreateConnection();
245 DirectoryBackingStore::DirectoryBackingStore(const string& dir_name,
246 sql::Connection* db)
247 : dir_name_(dir_name),
248 database_page_size_(IsSyncBackingDatabase32KEnabled() ? 32768 : 4096),
249 db_(db),
250 needs_column_refresh_(false) {
251 DCHECK(base::ThreadTaskRunnerHandle::IsSet());
254 DirectoryBackingStore::~DirectoryBackingStore() {
257 bool DirectoryBackingStore::DeleteEntries(EntryTable from,
258 const MetahandleSet& handles) {
259 if (handles.empty())
260 return true;
262 sql::Statement statement;
263 // Call GetCachedStatement() separately to get different statements for
264 // different tables.
265 switch (from) {
266 case METAS_TABLE:
267 statement.Assign(db_->GetCachedStatement(
268 SQL_FROM_HERE, "DELETE FROM metas WHERE metahandle = ?"));
269 break;
270 case DELETE_JOURNAL_TABLE:
271 statement.Assign(db_->GetCachedStatement(
272 SQL_FROM_HERE, "DELETE FROM deleted_metas WHERE metahandle = ?"));
273 break;
276 for (MetahandleSet::const_iterator i = handles.begin(); i != handles.end();
277 ++i) {
278 statement.BindInt64(0, *i);
279 if (!statement.Run())
280 return false;
281 statement.Reset(true);
283 return true;
286 bool DirectoryBackingStore::SaveChanges(
287 const Directory::SaveChangesSnapshot& snapshot) {
288 DCHECK(CalledOnValidThread());
289 DCHECK(db_->is_open());
291 // Back out early if there is nothing to write.
292 bool save_info =
293 (Directory::KERNEL_SHARE_INFO_DIRTY == snapshot.kernel_info_status);
294 if (!snapshot.HasUnsavedMetahandleChanges() && !save_info) {
295 return true;
298 sql::Transaction transaction(db_.get());
299 if (!transaction.Begin())
300 return false;
302 PrepareSaveEntryStatement(METAS_TABLE, &save_meta_statement_);
303 for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin();
304 i != snapshot.dirty_metas.end(); ++i) {
305 DCHECK((*i)->is_dirty());
306 if (!SaveEntryToDB(&save_meta_statement_, **i))
307 return false;
310 if (!DeleteEntries(METAS_TABLE, snapshot.metahandles_to_purge))
311 return false;
313 PrepareSaveEntryStatement(DELETE_JOURNAL_TABLE,
314 &save_delete_journal_statement_);
315 for (EntryKernelSet::const_iterator i = snapshot.delete_journals.begin();
316 i != snapshot.delete_journals.end(); ++i) {
317 if (!SaveEntryToDB(&save_delete_journal_statement_, **i))
318 return false;
321 if (!DeleteEntries(DELETE_JOURNAL_TABLE, snapshot.delete_journals_to_purge))
322 return false;
324 if (save_info) {
325 const Directory::PersistedKernelInfo& info = snapshot.kernel_info;
326 sql::Statement s1(db_->GetCachedStatement(
327 SQL_FROM_HERE,
328 "UPDATE share_info "
329 "SET store_birthday = ?, "
330 "bag_of_chips = ?"));
331 s1.BindString(0, info.store_birthday);
332 s1.BindBlob(1, info.bag_of_chips.data(), info.bag_of_chips.size());
334 if (!s1.Run())
335 return false;
336 DCHECK_EQ(db_->GetLastChangeCount(), 1);
338 sql::Statement s2(db_->GetCachedStatement(
339 SQL_FROM_HERE,
340 "INSERT OR REPLACE "
341 "INTO models (model_id, "
342 "progress_marker, "
343 "transaction_version, "
344 "context) "
345 "VALUES (?, ?, ?, ?)"));
347 ModelTypeSet protocol_types = ProtocolTypes();
348 for (ModelTypeSet::Iterator iter = protocol_types.First(); iter.Good();
349 iter.Inc()) {
350 ModelType type = iter.Get();
351 // We persist not ModelType but rather a protobuf-derived ID.
352 string model_id = ModelTypeEnumToModelId(type);
353 string progress_marker;
354 info.download_progress[type].SerializeToString(&progress_marker);
355 s2.BindBlob(0, model_id.data(), model_id.length());
356 s2.BindBlob(1, progress_marker.data(), progress_marker.length());
357 s2.BindInt64(2, info.transaction_version[type]);
358 string context;
359 info.datatype_context[type].SerializeToString(&context);
360 s2.BindBlob(3, context.data(), context.length());
361 if (!s2.Run())
362 return false;
363 DCHECK_EQ(db_->GetLastChangeCount(), 1);
364 s2.Reset(true);
368 return transaction.Commit();
371 sql::Connection* DirectoryBackingStore::db() {
372 return db_.get();
375 bool DirectoryBackingStore::IsOpen() const {
376 return db_->is_open();
379 bool DirectoryBackingStore::Open(const base::FilePath& path) {
380 DCHECK(!db_->is_open());
381 return db_->Open(path);
384 bool DirectoryBackingStore::OpenInMemory() {
385 DCHECK(!db_->is_open());
386 return db_->OpenInMemory();
389 bool DirectoryBackingStore::InitializeTables() {
390 int page_size = 0;
391 if (IsSyncBackingDatabase32KEnabled() && GetDatabasePageSize(&page_size) &&
392 page_size == 4096) {
393 IncreasePageSizeTo32K();
395 sql::Transaction transaction(db_.get());
396 if (!transaction.Begin())
397 return false;
399 int version_on_disk = GetVersion();
401 // Upgrade from version 67. Version 67 was widely distributed as the original
402 // Bookmark Sync release. Version 68 removed unique naming.
403 if (version_on_disk == 67) {
404 if (MigrateVersion67To68())
405 version_on_disk = 68;
407 // Version 69 introduced additional datatypes.
408 if (version_on_disk == 68) {
409 if (MigrateVersion68To69())
410 version_on_disk = 69;
413 if (version_on_disk == 69) {
414 if (MigrateVersion69To70())
415 version_on_disk = 70;
418 // Version 71 changed the sync progress information to be per-datatype.
419 if (version_on_disk == 70) {
420 if (MigrateVersion70To71())
421 version_on_disk = 71;
424 // Version 72 removed extended attributes, a legacy way to do extensible
425 // key/value information, stored in their own table.
426 if (version_on_disk == 71) {
427 if (MigrateVersion71To72())
428 version_on_disk = 72;
431 // Version 73 added a field for notification state.
432 if (version_on_disk == 72) {
433 if (MigrateVersion72To73())
434 version_on_disk = 73;
437 // Version 74 added state for the autofill migration.
438 if (version_on_disk == 73) {
439 if (MigrateVersion73To74())
440 version_on_disk = 74;
443 // Version 75 migrated from int64-based timestamps to per-datatype tokens.
444 if (version_on_disk == 74) {
445 if (MigrateVersion74To75())
446 version_on_disk = 75;
449 // Version 76 removed all (5) autofill migration related columns.
450 if (version_on_disk == 75) {
451 if (MigrateVersion75To76())
452 version_on_disk = 76;
455 // Version 77 standardized all time fields to ms since the Unix
456 // epoch.
457 if (version_on_disk == 76) {
458 if (MigrateVersion76To77())
459 version_on_disk = 77;
462 // Version 78 added the column base_server_specifics to the metas table.
463 if (version_on_disk == 77) {
464 if (MigrateVersion77To78())
465 version_on_disk = 78;
468 // Version 79 migration is a one-time fix for some users in a bad state.
469 if (version_on_disk == 78) {
470 if (MigrateVersion78To79())
471 version_on_disk = 79;
474 // Version 80 migration is adding the bag_of_chips column.
475 if (version_on_disk == 79) {
476 if (MigrateVersion79To80())
477 version_on_disk = 80;
480 // Version 81 replaces the int64 server_position_in_parent_field
481 // with a blob server_ordinal_in_parent field.
482 if (version_on_disk == 80) {
483 if (MigrateVersion80To81())
484 version_on_disk = 81;
487 // Version 82 migration added transaction_version column per data type.
488 if (version_on_disk == 81) {
489 if (MigrateVersion81To82())
490 version_on_disk = 82;
493 // Version 83 migration added transaction_version column per sync entry.
494 if (version_on_disk == 82) {
495 if (MigrateVersion82To83())
496 version_on_disk = 83;
499 // Version 84 migration added deleted_metas table.
500 if (version_on_disk == 83) {
501 if (MigrateVersion83To84())
502 version_on_disk = 84;
505 // Version 85 migration removes the initial_sync_ended bits.
506 if (version_on_disk == 84) {
507 if (MigrateVersion84To85())
508 version_on_disk = 85;
511 // Version 86 migration converts bookmarks to the unique positioning system.
512 // It also introduces a new field to store a unique ID for each bookmark.
513 if (version_on_disk == 85) {
514 if (MigrateVersion85To86())
515 version_on_disk = 86;
518 // Version 87 migration adds a collection of attachment ids per sync entry.
519 if (version_on_disk == 86) {
520 if (MigrateVersion86To87())
521 version_on_disk = 87;
524 // Version 88 migration adds datatype contexts to the models table.
525 if (version_on_disk == 87) {
526 if (MigrateVersion87To88())
527 version_on_disk = 88;
530 // Version 89 migration adds server attachment metadata to the metas table.
531 if (version_on_disk == 88) {
532 if (MigrateVersion88To89())
533 version_on_disk = 89;
536 // If one of the migrations requested it, drop columns that aren't current.
537 // It's only safe to do this after migrating all the way to the current
538 // version.
539 if (version_on_disk == kCurrentDBVersion && needs_column_refresh_) {
540 if (!RefreshColumns())
541 version_on_disk = 0;
544 // A final, alternative catch-all migration to simply re-sync everything.
545 if (version_on_disk != kCurrentDBVersion) {
546 if (version_on_disk > kCurrentDBVersion)
547 return false;
549 // Fallback (re-sync everything) migration path.
550 DVLOG(1) << "Old/null sync database, version " << version_on_disk;
551 // Delete the existing database (if any), and create a fresh one.
552 DropAllTables();
553 if (!CreateTables())
554 return false;
557 sql::Statement s(db_->GetUniqueStatement(
558 "SELECT db_create_version, db_create_time FROM share_info"));
559 if (!s.Step())
560 return false;
561 string db_create_version = s.ColumnString(0);
562 int db_create_time = s.ColumnInt(1);
563 DVLOG(1) << "DB created at " << db_create_time << " by version " <<
564 db_create_version;
566 return transaction.Commit();
569 // This function drops unused columns by creating a new table that contains only
570 // the currently used columns then copying all rows from the old tables into
571 // this new one. The tables are then rearranged so the new replaces the old.
572 bool DirectoryBackingStore::RefreshColumns() {
573 DCHECK(needs_column_refresh_);
575 // Create a new table named temp_metas.
576 SafeDropTable("temp_metas");
577 if (!CreateMetasTable(true))
578 return false;
580 // Populate temp_metas from metas.
582 // At this point, the metas table may contain columns belonging to obsolete
583 // schema versions. This statement explicitly lists only the columns that
584 // belong to the current schema version, so the obsolete columns will be
585 // effectively dropped once we rename temp_metas over top of metas.
586 std::string query = "INSERT INTO temp_metas (";
587 AppendColumnList(&query);
588 query.append(") SELECT ");
589 AppendColumnList(&query);
590 query.append(" FROM metas");
591 if (!db_->Execute(query.c_str()))
592 return false;
594 // Drop metas.
595 SafeDropTable("metas");
597 // Rename temp_metas -> metas.
598 if (!db_->Execute("ALTER TABLE temp_metas RENAME TO metas"))
599 return false;
601 // Repeat the process for share_info.
602 SafeDropTable("temp_share_info");
603 if (!CreateShareInfoTable(true))
604 return false;
606 // TODO(rlarocque, 124140): Remove notification_state.
607 if (!db_->Execute(
608 "INSERT INTO temp_share_info (id, name, store_birthday, "
609 "db_create_version, db_create_time, next_id, cache_guid,"
610 "notification_state, bag_of_chips) "
611 "SELECT id, name, store_birthday, db_create_version, "
612 "db_create_time, next_id, cache_guid, notification_state, "
613 "bag_of_chips "
614 "FROM share_info"))
615 return false;
617 SafeDropTable("share_info");
618 if (!db_->Execute("ALTER TABLE temp_share_info RENAME TO share_info"))
619 return false;
621 needs_column_refresh_ = false;
622 return true;
625 bool DirectoryBackingStore::LoadEntries(Directory::MetahandlesMap* handles_map,
626 MetahandleSet* metahandles_to_purge) {
627 string select;
628 select.reserve(kUpdateStatementBufferSize);
629 select.append("SELECT ");
630 AppendColumnList(&select);
631 select.append(" FROM metas");
633 sql::Statement s(db_->GetUniqueStatement(select.c_str()));
635 while (s.Step()) {
636 scoped_ptr<EntryKernel> kernel = UnpackEntry(&s);
637 // A null kernel is evidence of external data corruption.
638 if (!kernel)
639 return false;
641 int64 handle = kernel->ref(META_HANDLE);
642 if (SafeToPurgeOnLoading(*kernel))
643 metahandles_to_purge->insert(handle);
644 else
645 (*handles_map)[handle] = kernel.release();
647 return s.Succeeded();
650 bool DirectoryBackingStore::SafeToPurgeOnLoading(
651 const EntryKernel& entry) const {
652 if (entry.ref(IS_DEL)) {
653 if (!entry.ref(IS_UNSYNCED) && !entry.ref(IS_UNAPPLIED_UPDATE))
654 return true;
655 else if (!entry.ref(ID).ServerKnows())
656 return true;
658 return false;
661 bool DirectoryBackingStore::LoadDeleteJournals(
662 JournalIndex* delete_journals) {
663 string select;
664 select.reserve(kUpdateStatementBufferSize);
665 select.append("SELECT ");
666 AppendColumnList(&select);
667 select.append(" FROM deleted_metas");
669 sql::Statement s(db_->GetUniqueStatement(select.c_str()));
671 while (s.Step()) {
672 scoped_ptr<EntryKernel> kernel = UnpackEntry(&s);
673 // A null kernel is evidence of external data corruption.
674 if (!kernel)
675 return false;
676 delete_journals->insert(kernel.release());
678 return s.Succeeded();
681 bool DirectoryBackingStore::LoadInfo(Directory::KernelLoadInfo* info) {
683 sql::Statement s(db_->GetUniqueStatement(
684 "SELECT store_birthday, cache_guid, bag_of_chips "
685 "FROM share_info"));
686 if (!s.Step())
687 return false;
689 info->kernel_info.store_birthday = s.ColumnString(0);
690 info->cache_guid = s.ColumnString(1);
691 s.ColumnBlobAsString(2, &(info->kernel_info.bag_of_chips));
693 // Verify there was only one row returned.
694 DCHECK(!s.Step());
695 DCHECK(s.Succeeded());
699 sql::Statement s(
700 db_->GetUniqueStatement(
701 "SELECT model_id, progress_marker, "
702 "transaction_version, context FROM models"));
704 while (s.Step()) {
705 ModelType type = ModelIdToModelTypeEnum(s.ColumnBlob(0),
706 s.ColumnByteLength(0));
707 if (type != UNSPECIFIED && type != TOP_LEVEL_FOLDER) {
708 info->kernel_info.download_progress[type].ParseFromArray(
709 s.ColumnBlob(1), s.ColumnByteLength(1));
710 info->kernel_info.transaction_version[type] = s.ColumnInt64(2);
711 info->kernel_info.datatype_context[type].ParseFromArray(
712 s.ColumnBlob(3), s.ColumnByteLength(3));
715 if (!s.Succeeded())
716 return false;
719 sql::Statement s(
720 db_->GetUniqueStatement(
721 "SELECT MAX(metahandle) FROM metas"));
722 if (!s.Step())
723 return false;
725 info->max_metahandle = s.ColumnInt64(0);
727 // Verify only one row was returned.
728 DCHECK(!s.Step());
729 DCHECK(s.Succeeded());
731 return true;
734 bool DirectoryBackingStore::SafeDropTable(const char* table_name) {
735 string query = "DROP TABLE IF EXISTS ";
736 query.append(table_name);
737 return db_->Execute(query.c_str());
740 void DirectoryBackingStore::DropAllTables() {
741 SafeDropTable("metas");
742 SafeDropTable("temp_metas");
743 SafeDropTable("share_info");
744 SafeDropTable("temp_share_info");
745 SafeDropTable("share_version");
746 SafeDropTable("extended_attributes");
747 SafeDropTable("models");
748 SafeDropTable("temp_models");
749 needs_column_refresh_ = false;
752 // static
753 ModelType DirectoryBackingStore::ModelIdToModelTypeEnum(
754 const void* data, int size) {
755 sync_pb::EntitySpecifics specifics;
756 if (!specifics.ParseFromArray(data, size))
757 return UNSPECIFIED;
758 return GetModelTypeFromSpecifics(specifics);
761 // static
762 string DirectoryBackingStore::ModelTypeEnumToModelId(ModelType model_type) {
763 sync_pb::EntitySpecifics specifics;
764 AddDefaultFieldValue(model_type, &specifics);
765 return specifics.SerializeAsString();
768 // static
769 std::string DirectoryBackingStore::GenerateCacheGUID() {
770 // Generate a GUID with 128 bits of randomness.
771 const int kGuidBytes = 128 / 8;
772 std::string guid;
773 base::Base64Encode(base::RandBytesAsString(kGuidBytes), &guid);
774 return guid;
777 bool DirectoryBackingStore::MigrateToSpecifics(
778 const char* old_columns,
779 const char* specifics_column,
780 void (*handler_function)(sql::Statement* old_value_query,
781 int old_value_column,
782 sync_pb::EntitySpecifics* mutable_new_value)) {
783 std::string query_sql = base::StringPrintf(
784 "SELECT metahandle, %s, %s FROM metas", specifics_column, old_columns);
785 std::string update_sql = base::StringPrintf(
786 "UPDATE metas SET %s = ? WHERE metahandle = ?", specifics_column);
788 sql::Statement query(db_->GetUniqueStatement(query_sql.c_str()));
789 sql::Statement update(db_->GetUniqueStatement(update_sql.c_str()));
791 while (query.Step()) {
792 int64 metahandle = query.ColumnInt64(0);
793 std::string new_value_bytes;
794 query.ColumnBlobAsString(1, &new_value_bytes);
795 sync_pb::EntitySpecifics new_value;
796 new_value.ParseFromString(new_value_bytes);
797 handler_function(&query, 2, &new_value);
798 new_value.SerializeToString(&new_value_bytes);
800 update.BindBlob(0, new_value_bytes.data(), new_value_bytes.length());
801 update.BindInt64(1, metahandle);
802 if (!update.Run())
803 return false;
804 update.Reset(true);
806 return query.Succeeded();
809 bool DirectoryBackingStore::SetVersion(int version) {
810 sql::Statement s(db_->GetCachedStatement(
811 SQL_FROM_HERE, "UPDATE share_version SET data = ?"));
812 s.BindInt(0, version);
814 return s.Run();
817 int DirectoryBackingStore::GetVersion() {
818 if (!db_->DoesTableExist("share_version"))
819 return 0;
821 sql::Statement statement(db_->GetUniqueStatement(
822 "SELECT data FROM share_version"));
823 if (statement.Step()) {
824 return statement.ColumnInt(0);
825 } else {
826 return 0;
830 bool DirectoryBackingStore::MigrateVersion67To68() {
831 // This change simply removed three columns:
832 // string NAME
833 // string UNSANITIZED_NAME
834 // string SERVER_NAME
835 // No data migration is necessary, but we should do a column refresh.
836 SetVersion(68);
837 needs_column_refresh_ = true;
838 return true;
841 bool DirectoryBackingStore::MigrateVersion69To70() {
842 // Added "unique_client_tag", renamed "singleton_tag" to unique_server_tag
843 SetVersion(70);
844 if (!db_->Execute(
845 "ALTER TABLE metas ADD COLUMN unique_server_tag varchar"))
846 return false;
847 if (!db_->Execute(
848 "ALTER TABLE metas ADD COLUMN unique_client_tag varchar"))
849 return false;
850 needs_column_refresh_ = true;
852 if (!db_->Execute(
853 "UPDATE metas SET unique_server_tag = singleton_tag"))
854 return false;
856 return true;
859 namespace {
861 // Callback passed to MigrateToSpecifics for the v68->v69 migration. See
862 // MigrateVersion68To69().
863 void EncodeBookmarkURLAndFavicon(sql::Statement* old_value_query,
864 int old_value_column,
865 sync_pb::EntitySpecifics* mutable_new_value) {
866 // Extract data from the column trio we expect.
867 bool old_is_bookmark_object = old_value_query->ColumnBool(old_value_column);
868 std::string old_url = old_value_query->ColumnString(old_value_column + 1);
869 std::string old_favicon;
870 old_value_query->ColumnBlobAsString(old_value_column + 2, &old_favicon);
871 bool old_is_dir = old_value_query->ColumnBool(old_value_column + 3);
873 if (old_is_bookmark_object) {
874 sync_pb::BookmarkSpecifics* bookmark_data =
875 mutable_new_value->mutable_bookmark();
876 if (!old_is_dir) {
877 bookmark_data->set_url(old_url);
878 bookmark_data->set_favicon(old_favicon);
883 } // namespace
885 bool DirectoryBackingStore::MigrateVersion68To69() {
886 // In Version 68, there were columns on table 'metas':
887 // string BOOKMARK_URL
888 // string SERVER_BOOKMARK_URL
889 // blob BOOKMARK_FAVICON
890 // blob SERVER_BOOKMARK_FAVICON
891 // In version 69, these columns went away in favor of storing
892 // a serialized EntrySpecifics protobuf in the columns:
893 // protobuf blob SPECIFICS
894 // protobuf blob SERVER_SPECIFICS
895 // For bookmarks, EntrySpecifics is extended as per
896 // bookmark_specifics.proto. This migration converts bookmarks from the
897 // former scheme to the latter scheme.
899 // First, add the two new columns to the schema.
900 if (!db_->Execute(
901 "ALTER TABLE metas ADD COLUMN specifics blob"))
902 return false;
903 if (!db_->Execute(
904 "ALTER TABLE metas ADD COLUMN server_specifics blob"))
905 return false;
907 // Next, fold data from the old columns into the new protobuf columns.
908 if (!MigrateToSpecifics(("is_bookmark_object, bookmark_url, "
909 "bookmark_favicon, is_dir"),
910 "specifics",
911 &EncodeBookmarkURLAndFavicon)) {
912 return false;
914 if (!MigrateToSpecifics(("server_is_bookmark_object, "
915 "server_bookmark_url, "
916 "server_bookmark_favicon, "
917 "server_is_dir"),
918 "server_specifics",
919 &EncodeBookmarkURLAndFavicon)) {
920 return false;
923 // Lastly, fix up the "Google Chrome" folder, which is of the TOP_LEVEL_FOLDER
924 // ModelType: it shouldn't have BookmarkSpecifics.
925 if (!db_->Execute(
926 "UPDATE metas SET specifics = NULL, server_specifics = NULL WHERE "
927 "singleton_tag IN ('google_chrome')"))
928 return false;
930 SetVersion(69);
931 needs_column_refresh_ = true; // Trigger deletion of old columns.
932 return true;
935 // Version 71, the columns 'initial_sync_ended' and 'last_sync_timestamp'
936 // were removed from the share_info table. They were replaced by
937 // the 'models' table, which has these values on a per-datatype basis.
938 bool DirectoryBackingStore::MigrateVersion70To71() {
939 if (!CreateV71ModelsTable())
940 return false;
942 // Move data from the old share_info columns to the new models table.
944 sql::Statement fetch(db_->GetUniqueStatement(
945 "SELECT last_sync_timestamp, initial_sync_ended FROM share_info"));
946 if (!fetch.Step())
947 return false;
949 int64 last_sync_timestamp = fetch.ColumnInt64(0);
950 bool initial_sync_ended = fetch.ColumnBool(1);
952 // Verify there were no additional rows returned.
953 DCHECK(!fetch.Step());
954 DCHECK(fetch.Succeeded());
956 sql::Statement update(db_->GetUniqueStatement(
957 "INSERT INTO models (model_id, "
958 "last_download_timestamp, initial_sync_ended) VALUES (?, ?, ?)"));
959 string bookmark_model_id = ModelTypeEnumToModelId(BOOKMARKS);
960 update.BindBlob(0, bookmark_model_id.data(), bookmark_model_id.size());
961 update.BindInt64(1, last_sync_timestamp);
962 update.BindBool(2, initial_sync_ended);
964 if (!update.Run())
965 return false;
968 // Drop the columns from the old share_info table via a temp table.
969 const bool kCreateAsTempShareInfo = true;
971 if (!CreateShareInfoTableVersion71(kCreateAsTempShareInfo))
972 return false;
973 if (!db_->Execute(
974 "INSERT INTO temp_share_info (id, name, store_birthday, "
975 "db_create_version, db_create_time, next_id, cache_guid) "
976 "SELECT id, name, store_birthday, db_create_version, "
977 "db_create_time, next_id, cache_guid FROM share_info"))
978 return false;
979 SafeDropTable("share_info");
980 if (!db_->Execute(
981 "ALTER TABLE temp_share_info RENAME TO share_info"))
982 return false;
983 SetVersion(71);
984 return true;
987 bool DirectoryBackingStore::MigrateVersion71To72() {
988 // Version 72 removed a table 'extended_attributes', whose
989 // contents didn't matter.
990 SafeDropTable("extended_attributes");
991 SetVersion(72);
992 return true;
995 bool DirectoryBackingStore::MigrateVersion72To73() {
996 // Version 73 added one column to the table 'share_info': notification_state
997 if (!db_->Execute(
998 "ALTER TABLE share_info ADD COLUMN notification_state BLOB"))
999 return false;
1000 SetVersion(73);
1001 return true;
1004 bool DirectoryBackingStore::MigrateVersion73To74() {
1005 // Version 74 added the following columns to the table 'share_info':
1006 // autofill_migration_state
1007 // bookmarks_added_during_autofill_migration
1008 // autofill_migration_time
1009 // autofill_entries_added_during_migration
1010 // autofill_profiles_added_during_migration
1012 if (!db_->Execute(
1013 "ALTER TABLE share_info ADD COLUMN "
1014 "autofill_migration_state INT default 0"))
1015 return false;
1017 if (!db_->Execute(
1018 "ALTER TABLE share_info ADD COLUMN "
1019 "bookmarks_added_during_autofill_migration "
1020 "INT default 0"))
1021 return false;
1023 if (!db_->Execute(
1024 "ALTER TABLE share_info ADD COLUMN autofill_migration_time "
1025 "INT default 0"))
1026 return false;
1028 if (!db_->Execute(
1029 "ALTER TABLE share_info ADD COLUMN "
1030 "autofill_entries_added_during_migration "
1031 "INT default 0"))
1032 return false;
1034 if (!db_->Execute(
1035 "ALTER TABLE share_info ADD COLUMN "
1036 "autofill_profiles_added_during_migration "
1037 "INT default 0"))
1038 return false;
1040 SetVersion(74);
1041 return true;
1044 bool DirectoryBackingStore::MigrateVersion74To75() {
1045 // In version 74, there was a table 'models':
1046 // blob model_id (entity specifics, primary key)
1047 // int last_download_timestamp
1048 // boolean initial_sync_ended
1049 // In version 75, we deprecated the integer-valued last_download_timestamp,
1050 // using insted a protobuf-valued progress_marker field:
1051 // blob progress_marker
1052 // The progress_marker values are initialized from the value of
1053 // last_download_timestamp, thereby preserving the download state.
1055 // Move aside the old table and create a new empty one at the current schema.
1056 if (!db_->Execute("ALTER TABLE models RENAME TO temp_models"))
1057 return false;
1058 if (!CreateV75ModelsTable())
1059 return false;
1061 sql::Statement query(db_->GetUniqueStatement(
1062 "SELECT model_id, last_download_timestamp, initial_sync_ended "
1063 "FROM temp_models"));
1065 sql::Statement update(db_->GetUniqueStatement(
1066 "INSERT INTO models (model_id, "
1067 "progress_marker, initial_sync_ended) VALUES (?, ?, ?)"));
1069 while (query.Step()) {
1070 ModelType type = ModelIdToModelTypeEnum(query.ColumnBlob(0),
1071 query.ColumnByteLength(0));
1072 if (type != UNSPECIFIED) {
1073 // Set the |timestamp_token_for_migration| on a new
1074 // DataTypeProgressMarker, using the old value of last_download_timestamp.
1075 // The server will turn this into a real token on our behalf the next
1076 // time we check for updates.
1077 sync_pb::DataTypeProgressMarker progress_marker;
1078 progress_marker.set_data_type_id(
1079 GetSpecificsFieldNumberFromModelType(type));
1080 progress_marker.set_timestamp_token_for_migration(query.ColumnInt64(1));
1081 std::string progress_blob;
1082 progress_marker.SerializeToString(&progress_blob);
1084 update.BindBlob(0, query.ColumnBlob(0), query.ColumnByteLength(0));
1085 update.BindBlob(1, progress_blob.data(), progress_blob.length());
1086 update.BindBool(2, query.ColumnBool(2));
1087 if (!update.Run())
1088 return false;
1089 update.Reset(true);
1092 if (!query.Succeeded())
1093 return false;
1095 // Drop the old table.
1096 SafeDropTable("temp_models");
1098 SetVersion(75);
1099 return true;
1102 bool DirectoryBackingStore::MigrateVersion75To76() {
1103 // This change removed five columns:
1104 // autofill_migration_state
1105 // bookmarks_added_during_autofill_migration
1106 // autofill_migration_time
1107 // autofill_entries_added_during_migration
1108 // autofill_profiles_added_during_migration
1109 // No data migration is necessary, but we should do a column refresh.
1110 SetVersion(76);
1111 needs_column_refresh_ = true;
1112 return true;
1115 bool DirectoryBackingStore::MigrateVersion76To77() {
1116 // This change changes the format of stored timestamps to ms since
1117 // the Unix epoch.
1118 #if defined(OS_WIN)
1119 // On Windows, we used to store timestamps in FILETIME format (100s of
1120 // ns since Jan 1, 1601). Magic numbers taken from
1121 // http://stackoverflow.com/questions/5398557/
1122 // java-library-for-dealing-with-win32-filetime
1123 // .
1124 #define TO_UNIX_TIME_MS(x) #x " = " #x " / 10000 - 11644473600000"
1125 #else
1126 // On other platforms, we used to store timestamps in time_t format (s
1127 // since the Unix epoch).
1128 #define TO_UNIX_TIME_MS(x) #x " = " #x " * 1000"
1129 #endif
1130 sql::Statement update_timestamps(db_->GetUniqueStatement(
1131 "UPDATE metas SET "
1132 TO_UNIX_TIME_MS(mtime) ", "
1133 TO_UNIX_TIME_MS(server_mtime) ", "
1134 TO_UNIX_TIME_MS(ctime) ", "
1135 TO_UNIX_TIME_MS(server_ctime)));
1136 #undef TO_UNIX_TIME_MS
1137 if (!update_timestamps.Run())
1138 return false;
1139 SetVersion(77);
1140 return true;
1143 bool DirectoryBackingStore::MigrateVersion77To78() {
1144 // Version 78 added one column to table 'metas': base_server_specifics.
1145 if (!db_->Execute(
1146 "ALTER TABLE metas ADD COLUMN base_server_specifics BLOB")) {
1147 return false;
1149 SetVersion(78);
1150 return true;
1153 bool DirectoryBackingStore::MigrateVersion78To79() {
1154 // Some users are stuck with a DB that causes them to reuse existing IDs. We
1155 // perform this one-time fixup on all users to help the few that are stuck.
1156 // See crbug.com/142987 for details.
1157 if (!db_->Execute(
1158 "UPDATE share_info SET next_id = next_id - 65536")) {
1159 return false;
1161 SetVersion(79);
1162 return true;
1165 bool DirectoryBackingStore::MigrateVersion79To80() {
1166 if (!db_->Execute(
1167 "ALTER TABLE share_info ADD COLUMN bag_of_chips BLOB"))
1168 return false;
1169 sql::Statement update(db_->GetUniqueStatement(
1170 "UPDATE share_info SET bag_of_chips = ?"));
1171 // An empty message is serialized to an empty string.
1172 update.BindBlob(0, NULL, 0);
1173 if (!update.Run())
1174 return false;
1175 SetVersion(80);
1176 return true;
1179 bool DirectoryBackingStore::MigrateVersion80To81() {
1180 if(!db_->Execute(
1181 "ALTER TABLE metas ADD COLUMN server_ordinal_in_parent BLOB"))
1182 return false;
1184 sql::Statement get_positions(db_->GetUniqueStatement(
1185 "SELECT metahandle, server_position_in_parent FROM metas"));
1187 sql::Statement put_ordinals(db_->GetUniqueStatement(
1188 "UPDATE metas SET server_ordinal_in_parent = ?"
1189 "WHERE metahandle = ?"));
1191 while(get_positions.Step()) {
1192 int64 metahandle = get_positions.ColumnInt64(0);
1193 int64 position = get_positions.ColumnInt64(1);
1195 const std::string& ordinal = Int64ToNodeOrdinal(position).ToInternalValue();
1196 put_ordinals.BindBlob(0, ordinal.data(), ordinal.length());
1197 put_ordinals.BindInt64(1, metahandle);
1199 if(!put_ordinals.Run())
1200 return false;
1201 put_ordinals.Reset(true);
1204 SetVersion(81);
1205 needs_column_refresh_ = true;
1206 return true;
1209 bool DirectoryBackingStore::MigrateVersion81To82() {
1210 if (!db_->Execute(
1211 "ALTER TABLE models ADD COLUMN transaction_version BIGINT default 0"))
1212 return false;
1213 sql::Statement update(db_->GetUniqueStatement(
1214 "UPDATE models SET transaction_version = 0"));
1215 if (!update.Run())
1216 return false;
1217 SetVersion(82);
1218 return true;
1221 bool DirectoryBackingStore::MigrateVersion82To83() {
1222 // Version 83 added transaction_version on sync node.
1223 if (!db_->Execute(
1224 "ALTER TABLE metas ADD COLUMN transaction_version BIGINT default 0"))
1225 return false;
1226 sql::Statement update(db_->GetUniqueStatement(
1227 "UPDATE metas SET transaction_version = 0"));
1228 if (!update.Run())
1229 return false;
1230 SetVersion(83);
1231 return true;
1234 bool DirectoryBackingStore::MigrateVersion83To84() {
1235 // Version 84 added deleted_metas table to store deleted metas until we know
1236 // for sure that the deletions are persisted in native models.
1237 string query = "CREATE TABLE deleted_metas ";
1238 query.append(ComposeCreateTableColumnSpecs());
1239 if (!db_->Execute(query.c_str()))
1240 return false;
1241 SetVersion(84);
1242 return true;
1245 bool DirectoryBackingStore::MigrateVersion84To85() {
1246 // Version 85 removes the initial_sync_ended flag.
1247 if (!db_->Execute("ALTER TABLE models RENAME TO temp_models"))
1248 return false;
1249 if (!CreateV81ModelsTable())
1250 return false;
1251 if (!db_->Execute("INSERT INTO models SELECT "
1252 "model_id, progress_marker, transaction_version "
1253 "FROM temp_models")) {
1254 return false;
1256 SafeDropTable("temp_models");
1258 SetVersion(85);
1259 return true;
1262 bool DirectoryBackingStore::MigrateVersion85To86() {
1263 // Version 86 removes both server ordinals and local NEXT_ID, PREV_ID and
1264 // SERVER_{POSITION,ORDINAL}_IN_PARENT and replaces them with UNIQUE_POSITION
1265 // and SERVER_UNIQUE_POSITION.
1266 if (!db_->Execute("ALTER TABLE metas ADD COLUMN "
1267 "server_unique_position BLOB")) {
1268 return false;
1270 if (!db_->Execute("ALTER TABLE metas ADD COLUMN "
1271 "unique_position BLOB")) {
1272 return false;
1274 if (!db_->Execute("ALTER TABLE metas ADD COLUMN "
1275 "unique_bookmark_tag VARCHAR")) {
1276 return false;
1279 // Fetch the cache_guid from the DB, because we don't otherwise have access to
1280 // it from here.
1281 sql::Statement get_cache_guid(db_->GetUniqueStatement(
1282 "SELECT cache_guid FROM share_info"));
1283 if (!get_cache_guid.Step()) {
1284 return false;
1286 std::string cache_guid = get_cache_guid.ColumnString(0);
1287 DCHECK(!get_cache_guid.Step());
1288 DCHECK(get_cache_guid.Succeeded());
1290 sql::Statement get(db_->GetUniqueStatement(
1291 "SELECT "
1292 " metahandle, "
1293 " id, "
1294 " specifics, "
1295 " is_dir, "
1296 " unique_server_tag, "
1297 " server_ordinal_in_parent "
1298 "FROM metas"));
1300 // Note that we set both the local and server position based on the server
1301 // position. We wll lose any unsynced local position changes. Unfortunately,
1302 // there's nothing we can do to avoid that. The NEXT_ID / PREV_ID values
1303 // can't be translated into a UNIQUE_POSTION in a reliable way.
1304 sql::Statement put(db_->GetCachedStatement(
1305 SQL_FROM_HERE,
1306 "UPDATE metas SET"
1307 " server_unique_position = ?,"
1308 " unique_position = ?,"
1309 " unique_bookmark_tag = ?"
1310 "WHERE metahandle = ?"));
1312 while (get.Step()) {
1313 int64 metahandle = get.ColumnInt64(0);
1315 std::string id_string;
1316 get.ColumnBlobAsString(1, &id_string);
1318 sync_pb::EntitySpecifics specifics;
1319 specifics.ParseFromArray(
1320 get.ColumnBlob(2), get.ColumnByteLength(2));
1322 bool is_dir = get.ColumnBool(3);
1324 std::string server_unique_tag = get.ColumnString(4);
1326 std::string ordinal_string;
1327 get.ColumnBlobAsString(5, &ordinal_string);
1328 NodeOrdinal ordinal(ordinal_string);
1331 std::string unique_bookmark_tag;
1333 // We only maintain positions for bookmarks that are not server-defined
1334 // top-level folders.
1335 UniquePosition position;
1336 if (GetModelTypeFromSpecifics(specifics) == BOOKMARKS
1337 && !(is_dir && !server_unique_tag.empty())) {
1338 if (id_string.at(0) == 'c') {
1339 // We found an uncommitted item. This is rare, but fortunate. This
1340 // means we can set the bookmark tag according to the originator client
1341 // item ID and originator cache guid, because (unlike the other case) we
1342 // know that this client is the originator.
1343 unique_bookmark_tag = syncable::GenerateSyncableBookmarkHash(
1344 cache_guid,
1345 id_string.substr(1));
1346 } else {
1347 // If we've already committed the item, then we don't know who the
1348 // originator was. We do not have access to the originator client item
1349 // ID and originator cache guid at this point.
1351 // We will base our hash entirely on the server ID instead. This is
1352 // incorrect, but at least all clients that undergo this migration step
1353 // will be incorrect in the same way.
1355 // To get everyone back into a synced state, we will update the bookmark
1356 // tag according to the originator_cache_guid and originator_item_id
1357 // when we see updates for this item. That should ensure that commonly
1358 // modified items will end up with the proper tag values eventually.
1359 unique_bookmark_tag = syncable::GenerateSyncableBookmarkHash(
1360 std::string(), // cache_guid left intentionally blank.
1361 id_string.substr(1));
1364 int64 int_position = NodeOrdinalToInt64(ordinal);
1365 position = UniquePosition::FromInt64(int_position, unique_bookmark_tag);
1366 } else {
1367 // Leave bookmark_tag and position at their default (invalid) values.
1370 std::string position_blob;
1371 position.SerializeToString(&position_blob);
1372 put.BindBlob(0, position_blob.data(), position_blob.length());
1373 put.BindBlob(1, position_blob.data(), position_blob.length());
1374 put.BindBlob(2, unique_bookmark_tag.data(), unique_bookmark_tag.length());
1375 put.BindInt64(3, metahandle);
1377 if (!put.Run())
1378 return false;
1379 put.Reset(true);
1382 SetVersion(86);
1383 needs_column_refresh_ = true;
1384 return true;
1387 bool DirectoryBackingStore::MigrateVersion86To87() {
1388 // Version 87 adds AttachmentMetadata proto.
1389 if (!db_->Execute(
1390 "ALTER TABLE metas ADD COLUMN "
1391 "attachment_metadata BLOB")) {
1392 return false;
1394 SetVersion(87);
1395 needs_column_refresh_ = true;
1396 return true;
1399 bool DirectoryBackingStore::MigrateVersion87To88() {
1400 // Version 88 adds the datatype context to the models table.
1401 if (!db_->Execute("ALTER TABLE models ADD COLUMN context blob"))
1402 return false;
1404 SetVersion(88);
1405 return true;
1408 bool DirectoryBackingStore::MigrateVersion88To89() {
1409 // Version 89 adds server_attachment_metadata.
1410 if (!db_->Execute(
1411 "ALTER TABLE metas ADD COLUMN "
1412 "server_attachment_metadata BLOB")) {
1413 return false;
1415 SetVersion(89);
1416 needs_column_refresh_ = true;
1417 return true;
1420 bool DirectoryBackingStore::CreateTables() {
1421 DVLOG(1) << "First run, creating tables";
1422 // Create two little tables share_version and share_info
1423 if (!db_->Execute(
1424 "CREATE TABLE share_version ("
1425 "id VARCHAR(128) primary key, data INT)")) {
1426 return false;
1430 sql::Statement s(db_->GetUniqueStatement(
1431 "INSERT INTO share_version VALUES(?, ?)"));
1432 s.BindString(0, dir_name_);
1433 s.BindInt(1, kCurrentDBVersion);
1435 if (!s.Run())
1436 return false;
1439 const bool kCreateAsTempShareInfo = false;
1440 if (!CreateShareInfoTable(kCreateAsTempShareInfo)) {
1441 return false;
1445 sql::Statement s(db_->GetUniqueStatement(
1446 "INSERT INTO share_info VALUES"
1447 "(?, " // id
1448 "?, " // name
1449 "?, " // store_birthday
1450 "?, " // db_create_version
1451 "?, " // db_create_time
1452 "-2, " // next_id
1453 "?, " // cache_guid
1454 // TODO(rlarocque, 124140): Remove notification_state field.
1455 "?, " // notification_state
1456 "?);")); // bag_of_chips
1457 s.BindString(0, dir_name_); // id
1458 s.BindString(1, dir_name_); // name
1459 s.BindString(2, std::string()); // store_birthday
1460 // TODO(akalin): Remove this unused db_create_version field. (Or
1461 // actually use it for something.) http://crbug.com/118356
1462 s.BindString(3, "Unknown"); // db_create_version
1463 s.BindInt(4, static_cast<int32>(time(0))); // db_create_time
1464 s.BindString(5, GenerateCacheGUID()); // cache_guid
1465 // TODO(rlarocque, 124140): Remove this unused notification-state field.
1466 s.BindBlob(6, NULL, 0); // notification_state
1467 s.BindBlob(7, NULL, 0); // bag_of_chips
1468 if (!s.Run())
1469 return false;
1472 if (!CreateModelsTable())
1473 return false;
1475 // Create the big metas table.
1476 if (!CreateMetasTable(false))
1477 return false;
1480 // Insert the entry for the root into the metas table.
1481 const int64 now = TimeToProtoTime(base::Time::Now());
1482 sql::Statement s(db_->GetUniqueStatement(
1483 "INSERT INTO metas "
1484 "( id, metahandle, is_dir, ctime, mtime ) "
1485 "VALUES ( \"r\", 1, 1, ?, ? )"));
1486 s.BindInt64(0, now);
1487 s.BindInt64(1, now);
1489 if (!s.Run())
1490 return false;
1493 return true;
1496 bool DirectoryBackingStore::CreateMetasTable(bool is_temporary) {
1497 string query = "CREATE TABLE ";
1498 query.append(is_temporary ? "temp_metas" : "metas");
1499 query.append(ComposeCreateTableColumnSpecs());
1500 if (!db_->Execute(query.c_str()))
1501 return false;
1503 // Create a deleted_metas table to save copies of deleted metas until the
1504 // deletions are persisted. For simplicity, don't try to migrate existing
1505 // data because it's rarely used.
1506 SafeDropTable("deleted_metas");
1507 query = "CREATE TABLE deleted_metas ";
1508 query.append(ComposeCreateTableColumnSpecs());
1509 return db_->Execute(query.c_str());
1512 bool DirectoryBackingStore::CreateV71ModelsTable() {
1513 // This is an old schema for the Models table, used from versions 71 to 74.
1514 return db_->Execute(
1515 "CREATE TABLE models ("
1516 "model_id BLOB primary key, "
1517 "last_download_timestamp INT, "
1518 // Gets set if the syncer ever gets updates from the
1519 // server and the server returns 0. Lets us detect the
1520 // end of the initial sync.
1521 "initial_sync_ended BOOLEAN default 0)");
1524 bool DirectoryBackingStore::CreateV75ModelsTable() {
1525 // This is an old schema for the Models table, used from versions 75 to 80.
1526 return db_->Execute(
1527 "CREATE TABLE models ("
1528 "model_id BLOB primary key, "
1529 "progress_marker BLOB, "
1530 // Gets set if the syncer ever gets updates from the
1531 // server and the server returns 0. Lets us detect the
1532 // end of the initial sync.
1533 "initial_sync_ended BOOLEAN default 0)");
1536 bool DirectoryBackingStore::CreateV81ModelsTable() {
1537 // This is an old schema for the Models table, used from versions 81 to 87.
1538 return db_->Execute(
1539 "CREATE TABLE models ("
1540 "model_id BLOB primary key, "
1541 "progress_marker BLOB, "
1542 // Gets set if the syncer ever gets updates from the
1543 // server and the server returns 0. Lets us detect the
1544 // end of the initial sync.
1545 "transaction_version BIGINT default 0)");
1548 bool DirectoryBackingStore::CreateModelsTable() {
1549 // This is the current schema for the Models table, from version 88
1550 // onward. If you change the schema, you'll probably want to double-check
1551 // the use of this function in the v84-v85 migration.
1552 return db_->Execute(
1553 "CREATE TABLE models ("
1554 "model_id BLOB primary key, "
1555 "progress_marker BLOB, "
1556 // Gets set if the syncer ever gets updates from the
1557 // server and the server returns 0. Lets us detect the
1558 // end of the initial sync.
1559 "transaction_version BIGINT default 0,"
1560 "context BLOB)");
1563 bool DirectoryBackingStore::CreateShareInfoTable(bool is_temporary) {
1564 const char* name = is_temporary ? "temp_share_info" : "share_info";
1565 string query = "CREATE TABLE ";
1566 query.append(name);
1567 // This is the current schema for the ShareInfo table, from version 76
1568 // onward.
1569 query.append(" ("
1570 "id TEXT primary key, "
1571 "name TEXT, "
1572 "store_birthday TEXT, "
1573 "db_create_version TEXT, "
1574 "db_create_time INT, "
1575 "next_id INT default -2, "
1576 "cache_guid TEXT, "
1577 // TODO(rlarocque, 124140): Remove notification_state field.
1578 "notification_state BLOB, "
1579 "bag_of_chips BLOB"
1580 ")");
1581 return db_->Execute(query.c_str());
1584 bool DirectoryBackingStore::CreateShareInfoTableVersion71(
1585 bool is_temporary) {
1586 const char* name = is_temporary ? "temp_share_info" : "share_info";
1587 string query = "CREATE TABLE ";
1588 query.append(name);
1589 // This is the schema for the ShareInfo table used from versions 71 to 72.
1590 query.append(" ("
1591 "id TEXT primary key, "
1592 "name TEXT, "
1593 "store_birthday TEXT, "
1594 "db_create_version TEXT, "
1595 "db_create_time INT, "
1596 "next_id INT default -2, "
1597 "cache_guid TEXT )");
1598 return db_->Execute(query.c_str());
1601 // This function checks to see if the given list of Metahandles has any nodes
1602 // whose PARENT_ID values refer to ID values that do not actually exist.
1603 // Returns true on success.
1604 bool DirectoryBackingStore::VerifyReferenceIntegrity(
1605 const Directory::MetahandlesMap* handles_map) {
1606 TRACE_EVENT0("sync", "SyncDatabaseIntegrityCheck");
1607 using namespace syncable;
1608 typedef base::hash_set<std::string> IdsSet;
1610 IdsSet ids_set;
1611 bool is_ok = true;
1613 for (Directory::MetahandlesMap::const_iterator it = handles_map->begin();
1614 it != handles_map->end(); ++it) {
1615 EntryKernel* entry = it->second;
1616 bool is_duplicate_id = !(ids_set.insert(entry->ref(ID).value()).second);
1617 is_ok = is_ok && !is_duplicate_id;
1620 IdsSet::iterator end = ids_set.end();
1621 for (Directory::MetahandlesMap::const_iterator it = handles_map->begin();
1622 it != handles_map->end(); ++it) {
1623 EntryKernel* entry = it->second;
1624 if (!entry->ref(PARENT_ID).IsNull()) {
1625 bool parent_exists = (ids_set.find(entry->ref(PARENT_ID).value()) != end);
1626 if (!parent_exists) {
1627 return false;
1631 return is_ok;
1634 void DirectoryBackingStore::PrepareSaveEntryStatement(
1635 EntryTable table, sql::Statement* save_statement) {
1636 if (save_statement->is_valid())
1637 return;
1639 string query;
1640 query.reserve(kUpdateStatementBufferSize);
1641 switch (table) {
1642 case METAS_TABLE:
1643 query.append("INSERT OR REPLACE INTO metas ");
1644 break;
1645 case DELETE_JOURNAL_TABLE:
1646 query.append("INSERT OR REPLACE INTO deleted_metas ");
1647 break;
1650 string values;
1651 values.reserve(kUpdateStatementBufferSize);
1652 values.append(" VALUES ");
1653 const char* separator = "( ";
1654 int i = 0;
1655 for (i = BEGIN_FIELDS; i < FIELD_COUNT; ++i) {
1656 query.append(separator);
1657 values.append(separator);
1658 separator = ", ";
1659 query.append(ColumnName(i));
1660 values.append("?");
1662 query.append(" ) ");
1663 values.append(" )");
1664 query.append(values);
1665 save_statement->Assign(db_->GetUniqueStatement(
1666 base::StringPrintf(query.c_str(), "metas").c_str()));
1669 // Get page size for the database.
1670 bool DirectoryBackingStore::GetDatabasePageSize(int* page_size) {
1671 sql::Statement s(db_->GetUniqueStatement("PRAGMA page_size"));
1672 if (!s.Step())
1673 return false;
1674 *page_size = s.ColumnInt(0);
1675 return true;
1678 bool DirectoryBackingStore::IncreasePageSizeTo32K() {
1679 if (!db_->Execute("PRAGMA page_size=32768;") || !Vacuum()) {
1680 return false;
1682 return true;
1685 bool DirectoryBackingStore::Vacuum() {
1686 DCHECK_EQ(db_->transaction_nesting(), 0);
1687 if (!db_->Execute("VACUUM;")) {
1688 return false;
1690 return true;
1693 bool DirectoryBackingStore::needs_column_refresh() const {
1694 return needs_column_refresh_;
1697 void DirectoryBackingStore::ResetAndCreateConnection() {
1698 db_.reset(new sql::Connection());
1699 db_->set_histogram_tag("SyncDirectory");
1700 db_->set_exclusive_locking();
1701 db_->set_cache_size(32);
1702 db_->set_page_size(database_page_size_);
1703 if (!catastrophic_error_handler_.is_null())
1704 SetCatastrophicErrorHandler(catastrophic_error_handler_);
1707 void DirectoryBackingStore::SetCatastrophicErrorHandler(
1708 const base::Closure& catastrophic_error_handler) {
1709 DCHECK(CalledOnValidThread());
1710 DCHECK(!catastrophic_error_handler.is_null());
1711 catastrophic_error_handler_ = catastrophic_error_handler;
1712 sql::Connection::ErrorCallback error_callback =
1713 base::Bind(&OnSqliteError, catastrophic_error_handler_);
1714 db_->set_error_callback(error_callback);
1717 } // namespace syncable
1718 } // namespace syncer