1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "sync/syncable/directory_backing_store.h"
7 #include "build/build_config.h"
11 #include "base/base64.h"
12 #include "base/debug/trace_event.h"
13 #include "base/logging.h"
14 #include "base/rand_util.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/time/time.h"
17 #include "sql/connection.h"
18 #include "sql/statement.h"
19 #include "sql/transaction.h"
20 #include "sync/internal_api/public/base/node_ordinal.h"
21 #include "sync/protocol/bookmark_specifics.pb.h"
22 #include "sync/protocol/sync.pb.h"
23 #include "sync/syncable/syncable-inl.h"
24 #include "sync/syncable/syncable_columns.h"
25 #include "sync/syncable/syncable_util.h"
26 #include "sync/util/time.h"
33 // This just has to be big enough to hold an UPDATE or INSERT statement that
34 // modifies all the columns in the entry table.
35 static const string::size_type kUpdateStatementBufferSize
= 2048;
37 // Increment this version whenever updating DB tables.
38 const int32 kCurrentDBVersion
= 86;
40 // Iterate over the fields of |entry| and bind each to |statement| for
41 // updating. Returns the number of args bound.
42 void BindFields(const EntryKernel
& entry
,
43 sql::Statement
* statement
) {
46 for (i
= BEGIN_FIELDS
; i
< INT64_FIELDS_END
; ++i
) {
47 statement
->BindInt64(index
++, entry
.ref(static_cast<Int64Field
>(i
)));
49 for ( ; i
< TIME_FIELDS_END
; ++i
) {
50 statement
->BindInt64(index
++,
52 entry
.ref(static_cast<TimeField
>(i
))));
54 for ( ; i
< ID_FIELDS_END
; ++i
) {
55 statement
->BindString(index
++, entry
.ref(static_cast<IdField
>(i
)).s_
);
57 for ( ; i
< BIT_FIELDS_END
; ++i
) {
58 statement
->BindInt(index
++, entry
.ref(static_cast<BitField
>(i
)));
60 for ( ; i
< STRING_FIELDS_END
; ++i
) {
61 statement
->BindString(index
++, entry
.ref(static_cast<StringField
>(i
)));
63 for ( ; i
< PROTO_FIELDS_END
; ++i
) {
65 entry
.ref(static_cast<ProtoField
>(i
)).SerializeToString(&temp
);
66 statement
->BindBlob(index
++, temp
.data(), temp
.length());
68 for ( ; i
< UNIQUE_POSITION_FIELDS_END
; ++i
) {
70 entry
.ref(static_cast<UniquePositionField
>(i
)).SerializeToString(&temp
);
71 statement
->BindBlob(index
++, temp
.data(), temp
.length());
75 // The caller owns the returned EntryKernel*. Assumes the statement currently
76 // points to a valid row in the metas table. Returns NULL to indicate that
77 // it detected a corruption in the data on unpacking.
78 scoped_ptr
<EntryKernel
> UnpackEntry(sql::Statement
* statement
) {
79 scoped_ptr
<EntryKernel
> kernel(new EntryKernel());
80 DCHECK_EQ(statement
->ColumnCount(), static_cast<int>(FIELD_COUNT
));
82 for (i
= BEGIN_FIELDS
; i
< INT64_FIELDS_END
; ++i
) {
83 kernel
->put(static_cast<Int64Field
>(i
), statement
->ColumnInt64(i
));
85 for ( ; i
< TIME_FIELDS_END
; ++i
) {
86 kernel
->put(static_cast<TimeField
>(i
),
87 ProtoTimeToTime(statement
->ColumnInt64(i
)));
89 for ( ; i
< ID_FIELDS_END
; ++i
) {
90 kernel
->mutable_ref(static_cast<IdField
>(i
)).s_
=
91 statement
->ColumnString(i
);
93 for ( ; i
< BIT_FIELDS_END
; ++i
) {
94 kernel
->put(static_cast<BitField
>(i
), (0 != statement
->ColumnInt(i
)));
96 for ( ; i
< STRING_FIELDS_END
; ++i
) {
97 kernel
->put(static_cast<StringField
>(i
),
98 statement
->ColumnString(i
));
100 for ( ; i
< PROTO_FIELDS_END
; ++i
) {
101 kernel
->mutable_ref(static_cast<ProtoField
>(i
)).ParseFromArray(
102 statement
->ColumnBlob(i
), statement
->ColumnByteLength(i
));
104 for ( ; i
< UNIQUE_POSITION_FIELDS_END
; ++i
) {
106 statement
->ColumnBlobAsString(i
, &temp
);
108 sync_pb::UniquePosition proto
;
109 if (!proto
.ParseFromString(temp
)) {
110 DVLOG(1) << "Unpacked invalid position. Assuming the DB is corrupt";
111 return scoped_ptr
<EntryKernel
>();
114 kernel
->mutable_ref(static_cast<UniquePositionField
>(i
)) =
115 UniquePosition::FromProto(proto
);
117 return kernel
.Pass();
122 string
ComposeCreateTableColumnSpecs() {
123 const ColumnSpec
* begin
= g_metas_columns
;
124 const ColumnSpec
* end
= g_metas_columns
+ arraysize(g_metas_columns
);
126 query
.reserve(kUpdateStatementBufferSize
);
127 char separator
= '(';
128 for (const ColumnSpec
* column
= begin
; column
!= end
; ++column
) {
129 query
.push_back(separator
);
131 query
.append(column
->name
);
132 query
.push_back(' ');
133 query
.append(column
->spec
);
135 query
.push_back(')');
139 void AppendColumnList(std::string
* output
) {
140 const char* joiner
= " ";
141 // Be explicit in SELECT order to match up with UnpackEntry.
142 for (int i
= BEGIN_FIELDS
; i
< FIELD_COUNT
; ++i
) {
143 output
->append(joiner
);
144 output
->append(ColumnName(i
));
151 ///////////////////////////////////////////////////////////////////////////////
152 // DirectoryBackingStore implementation.
154 DirectoryBackingStore::DirectoryBackingStore(const string
& dir_name
)
155 : db_(new sql::Connection()),
157 needs_column_refresh_(false) {
158 db_
->set_histogram_tag("SyncDirectory");
159 db_
->set_page_size(4096);
160 db_
->set_cache_size(32);
163 DirectoryBackingStore::DirectoryBackingStore(const string
& dir_name
,
167 needs_column_refresh_(false) {
170 DirectoryBackingStore::~DirectoryBackingStore() {
173 bool DirectoryBackingStore::DeleteEntries(EntryTable from
,
174 const MetahandleSet
& handles
) {
178 sql::Statement statement
;
179 // Call GetCachedStatement() separately to get different statements for
183 statement
.Assign(db_
->GetCachedStatement(
184 SQL_FROM_HERE
, "DELETE FROM metas WHERE metahandle = ?"));
186 case DELETE_JOURNAL_TABLE
:
187 statement
.Assign(db_
->GetCachedStatement(
188 SQL_FROM_HERE
, "DELETE FROM deleted_metas WHERE metahandle = ?"));
192 for (MetahandleSet::const_iterator i
= handles
.begin(); i
!= handles
.end();
194 statement
.BindInt64(0, *i
);
195 if (!statement
.Run())
197 statement
.Reset(true);
202 bool DirectoryBackingStore::SaveChanges(
203 const Directory::SaveChangesSnapshot
& snapshot
) {
204 DCHECK(CalledOnValidThread());
205 DCHECK(db_
->is_open());
207 // Back out early if there is nothing to write.
209 (Directory::KERNEL_SHARE_INFO_DIRTY
== snapshot
.kernel_info_status
);
210 if (snapshot
.dirty_metas
.empty() && snapshot
.metahandles_to_purge
.empty() &&
211 snapshot
.delete_journals
.empty() &&
212 snapshot
.delete_journals_to_purge
.empty() && !save_info
) {
216 sql::Transaction
transaction(db_
.get());
217 if (!transaction
.Begin())
220 PrepareSaveEntryStatement(METAS_TABLE
, &save_meta_statment_
);
221 for (EntryKernelSet::const_iterator i
= snapshot
.dirty_metas
.begin();
222 i
!= snapshot
.dirty_metas
.end(); ++i
) {
223 DCHECK((*i
)->is_dirty());
224 if (!SaveEntryToDB(&save_meta_statment_
, **i
))
228 if (!DeleteEntries(METAS_TABLE
, snapshot
.metahandles_to_purge
))
231 PrepareSaveEntryStatement(DELETE_JOURNAL_TABLE
,
232 &save_delete_journal_statment_
);
233 for (EntryKernelSet::const_iterator i
= snapshot
.delete_journals
.begin();
234 i
!= snapshot
.delete_journals
.end(); ++i
) {
235 if (!SaveEntryToDB(&save_delete_journal_statment_
, **i
))
239 if (!DeleteEntries(DELETE_JOURNAL_TABLE
, snapshot
.delete_journals_to_purge
))
243 const Directory::PersistedKernelInfo
& info
= snapshot
.kernel_info
;
244 sql::Statement
s1(db_
->GetCachedStatement(
247 "SET store_birthday = ?, "
249 "bag_of_chips = ?"));
250 s1
.BindString(0, info
.store_birthday
);
251 s1
.BindInt64(1, info
.next_id
);
252 s1
.BindBlob(2, info
.bag_of_chips
.data(), info
.bag_of_chips
.size());
256 DCHECK_EQ(db_
->GetLastChangeCount(), 1);
258 sql::Statement
s2(db_
->GetCachedStatement(
261 "INTO models (model_id, progress_marker, transaction_version) "
262 "VALUES (?, ?, ?)"));
264 ModelTypeSet protocol_types
= ProtocolTypes();
265 for (ModelTypeSet::Iterator iter
= protocol_types
.First(); iter
.Good();
267 ModelType type
= iter
.Get();
268 // We persist not ModelType but rather a protobuf-derived ID.
269 string model_id
= ModelTypeEnumToModelId(type
);
270 string progress_marker
;
271 info
.download_progress
[type
].SerializeToString(&progress_marker
);
272 s2
.BindBlob(0, model_id
.data(), model_id
.length());
273 s2
.BindBlob(1, progress_marker
.data(), progress_marker
.length());
274 s2
.BindInt64(2, info
.transaction_version
[type
]);
277 DCHECK_EQ(db_
->GetLastChangeCount(), 1);
282 return transaction
.Commit();
285 bool DirectoryBackingStore::InitializeTables() {
286 sql::Transaction
transaction(db_
.get());
287 if (!transaction
.Begin())
290 int version_on_disk
= GetVersion();
292 // Upgrade from version 67. Version 67 was widely distributed as the original
293 // Bookmark Sync release. Version 68 removed unique naming.
294 if (version_on_disk
== 67) {
295 if (MigrateVersion67To68())
296 version_on_disk
= 68;
298 // Version 69 introduced additional datatypes.
299 if (version_on_disk
== 68) {
300 if (MigrateVersion68To69())
301 version_on_disk
= 69;
304 if (version_on_disk
== 69) {
305 if (MigrateVersion69To70())
306 version_on_disk
= 70;
309 // Version 71 changed the sync progress information to be per-datatype.
310 if (version_on_disk
== 70) {
311 if (MigrateVersion70To71())
312 version_on_disk
= 71;
315 // Version 72 removed extended attributes, a legacy way to do extensible
316 // key/value information, stored in their own table.
317 if (version_on_disk
== 71) {
318 if (MigrateVersion71To72())
319 version_on_disk
= 72;
322 // Version 73 added a field for notification state.
323 if (version_on_disk
== 72) {
324 if (MigrateVersion72To73())
325 version_on_disk
= 73;
328 // Version 74 added state for the autofill migration.
329 if (version_on_disk
== 73) {
330 if (MigrateVersion73To74())
331 version_on_disk
= 74;
334 // Version 75 migrated from int64-based timestamps to per-datatype tokens.
335 if (version_on_disk
== 74) {
336 if (MigrateVersion74To75())
337 version_on_disk
= 75;
340 // Version 76 removed all (5) autofill migration related columns.
341 if (version_on_disk
== 75) {
342 if (MigrateVersion75To76())
343 version_on_disk
= 76;
346 // Version 77 standardized all time fields to ms since the Unix
348 if (version_on_disk
== 76) {
349 if (MigrateVersion76To77())
350 version_on_disk
= 77;
353 // Version 78 added the column base_server_specifics to the metas table.
354 if (version_on_disk
== 77) {
355 if (MigrateVersion77To78())
356 version_on_disk
= 78;
359 // Version 79 migration is a one-time fix for some users in a bad state.
360 if (version_on_disk
== 78) {
361 if (MigrateVersion78To79())
362 version_on_disk
= 79;
365 // Version 80 migration is adding the bag_of_chips column.
366 if (version_on_disk
== 79) {
367 if (MigrateVersion79To80())
368 version_on_disk
= 80;
371 // Version 81 replaces the int64 server_position_in_parent_field
372 // with a blob server_ordinal_in_parent field.
373 if (version_on_disk
== 80) {
374 if (MigrateVersion80To81())
375 version_on_disk
= 81;
378 // Version 82 migration added transaction_version column per data type.
379 if (version_on_disk
== 81) {
380 if (MigrateVersion81To82())
381 version_on_disk
= 82;
384 // Version 83 migration added transaction_version column per sync entry.
385 if (version_on_disk
== 82) {
386 if (MigrateVersion82To83())
387 version_on_disk
= 83;
390 // Version 84 migration added deleted_metas table.
391 if (version_on_disk
== 83) {
392 if (MigrateVersion83To84())
393 version_on_disk
= 84;
396 // Version 85 migration removes the initial_sync_ended bits.
397 if (version_on_disk
== 84) {
398 if (MigrateVersion84To85())
399 version_on_disk
= 85;
402 // Version 86 migration converts bookmarks to the unique positioning system.
403 // It also introduces a new field to store a unique ID for each bookmark.
404 if (version_on_disk
== 85) {
405 if (MigrateVersion85To86())
406 version_on_disk
= 86;
409 // If one of the migrations requested it, drop columns that aren't current.
410 // It's only safe to do this after migrating all the way to the current
412 if (version_on_disk
== kCurrentDBVersion
&& needs_column_refresh_
) {
413 if (!RefreshColumns())
417 // A final, alternative catch-all migration to simply re-sync everything.
418 if (version_on_disk
!= kCurrentDBVersion
) {
419 if (version_on_disk
> kCurrentDBVersion
)
422 // Fallback (re-sync everything) migration path.
423 DVLOG(1) << "Old/null sync database, version " << version_on_disk
;
424 // Delete the existing database (if any), and create a fresh one.
430 sql::Statement
s(db_
->GetUniqueStatement(
431 "SELECT db_create_version, db_create_time FROM share_info"));
434 string db_create_version
= s
.ColumnString(0);
435 int db_create_time
= s
.ColumnInt(1);
436 DVLOG(1) << "DB created at " << db_create_time
<< " by version " <<
439 return transaction
.Commit();
442 // This function drops unused columns by creating a new table that contains only
443 // the currently used columns then copying all rows from the old tables into
444 // this new one. The tables are then rearranged so the new replaces the old.
445 bool DirectoryBackingStore::RefreshColumns() {
446 DCHECK(needs_column_refresh_
);
448 // Create a new table named temp_metas.
449 SafeDropTable("temp_metas");
450 if (!CreateMetasTable(true))
453 // Populate temp_metas from metas.
455 // At this point, the metas table may contain columns belonging to obsolete
456 // schema versions. This statement explicitly lists only the columns that
457 // belong to the current schema version, so the obsolete columns will be
458 // effectively dropped once we rename temp_metas over top of metas.
459 std::string query
= "INSERT INTO temp_metas (";
460 AppendColumnList(&query
);
461 query
.append(") SELECT ");
462 AppendColumnList(&query
);
463 query
.append(" FROM metas");
464 if (!db_
->Execute(query
.c_str()))
468 SafeDropTable("metas");
470 // Rename temp_metas -> metas.
471 if (!db_
->Execute("ALTER TABLE temp_metas RENAME TO metas"))
474 // Repeat the process for share_info.
475 SafeDropTable("temp_share_info");
476 if (!CreateShareInfoTable(true))
479 // TODO(rlarocque, 124140): Remove notification_state.
481 "INSERT INTO temp_share_info (id, name, store_birthday, "
482 "db_create_version, db_create_time, next_id, cache_guid,"
483 "notification_state, bag_of_chips) "
484 "SELECT id, name, store_birthday, db_create_version, "
485 "db_create_time, next_id, cache_guid, notification_state, "
490 SafeDropTable("share_info");
491 if (!db_
->Execute("ALTER TABLE temp_share_info RENAME TO share_info"))
494 needs_column_refresh_
= false;
498 bool DirectoryBackingStore::LoadEntries(
499 Directory::MetahandlesMap
* handles_map
) {
501 select
.reserve(kUpdateStatementBufferSize
);
502 select
.append("SELECT ");
503 AppendColumnList(&select
);
504 select
.append(" FROM metas");
506 sql::Statement
s(db_
->GetUniqueStatement(select
.c_str()));
509 scoped_ptr
<EntryKernel
> kernel
= UnpackEntry(&s
);
510 // A null kernel is evidence of external data corruption.
514 int64 handle
= kernel
->ref(META_HANDLE
);
515 (*handles_map
)[handle
] = kernel
.release();
517 return s
.Succeeded();
520 bool DirectoryBackingStore::LoadDeleteJournals(
521 JournalIndex
* delete_journals
) {
523 select
.reserve(kUpdateStatementBufferSize
);
524 select
.append("SELECT ");
525 AppendColumnList(&select
);
526 select
.append(" FROM deleted_metas");
528 sql::Statement
s(db_
->GetUniqueStatement(select
.c_str()));
531 scoped_ptr
<EntryKernel
> kernel
= UnpackEntry(&s
);
532 // A null kernel is evidence of external data corruption.
535 delete_journals
->insert(kernel
.release());
537 return s
.Succeeded();
540 bool DirectoryBackingStore::LoadInfo(Directory::KernelLoadInfo
* info
) {
543 db_
->GetUniqueStatement(
544 "SELECT store_birthday, next_id, cache_guid, bag_of_chips "
549 info
->kernel_info
.store_birthday
= s
.ColumnString(0);
550 info
->kernel_info
.next_id
= s
.ColumnInt64(1);
551 info
->cache_guid
= s
.ColumnString(2);
552 s
.ColumnBlobAsString(3, &(info
->kernel_info
.bag_of_chips
));
554 // Verify there was only one row returned.
556 DCHECK(s
.Succeeded());
561 db_
->GetUniqueStatement(
562 "SELECT model_id, progress_marker, "
563 "transaction_version FROM models"));
566 ModelType type
= ModelIdToModelTypeEnum(s
.ColumnBlob(0),
567 s
.ColumnByteLength(0));
568 if (type
!= UNSPECIFIED
&& type
!= TOP_LEVEL_FOLDER
) {
569 info
->kernel_info
.download_progress
[type
].ParseFromArray(
570 s
.ColumnBlob(1), s
.ColumnByteLength(1));
571 info
->kernel_info
.transaction_version
[type
] = s
.ColumnInt64(2);
579 db_
->GetUniqueStatement(
580 "SELECT MAX(metahandle) FROM metas"));
584 info
->max_metahandle
= s
.ColumnInt64(0);
586 // Verify only one row was returned.
588 DCHECK(s
.Succeeded());
594 bool DirectoryBackingStore::SaveEntryToDB(sql::Statement
* save_statement
,
595 const EntryKernel
& entry
) {
596 save_statement
->Reset(true);
597 BindFields(entry
, save_statement
);
598 return save_statement
->Run();
601 bool DirectoryBackingStore::DropDeletedEntries() {
602 if (!db_
->Execute("DELETE FROM metas "
604 "AND is_unsynced < 1 "
605 "AND is_unapplied_update < 1")) {
608 if (!db_
->Execute("DELETE FROM metas "
610 "AND id LIKE 'c%'")) {
616 bool DirectoryBackingStore::SafeDropTable(const char* table_name
) {
617 string query
= "DROP TABLE IF EXISTS ";
618 query
.append(table_name
);
619 return db_
->Execute(query
.c_str());
622 void DirectoryBackingStore::DropAllTables() {
623 SafeDropTable("metas");
624 SafeDropTable("temp_metas");
625 SafeDropTable("share_info");
626 SafeDropTable("temp_share_info");
627 SafeDropTable("share_version");
628 SafeDropTable("extended_attributes");
629 SafeDropTable("models");
630 SafeDropTable("temp_models");
631 needs_column_refresh_
= false;
635 ModelType
DirectoryBackingStore::ModelIdToModelTypeEnum(
636 const void* data
, int size
) {
637 sync_pb::EntitySpecifics specifics
;
638 if (!specifics
.ParseFromArray(data
, size
))
640 return GetModelTypeFromSpecifics(specifics
);
644 string
DirectoryBackingStore::ModelTypeEnumToModelId(ModelType model_type
) {
645 sync_pb::EntitySpecifics specifics
;
646 AddDefaultFieldValue(model_type
, &specifics
);
647 return specifics
.SerializeAsString();
651 std::string
DirectoryBackingStore::GenerateCacheGUID() {
652 // Generate a GUID with 128 bits of randomness.
653 const int kGuidBytes
= 128 / 8;
655 base::Base64Encode(base::RandBytesAsString(kGuidBytes
), &guid
);
659 bool DirectoryBackingStore::MigrateToSpecifics(
660 const char* old_columns
,
661 const char* specifics_column
,
662 void (*handler_function
)(sql::Statement
* old_value_query
,
663 int old_value_column
,
664 sync_pb::EntitySpecifics
* mutable_new_value
)) {
665 std::string query_sql
= base::StringPrintf(
666 "SELECT metahandle, %s, %s FROM metas", specifics_column
, old_columns
);
667 std::string update_sql
= base::StringPrintf(
668 "UPDATE metas SET %s = ? WHERE metahandle = ?", specifics_column
);
670 sql::Statement
query(db_
->GetUniqueStatement(query_sql
.c_str()));
671 sql::Statement
update(db_
->GetUniqueStatement(update_sql
.c_str()));
673 while (query
.Step()) {
674 int64 metahandle
= query
.ColumnInt64(0);
675 std::string new_value_bytes
;
676 query
.ColumnBlobAsString(1, &new_value_bytes
);
677 sync_pb::EntitySpecifics new_value
;
678 new_value
.ParseFromString(new_value_bytes
);
679 handler_function(&query
, 2, &new_value
);
680 new_value
.SerializeToString(&new_value_bytes
);
682 update
.BindBlob(0, new_value_bytes
.data(), new_value_bytes
.length());
683 update
.BindInt64(1, metahandle
);
688 return query
.Succeeded();
691 bool DirectoryBackingStore::SetVersion(int version
) {
692 sql::Statement
s(db_
->GetCachedStatement(
693 SQL_FROM_HERE
, "UPDATE share_version SET data = ?"));
694 s
.BindInt(0, version
);
699 int DirectoryBackingStore::GetVersion() {
700 if (!db_
->DoesTableExist("share_version"))
703 sql::Statement
statement(db_
->GetUniqueStatement(
704 "SELECT data FROM share_version"));
705 if (statement
.Step()) {
706 return statement
.ColumnInt(0);
712 bool DirectoryBackingStore::MigrateVersion67To68() {
713 // This change simply removed three columns:
715 // string UNSANITIZED_NAME
716 // string SERVER_NAME
717 // No data migration is necessary, but we should do a column refresh.
719 needs_column_refresh_
= true;
723 bool DirectoryBackingStore::MigrateVersion69To70() {
724 // Added "unique_client_tag", renamed "singleton_tag" to unique_server_tag
727 "ALTER TABLE metas ADD COLUMN unique_server_tag varchar"))
730 "ALTER TABLE metas ADD COLUMN unique_client_tag varchar"))
732 needs_column_refresh_
= true;
735 "UPDATE metas SET unique_server_tag = singleton_tag"))
743 // Callback passed to MigrateToSpecifics for the v68->v69 migration. See
744 // MigrateVersion68To69().
745 void EncodeBookmarkURLAndFavicon(sql::Statement
* old_value_query
,
746 int old_value_column
,
747 sync_pb::EntitySpecifics
* mutable_new_value
) {
748 // Extract data from the column trio we expect.
749 bool old_is_bookmark_object
= old_value_query
->ColumnBool(old_value_column
);
750 std::string old_url
= old_value_query
->ColumnString(old_value_column
+ 1);
751 std::string old_favicon
;
752 old_value_query
->ColumnBlobAsString(old_value_column
+ 2, &old_favicon
);
753 bool old_is_dir
= old_value_query
->ColumnBool(old_value_column
+ 3);
755 if (old_is_bookmark_object
) {
756 sync_pb::BookmarkSpecifics
* bookmark_data
=
757 mutable_new_value
->mutable_bookmark();
759 bookmark_data
->set_url(old_url
);
760 bookmark_data
->set_favicon(old_favicon
);
767 bool DirectoryBackingStore::MigrateVersion68To69() {
768 // In Version 68, there were columns on table 'metas':
769 // string BOOKMARK_URL
770 // string SERVER_BOOKMARK_URL
771 // blob BOOKMARK_FAVICON
772 // blob SERVER_BOOKMARK_FAVICON
773 // In version 69, these columns went away in favor of storing
774 // a serialized EntrySpecifics protobuf in the columns:
775 // protobuf blob SPECIFICS
776 // protobuf blob SERVER_SPECIFICS
777 // For bookmarks, EntrySpecifics is extended as per
778 // bookmark_specifics.proto. This migration converts bookmarks from the
779 // former scheme to the latter scheme.
781 // First, add the two new columns to the schema.
783 "ALTER TABLE metas ADD COLUMN specifics blob"))
786 "ALTER TABLE metas ADD COLUMN server_specifics blob"))
789 // Next, fold data from the old columns into the new protobuf columns.
790 if (!MigrateToSpecifics(("is_bookmark_object, bookmark_url, "
791 "bookmark_favicon, is_dir"),
793 &EncodeBookmarkURLAndFavicon
)) {
796 if (!MigrateToSpecifics(("server_is_bookmark_object, "
797 "server_bookmark_url, "
798 "server_bookmark_favicon, "
801 &EncodeBookmarkURLAndFavicon
)) {
805 // Lastly, fix up the "Google Chrome" folder, which is of the TOP_LEVEL_FOLDER
806 // ModelType: it shouldn't have BookmarkSpecifics.
808 "UPDATE metas SET specifics = NULL, server_specifics = NULL WHERE "
809 "singleton_tag IN ('google_chrome')"))
813 needs_column_refresh_
= true; // Trigger deletion of old columns.
817 // Version 71, the columns 'initial_sync_ended' and 'last_sync_timestamp'
818 // were removed from the share_info table. They were replaced by
819 // the 'models' table, which has these values on a per-datatype basis.
820 bool DirectoryBackingStore::MigrateVersion70To71() {
821 if (!CreateV71ModelsTable())
824 // Move data from the old share_info columns to the new models table.
826 sql::Statement
fetch(db_
->GetUniqueStatement(
827 "SELECT last_sync_timestamp, initial_sync_ended FROM share_info"));
831 int64 last_sync_timestamp
= fetch
.ColumnInt64(0);
832 bool initial_sync_ended
= fetch
.ColumnBool(1);
834 // Verify there were no additional rows returned.
835 DCHECK(!fetch
.Step());
836 DCHECK(fetch
.Succeeded());
838 sql::Statement
update(db_
->GetUniqueStatement(
839 "INSERT INTO models (model_id, "
840 "last_download_timestamp, initial_sync_ended) VALUES (?, ?, ?)"));
841 string bookmark_model_id
= ModelTypeEnumToModelId(BOOKMARKS
);
842 update
.BindBlob(0, bookmark_model_id
.data(), bookmark_model_id
.size());
843 update
.BindInt64(1, last_sync_timestamp
);
844 update
.BindBool(2, initial_sync_ended
);
850 // Drop the columns from the old share_info table via a temp table.
851 const bool kCreateAsTempShareInfo
= true;
853 if (!CreateShareInfoTableVersion71(kCreateAsTempShareInfo
))
856 "INSERT INTO temp_share_info (id, name, store_birthday, "
857 "db_create_version, db_create_time, next_id, cache_guid) "
858 "SELECT id, name, store_birthday, db_create_version, "
859 "db_create_time, next_id, cache_guid FROM share_info"))
861 SafeDropTable("share_info");
863 "ALTER TABLE temp_share_info RENAME TO share_info"))
869 bool DirectoryBackingStore::MigrateVersion71To72() {
870 // Version 72 removed a table 'extended_attributes', whose
871 // contents didn't matter.
872 SafeDropTable("extended_attributes");
877 bool DirectoryBackingStore::MigrateVersion72To73() {
878 // Version 73 added one column to the table 'share_info': notification_state
880 "ALTER TABLE share_info ADD COLUMN notification_state BLOB"))
886 bool DirectoryBackingStore::MigrateVersion73To74() {
887 // Version 74 added the following columns to the table 'share_info':
888 // autofill_migration_state
889 // bookmarks_added_during_autofill_migration
890 // autofill_migration_time
891 // autofill_entries_added_during_migration
892 // autofill_profiles_added_during_migration
895 "ALTER TABLE share_info ADD COLUMN "
896 "autofill_migration_state INT default 0"))
900 "ALTER TABLE share_info ADD COLUMN "
901 "bookmarks_added_during_autofill_migration "
906 "ALTER TABLE share_info ADD COLUMN autofill_migration_time "
911 "ALTER TABLE share_info ADD COLUMN "
912 "autofill_entries_added_during_migration "
917 "ALTER TABLE share_info ADD COLUMN "
918 "autofill_profiles_added_during_migration "
926 bool DirectoryBackingStore::MigrateVersion74To75() {
927 // In version 74, there was a table 'models':
928 // blob model_id (entity specifics, primary key)
929 // int last_download_timestamp
930 // boolean initial_sync_ended
931 // In version 75, we deprecated the integer-valued last_download_timestamp,
932 // using insted a protobuf-valued progress_marker field:
933 // blob progress_marker
934 // The progress_marker values are initialized from the value of
935 // last_download_timestamp, thereby preserving the download state.
937 // Move aside the old table and create a new empty one at the current schema.
938 if (!db_
->Execute("ALTER TABLE models RENAME TO temp_models"))
940 if (!CreateV75ModelsTable())
943 sql::Statement
query(db_
->GetUniqueStatement(
944 "SELECT model_id, last_download_timestamp, initial_sync_ended "
945 "FROM temp_models"));
947 sql::Statement
update(db_
->GetUniqueStatement(
948 "INSERT INTO models (model_id, "
949 "progress_marker, initial_sync_ended) VALUES (?, ?, ?)"));
951 while (query
.Step()) {
952 ModelType type
= ModelIdToModelTypeEnum(query
.ColumnBlob(0),
953 query
.ColumnByteLength(0));
954 if (type
!= UNSPECIFIED
) {
955 // Set the |timestamp_token_for_migration| on a new
956 // DataTypeProgressMarker, using the old value of last_download_timestamp.
957 // The server will turn this into a real token on our behalf the next
958 // time we check for updates.
959 sync_pb::DataTypeProgressMarker progress_marker
;
960 progress_marker
.set_data_type_id(
961 GetSpecificsFieldNumberFromModelType(type
));
962 progress_marker
.set_timestamp_token_for_migration(query
.ColumnInt64(1));
963 std::string progress_blob
;
964 progress_marker
.SerializeToString(&progress_blob
);
966 update
.BindBlob(0, query
.ColumnBlob(0), query
.ColumnByteLength(0));
967 update
.BindBlob(1, progress_blob
.data(), progress_blob
.length());
968 update
.BindBool(2, query
.ColumnBool(2));
974 if (!query
.Succeeded())
977 // Drop the old table.
978 SafeDropTable("temp_models");
984 bool DirectoryBackingStore::MigrateVersion75To76() {
985 // This change removed five columns:
986 // autofill_migration_state
987 // bookmarks_added_during_autofill_migration
988 // autofill_migration_time
989 // autofill_entries_added_during_migration
990 // autofill_profiles_added_during_migration
991 // No data migration is necessary, but we should do a column refresh.
993 needs_column_refresh_
= true;
997 bool DirectoryBackingStore::MigrateVersion76To77() {
998 // This change changes the format of stored timestamps to ms since
1001 // On Windows, we used to store timestamps in FILETIME format (100s of
1002 // ns since Jan 1, 1601). Magic numbers taken from
1003 // http://stackoverflow.com/questions/5398557/
1004 // java-library-for-dealing-with-win32-filetime
1006 #define TO_UNIX_TIME_MS(x) #x " = " #x " / 10000 - 11644473600000"
1008 // On other platforms, we used to store timestamps in time_t format (s
1009 // since the Unix epoch).
1010 #define TO_UNIX_TIME_MS(x) #x " = " #x " * 1000"
1012 sql::Statement
update_timestamps(db_
->GetUniqueStatement(
1014 TO_UNIX_TIME_MS(mtime
) ", "
1015 TO_UNIX_TIME_MS(server_mtime
) ", "
1016 TO_UNIX_TIME_MS(ctime
) ", "
1017 TO_UNIX_TIME_MS(server_ctime
)));
1018 #undef TO_UNIX_TIME_MS
1019 if (!update_timestamps
.Run())
1025 bool DirectoryBackingStore::MigrateVersion77To78() {
1026 // Version 78 added one column to table 'metas': base_server_specifics.
1028 "ALTER TABLE metas ADD COLUMN base_server_specifics BLOB")) {
1035 bool DirectoryBackingStore::MigrateVersion78To79() {
1036 // Some users are stuck with a DB that causes them to reuse existing IDs. We
1037 // perform this one-time fixup on all users to help the few that are stuck.
1038 // See crbug.com/142987 for details.
1040 "UPDATE share_info SET next_id = next_id - 65536")) {
1047 bool DirectoryBackingStore::MigrateVersion79To80() {
1049 "ALTER TABLE share_info ADD COLUMN bag_of_chips BLOB"))
1051 sql::Statement
update(db_
->GetUniqueStatement(
1052 "UPDATE share_info SET bag_of_chips = ?"));
1053 // An empty message is serialized to an empty string.
1054 update
.BindBlob(0, NULL
, 0);
1061 bool DirectoryBackingStore::MigrateVersion80To81() {
1063 "ALTER TABLE metas ADD COLUMN server_ordinal_in_parent BLOB"))
1066 sql::Statement
get_positions(db_
->GetUniqueStatement(
1067 "SELECT metahandle, server_position_in_parent FROM metas"));
1069 sql::Statement
put_ordinals(db_
->GetUniqueStatement(
1070 "UPDATE metas SET server_ordinal_in_parent = ?"
1071 "WHERE metahandle = ?"));
1073 while(get_positions
.Step()) {
1074 int64 metahandle
= get_positions
.ColumnInt64(0);
1075 int64 position
= get_positions
.ColumnInt64(1);
1077 const std::string
& ordinal
= Int64ToNodeOrdinal(position
).ToInternalValue();
1078 put_ordinals
.BindBlob(0, ordinal
.data(), ordinal
.length());
1079 put_ordinals
.BindInt64(1, metahandle
);
1081 if(!put_ordinals
.Run())
1083 put_ordinals
.Reset(true);
1087 needs_column_refresh_
= true;
1091 bool DirectoryBackingStore::MigrateVersion81To82() {
1093 "ALTER TABLE models ADD COLUMN transaction_version BIGINT default 0"))
1095 sql::Statement
update(db_
->GetUniqueStatement(
1096 "UPDATE models SET transaction_version = 0"));
1103 bool DirectoryBackingStore::MigrateVersion82To83() {
1104 // Version 83 added transaction_version on sync node.
1106 "ALTER TABLE metas ADD COLUMN transaction_version BIGINT default 0"))
1108 sql::Statement
update(db_
->GetUniqueStatement(
1109 "UPDATE metas SET transaction_version = 0"));
1116 bool DirectoryBackingStore::MigrateVersion83To84() {
1117 // Version 84 added deleted_metas table to store deleted metas until we know
1118 // for sure that the deletions are persisted in native models.
1119 string query
= "CREATE TABLE deleted_metas ";
1120 query
.append(ComposeCreateTableColumnSpecs());
1121 if (!db_
->Execute(query
.c_str()))
1127 bool DirectoryBackingStore::MigrateVersion84To85() {
1128 // Version 85 removes the initial_sync_ended flag.
1129 if (!db_
->Execute("ALTER TABLE models RENAME TO temp_models"))
1131 if (!CreateModelsTable())
1133 if (!db_
->Execute("INSERT INTO models SELECT "
1134 "model_id, progress_marker, transaction_version "
1135 "FROM temp_models")) {
1138 SafeDropTable("temp_models");
1144 bool DirectoryBackingStore::MigrateVersion85To86() {
1145 // Version 86 removes both server ordinals and local NEXT_ID, PREV_ID and
1146 // SERVER_{POSITION,ORDINAL}_IN_PARENT and replaces them with UNIQUE_POSITION
1147 // and SERVER_UNIQUE_POSITION.
1148 if (!db_
->Execute("ALTER TABLE metas ADD COLUMN "
1149 "server_unique_position BLOB")) {
1152 if (!db_
->Execute("ALTER TABLE metas ADD COLUMN "
1153 "unique_position BLOB")) {
1156 if (!db_
->Execute("ALTER TABLE metas ADD COLUMN "
1157 "unique_bookmark_tag VARCHAR")) {
1161 // Fetch the cache_guid from the DB, because we don't otherwise have access to
1163 sql::Statement
get_cache_guid(db_
->GetUniqueStatement(
1164 "SELECT cache_guid FROM share_info"));
1165 if (!get_cache_guid
.Step()) {
1168 std::string cache_guid
= get_cache_guid
.ColumnString(0);
1169 DCHECK(!get_cache_guid
.Step());
1170 DCHECK(get_cache_guid
.Succeeded());
1172 sql::Statement
get(db_
->GetUniqueStatement(
1178 " unique_server_tag, "
1179 " server_ordinal_in_parent "
1182 // Note that we set both the local and server position based on the server
1183 // position. We wll lose any unsynced local position changes. Unfortunately,
1184 // there's nothing we can do to avoid that. The NEXT_ID / PREV_ID values
1185 // can't be translated into a UNIQUE_POSTION in a reliable way.
1186 sql::Statement
put(db_
->GetCachedStatement(
1189 " server_unique_position = ?,"
1190 " unique_position = ?,"
1191 " unique_bookmark_tag = ?"
1192 "WHERE metahandle = ?"));
1194 while (get
.Step()) {
1195 int64 metahandle
= get
.ColumnInt64(0);
1197 std::string id_string
;
1198 get
.ColumnBlobAsString(1, &id_string
);
1200 sync_pb::EntitySpecifics specifics
;
1201 specifics
.ParseFromArray(
1202 get
.ColumnBlob(2), get
.ColumnByteLength(2));
1204 bool is_dir
= get
.ColumnBool(3);
1206 std::string server_unique_tag
= get
.ColumnString(4);
1208 std::string ordinal_string
;
1209 get
.ColumnBlobAsString(5, &ordinal_string
);
1210 NodeOrdinal
ordinal(ordinal_string
);
1213 std::string unique_bookmark_tag
;
1215 // We only maintain positions for bookmarks that are not server-defined
1216 // top-level folders.
1217 UniquePosition position
;
1218 if (GetModelTypeFromSpecifics(specifics
) == BOOKMARKS
1219 && !(is_dir
&& !server_unique_tag
.empty())) {
1220 if (id_string
.at(0) == 'c') {
1221 // We found an uncommitted item. This is rare, but fortunate. This
1222 // means we can set the bookmark tag according to the originator client
1223 // item ID and originator cache guid, because (unlike the other case) we
1224 // know that this client is the originator.
1225 unique_bookmark_tag
= syncable::GenerateSyncableBookmarkHash(
1227 id_string
.substr(1));
1229 // If we've already committed the item, then we don't know who the
1230 // originator was. We do not have access to the originator client item
1231 // ID and originator cache guid at this point.
1233 // We will base our hash entirely on the server ID instead. This is
1234 // incorrect, but at least all clients that undergo this migration step
1235 // will be incorrect in the same way.
1237 // To get everyone back into a synced state, we will update the bookmark
1238 // tag according to the originator_cache_guid and originator_item_id
1239 // when we see updates for this item. That should ensure that commonly
1240 // modified items will end up with the proper tag values eventually.
1241 unique_bookmark_tag
= syncable::GenerateSyncableBookmarkHash(
1242 std::string(), // cache_guid left intentionally blank.
1243 id_string
.substr(1));
1246 int64 int_position
= NodeOrdinalToInt64(ordinal
);
1247 position
= UniquePosition::FromInt64(int_position
, unique_bookmark_tag
);
1249 // Leave bookmark_tag and position at their default (invalid) values.
1252 std::string position_blob
;
1253 position
.SerializeToString(&position_blob
);
1254 put
.BindBlob(0, position_blob
.data(), position_blob
.length());
1255 put
.BindBlob(1, position_blob
.data(), position_blob
.length());
1256 put
.BindBlob(2, unique_bookmark_tag
.data(), unique_bookmark_tag
.length());
1257 put
.BindInt64(3, metahandle
);
1265 needs_column_refresh_
= true;
1269 bool DirectoryBackingStore::CreateTables() {
1270 DVLOG(1) << "First run, creating tables";
1271 // Create two little tables share_version and share_info
1273 "CREATE TABLE share_version ("
1274 "id VARCHAR(128) primary key, data INT)")) {
1279 sql::Statement
s(db_
->GetUniqueStatement(
1280 "INSERT INTO share_version VALUES(?, ?)"));
1281 s
.BindString(0, dir_name_
);
1282 s
.BindInt(1, kCurrentDBVersion
);
1288 const bool kCreateAsTempShareInfo
= false;
1289 if (!CreateShareInfoTable(kCreateAsTempShareInfo
)) {
1294 sql::Statement
s(db_
->GetUniqueStatement(
1295 "INSERT INTO share_info VALUES"
1298 "?, " // store_birthday
1299 "?, " // db_create_version
1300 "?, " // db_create_time
1303 // TODO(rlarocque, 124140): Remove notification_state field.
1304 "?, " // notification_state
1305 "?);")); // bag_of_chips
1306 s
.BindString(0, dir_name_
); // id
1307 s
.BindString(1, dir_name_
); // name
1308 s
.BindString(2, std::string()); // store_birthday
1309 // TODO(akalin): Remove this unused db_create_version field. (Or
1310 // actually use it for something.) http://crbug.com/118356
1311 s
.BindString(3, "Unknown"); // db_create_version
1312 s
.BindInt(4, static_cast<int32
>(time(0))); // db_create_time
1313 s
.BindString(5, GenerateCacheGUID()); // cache_guid
1314 // TODO(rlarocque, 124140): Remove this unused notification-state field.
1315 s
.BindBlob(6, NULL
, 0); // notification_state
1316 s
.BindBlob(7, NULL
, 0); // bag_of_chips
1321 if (!CreateModelsTable())
1324 // Create the big metas table.
1325 if (!CreateMetasTable(false))
1329 // Insert the entry for the root into the metas table.
1330 const int64 now
= TimeToProtoTime(base::Time::Now());
1331 sql::Statement
s(db_
->GetUniqueStatement(
1332 "INSERT INTO metas "
1333 "( id, metahandle, is_dir, ctime, mtime ) "
1334 "VALUES ( \"r\", 1, 1, ?, ? )"));
1335 s
.BindInt64(0, now
);
1336 s
.BindInt64(1, now
);
1345 bool DirectoryBackingStore::CreateMetasTable(bool is_temporary
) {
1346 string query
= "CREATE TABLE ";
1347 query
.append(is_temporary
? "temp_metas" : "metas");
1348 query
.append(ComposeCreateTableColumnSpecs());
1349 if (!db_
->Execute(query
.c_str()))
1352 // Create a deleted_metas table to save copies of deleted metas until the
1353 // deletions are persisted. For simplicity, don't try to migrate existing
1354 // data because it's rarely used.
1355 SafeDropTable("deleted_metas");
1356 query
= "CREATE TABLE deleted_metas ";
1357 query
.append(ComposeCreateTableColumnSpecs());
1358 return db_
->Execute(query
.c_str());
1361 bool DirectoryBackingStore::CreateV71ModelsTable() {
1362 // This is an old schema for the Models table, used from versions 71 to 74.
1363 return db_
->Execute(
1364 "CREATE TABLE models ("
1365 "model_id BLOB primary key, "
1366 "last_download_timestamp INT, "
1367 // Gets set if the syncer ever gets updates from the
1368 // server and the server returns 0. Lets us detect the
1369 // end of the initial sync.
1370 "initial_sync_ended BOOLEAN default 0)");
1373 bool DirectoryBackingStore::CreateV75ModelsTable() {
1374 // This is an old schema for the Models table, used from versions 75 to 80.
1375 return db_
->Execute(
1376 "CREATE TABLE models ("
1377 "model_id BLOB primary key, "
1378 "progress_marker BLOB, "
1379 // Gets set if the syncer ever gets updates from the
1380 // server and the server returns 0. Lets us detect the
1381 // end of the initial sync.
1382 "initial_sync_ended BOOLEAN default 0)");
1385 bool DirectoryBackingStore::CreateModelsTable() {
1386 // This is the current schema for the Models table, from version 81
1387 // onward. If you change the schema, you'll probably want to double-check
1388 // the use of this function in the v84-v85 migration.
1389 return db_
->Execute(
1390 "CREATE TABLE models ("
1391 "model_id BLOB primary key, "
1392 "progress_marker BLOB, "
1393 // Gets set if the syncer ever gets updates from the
1394 // server and the server returns 0. Lets us detect the
1395 // end of the initial sync.
1396 "transaction_version BIGINT default 0)");
1399 bool DirectoryBackingStore::CreateShareInfoTable(bool is_temporary
) {
1400 const char* name
= is_temporary
? "temp_share_info" : "share_info";
1401 string query
= "CREATE TABLE ";
1403 // This is the current schema for the ShareInfo table, from version 76
1406 "id TEXT primary key, "
1408 "store_birthday TEXT, "
1409 "db_create_version TEXT, "
1410 "db_create_time INT, "
1411 "next_id INT default -2, "
1413 // TODO(rlarocque, 124140): Remove notification_state field.
1414 "notification_state BLOB, "
1417 return db_
->Execute(query
.c_str());
1420 bool DirectoryBackingStore::CreateShareInfoTableVersion71(
1421 bool is_temporary
) {
1422 const char* name
= is_temporary
? "temp_share_info" : "share_info";
1423 string query
= "CREATE TABLE ";
1425 // This is the schema for the ShareInfo table used from versions 71 to 72.
1427 "id TEXT primary key, "
1429 "store_birthday TEXT, "
1430 "db_create_version TEXT, "
1431 "db_create_time INT, "
1432 "next_id INT default -2, "
1433 "cache_guid TEXT )");
1434 return db_
->Execute(query
.c_str());
1437 // This function checks to see if the given list of Metahandles has any nodes
1438 // whose PARENT_ID values refer to ID values that do not actually exist.
1439 // Returns true on success.
1440 bool DirectoryBackingStore::VerifyReferenceIntegrity(
1441 const Directory::MetahandlesMap
* handles_map
) {
1442 TRACE_EVENT0("sync", "SyncDatabaseIntegrityCheck");
1443 using namespace syncable
;
1444 typedef base::hash_set
<std::string
> IdsSet
;
1449 for (Directory::MetahandlesMap::const_iterator it
= handles_map
->begin();
1450 it
!= handles_map
->end(); ++it
) {
1451 EntryKernel
* entry
= it
->second
;
1452 bool is_duplicate_id
= !(ids_set
.insert(entry
->ref(ID
).value()).second
);
1453 is_ok
= is_ok
&& !is_duplicate_id
;
1456 IdsSet::iterator end
= ids_set
.end();
1457 for (Directory::MetahandlesMap::const_iterator it
= handles_map
->begin();
1458 it
!= handles_map
->end(); ++it
) {
1459 EntryKernel
* entry
= it
->second
;
1460 bool parent_exists
= (ids_set
.find(entry
->ref(PARENT_ID
).value()) != end
);
1461 if (!parent_exists
) {
1468 void DirectoryBackingStore::PrepareSaveEntryStatement(
1469 EntryTable table
, sql::Statement
* save_statement
) {
1470 if (save_statement
->is_valid())
1474 query
.reserve(kUpdateStatementBufferSize
);
1477 query
.append("INSERT OR REPLACE INTO metas ");
1479 case DELETE_JOURNAL_TABLE
:
1480 query
.append("INSERT OR REPLACE INTO deleted_metas ");
1485 values
.reserve(kUpdateStatementBufferSize
);
1486 values
.append(" VALUES ");
1487 const char* separator
= "( ";
1489 for (i
= BEGIN_FIELDS
; i
< FIELD_COUNT
; ++i
) {
1490 query
.append(separator
);
1491 values
.append(separator
);
1493 query
.append(ColumnName(i
));
1496 query
.append(" ) ");
1497 values
.append(" )");
1498 query
.append(values
);
1499 save_statement
->Assign(db_
->GetUniqueStatement(
1500 base::StringPrintf(query
.c_str(), "metas").c_str()));
1503 } // namespace syncable
1504 } // namespace syncer