Supervised user import: Listen for profile creation/deletion
[chromium-blink-merge.git] / sync / syncable / directory_backing_store.cc
blob4b3d3b538e31c37593917827c5c29db37a255788
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 // The caller owns the returned EntryKernel*. Assumes the statement currently
80 // points to a valid row in the metas table. Returns NULL to indicate that
81 // it detected a corruption in the data on unpacking.
82 scoped_ptr<EntryKernel> UnpackEntry(sql::Statement* statement) {
83 scoped_ptr<EntryKernel> kernel(new EntryKernel());
84 DCHECK_EQ(statement->ColumnCount(), static_cast<int>(FIELD_COUNT));
85 int i = 0;
86 for (i = BEGIN_FIELDS; i < INT64_FIELDS_END; ++i) {
87 kernel->put(static_cast<Int64Field>(i), statement->ColumnInt64(i));
89 for ( ; i < TIME_FIELDS_END; ++i) {
90 kernel->put(static_cast<TimeField>(i),
91 ProtoTimeToTime(statement->ColumnInt64(i)));
93 for ( ; i < ID_FIELDS_END; ++i) {
94 kernel->mutable_ref(static_cast<IdField>(i)).s_ =
95 statement->ColumnString(i);
97 for ( ; i < BIT_FIELDS_END; ++i) {
98 kernel->put(static_cast<BitField>(i), (0 != statement->ColumnInt(i)));
100 for ( ; i < STRING_FIELDS_END; ++i) {
101 kernel->put(static_cast<StringField>(i),
102 statement->ColumnString(i));
104 for ( ; i < PROTO_FIELDS_END; ++i) {
105 kernel->mutable_ref(static_cast<ProtoField>(i)).ParseFromArray(
106 statement->ColumnBlob(i), statement->ColumnByteLength(i));
108 for ( ; i < UNIQUE_POSITION_FIELDS_END; ++i) {
109 std::string temp;
110 statement->ColumnBlobAsString(i, &temp);
112 sync_pb::UniquePosition proto;
113 if (!proto.ParseFromString(temp)) {
114 DVLOG(1) << "Unpacked invalid position. Assuming the DB is corrupt";
115 return scoped_ptr<EntryKernel>();
118 kernel->mutable_ref(static_cast<UniquePositionField>(i)) =
119 UniquePosition::FromProto(proto);
121 for (; i < ATTACHMENT_METADATA_FIELDS_END; ++i) {
122 kernel->mutable_ref(static_cast<AttachmentMetadataField>(i)).ParseFromArray(
123 statement->ColumnBlob(i), statement->ColumnByteLength(i));
126 // Sanity check on positions. We risk strange and rare crashes if our
127 // assumptions about unique position values are broken.
128 if (kernel->ShouldMaintainPosition() &&
129 !kernel->ref(UNIQUE_POSITION).IsValid()) {
130 DVLOG(1) << "Unpacked invalid position on an entity that should have a "
131 << "valid position. Assuming the DB is corrupt.";
132 return scoped_ptr<EntryKernel>();
135 return kernel.Pass();
138 namespace {
140 // This just has to be big enough to hold an UPDATE or INSERT statement that
141 // modifies all the columns in the entry table.
142 static const string::size_type kUpdateStatementBufferSize = 2048;
144 bool IsSyncBackingDatabase32KEnabled() {
145 const std::string group_name =
146 base::FieldTrialList::FindFullName("SyncBackingDatabase32K");
147 return group_name == "Enabled";
150 void OnSqliteError(const base::Closure& catastrophic_error_handler,
151 int err,
152 sql::Statement* statement) {
153 // An error has been detected. Ignore unless it is catastrophic.
154 if (sql::IsErrorCatastrophic(err)) {
155 // At this point sql::* and DirectoryBackingStore may be on the callstack so
156 // don't invoke the error handler directly. Instead, PostTask to this thread
157 // to avoid potential reentrancy issues.
158 base::MessageLoop::current()->PostTask(FROM_HERE,
159 catastrophic_error_handler);
163 string ComposeCreateTableColumnSpecs() {
164 const ColumnSpec* begin = g_metas_columns;
165 const ColumnSpec* end = g_metas_columns + arraysize(g_metas_columns);
166 string query;
167 query.reserve(kUpdateStatementBufferSize);
168 char separator = '(';
169 for (const ColumnSpec* column = begin; column != end; ++column) {
170 query.push_back(separator);
171 separator = ',';
172 query.append(column->name);
173 query.push_back(' ');
174 query.append(column->spec);
176 query.push_back(')');
177 return query;
180 void AppendColumnList(std::string* output) {
181 const char* joiner = " ";
182 // Be explicit in SELECT order to match up with UnpackEntry.
183 for (int i = BEGIN_FIELDS; i < FIELD_COUNT; ++i) {
184 output->append(joiner);
185 output->append(ColumnName(i));
186 joiner = ", ";
190 bool SaveEntryToDB(sql::Statement* save_statement, const EntryKernel& entry) {
191 save_statement->Reset(true);
192 BindFields(entry, save_statement);
193 return save_statement->Run();
196 } // namespace
198 ///////////////////////////////////////////////////////////////////////////////
199 // DirectoryBackingStore implementation.
201 DirectoryBackingStore::DirectoryBackingStore(const string& dir_name)
202 : dir_name_(dir_name),
203 database_page_size_(IsSyncBackingDatabase32KEnabled() ? 32768 : 4096),
204 needs_column_refresh_(false) {
205 DCHECK(base::ThreadTaskRunnerHandle::IsSet());
206 ResetAndCreateConnection();
209 DirectoryBackingStore::DirectoryBackingStore(const string& dir_name,
210 sql::Connection* db)
211 : dir_name_(dir_name),
212 database_page_size_(IsSyncBackingDatabase32KEnabled() ? 32768 : 4096),
213 db_(db),
214 needs_column_refresh_(false) {
215 DCHECK(base::ThreadTaskRunnerHandle::IsSet());
218 DirectoryBackingStore::~DirectoryBackingStore() {
221 bool DirectoryBackingStore::DeleteEntries(EntryTable from,
222 const MetahandleSet& handles) {
223 if (handles.empty())
224 return true;
226 sql::Statement statement;
227 // Call GetCachedStatement() separately to get different statements for
228 // different tables.
229 switch (from) {
230 case METAS_TABLE:
231 statement.Assign(db_->GetCachedStatement(
232 SQL_FROM_HERE, "DELETE FROM metas WHERE metahandle = ?"));
233 break;
234 case DELETE_JOURNAL_TABLE:
235 statement.Assign(db_->GetCachedStatement(
236 SQL_FROM_HERE, "DELETE FROM deleted_metas WHERE metahandle = ?"));
237 break;
240 for (MetahandleSet::const_iterator i = handles.begin(); i != handles.end();
241 ++i) {
242 statement.BindInt64(0, *i);
243 if (!statement.Run())
244 return false;
245 statement.Reset(true);
247 return true;
250 bool DirectoryBackingStore::SaveChanges(
251 const Directory::SaveChangesSnapshot& snapshot) {
252 DCHECK(CalledOnValidThread());
253 DCHECK(db_->is_open());
255 // Back out early if there is nothing to write.
256 bool save_info =
257 (Directory::KERNEL_SHARE_INFO_DIRTY == snapshot.kernel_info_status);
258 if (!snapshot.HasUnsavedMetahandleChanges() && !save_info) {
259 return true;
262 sql::Transaction transaction(db_.get());
263 if (!transaction.Begin())
264 return false;
266 PrepareSaveEntryStatement(METAS_TABLE, &save_meta_statement_);
267 for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin();
268 i != snapshot.dirty_metas.end(); ++i) {
269 DCHECK((*i)->is_dirty());
270 if (!SaveEntryToDB(&save_meta_statement_, **i))
271 return false;
274 if (!DeleteEntries(METAS_TABLE, snapshot.metahandles_to_purge))
275 return false;
277 PrepareSaveEntryStatement(DELETE_JOURNAL_TABLE,
278 &save_delete_journal_statement_);
279 for (EntryKernelSet::const_iterator i = snapshot.delete_journals.begin();
280 i != snapshot.delete_journals.end(); ++i) {
281 if (!SaveEntryToDB(&save_delete_journal_statement_, **i))
282 return false;
285 if (!DeleteEntries(DELETE_JOURNAL_TABLE, snapshot.delete_journals_to_purge))
286 return false;
288 if (save_info) {
289 const Directory::PersistedKernelInfo& info = snapshot.kernel_info;
290 sql::Statement s1(db_->GetCachedStatement(
291 SQL_FROM_HERE,
292 "UPDATE share_info "
293 "SET store_birthday = ?, "
294 "next_id = ?, "
295 "bag_of_chips = ?"));
296 s1.BindString(0, info.store_birthday);
297 s1.BindInt64(1, info.next_id);
298 s1.BindBlob(2, info.bag_of_chips.data(), info.bag_of_chips.size());
300 if (!s1.Run())
301 return false;
302 DCHECK_EQ(db_->GetLastChangeCount(), 1);
304 sql::Statement s2(db_->GetCachedStatement(
305 SQL_FROM_HERE,
306 "INSERT OR REPLACE "
307 "INTO models (model_id, "
308 "progress_marker, "
309 "transaction_version, "
310 "context) "
311 "VALUES (?, ?, ?, ?)"));
313 ModelTypeSet protocol_types = ProtocolTypes();
314 for (ModelTypeSet::Iterator iter = protocol_types.First(); iter.Good();
315 iter.Inc()) {
316 ModelType type = iter.Get();
317 // We persist not ModelType but rather a protobuf-derived ID.
318 string model_id = ModelTypeEnumToModelId(type);
319 string progress_marker;
320 info.download_progress[type].SerializeToString(&progress_marker);
321 s2.BindBlob(0, model_id.data(), model_id.length());
322 s2.BindBlob(1, progress_marker.data(), progress_marker.length());
323 s2.BindInt64(2, info.transaction_version[type]);
324 string context;
325 info.datatype_context[type].SerializeToString(&context);
326 s2.BindBlob(3, context.data(), context.length());
327 if (!s2.Run())
328 return false;
329 DCHECK_EQ(db_->GetLastChangeCount(), 1);
330 s2.Reset(true);
334 return transaction.Commit();
337 sql::Connection* DirectoryBackingStore::db() {
338 return db_.get();
341 bool DirectoryBackingStore::IsOpen() const {
342 return db_->is_open();
345 bool DirectoryBackingStore::Open(const base::FilePath& path) {
346 DCHECK(!db_->is_open());
347 return db_->Open(path);
350 bool DirectoryBackingStore::OpenInMemory() {
351 DCHECK(!db_->is_open());
352 return db_->OpenInMemory();
355 bool DirectoryBackingStore::InitializeTables() {
356 int page_size = 0;
357 if (IsSyncBackingDatabase32KEnabled() && GetDatabasePageSize(&page_size) &&
358 page_size == 4096) {
359 IncreasePageSizeTo32K();
361 sql::Transaction transaction(db_.get());
362 if (!transaction.Begin())
363 return false;
365 int version_on_disk = GetVersion();
367 // Upgrade from version 67. Version 67 was widely distributed as the original
368 // Bookmark Sync release. Version 68 removed unique naming.
369 if (version_on_disk == 67) {
370 if (MigrateVersion67To68())
371 version_on_disk = 68;
373 // Version 69 introduced additional datatypes.
374 if (version_on_disk == 68) {
375 if (MigrateVersion68To69())
376 version_on_disk = 69;
379 if (version_on_disk == 69) {
380 if (MigrateVersion69To70())
381 version_on_disk = 70;
384 // Version 71 changed the sync progress information to be per-datatype.
385 if (version_on_disk == 70) {
386 if (MigrateVersion70To71())
387 version_on_disk = 71;
390 // Version 72 removed extended attributes, a legacy way to do extensible
391 // key/value information, stored in their own table.
392 if (version_on_disk == 71) {
393 if (MigrateVersion71To72())
394 version_on_disk = 72;
397 // Version 73 added a field for notification state.
398 if (version_on_disk == 72) {
399 if (MigrateVersion72To73())
400 version_on_disk = 73;
403 // Version 74 added state for the autofill migration.
404 if (version_on_disk == 73) {
405 if (MigrateVersion73To74())
406 version_on_disk = 74;
409 // Version 75 migrated from int64-based timestamps to per-datatype tokens.
410 if (version_on_disk == 74) {
411 if (MigrateVersion74To75())
412 version_on_disk = 75;
415 // Version 76 removed all (5) autofill migration related columns.
416 if (version_on_disk == 75) {
417 if (MigrateVersion75To76())
418 version_on_disk = 76;
421 // Version 77 standardized all time fields to ms since the Unix
422 // epoch.
423 if (version_on_disk == 76) {
424 if (MigrateVersion76To77())
425 version_on_disk = 77;
428 // Version 78 added the column base_server_specifics to the metas table.
429 if (version_on_disk == 77) {
430 if (MigrateVersion77To78())
431 version_on_disk = 78;
434 // Version 79 migration is a one-time fix for some users in a bad state.
435 if (version_on_disk == 78) {
436 if (MigrateVersion78To79())
437 version_on_disk = 79;
440 // Version 80 migration is adding the bag_of_chips column.
441 if (version_on_disk == 79) {
442 if (MigrateVersion79To80())
443 version_on_disk = 80;
446 // Version 81 replaces the int64 server_position_in_parent_field
447 // with a blob server_ordinal_in_parent field.
448 if (version_on_disk == 80) {
449 if (MigrateVersion80To81())
450 version_on_disk = 81;
453 // Version 82 migration added transaction_version column per data type.
454 if (version_on_disk == 81) {
455 if (MigrateVersion81To82())
456 version_on_disk = 82;
459 // Version 83 migration added transaction_version column per sync entry.
460 if (version_on_disk == 82) {
461 if (MigrateVersion82To83())
462 version_on_disk = 83;
465 // Version 84 migration added deleted_metas table.
466 if (version_on_disk == 83) {
467 if (MigrateVersion83To84())
468 version_on_disk = 84;
471 // Version 85 migration removes the initial_sync_ended bits.
472 if (version_on_disk == 84) {
473 if (MigrateVersion84To85())
474 version_on_disk = 85;
477 // Version 86 migration converts bookmarks to the unique positioning system.
478 // It also introduces a new field to store a unique ID for each bookmark.
479 if (version_on_disk == 85) {
480 if (MigrateVersion85To86())
481 version_on_disk = 86;
484 // Version 87 migration adds a collection of attachment ids per sync entry.
485 if (version_on_disk == 86) {
486 if (MigrateVersion86To87())
487 version_on_disk = 87;
490 // Version 88 migration adds datatype contexts to the models table.
491 if (version_on_disk == 87) {
492 if (MigrateVersion87To88())
493 version_on_disk = 88;
496 // Version 89 migration adds server attachment metadata to the metas table.
497 if (version_on_disk == 88) {
498 if (MigrateVersion88To89())
499 version_on_disk = 89;
502 // If one of the migrations requested it, drop columns that aren't current.
503 // It's only safe to do this after migrating all the way to the current
504 // version.
505 if (version_on_disk == kCurrentDBVersion && needs_column_refresh_) {
506 if (!RefreshColumns())
507 version_on_disk = 0;
510 // A final, alternative catch-all migration to simply re-sync everything.
511 if (version_on_disk != kCurrentDBVersion) {
512 if (version_on_disk > kCurrentDBVersion)
513 return false;
515 // Fallback (re-sync everything) migration path.
516 DVLOG(1) << "Old/null sync database, version " << version_on_disk;
517 // Delete the existing database (if any), and create a fresh one.
518 DropAllTables();
519 if (!CreateTables())
520 return false;
523 sql::Statement s(db_->GetUniqueStatement(
524 "SELECT db_create_version, db_create_time FROM share_info"));
525 if (!s.Step())
526 return false;
527 string db_create_version = s.ColumnString(0);
528 int db_create_time = s.ColumnInt(1);
529 DVLOG(1) << "DB created at " << db_create_time << " by version " <<
530 db_create_version;
532 return transaction.Commit();
535 // This function drops unused columns by creating a new table that contains only
536 // the currently used columns then copying all rows from the old tables into
537 // this new one. The tables are then rearranged so the new replaces the old.
538 bool DirectoryBackingStore::RefreshColumns() {
539 DCHECK(needs_column_refresh_);
541 // Create a new table named temp_metas.
542 SafeDropTable("temp_metas");
543 if (!CreateMetasTable(true))
544 return false;
546 // Populate temp_metas from metas.
548 // At this point, the metas table may contain columns belonging to obsolete
549 // schema versions. This statement explicitly lists only the columns that
550 // belong to the current schema version, so the obsolete columns will be
551 // effectively dropped once we rename temp_metas over top of metas.
552 std::string query = "INSERT INTO temp_metas (";
553 AppendColumnList(&query);
554 query.append(") SELECT ");
555 AppendColumnList(&query);
556 query.append(" FROM metas");
557 if (!db_->Execute(query.c_str()))
558 return false;
560 // Drop metas.
561 SafeDropTable("metas");
563 // Rename temp_metas -> metas.
564 if (!db_->Execute("ALTER TABLE temp_metas RENAME TO metas"))
565 return false;
567 // Repeat the process for share_info.
568 SafeDropTable("temp_share_info");
569 if (!CreateShareInfoTable(true))
570 return false;
572 // TODO(rlarocque, 124140): Remove notification_state.
573 if (!db_->Execute(
574 "INSERT INTO temp_share_info (id, name, store_birthday, "
575 "db_create_version, db_create_time, next_id, cache_guid,"
576 "notification_state, bag_of_chips) "
577 "SELECT id, name, store_birthday, db_create_version, "
578 "db_create_time, next_id, cache_guid, notification_state, "
579 "bag_of_chips "
580 "FROM share_info"))
581 return false;
583 SafeDropTable("share_info");
584 if (!db_->Execute("ALTER TABLE temp_share_info RENAME TO share_info"))
585 return false;
587 needs_column_refresh_ = false;
588 return true;
591 bool DirectoryBackingStore::LoadEntries(Directory::MetahandlesMap* handles_map,
592 MetahandleSet* metahandles_to_purge) {
593 string select;
594 select.reserve(kUpdateStatementBufferSize);
595 select.append("SELECT ");
596 AppendColumnList(&select);
597 select.append(" FROM metas");
599 sql::Statement s(db_->GetUniqueStatement(select.c_str()));
601 while (s.Step()) {
602 scoped_ptr<EntryKernel> kernel = UnpackEntry(&s);
603 // A null kernel is evidence of external data corruption.
604 if (!kernel)
605 return false;
607 int64 handle = kernel->ref(META_HANDLE);
608 if (SafeToPurgeOnLoading(*kernel))
609 metahandles_to_purge->insert(handle);
610 else
611 (*handles_map)[handle] = kernel.release();
613 return s.Succeeded();
616 bool DirectoryBackingStore::SafeToPurgeOnLoading(
617 const EntryKernel& entry) const {
618 if (entry.ref(IS_DEL)) {
619 if (!entry.ref(IS_UNSYNCED) && !entry.ref(IS_UNAPPLIED_UPDATE))
620 return true;
621 else if (!entry.ref(ID).ServerKnows())
622 return true;
624 return false;
627 bool DirectoryBackingStore::LoadDeleteJournals(
628 JournalIndex* delete_journals) {
629 string select;
630 select.reserve(kUpdateStatementBufferSize);
631 select.append("SELECT ");
632 AppendColumnList(&select);
633 select.append(" FROM deleted_metas");
635 sql::Statement s(db_->GetUniqueStatement(select.c_str()));
637 while (s.Step()) {
638 scoped_ptr<EntryKernel> kernel = UnpackEntry(&s);
639 // A null kernel is evidence of external data corruption.
640 if (!kernel)
641 return false;
642 delete_journals->insert(kernel.release());
644 return s.Succeeded();
647 bool DirectoryBackingStore::LoadInfo(Directory::KernelLoadInfo* info) {
649 sql::Statement s(
650 db_->GetUniqueStatement(
651 "SELECT store_birthday, next_id, cache_guid, bag_of_chips "
652 "FROM share_info"));
653 if (!s.Step())
654 return false;
656 info->kernel_info.store_birthday = s.ColumnString(0);
657 info->kernel_info.next_id = s.ColumnInt64(1);
658 info->cache_guid = s.ColumnString(2);
659 s.ColumnBlobAsString(3, &(info->kernel_info.bag_of_chips));
661 // Verify there was only one row returned.
662 DCHECK(!s.Step());
663 DCHECK(s.Succeeded());
667 sql::Statement s(
668 db_->GetUniqueStatement(
669 "SELECT model_id, progress_marker, "
670 "transaction_version, context FROM models"));
672 while (s.Step()) {
673 ModelType type = ModelIdToModelTypeEnum(s.ColumnBlob(0),
674 s.ColumnByteLength(0));
675 if (type != UNSPECIFIED && type != TOP_LEVEL_FOLDER) {
676 info->kernel_info.download_progress[type].ParseFromArray(
677 s.ColumnBlob(1), s.ColumnByteLength(1));
678 info->kernel_info.transaction_version[type] = s.ColumnInt64(2);
679 info->kernel_info.datatype_context[type].ParseFromArray(
680 s.ColumnBlob(3), s.ColumnByteLength(3));
683 if (!s.Succeeded())
684 return false;
687 sql::Statement s(
688 db_->GetUniqueStatement(
689 "SELECT MAX(metahandle) FROM metas"));
690 if (!s.Step())
691 return false;
693 info->max_metahandle = s.ColumnInt64(0);
695 // Verify only one row was returned.
696 DCHECK(!s.Step());
697 DCHECK(s.Succeeded());
699 return true;
702 bool DirectoryBackingStore::SafeDropTable(const char* table_name) {
703 string query = "DROP TABLE IF EXISTS ";
704 query.append(table_name);
705 return db_->Execute(query.c_str());
708 void DirectoryBackingStore::DropAllTables() {
709 SafeDropTable("metas");
710 SafeDropTable("temp_metas");
711 SafeDropTable("share_info");
712 SafeDropTable("temp_share_info");
713 SafeDropTable("share_version");
714 SafeDropTable("extended_attributes");
715 SafeDropTable("models");
716 SafeDropTable("temp_models");
717 needs_column_refresh_ = false;
720 // static
721 ModelType DirectoryBackingStore::ModelIdToModelTypeEnum(
722 const void* data, int size) {
723 sync_pb::EntitySpecifics specifics;
724 if (!specifics.ParseFromArray(data, size))
725 return UNSPECIFIED;
726 return GetModelTypeFromSpecifics(specifics);
729 // static
730 string DirectoryBackingStore::ModelTypeEnumToModelId(ModelType model_type) {
731 sync_pb::EntitySpecifics specifics;
732 AddDefaultFieldValue(model_type, &specifics);
733 return specifics.SerializeAsString();
736 // static
737 std::string DirectoryBackingStore::GenerateCacheGUID() {
738 // Generate a GUID with 128 bits of randomness.
739 const int kGuidBytes = 128 / 8;
740 std::string guid;
741 base::Base64Encode(base::RandBytesAsString(kGuidBytes), &guid);
742 return guid;
745 bool DirectoryBackingStore::MigrateToSpecifics(
746 const char* old_columns,
747 const char* specifics_column,
748 void (*handler_function)(sql::Statement* old_value_query,
749 int old_value_column,
750 sync_pb::EntitySpecifics* mutable_new_value)) {
751 std::string query_sql = base::StringPrintf(
752 "SELECT metahandle, %s, %s FROM metas", specifics_column, old_columns);
753 std::string update_sql = base::StringPrintf(
754 "UPDATE metas SET %s = ? WHERE metahandle = ?", specifics_column);
756 sql::Statement query(db_->GetUniqueStatement(query_sql.c_str()));
757 sql::Statement update(db_->GetUniqueStatement(update_sql.c_str()));
759 while (query.Step()) {
760 int64 metahandle = query.ColumnInt64(0);
761 std::string new_value_bytes;
762 query.ColumnBlobAsString(1, &new_value_bytes);
763 sync_pb::EntitySpecifics new_value;
764 new_value.ParseFromString(new_value_bytes);
765 handler_function(&query, 2, &new_value);
766 new_value.SerializeToString(&new_value_bytes);
768 update.BindBlob(0, new_value_bytes.data(), new_value_bytes.length());
769 update.BindInt64(1, metahandle);
770 if (!update.Run())
771 return false;
772 update.Reset(true);
774 return query.Succeeded();
777 bool DirectoryBackingStore::SetVersion(int version) {
778 sql::Statement s(db_->GetCachedStatement(
779 SQL_FROM_HERE, "UPDATE share_version SET data = ?"));
780 s.BindInt(0, version);
782 return s.Run();
785 int DirectoryBackingStore::GetVersion() {
786 if (!db_->DoesTableExist("share_version"))
787 return 0;
789 sql::Statement statement(db_->GetUniqueStatement(
790 "SELECT data FROM share_version"));
791 if (statement.Step()) {
792 return statement.ColumnInt(0);
793 } else {
794 return 0;
798 bool DirectoryBackingStore::MigrateVersion67To68() {
799 // This change simply removed three columns:
800 // string NAME
801 // string UNSANITIZED_NAME
802 // string SERVER_NAME
803 // No data migration is necessary, but we should do a column refresh.
804 SetVersion(68);
805 needs_column_refresh_ = true;
806 return true;
809 bool DirectoryBackingStore::MigrateVersion69To70() {
810 // Added "unique_client_tag", renamed "singleton_tag" to unique_server_tag
811 SetVersion(70);
812 if (!db_->Execute(
813 "ALTER TABLE metas ADD COLUMN unique_server_tag varchar"))
814 return false;
815 if (!db_->Execute(
816 "ALTER TABLE metas ADD COLUMN unique_client_tag varchar"))
817 return false;
818 needs_column_refresh_ = true;
820 if (!db_->Execute(
821 "UPDATE metas SET unique_server_tag = singleton_tag"))
822 return false;
824 return true;
827 namespace {
829 // Callback passed to MigrateToSpecifics for the v68->v69 migration. See
830 // MigrateVersion68To69().
831 void EncodeBookmarkURLAndFavicon(sql::Statement* old_value_query,
832 int old_value_column,
833 sync_pb::EntitySpecifics* mutable_new_value) {
834 // Extract data from the column trio we expect.
835 bool old_is_bookmark_object = old_value_query->ColumnBool(old_value_column);
836 std::string old_url = old_value_query->ColumnString(old_value_column + 1);
837 std::string old_favicon;
838 old_value_query->ColumnBlobAsString(old_value_column + 2, &old_favicon);
839 bool old_is_dir = old_value_query->ColumnBool(old_value_column + 3);
841 if (old_is_bookmark_object) {
842 sync_pb::BookmarkSpecifics* bookmark_data =
843 mutable_new_value->mutable_bookmark();
844 if (!old_is_dir) {
845 bookmark_data->set_url(old_url);
846 bookmark_data->set_favicon(old_favicon);
851 } // namespace
853 bool DirectoryBackingStore::MigrateVersion68To69() {
854 // In Version 68, there were columns on table 'metas':
855 // string BOOKMARK_URL
856 // string SERVER_BOOKMARK_URL
857 // blob BOOKMARK_FAVICON
858 // blob SERVER_BOOKMARK_FAVICON
859 // In version 69, these columns went away in favor of storing
860 // a serialized EntrySpecifics protobuf in the columns:
861 // protobuf blob SPECIFICS
862 // protobuf blob SERVER_SPECIFICS
863 // For bookmarks, EntrySpecifics is extended as per
864 // bookmark_specifics.proto. This migration converts bookmarks from the
865 // former scheme to the latter scheme.
867 // First, add the two new columns to the schema.
868 if (!db_->Execute(
869 "ALTER TABLE metas ADD COLUMN specifics blob"))
870 return false;
871 if (!db_->Execute(
872 "ALTER TABLE metas ADD COLUMN server_specifics blob"))
873 return false;
875 // Next, fold data from the old columns into the new protobuf columns.
876 if (!MigrateToSpecifics(("is_bookmark_object, bookmark_url, "
877 "bookmark_favicon, is_dir"),
878 "specifics",
879 &EncodeBookmarkURLAndFavicon)) {
880 return false;
882 if (!MigrateToSpecifics(("server_is_bookmark_object, "
883 "server_bookmark_url, "
884 "server_bookmark_favicon, "
885 "server_is_dir"),
886 "server_specifics",
887 &EncodeBookmarkURLAndFavicon)) {
888 return false;
891 // Lastly, fix up the "Google Chrome" folder, which is of the TOP_LEVEL_FOLDER
892 // ModelType: it shouldn't have BookmarkSpecifics.
893 if (!db_->Execute(
894 "UPDATE metas SET specifics = NULL, server_specifics = NULL WHERE "
895 "singleton_tag IN ('google_chrome')"))
896 return false;
898 SetVersion(69);
899 needs_column_refresh_ = true; // Trigger deletion of old columns.
900 return true;
903 // Version 71, the columns 'initial_sync_ended' and 'last_sync_timestamp'
904 // were removed from the share_info table. They were replaced by
905 // the 'models' table, which has these values on a per-datatype basis.
906 bool DirectoryBackingStore::MigrateVersion70To71() {
907 if (!CreateV71ModelsTable())
908 return false;
910 // Move data from the old share_info columns to the new models table.
912 sql::Statement fetch(db_->GetUniqueStatement(
913 "SELECT last_sync_timestamp, initial_sync_ended FROM share_info"));
914 if (!fetch.Step())
915 return false;
917 int64 last_sync_timestamp = fetch.ColumnInt64(0);
918 bool initial_sync_ended = fetch.ColumnBool(1);
920 // Verify there were no additional rows returned.
921 DCHECK(!fetch.Step());
922 DCHECK(fetch.Succeeded());
924 sql::Statement update(db_->GetUniqueStatement(
925 "INSERT INTO models (model_id, "
926 "last_download_timestamp, initial_sync_ended) VALUES (?, ?, ?)"));
927 string bookmark_model_id = ModelTypeEnumToModelId(BOOKMARKS);
928 update.BindBlob(0, bookmark_model_id.data(), bookmark_model_id.size());
929 update.BindInt64(1, last_sync_timestamp);
930 update.BindBool(2, initial_sync_ended);
932 if (!update.Run())
933 return false;
936 // Drop the columns from the old share_info table via a temp table.
937 const bool kCreateAsTempShareInfo = true;
939 if (!CreateShareInfoTableVersion71(kCreateAsTempShareInfo))
940 return false;
941 if (!db_->Execute(
942 "INSERT INTO temp_share_info (id, name, store_birthday, "
943 "db_create_version, db_create_time, next_id, cache_guid) "
944 "SELECT id, name, store_birthday, db_create_version, "
945 "db_create_time, next_id, cache_guid FROM share_info"))
946 return false;
947 SafeDropTable("share_info");
948 if (!db_->Execute(
949 "ALTER TABLE temp_share_info RENAME TO share_info"))
950 return false;
951 SetVersion(71);
952 return true;
955 bool DirectoryBackingStore::MigrateVersion71To72() {
956 // Version 72 removed a table 'extended_attributes', whose
957 // contents didn't matter.
958 SafeDropTable("extended_attributes");
959 SetVersion(72);
960 return true;
963 bool DirectoryBackingStore::MigrateVersion72To73() {
964 // Version 73 added one column to the table 'share_info': notification_state
965 if (!db_->Execute(
966 "ALTER TABLE share_info ADD COLUMN notification_state BLOB"))
967 return false;
968 SetVersion(73);
969 return true;
972 bool DirectoryBackingStore::MigrateVersion73To74() {
973 // Version 74 added the following columns to the table 'share_info':
974 // autofill_migration_state
975 // bookmarks_added_during_autofill_migration
976 // autofill_migration_time
977 // autofill_entries_added_during_migration
978 // autofill_profiles_added_during_migration
980 if (!db_->Execute(
981 "ALTER TABLE share_info ADD COLUMN "
982 "autofill_migration_state INT default 0"))
983 return false;
985 if (!db_->Execute(
986 "ALTER TABLE share_info ADD COLUMN "
987 "bookmarks_added_during_autofill_migration "
988 "INT default 0"))
989 return false;
991 if (!db_->Execute(
992 "ALTER TABLE share_info ADD COLUMN autofill_migration_time "
993 "INT default 0"))
994 return false;
996 if (!db_->Execute(
997 "ALTER TABLE share_info ADD COLUMN "
998 "autofill_entries_added_during_migration "
999 "INT default 0"))
1000 return false;
1002 if (!db_->Execute(
1003 "ALTER TABLE share_info ADD COLUMN "
1004 "autofill_profiles_added_during_migration "
1005 "INT default 0"))
1006 return false;
1008 SetVersion(74);
1009 return true;
1012 bool DirectoryBackingStore::MigrateVersion74To75() {
1013 // In version 74, there was a table 'models':
1014 // blob model_id (entity specifics, primary key)
1015 // int last_download_timestamp
1016 // boolean initial_sync_ended
1017 // In version 75, we deprecated the integer-valued last_download_timestamp,
1018 // using insted a protobuf-valued progress_marker field:
1019 // blob progress_marker
1020 // The progress_marker values are initialized from the value of
1021 // last_download_timestamp, thereby preserving the download state.
1023 // Move aside the old table and create a new empty one at the current schema.
1024 if (!db_->Execute("ALTER TABLE models RENAME TO temp_models"))
1025 return false;
1026 if (!CreateV75ModelsTable())
1027 return false;
1029 sql::Statement query(db_->GetUniqueStatement(
1030 "SELECT model_id, last_download_timestamp, initial_sync_ended "
1031 "FROM temp_models"));
1033 sql::Statement update(db_->GetUniqueStatement(
1034 "INSERT INTO models (model_id, "
1035 "progress_marker, initial_sync_ended) VALUES (?, ?, ?)"));
1037 while (query.Step()) {
1038 ModelType type = ModelIdToModelTypeEnum(query.ColumnBlob(0),
1039 query.ColumnByteLength(0));
1040 if (type != UNSPECIFIED) {
1041 // Set the |timestamp_token_for_migration| on a new
1042 // DataTypeProgressMarker, using the old value of last_download_timestamp.
1043 // The server will turn this into a real token on our behalf the next
1044 // time we check for updates.
1045 sync_pb::DataTypeProgressMarker progress_marker;
1046 progress_marker.set_data_type_id(
1047 GetSpecificsFieldNumberFromModelType(type));
1048 progress_marker.set_timestamp_token_for_migration(query.ColumnInt64(1));
1049 std::string progress_blob;
1050 progress_marker.SerializeToString(&progress_blob);
1052 update.BindBlob(0, query.ColumnBlob(0), query.ColumnByteLength(0));
1053 update.BindBlob(1, progress_blob.data(), progress_blob.length());
1054 update.BindBool(2, query.ColumnBool(2));
1055 if (!update.Run())
1056 return false;
1057 update.Reset(true);
1060 if (!query.Succeeded())
1061 return false;
1063 // Drop the old table.
1064 SafeDropTable("temp_models");
1066 SetVersion(75);
1067 return true;
1070 bool DirectoryBackingStore::MigrateVersion75To76() {
1071 // This change removed five columns:
1072 // autofill_migration_state
1073 // bookmarks_added_during_autofill_migration
1074 // autofill_migration_time
1075 // autofill_entries_added_during_migration
1076 // autofill_profiles_added_during_migration
1077 // No data migration is necessary, but we should do a column refresh.
1078 SetVersion(76);
1079 needs_column_refresh_ = true;
1080 return true;
1083 bool DirectoryBackingStore::MigrateVersion76To77() {
1084 // This change changes the format of stored timestamps to ms since
1085 // the Unix epoch.
1086 #if defined(OS_WIN)
1087 // On Windows, we used to store timestamps in FILETIME format (100s of
1088 // ns since Jan 1, 1601). Magic numbers taken from
1089 // http://stackoverflow.com/questions/5398557/
1090 // java-library-for-dealing-with-win32-filetime
1091 // .
1092 #define TO_UNIX_TIME_MS(x) #x " = " #x " / 10000 - 11644473600000"
1093 #else
1094 // On other platforms, we used to store timestamps in time_t format (s
1095 // since the Unix epoch).
1096 #define TO_UNIX_TIME_MS(x) #x " = " #x " * 1000"
1097 #endif
1098 sql::Statement update_timestamps(db_->GetUniqueStatement(
1099 "UPDATE metas SET "
1100 TO_UNIX_TIME_MS(mtime) ", "
1101 TO_UNIX_TIME_MS(server_mtime) ", "
1102 TO_UNIX_TIME_MS(ctime) ", "
1103 TO_UNIX_TIME_MS(server_ctime)));
1104 #undef TO_UNIX_TIME_MS
1105 if (!update_timestamps.Run())
1106 return false;
1107 SetVersion(77);
1108 return true;
1111 bool DirectoryBackingStore::MigrateVersion77To78() {
1112 // Version 78 added one column to table 'metas': base_server_specifics.
1113 if (!db_->Execute(
1114 "ALTER TABLE metas ADD COLUMN base_server_specifics BLOB")) {
1115 return false;
1117 SetVersion(78);
1118 return true;
1121 bool DirectoryBackingStore::MigrateVersion78To79() {
1122 // Some users are stuck with a DB that causes them to reuse existing IDs. We
1123 // perform this one-time fixup on all users to help the few that are stuck.
1124 // See crbug.com/142987 for details.
1125 if (!db_->Execute(
1126 "UPDATE share_info SET next_id = next_id - 65536")) {
1127 return false;
1129 SetVersion(79);
1130 return true;
1133 bool DirectoryBackingStore::MigrateVersion79To80() {
1134 if (!db_->Execute(
1135 "ALTER TABLE share_info ADD COLUMN bag_of_chips BLOB"))
1136 return false;
1137 sql::Statement update(db_->GetUniqueStatement(
1138 "UPDATE share_info SET bag_of_chips = ?"));
1139 // An empty message is serialized to an empty string.
1140 update.BindBlob(0, NULL, 0);
1141 if (!update.Run())
1142 return false;
1143 SetVersion(80);
1144 return true;
1147 bool DirectoryBackingStore::MigrateVersion80To81() {
1148 if(!db_->Execute(
1149 "ALTER TABLE metas ADD COLUMN server_ordinal_in_parent BLOB"))
1150 return false;
1152 sql::Statement get_positions(db_->GetUniqueStatement(
1153 "SELECT metahandle, server_position_in_parent FROM metas"));
1155 sql::Statement put_ordinals(db_->GetUniqueStatement(
1156 "UPDATE metas SET server_ordinal_in_parent = ?"
1157 "WHERE metahandle = ?"));
1159 while(get_positions.Step()) {
1160 int64 metahandle = get_positions.ColumnInt64(0);
1161 int64 position = get_positions.ColumnInt64(1);
1163 const std::string& ordinal = Int64ToNodeOrdinal(position).ToInternalValue();
1164 put_ordinals.BindBlob(0, ordinal.data(), ordinal.length());
1165 put_ordinals.BindInt64(1, metahandle);
1167 if(!put_ordinals.Run())
1168 return false;
1169 put_ordinals.Reset(true);
1172 SetVersion(81);
1173 needs_column_refresh_ = true;
1174 return true;
1177 bool DirectoryBackingStore::MigrateVersion81To82() {
1178 if (!db_->Execute(
1179 "ALTER TABLE models ADD COLUMN transaction_version BIGINT default 0"))
1180 return false;
1181 sql::Statement update(db_->GetUniqueStatement(
1182 "UPDATE models SET transaction_version = 0"));
1183 if (!update.Run())
1184 return false;
1185 SetVersion(82);
1186 return true;
1189 bool DirectoryBackingStore::MigrateVersion82To83() {
1190 // Version 83 added transaction_version on sync node.
1191 if (!db_->Execute(
1192 "ALTER TABLE metas ADD COLUMN transaction_version BIGINT default 0"))
1193 return false;
1194 sql::Statement update(db_->GetUniqueStatement(
1195 "UPDATE metas SET transaction_version = 0"));
1196 if (!update.Run())
1197 return false;
1198 SetVersion(83);
1199 return true;
1202 bool DirectoryBackingStore::MigrateVersion83To84() {
1203 // Version 84 added deleted_metas table to store deleted metas until we know
1204 // for sure that the deletions are persisted in native models.
1205 string query = "CREATE TABLE deleted_metas ";
1206 query.append(ComposeCreateTableColumnSpecs());
1207 if (!db_->Execute(query.c_str()))
1208 return false;
1209 SetVersion(84);
1210 return true;
1213 bool DirectoryBackingStore::MigrateVersion84To85() {
1214 // Version 85 removes the initial_sync_ended flag.
1215 if (!db_->Execute("ALTER TABLE models RENAME TO temp_models"))
1216 return false;
1217 if (!CreateV81ModelsTable())
1218 return false;
1219 if (!db_->Execute("INSERT INTO models SELECT "
1220 "model_id, progress_marker, transaction_version "
1221 "FROM temp_models")) {
1222 return false;
1224 SafeDropTable("temp_models");
1226 SetVersion(85);
1227 return true;
1230 bool DirectoryBackingStore::MigrateVersion85To86() {
1231 // Version 86 removes both server ordinals and local NEXT_ID, PREV_ID and
1232 // SERVER_{POSITION,ORDINAL}_IN_PARENT and replaces them with UNIQUE_POSITION
1233 // and SERVER_UNIQUE_POSITION.
1234 if (!db_->Execute("ALTER TABLE metas ADD COLUMN "
1235 "server_unique_position BLOB")) {
1236 return false;
1238 if (!db_->Execute("ALTER TABLE metas ADD COLUMN "
1239 "unique_position BLOB")) {
1240 return false;
1242 if (!db_->Execute("ALTER TABLE metas ADD COLUMN "
1243 "unique_bookmark_tag VARCHAR")) {
1244 return false;
1247 // Fetch the cache_guid from the DB, because we don't otherwise have access to
1248 // it from here.
1249 sql::Statement get_cache_guid(db_->GetUniqueStatement(
1250 "SELECT cache_guid FROM share_info"));
1251 if (!get_cache_guid.Step()) {
1252 return false;
1254 std::string cache_guid = get_cache_guid.ColumnString(0);
1255 DCHECK(!get_cache_guid.Step());
1256 DCHECK(get_cache_guid.Succeeded());
1258 sql::Statement get(db_->GetUniqueStatement(
1259 "SELECT "
1260 " metahandle, "
1261 " id, "
1262 " specifics, "
1263 " is_dir, "
1264 " unique_server_tag, "
1265 " server_ordinal_in_parent "
1266 "FROM metas"));
1268 // Note that we set both the local and server position based on the server
1269 // position. We wll lose any unsynced local position changes. Unfortunately,
1270 // there's nothing we can do to avoid that. The NEXT_ID / PREV_ID values
1271 // can't be translated into a UNIQUE_POSTION in a reliable way.
1272 sql::Statement put(db_->GetCachedStatement(
1273 SQL_FROM_HERE,
1274 "UPDATE metas SET"
1275 " server_unique_position = ?,"
1276 " unique_position = ?,"
1277 " unique_bookmark_tag = ?"
1278 "WHERE metahandle = ?"));
1280 while (get.Step()) {
1281 int64 metahandle = get.ColumnInt64(0);
1283 std::string id_string;
1284 get.ColumnBlobAsString(1, &id_string);
1286 sync_pb::EntitySpecifics specifics;
1287 specifics.ParseFromArray(
1288 get.ColumnBlob(2), get.ColumnByteLength(2));
1290 bool is_dir = get.ColumnBool(3);
1292 std::string server_unique_tag = get.ColumnString(4);
1294 std::string ordinal_string;
1295 get.ColumnBlobAsString(5, &ordinal_string);
1296 NodeOrdinal ordinal(ordinal_string);
1299 std::string unique_bookmark_tag;
1301 // We only maintain positions for bookmarks that are not server-defined
1302 // top-level folders.
1303 UniquePosition position;
1304 if (GetModelTypeFromSpecifics(specifics) == BOOKMARKS
1305 && !(is_dir && !server_unique_tag.empty())) {
1306 if (id_string.at(0) == 'c') {
1307 // We found an uncommitted item. This is rare, but fortunate. This
1308 // means we can set the bookmark tag according to the originator client
1309 // item ID and originator cache guid, because (unlike the other case) we
1310 // know that this client is the originator.
1311 unique_bookmark_tag = syncable::GenerateSyncableBookmarkHash(
1312 cache_guid,
1313 id_string.substr(1));
1314 } else {
1315 // If we've already committed the item, then we don't know who the
1316 // originator was. We do not have access to the originator client item
1317 // ID and originator cache guid at this point.
1319 // We will base our hash entirely on the server ID instead. This is
1320 // incorrect, but at least all clients that undergo this migration step
1321 // will be incorrect in the same way.
1323 // To get everyone back into a synced state, we will update the bookmark
1324 // tag according to the originator_cache_guid and originator_item_id
1325 // when we see updates for this item. That should ensure that commonly
1326 // modified items will end up with the proper tag values eventually.
1327 unique_bookmark_tag = syncable::GenerateSyncableBookmarkHash(
1328 std::string(), // cache_guid left intentionally blank.
1329 id_string.substr(1));
1332 int64 int_position = NodeOrdinalToInt64(ordinal);
1333 position = UniquePosition::FromInt64(int_position, unique_bookmark_tag);
1334 } else {
1335 // Leave bookmark_tag and position at their default (invalid) values.
1338 std::string position_blob;
1339 position.SerializeToString(&position_blob);
1340 put.BindBlob(0, position_blob.data(), position_blob.length());
1341 put.BindBlob(1, position_blob.data(), position_blob.length());
1342 put.BindBlob(2, unique_bookmark_tag.data(), unique_bookmark_tag.length());
1343 put.BindInt64(3, metahandle);
1345 if (!put.Run())
1346 return false;
1347 put.Reset(true);
1350 SetVersion(86);
1351 needs_column_refresh_ = true;
1352 return true;
1355 bool DirectoryBackingStore::MigrateVersion86To87() {
1356 // Version 87 adds AttachmentMetadata proto.
1357 if (!db_->Execute(
1358 "ALTER TABLE metas ADD COLUMN "
1359 "attachment_metadata BLOB")) {
1360 return false;
1362 SetVersion(87);
1363 needs_column_refresh_ = true;
1364 return true;
1367 bool DirectoryBackingStore::MigrateVersion87To88() {
1368 // Version 88 adds the datatype context to the models table.
1369 if (!db_->Execute("ALTER TABLE models ADD COLUMN context blob"))
1370 return false;
1372 SetVersion(88);
1373 return true;
1376 bool DirectoryBackingStore::MigrateVersion88To89() {
1377 // Version 89 adds server_attachment_metadata.
1378 if (!db_->Execute(
1379 "ALTER TABLE metas ADD COLUMN "
1380 "server_attachment_metadata BLOB")) {
1381 return false;
1383 SetVersion(89);
1384 needs_column_refresh_ = true;
1385 return true;
1388 bool DirectoryBackingStore::CreateTables() {
1389 DVLOG(1) << "First run, creating tables";
1390 // Create two little tables share_version and share_info
1391 if (!db_->Execute(
1392 "CREATE TABLE share_version ("
1393 "id VARCHAR(128) primary key, data INT)")) {
1394 return false;
1398 sql::Statement s(db_->GetUniqueStatement(
1399 "INSERT INTO share_version VALUES(?, ?)"));
1400 s.BindString(0, dir_name_);
1401 s.BindInt(1, kCurrentDBVersion);
1403 if (!s.Run())
1404 return false;
1407 const bool kCreateAsTempShareInfo = false;
1408 if (!CreateShareInfoTable(kCreateAsTempShareInfo)) {
1409 return false;
1413 sql::Statement s(db_->GetUniqueStatement(
1414 "INSERT INTO share_info VALUES"
1415 "(?, " // id
1416 "?, " // name
1417 "?, " // store_birthday
1418 "?, " // db_create_version
1419 "?, " // db_create_time
1420 "-2, " // next_id
1421 "?, " // cache_guid
1422 // TODO(rlarocque, 124140): Remove notification_state field.
1423 "?, " // notification_state
1424 "?);")); // bag_of_chips
1425 s.BindString(0, dir_name_); // id
1426 s.BindString(1, dir_name_); // name
1427 s.BindString(2, std::string()); // store_birthday
1428 // TODO(akalin): Remove this unused db_create_version field. (Or
1429 // actually use it for something.) http://crbug.com/118356
1430 s.BindString(3, "Unknown"); // db_create_version
1431 s.BindInt(4, static_cast<int32>(time(0))); // db_create_time
1432 s.BindString(5, GenerateCacheGUID()); // cache_guid
1433 // TODO(rlarocque, 124140): Remove this unused notification-state field.
1434 s.BindBlob(6, NULL, 0); // notification_state
1435 s.BindBlob(7, NULL, 0); // bag_of_chips
1436 if (!s.Run())
1437 return false;
1440 if (!CreateModelsTable())
1441 return false;
1443 // Create the big metas table.
1444 if (!CreateMetasTable(false))
1445 return false;
1448 // Insert the entry for the root into the metas table.
1449 const int64 now = TimeToProtoTime(base::Time::Now());
1450 sql::Statement s(db_->GetUniqueStatement(
1451 "INSERT INTO metas "
1452 "( id, metahandle, is_dir, ctime, mtime ) "
1453 "VALUES ( \"r\", 1, 1, ?, ? )"));
1454 s.BindInt64(0, now);
1455 s.BindInt64(1, now);
1457 if (!s.Run())
1458 return false;
1461 return true;
1464 bool DirectoryBackingStore::CreateMetasTable(bool is_temporary) {
1465 string query = "CREATE TABLE ";
1466 query.append(is_temporary ? "temp_metas" : "metas");
1467 query.append(ComposeCreateTableColumnSpecs());
1468 if (!db_->Execute(query.c_str()))
1469 return false;
1471 // Create a deleted_metas table to save copies of deleted metas until the
1472 // deletions are persisted. For simplicity, don't try to migrate existing
1473 // data because it's rarely used.
1474 SafeDropTable("deleted_metas");
1475 query = "CREATE TABLE deleted_metas ";
1476 query.append(ComposeCreateTableColumnSpecs());
1477 return db_->Execute(query.c_str());
1480 bool DirectoryBackingStore::CreateV71ModelsTable() {
1481 // This is an old schema for the Models table, used from versions 71 to 74.
1482 return db_->Execute(
1483 "CREATE TABLE models ("
1484 "model_id BLOB primary key, "
1485 "last_download_timestamp INT, "
1486 // Gets set if the syncer ever gets updates from the
1487 // server and the server returns 0. Lets us detect the
1488 // end of the initial sync.
1489 "initial_sync_ended BOOLEAN default 0)");
1492 bool DirectoryBackingStore::CreateV75ModelsTable() {
1493 // This is an old schema for the Models table, used from versions 75 to 80.
1494 return db_->Execute(
1495 "CREATE TABLE models ("
1496 "model_id BLOB primary key, "
1497 "progress_marker BLOB, "
1498 // Gets set if the syncer ever gets updates from the
1499 // server and the server returns 0. Lets us detect the
1500 // end of the initial sync.
1501 "initial_sync_ended BOOLEAN default 0)");
1504 bool DirectoryBackingStore::CreateV81ModelsTable() {
1505 // This is an old schema for the Models table, used from versions 81 to 87.
1506 return db_->Execute(
1507 "CREATE TABLE models ("
1508 "model_id BLOB primary key, "
1509 "progress_marker BLOB, "
1510 // Gets set if the syncer ever gets updates from the
1511 // server and the server returns 0. Lets us detect the
1512 // end of the initial sync.
1513 "transaction_version BIGINT default 0)");
1516 bool DirectoryBackingStore::CreateModelsTable() {
1517 // This is the current schema for the Models table, from version 88
1518 // onward. If you change the schema, you'll probably want to double-check
1519 // the use of this function in the v84-v85 migration.
1520 return db_->Execute(
1521 "CREATE TABLE models ("
1522 "model_id BLOB primary key, "
1523 "progress_marker BLOB, "
1524 // Gets set if the syncer ever gets updates from the
1525 // server and the server returns 0. Lets us detect the
1526 // end of the initial sync.
1527 "transaction_version BIGINT default 0,"
1528 "context BLOB)");
1531 bool DirectoryBackingStore::CreateShareInfoTable(bool is_temporary) {
1532 const char* name = is_temporary ? "temp_share_info" : "share_info";
1533 string query = "CREATE TABLE ";
1534 query.append(name);
1535 // This is the current schema for the ShareInfo table, from version 76
1536 // onward.
1537 query.append(" ("
1538 "id TEXT primary key, "
1539 "name TEXT, "
1540 "store_birthday TEXT, "
1541 "db_create_version TEXT, "
1542 "db_create_time INT, "
1543 "next_id INT default -2, "
1544 "cache_guid TEXT, "
1545 // TODO(rlarocque, 124140): Remove notification_state field.
1546 "notification_state BLOB, "
1547 "bag_of_chips BLOB"
1548 ")");
1549 return db_->Execute(query.c_str());
1552 bool DirectoryBackingStore::CreateShareInfoTableVersion71(
1553 bool is_temporary) {
1554 const char* name = is_temporary ? "temp_share_info" : "share_info";
1555 string query = "CREATE TABLE ";
1556 query.append(name);
1557 // This is the schema for the ShareInfo table used from versions 71 to 72.
1558 query.append(" ("
1559 "id TEXT primary key, "
1560 "name TEXT, "
1561 "store_birthday TEXT, "
1562 "db_create_version TEXT, "
1563 "db_create_time INT, "
1564 "next_id INT default -2, "
1565 "cache_guid TEXT )");
1566 return db_->Execute(query.c_str());
1569 // This function checks to see if the given list of Metahandles has any nodes
1570 // whose PARENT_ID values refer to ID values that do not actually exist.
1571 // Returns true on success.
1572 bool DirectoryBackingStore::VerifyReferenceIntegrity(
1573 const Directory::MetahandlesMap* handles_map) {
1574 TRACE_EVENT0("sync", "SyncDatabaseIntegrityCheck");
1575 using namespace syncable;
1576 typedef base::hash_set<std::string> IdsSet;
1578 IdsSet ids_set;
1579 bool is_ok = true;
1581 for (Directory::MetahandlesMap::const_iterator it = handles_map->begin();
1582 it != handles_map->end(); ++it) {
1583 EntryKernel* entry = it->second;
1584 bool is_duplicate_id = !(ids_set.insert(entry->ref(ID).value()).second);
1585 is_ok = is_ok && !is_duplicate_id;
1588 IdsSet::iterator end = ids_set.end();
1589 for (Directory::MetahandlesMap::const_iterator it = handles_map->begin();
1590 it != handles_map->end(); ++it) {
1591 EntryKernel* entry = it->second;
1592 if (!entry->ref(PARENT_ID).IsNull()) {
1593 bool parent_exists = (ids_set.find(entry->ref(PARENT_ID).value()) != end);
1594 if (!parent_exists) {
1595 return false;
1599 return is_ok;
1602 void DirectoryBackingStore::PrepareSaveEntryStatement(
1603 EntryTable table, sql::Statement* save_statement) {
1604 if (save_statement->is_valid())
1605 return;
1607 string query;
1608 query.reserve(kUpdateStatementBufferSize);
1609 switch (table) {
1610 case METAS_TABLE:
1611 query.append("INSERT OR REPLACE INTO metas ");
1612 break;
1613 case DELETE_JOURNAL_TABLE:
1614 query.append("INSERT OR REPLACE INTO deleted_metas ");
1615 break;
1618 string values;
1619 values.reserve(kUpdateStatementBufferSize);
1620 values.append(" VALUES ");
1621 const char* separator = "( ";
1622 int i = 0;
1623 for (i = BEGIN_FIELDS; i < FIELD_COUNT; ++i) {
1624 query.append(separator);
1625 values.append(separator);
1626 separator = ", ";
1627 query.append(ColumnName(i));
1628 values.append("?");
1630 query.append(" ) ");
1631 values.append(" )");
1632 query.append(values);
1633 save_statement->Assign(db_->GetUniqueStatement(
1634 base::StringPrintf(query.c_str(), "metas").c_str()));
1637 // Get page size for the database.
1638 bool DirectoryBackingStore::GetDatabasePageSize(int* page_size) {
1639 sql::Statement s(db_->GetUniqueStatement("PRAGMA page_size"));
1640 if (!s.Step())
1641 return false;
1642 *page_size = s.ColumnInt(0);
1643 return true;
1646 bool DirectoryBackingStore::IncreasePageSizeTo32K() {
1647 if (!db_->Execute("PRAGMA page_size=32768;") || !Vacuum()) {
1648 return false;
1650 return true;
1653 bool DirectoryBackingStore::Vacuum() {
1654 DCHECK_EQ(db_->transaction_nesting(), 0);
1655 if (!db_->Execute("VACUUM;")) {
1656 return false;
1658 return true;
1661 bool DirectoryBackingStore::needs_column_refresh() const {
1662 return needs_column_refresh_;
1665 void DirectoryBackingStore::ResetAndCreateConnection() {
1666 db_.reset(new sql::Connection());
1667 db_->set_histogram_tag("SyncDirectory");
1668 db_->set_exclusive_locking();
1669 db_->set_cache_size(32);
1670 db_->set_page_size(database_page_size_);
1671 if (!catastrophic_error_handler_.is_null())
1672 SetCatastrophicErrorHandler(catastrophic_error_handler_);
1675 void DirectoryBackingStore::SetCatastrophicErrorHandler(
1676 const base::Closure& catastrophic_error_handler) {
1677 DCHECK(CalledOnValidThread());
1678 DCHECK(!catastrophic_error_handler.is_null());
1679 catastrophic_error_handler_ = catastrophic_error_handler;
1680 sql::Connection::ErrorCallback error_callback =
1681 base::Bind(&OnSqliteError, catastrophic_error_handler_);
1682 db_->set_error_callback(error_callback);
1685 } // namespace syncable
1686 } // namespace syncer