diff options
author | chron@google.com <chron@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-02-09 22:00:06 +0000 |
---|---|---|
committer | chron@google.com <chron@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-02-09 22:00:06 +0000 |
commit | 7377db59c9a1b6d5b3724acee85fb5171ddc37a4 (patch) | |
tree | 5b8b14e1a16be3e1db553a6dff96c4ee6689eecc /chrome/browser/sync/syncable | |
parent | 0cf9160a53a0dbd523656858a36e3bbeeac76b25 (diff) | |
download | chromium_src-7377db59c9a1b6d5b3724acee85fb5171ddc37a4.zip chromium_src-7377db59c9a1b6d5b3724acee85fb5171ddc37a4.tar.gz chromium_src-7377db59c9a1b6d5b3724acee85fb5171ddc37a4.tar.bz2 |
Added UNIQUE_CLIENT_TAG to sync engine. Added some syncapi unit tests. Added new index to syncable. Added new DB col to syncable. Renamed singleton tag to UNIQUE_CLIENT_TAG. Added syncapi layer support for unique client tags.
Hooked up wire protocol to use this index.
Downintegrate of sync.proto from trunk.
TEST=unit tests included
BUG=32636
Review URL: http://codereview.chromium.org/558015
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@38518 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/sync/syncable')
-rwxr-xr-x | chrome/browser/sync/syncable/directory_backing_store.cc | 27 | ||||
-rw-r--r-- | chrome/browser/sync/syncable/directory_backing_store.h | 2 | ||||
-rwxr-xr-x | chrome/browser/sync/syncable/directory_backing_store_unittest.cc | 177 | ||||
-rwxr-xr-x | chrome/browser/sync/syncable/syncable.cc | 99 | ||||
-rwxr-xr-x | chrome/browser/sync/syncable/syncable.h | 28 | ||||
-rwxr-xr-x | chrome/browser/sync/syncable/syncable_columns.h | 3 | ||||
-rwxr-xr-x | chrome/browser/sync/syncable/syncable_unittest.cc | 195 |
7 files changed, 501 insertions, 30 deletions
diff --git a/chrome/browser/sync/syncable/directory_backing_store.cc b/chrome/browser/sync/syncable/directory_backing_store.cc index 216e785..20c6581 100755 --- a/chrome/browser/sync/syncable/directory_backing_store.cc +++ b/chrome/browser/sync/syncable/directory_backing_store.cc @@ -38,7 +38,7 @@ namespace syncable { static const string::size_type kUpdateStatementBufferSize = 2048; // Increment this version whenever updating DB tables. -extern const int32 kCurrentDBVersion = 69; // Extern only for our unittest. +extern const int32 kCurrentDBVersion = 70; // Extern only for our unittest. namespace { @@ -315,6 +315,11 @@ DirOpenResult DirectoryBackingStore::InitializeTables() { version_on_disk = 69; } + if (version_on_disk == 69) { + if (MigrateVersion69To70()) + version_on_disk = 70; + } + // If one of the migrations requested it, drop columns that aren't current. // It's only safe to do this after migrating all the way to the current // version. @@ -659,6 +664,26 @@ bool DirectoryBackingStore::MigrateVersion67To68() { return true; } +bool DirectoryBackingStore::MigrateVersion69To70() { + // Added "unique_client_tag", renamed "singleton_tag" to unique_server_tag + SetVersion(70); + // We use these metas column names but if in the future + // we rename the column again, we need to inline the old + // intermediate name / column spec. + if (!AddColumn(&g_metas_columns[UNIQUE_SERVER_TAG])) { + return false; + } + if (!AddColumn(&g_metas_columns[UNIQUE_CLIENT_TAG])) { + return false; + } + needs_column_refresh_ = true; + + SQLStatement statement; + statement.prepare(load_dbhandle_, + "UPDATE metas SET unique_server_tag = singleton_tag"); + return statement.step() == SQLITE_DONE; +} + namespace { // Callback passed to MigrateToSpecifics for the v68->v69 migration. See diff --git a/chrome/browser/sync/syncable/directory_backing_store.h b/chrome/browser/sync/syncable/directory_backing_store.h index a4294ae..65bf1c8 100644 --- a/chrome/browser/sync/syncable/directory_backing_store.h +++ b/chrome/browser/sync/syncable/directory_backing_store.h @@ -77,6 +77,7 @@ class DirectoryBackingStore { private: FRIEND_TEST(DirectoryBackingStoreTest, MigrateVersion67To68); FRIEND_TEST(DirectoryBackingStoreTest, MigrateVersion68To69); + FRIEND_TEST(DirectoryBackingStoreTest, MigrateVersion69To70); FRIEND_TEST(MigrationTest, ToCurrentVersion); // General Directory initialization and load helpers. @@ -138,6 +139,7 @@ class DirectoryBackingStore { // Individual version migrations. bool MigrateVersion67To68(); bool MigrateVersion68To69(); + bool MigrateVersion69To70(); // The handle to our sqlite on-disk store for initialization and loading, and // for saving changes periodically via SaveChanges, respectively. diff --git a/chrome/browser/sync/syncable/directory_backing_store_unittest.cc b/chrome/browser/sync/syncable/directory_backing_store_unittest.cc index 2bdded7..70cd23c 100755 --- a/chrome/browser/sync/syncable/directory_backing_store_unittest.cc +++ b/chrome/browser/sync/syncable/directory_backing_store_unittest.cc @@ -43,6 +43,7 @@ class MigrationTest : public testing::TestWithParam<int> { } void SetUpVersion67Database(); void SetUpVersion68Database(); + void SetUpVersion69Database(); private: ScopedTempDir temp_dir_; @@ -282,6 +283,127 @@ void MigrationTest::SetUpVersion68Database() { ASSERT_TRUE(connection.CommitTransaction()); } +void MigrationTest::SetUpVersion69Database() { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(connection.BeginTransaction()); + ASSERT_TRUE(connection.Execute( + "CREATE TABLE extended_attributes(metahandle bigint, key varchar(127), " + "value blob, PRIMARY KEY(metahandle, key) ON CONFLICT REPLACE);" + "CREATE TABLE metas (metahandle bigint primary key ON CONFLICT FAIL," + "base_version bigint default -1,server_version bigint default 0," + "mtime bigint default 0,server_mtime bigint default 0," + "ctime bigint default 0,server_ctime bigint default 0," + "server_position_in_parent bigint default 0," + "local_external_id bigint default 0,id varchar(255) default 'r'," + "parent_id varchar(255) default 'r'," + "server_parent_id varchar(255) default 'r'," + "prev_id varchar(255) default 'r',next_id varchar(255) default 'r'," + "is_unsynced bit default 0,is_unapplied_update bit default 0," + "is_del bit default 0,is_dir bit default 0," + "is_bookmark_object bit default 0,server_is_dir bit default 0," + "server_is_del bit default 0," + "server_is_bookmark_object bit default 0," + "non_unique_name varchar,server_non_unique_name varchar(255)," + "bookmark_url varchar,server_bookmark_url varchar," + "singleton_tag varchar,bookmark_favicon blob," + "server_bookmark_favicon blob, specifics blob, " + "server_specifics blob);" + "INSERT INTO metas VALUES(1,-1,0,129079956640320000,0,129079956640320000," + "0,0,0,'r','r','r','r','r',0,0,0,1,0,0,0,0,NULL,NULL,NULL,NULL,NULL," + "NULL,NULL,X'',X'');" + "INSERT INTO metas VALUES(2,669,669,128976886618480000," + "128976886618480000,128976886618480000,128976886618480000,-2097152," + "4,'s_ID_2','s_ID_9','s_ID_9','s_ID_2','s_ID_2',0,0,1,0,1,0,1,1," + "'Deleted Item','Deleted Item','http://www.google.com/'," + "'http://www.google.com/2',NULL,'AASGASGA','ASADGADGADG'," + "X'C28810220A16687474703A2F2F7777772E676F6F676C652E636F6D2F120841415" + "34741534741',X'C28810260A17687474703A2F2F7777772E676F6F676C652E636F" + "6D2F32120B4153414447414447414447');" + "INSERT INTO metas VALUES(4,681,681,129002163642690000," + "129002163642690000,129002163642690000,129002163642690000,-3145728," + "3,'s_ID_4','s_ID_9','s_ID_9','s_ID_4','s_ID_4',0,0,1,0,1,0,1,1," + "'Welcome to Chromium','Welcome to Chromium'," + "'http://www.google.com/chrome/intl/en/welcome.html'," + "'http://www.google.com/chrome/intl/en/welcome.html',NULL,NULL,NULL," + "X'C28810350A31687474703A2F2F7777772E676F6F676C652E636F6D2F6368726F6" + "D652F696E746C2F656E2F77656C636F6D652E68746D6C1200',X'C28810350A3168" + "7474703A2F2F7777772E676F6F676C652E636F6D2F6368726F6D652F696E746C2F6" + "56E2F77656C636F6D652E68746D6C1200');" + "INSERT INTO metas VALUES(5,677,677,129001555500000000," + "129001555500000000,129001555500000000,129001555500000000,1048576,7," + "'s_ID_5','s_ID_9','s_ID_9','s_ID_5','s_ID_5',0,0,1,0,1,0,1,1," + "'Google','Google','http://www.google.com/'," + "'http://www.google.com/',NULL,'AGASGASG','AGFDGASG',X'C28810220A166" + "87474703A2F2F7777772E676F6F676C652E636F6D2F12084147415347415347',X'" + "C28810220A16687474703A2F2F7777772E676F6F676C652E636F6D2F12084147464" + "447415347');" + "INSERT INTO metas VALUES(6,694,694,129053976170000000," + "129053976170000000,129053976170000000,129053976170000000,-4194304,6" + ",'s_ID_6','s_ID_9','s_ID_9','r','r',0,0,0,1,1,1,0,1,'The Internet'," + "'The Internet',NULL,NULL,NULL,NULL,NULL,X'C2881000',X'C2881000');" + "INSERT INTO metas VALUES(7,663,663,128976864758480000," + "128976864758480000,128976864758480000,128976864758480000,1048576,0," + "'s_ID_7','r','r','r','r',0,0,0,1,1,1,0,1,'Google Chrome'," + "'Google Chrome',NULL,NULL,'google_chrome',NULL,NULL,NULL,NULL);" + "INSERT INTO metas VALUES(8,664,664,128976864758480000," + "128976864758480000,128976864758480000,128976864758480000,1048576,0," + "'s_ID_8','s_ID_7','s_ID_7','r','r',0,0,0,1,1,1,0,1,'Bookmarks'," + "'Bookmarks',NULL,NULL,'google_chrome_bookmarks',NULL,NULL," + "X'C2881000',X'C2881000');" + "INSERT INTO metas VALUES(9,665,665,128976864758480000," + "128976864758480000,128976864758480000,128976864758480000,1048576,1," + "'s_ID_9','s_ID_8','s_ID_8','r','s_ID_10',0,0,0,1,1,1,0,1," + "'Bookmark Bar','Bookmark Bar',NULL,NULL,'bookmark_bar',NULL,NULL," + "X'C2881000',X'C2881000');" + "INSERT INTO metas VALUES(10,666,666,128976864758480000," + "128976864758480000,128976864758480000,128976864758480000,2097152,2," + "'s_ID_10','s_ID_8','s_ID_8','s_ID_9','r',0,0,0,1,1,1,0,1," + "'Other Bookmarks','Other Bookmarks',NULL,NULL,'other_bookmarks'," + "NULL,NULL,X'C2881000',X'C2881000');" + "INSERT INTO metas VALUES(11,683,683,129079956948440000," + "129079956948440000,129079956948440000,129079956948440000,-1048576," + "8,'s_ID_11','s_ID_6','s_ID_6','r','s_ID_13',0,0,0,0,1,0,0,1," + "'Home (The Chromium Projects)','Home (The Chromium Projects)'," + "'http://dev.chromium.org/','http://dev.chromium.org/other',NULL," + "'AGATWA','AFAGVASF',X'C28810220A18687474703A2F2F6465762E6368726F6D6" + "9756D2E6F72672F1206414741545741',X'C28810290A1D687474703A2F2F646576" + "2E6368726F6D69756D2E6F72672F6F7468657212084146414756415346');" + "INSERT INTO metas VALUES(12,685,685,129079957513650000," + "129079957513650000,129079957513650000,129079957513650000,0,9," + "'s_ID_12','s_ID_6','s_ID_6','s_ID_13','s_ID_14',0,0,0,1,1,1,0,1," + "'Extra Bookmarks','Extra Bookmarks',NULL,NULL,NULL,NULL,NULL," + "X'C2881000',X'C2881000');" + "INSERT INTO metas VALUES(13,687,687,129079957985300000," + "129079957985300000,129079957985300000,129079957985300000,-917504," + "10,'s_ID_13','s_ID_6','s_ID_6','s_ID_11','s_ID_12',0,0,0,0,1,0,0," + "1,'ICANN | Internet Corporation for Assigned Names and Numbers'," + "'ICANN | Internet Corporation for Assigned Names and Numbers'," + "'http://www.icann.com/','http://www.icann.com/',NULL,'PNGAXF0AAFF'," + "'DAAFASF',X'C28810240A15687474703A2F2F7777772E6963616E6E2E636F6D2F1" + "20B504E474158463041414646',X'C28810200A15687474703A2F2F7777772E6963" + "616E6E2E636F6D2F120744414146415346');" + "INSERT INTO metas VALUES(14,692,692,129079958383000000," + "129079958383000000,129079958383000000,129079958383000000,1048576,11," + "'s_ID_14','s_ID_6','s_ID_6','s_ID_12','r',0,0,0,0,1,0,0,1," + "'The WebKit Open Source Project','The WebKit Open Source Project'," + "'http://webkit.org/','http://webkit.org/x',NULL,'PNGX','PNG2Y'," + "X'C288101A0A12687474703A2F2F7765626B69742E6F72672F1204504E4758',X'C2" + "88101C0A13687474703A2F2F7765626B69742E6F72672F781205504E473259');" + "CREATE TABLE share_info (id VARCHAR(128) primary key, " + "last_sync_timestamp INT, name VARCHAR(128), " + "initial_sync_ended BIT default 0, store_birthday VARCHAR(256), " + "db_create_version VARCHAR(128), db_create_time int, " + "next_id bigint default -2, cache_guid VARCHAR(32));" + "INSERT INTO share_info VALUES('nick@chromium.org',694," + "'nick@chromium.org',1,'c27e9f59-08ca-46f8-b0cc-f16a2ed778bb'," + "'Unknown',1263522064,-65542," + "'9010788312004066376x-6609234393368420856x');" + "CREATE TABLE share_version (id VARCHAR(128) primary key, data INT);" + "INSERT INTO share_version VALUES('nick@chromium.org',69);" + )); + ASSERT_TRUE(connection.CommitTransaction()); +} TEST_F(DirectoryBackingStoreTest, MigrateVersion67To68) { SetUpVersion67Database(); @@ -289,7 +411,7 @@ TEST_F(DirectoryBackingStoreTest, MigrateVersion67To68) { sql::Connection connection; ASSERT_TRUE(connection.Open(GetDatabasePath())); - // Columns existing befor version 67. + // Columns existing before version 67. ASSERT_TRUE(connection.DoesColumnExist("metas", "name")); ASSERT_TRUE(connection.DoesColumnExist("metas", "unsanitized_name")); ASSERT_TRUE(connection.DoesColumnExist("metas", "server_name")); @@ -345,6 +467,39 @@ TEST_F(DirectoryBackingStoreTest, MigrateVersion68To69) { ASSERT_FALSE(s.Step()); } +TEST_F(DirectoryBackingStoreTest, MigrateVersion69To70) { + SetUpVersion69Database(); + + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + ASSERT_TRUE(connection.DoesColumnExist("metas", "singleton_tag")); + ASSERT_FALSE(connection.DoesColumnExist("metas", "unique_server_tag")); + ASSERT_FALSE(connection.DoesColumnExist("metas", "unique_client_tag")); + } + + scoped_ptr<DirectoryBackingStore> dbs( + new DirectoryBackingStore(GetUsername(), GetDatabasePath())); + + dbs->BeginLoad(); + ASSERT_FALSE(dbs->needs_column_refresh_); + ASSERT_TRUE(dbs->MigrateVersion69To70()); + ASSERT_EQ(70, dbs->GetVersion()); + dbs->EndLoad(); + ASSERT_TRUE(dbs->needs_column_refresh_); + + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + EXPECT_TRUE(connection.DoesColumnExist("metas", "unique_server_tag")); + EXPECT_TRUE(connection.DoesColumnExist("metas", "unique_client_tag")); + sql::Statement s(connection.GetUniqueStatement("SELECT id" + " FROM metas WHERE unique_server_tag = 'google_chrome'")); + ASSERT_TRUE(s.Step()); + EXPECT_EQ("s_ID_7", s.ColumnString(0)); +} + TEST_P(MigrationTest, ToCurrentVersion) { switch (GetParam()) { case 67: @@ -353,6 +508,9 @@ TEST_P(MigrationTest, ToCurrentVersion) { case 68: SetUpVersion68Database(); break; + case 69: + SetUpVersion69Database(); + break; default: FAIL() << "Need to supply database dump for version " << GetParam(); } @@ -385,6 +543,11 @@ TEST_P(MigrationTest, ToCurrentVersion) { ASSERT_FALSE(connection.DoesColumnExist("metas", "bookmark_favicon")); ASSERT_FALSE(connection.DoesColumnExist("metas", "bookmark_url")); ASSERT_FALSE(connection.DoesColumnExist("metas", "server_bookmark_url")); + + // Renamed a column in Version 70 + ASSERT_FALSE(connection.DoesColumnExist("metas", "singleton_tag")); + ASSERT_TRUE(connection.DoesColumnExist("metas", "unique_server_tag")); + ASSERT_TRUE(connection.DoesColumnExist("metas", "unique_client_tag")); } MetahandlesIndex index; @@ -410,7 +573,7 @@ TEST_P(MigrationTest, ToCurrentVersion) { (*it)->ref(SERVER_SPECIFICS).GetExtension(sync_pb::bookmark).url()); EXPECT_EQ("ASADGADGADG", (*it)->ref(SERVER_SPECIFICS).GetExtension(sync_pb::bookmark).favicon()); - EXPECT_EQ("", (*it)->ref(SINGLETON_TAG)); + EXPECT_EQ("", (*it)->ref(UNIQUE_SERVER_TAG)); EXPECT_EQ("Deleted Item", (*it)->ref(NON_UNIQUE_NAME)); EXPECT_EQ("Deleted Item", (*it)->ref(SERVER_NON_UNIQUE_NAME)); @@ -439,19 +602,19 @@ TEST_P(MigrationTest, ToCurrentVersion) { ASSERT_TRUE(++it != index.end()); ASSERT_EQ(7, (*it)->ref(META_HANDLE)); - EXPECT_EQ("google_chrome", (*it)->ref(SINGLETON_TAG)); + EXPECT_EQ("google_chrome", (*it)->ref(UNIQUE_SERVER_TAG)); EXPECT_FALSE((*it)->ref(SPECIFICS).HasExtension(sync_pb::bookmark)); EXPECT_FALSE((*it)->ref(SERVER_SPECIFICS).HasExtension(sync_pb::bookmark)); ASSERT_TRUE(++it != index.end()); ASSERT_EQ(8, (*it)->ref(META_HANDLE)); - EXPECT_EQ("google_chrome_bookmarks", (*it)->ref(SINGLETON_TAG)); + EXPECT_EQ("google_chrome_bookmarks", (*it)->ref(UNIQUE_SERVER_TAG)); EXPECT_TRUE((*it)->ref(SPECIFICS).HasExtension(sync_pb::bookmark)); EXPECT_TRUE((*it)->ref(SERVER_SPECIFICS).HasExtension(sync_pb::bookmark)); ASSERT_TRUE(++it != index.end()); ASSERT_EQ(9, (*it)->ref(META_HANDLE)); - EXPECT_EQ("bookmark_bar", (*it)->ref(SINGLETON_TAG)); + EXPECT_EQ("bookmark_bar", (*it)->ref(UNIQUE_SERVER_TAG)); EXPECT_TRUE((*it)->ref(SPECIFICS).HasExtension(sync_pb::bookmark)); EXPECT_TRUE((*it)->ref(SERVER_SPECIFICS).HasExtension(sync_pb::bookmark)); @@ -467,7 +630,7 @@ TEST_P(MigrationTest, ToCurrentVersion) { (*it)->ref(SERVER_SPECIFICS).GetExtension(sync_pb::bookmark).has_url()); EXPECT_FALSE((*it)->ref(SERVER_SPECIFICS). GetExtension(sync_pb::bookmark).has_favicon()); - EXPECT_EQ("other_bookmarks", (*it)->ref(SINGLETON_TAG)); + EXPECT_EQ("other_bookmarks", (*it)->ref(UNIQUE_SERVER_TAG)); EXPECT_EQ("Other Bookmarks", (*it)->ref(NON_UNIQUE_NAME)); EXPECT_EQ("Other Bookmarks", (*it)->ref(SERVER_NON_UNIQUE_NAME)); @@ -485,7 +648,7 @@ TEST_P(MigrationTest, ToCurrentVersion) { (*it)->ref(SERVER_SPECIFICS).GetExtension(sync_pb::bookmark).url()); EXPECT_EQ("AFAGVASF", (*it)->ref(SERVER_SPECIFICS).GetExtension(sync_pb::bookmark).favicon()); - EXPECT_EQ("", (*it)->ref(SINGLETON_TAG)); + EXPECT_EQ("", (*it)->ref(UNIQUE_SERVER_TAG)); EXPECT_EQ("Home (The Chromium Projects)", (*it)->ref(NON_UNIQUE_NAME)); EXPECT_EQ("Home (The Chromium Projects)", (*it)->ref(SERVER_NON_UNIQUE_NAME)); diff --git a/chrome/browser/sync/syncable/syncable.cc b/chrome/browser/sync/syncable/syncable.cc index 09f75b5..f656e05 100755 --- a/chrome/browser/sync/syncable/syncable.cc +++ b/chrome/browser/sync/syncable/syncable.cc @@ -161,6 +161,7 @@ Directory::Kernel::Kernel(const FilePath& db_path, metahandles_index(new Directory::MetahandlesIndex), ids_index(new Directory::IdsIndex), parent_id_child_index(new Directory::ParentIdChildIndex), + client_tag_index(new Directory::ClientTagIndex), extended_attributes(new ExtendedAttributes), unapplied_update_metahandles(new MetahandleSet), unsynced_metahandles(new MetahandleSet), @@ -196,6 +197,7 @@ Directory::Kernel::~Kernel() { delete unapplied_update_metahandles; delete extended_attributes; delete parent_id_child_index; + delete client_tag_index; delete ids_index; for_each(metahandles_index->begin(), metahandles_index->end(), DeleteEntry); delete metahandles_index; @@ -222,6 +224,9 @@ void Directory::InitializeIndices() { if (!entry->ref(IS_DEL)) kernel_->parent_id_child_index->insert(entry); kernel_->ids_index->insert(entry); + if (!entry->ref(UNIQUE_CLIENT_TAG).empty()) { + kernel_->client_tag_index->insert(entry); + } if (entry->ref(IS_UNSYNCED)) kernel_->unsynced_metahandles->insert(entry->ref(META_HANDLE)); if (entry->ref(IS_UNAPPLIED_UPDATE)) @@ -278,17 +283,29 @@ EntryKernel* Directory::GetEntryById(const Id& id) { EntryKernel* Directory::GetEntryById(const Id& id, ScopedKernelLock* const lock) { DCHECK(kernel_); - // First look up in memory + // Find it in the in memory ID index. kernel_->needle.put(ID, id); IdsIndex::iterator id_found = kernel_->ids_index->find(&kernel_->needle); if (id_found != kernel_->ids_index->end()) { - // Found it in memory. Easy. return *id_found; } return NULL; } -EntryKernel* Directory::GetEntryByTag(const string& tag) { +EntryKernel* Directory::GetEntryByClientTag(const string& tag) { + ScopedKernelLock lock(this); + DCHECK(kernel_); + // Find it in the ClientTagIndex. + kernel_->needle.put(UNIQUE_CLIENT_TAG, tag); + ClientTagIndex::iterator found = kernel_->client_tag_index->find( + &kernel_->needle); + if (found != kernel_->client_tag_index->end()) { + return *found; + } + return NULL; +} + +EntryKernel* Directory::GetEntryByServerTag(const string& tag) { ScopedKernelLock lock(this); DCHECK(kernel_); // We don't currently keep a separate index for the tags. Since tags @@ -298,7 +315,7 @@ EntryKernel* Directory::GetEntryByTag(const string& tag) { // looking for a match. MetahandlesIndex& set = *kernel_->metahandles_index; for (MetahandlesIndex::iterator i = set.begin(); i != set.end(); ++i) { - if ((*i)->ref(SINGLETON_TAG) == tag) { + if ((*i)->ref(UNIQUE_SERVER_TAG) == tag) { return *i; } } @@ -417,6 +434,9 @@ void Directory::InsertEntry(EntryKernel* entry, ScopedKernelLock* lock) { CHECK(kernel_->parent_id_child_index->insert(entry).second) << error; } CHECK(kernel_->ids_index->insert(entry).second) << error; + + // Should NEVER be created with a client tag. + CHECK(entry->ref(UNIQUE_CLIENT_TAG).empty()); } void Directory::Undelete(EntryKernel* const entry) { @@ -548,6 +568,9 @@ void Directory::VacuumAfterSaveChanges(const SaveChangesSnapshot& snapshot) { DCHECK(1 == num_erased); num_erased = kernel_->metahandles_index->erase(entry); DCHECK(1 == num_erased); + + num_erased = kernel_->client_tag_index->erase(entry); // Might not be in it + DCHECK(!entry->ref(UNIQUE_CLIENT_TAG).empty() == num_erased); delete entry; } } @@ -798,6 +821,7 @@ void Directory::CheckTreeInvariants(syncable::BaseTransaction* trans, ++entries_done; continue; } + if (!e.Get(IS_DEL)) { CHECK(id != parentid) << e; CHECK(!e.Get(NON_UNIQUE_NAME).empty()) << e; @@ -823,6 +847,11 @@ void Directory::CheckTreeInvariants(syncable::BaseTransaction* trans, CHECK(e.Get(IS_DEL)) << e; CHECK(id.ServerKnows()) << e; } else { + if (e.Get(IS_DIR)) { + // TODO(chron): Implement this mode if clients ever need it. + // For now, you can't combine a client tag and a directory. + CHECK(e.Get(UNIQUE_CLIENT_TAG).empty()) << e; + } // Uncommitted item. if (!e.Get(IS_DEL)) { CHECK(e.Get(IS_UNSYNCED)) << e; @@ -967,9 +996,14 @@ Entry::Entry(BaseTransaction* trans, GetById, const Id& id) kernel_ = trans->directory()->GetEntryById(id); } -Entry::Entry(BaseTransaction* trans, GetByTag, const string& tag) +Entry::Entry(BaseTransaction* trans, GetByClientTag, const string& tag) : basetrans_(trans) { - kernel_ = trans->directory()->GetEntryByTag(tag); + kernel_ = trans->directory()->GetEntryByClientTag(tag); +} + +Entry::Entry(BaseTransaction* trans, GetByServerTag, const string& tag) + : basetrans_(trans) { + kernel_ = trans->directory()->GetEntryByServerTag(tag); } Entry::Entry(BaseTransaction* trans, GetByHandle, int64 metahandle) @@ -1007,7 +1041,7 @@ syncable::ModelType Entry::GetServerModelType() const { return TOP_LEVEL_FOLDER; // Loose check for server-created top-level folders that aren't // bound to a particular model type. - if (!Get(SINGLETON_TAG).empty() && Get(SERVER_IS_DIR)) + if (!Get(UNIQUE_SERVER_TAG).empty() && Get(SERVER_IS_DIR)) return TOP_LEVEL_FOLDER; // Otherwise, we don't have a server type yet. That should only happen @@ -1032,7 +1066,7 @@ syncable::ModelType Entry::GetModelType() const { return TOP_LEVEL_FOLDER; // Loose check for server-created top-level folders that aren't // bound to a particular model type. - if (!Get(SINGLETON_TAG).empty() && Get(IS_DIR)) + if (!Get(UNIQUE_SERVER_TAG).empty() && Get(IS_DIR)) return TOP_LEVEL_FOLDER; // Otherwise, we don't have a local type yet. That should only happen @@ -1081,7 +1115,7 @@ void MutableEntry::Init(WriteTransaction* trans, const Id& parent_id, MutableEntry::MutableEntry(WriteTransaction* trans, CreateNewUpdateItem, const Id& id) - : Entry(trans), write_transaction_(trans) { + : Entry(trans), write_transaction_(trans) { Entry same_id(trans, GET_BY_ID, id); if (same_id.good()) { kernel_ = NULL; // already have an item with this ID. @@ -1100,13 +1134,19 @@ MutableEntry::MutableEntry(WriteTransaction* trans, CreateNewUpdateItem, } MutableEntry::MutableEntry(WriteTransaction* trans, GetById, const Id& id) - : Entry(trans, GET_BY_ID, id), write_transaction_(trans) { + : Entry(trans, GET_BY_ID, id), write_transaction_(trans) { trans->SaveOriginal(kernel_); } MutableEntry::MutableEntry(WriteTransaction* trans, GetByHandle, int64 metahandle) - : Entry(trans, GET_BY_HANDLE, metahandle), write_transaction_(trans) { + : Entry(trans, GET_BY_HANDLE, metahandle), write_transaction_(trans) { + trans->SaveOriginal(kernel_); +} + +MutableEntry::MutableEntry(WriteTransaction* trans, GetByClientTag, + const std::string& tag) + : Entry(trans, GET_BY_CLIENT_TAG, tag), write_transaction_(trans) { trans->SaveOriginal(kernel_); } @@ -1170,8 +1210,45 @@ bool MutableEntry::Put(StringField field, const string& value) { return PutImpl(field, value); } +bool MutableEntry::PutUniqueClientTag(const string& new_tag) { + // There is no SERVER_UNIQUE_CLIENT_TAG. This field is similar to ID. + string old_tag = kernel_->ref(UNIQUE_CLIENT_TAG); + if (old_tag == new_tag) { + return true; + } + + if (!new_tag.empty()) { + // Make sure your new value is not in there already. + EntryKernel lookup_kernel_ = *kernel_; + lookup_kernel_.put(UNIQUE_CLIENT_TAG, new_tag); + bool new_tag_conflicts = + (dir()->kernel_->client_tag_index->count(&lookup_kernel_) > 0); + if (new_tag_conflicts) { + return false; + } + + // We're sure that the new tag doesn't exist now so, + // erase the old tag and finish up. + dir()->kernel_->client_tag_index->erase(kernel_); + kernel_->put(UNIQUE_CLIENT_TAG, new_tag); + kernel_->mark_dirty(); + CHECK(dir()->kernel_->client_tag_index->insert(kernel_).second); + } else { + // The new tag is empty. Since the old tag is not equal to the new tag, + // The old tag isn't empty, and thus must exist in the index. + CHECK(dir()->kernel_->client_tag_index->erase(kernel_)); + kernel_->put(UNIQUE_CLIENT_TAG, new_tag); + kernel_->mark_dirty(); + } + return true; +} + bool MutableEntry::PutImpl(StringField field, const string& value) { DCHECK(kernel_); + if (field == UNIQUE_CLIENT_TAG) { + return PutUniqueClientTag(value); + } + if (kernel_->ref(field) != value) { kernel_->put(field, value); kernel_->mark_dirty(); diff --git a/chrome/browser/sync/syncable/syncable.h b/chrome/browser/sync/syncable/syncable.h index be091d0..fd328b3 100755 --- a/chrome/browser/sync/syncable/syncable.h +++ b/chrome/browser/sync/syncable/syncable.h @@ -136,7 +136,8 @@ enum StringField { // A tag string which identifies this node as a particular top-level // permanent object. The tag can be thought of as a unique key that // identifies a singleton instance. - SINGLETON_TAG, + UNIQUE_SERVER_TAG, // Tagged by the server + UNIQUE_CLIENT_TAG, // Tagged by the client STRING_FIELDS_END, }; @@ -192,8 +193,12 @@ enum GetById { GET_BY_ID }; -enum GetByTag { - GET_BY_TAG +enum GetByClientTag { + GET_BY_CLIENT_TAG +}; + +enum GetByServerTag { + GET_BY_SERVER_TAG }; enum GetByHandle { @@ -325,7 +330,8 @@ class Entry { // succeeded. Entry(BaseTransaction* trans, GetByHandle, int64 handle); Entry(BaseTransaction* trans, GetById, const Id& id); - Entry(BaseTransaction* trans, GetByTag, const std::string& tag); + Entry(BaseTransaction* trans, GetByServerTag, const std::string& tag); + Entry(BaseTransaction* trans, GetByClientTag, const std::string& tag); bool good() const { return 0 != kernel_; } @@ -431,6 +437,7 @@ class MutableEntry : public Entry { MutableEntry(WriteTransaction* trans, CreateNewUpdateItem, const Id& id); MutableEntry(WriteTransaction* trans, GetByHandle, int64); MutableEntry(WriteTransaction* trans, GetById, const Id&); + MutableEntry(WriteTransaction* trans, GetByClientTag, const std::string& tag); inline WriteTransaction* write_transaction() const { return write_transaction_; @@ -510,6 +517,7 @@ class MutableEntry : public Entry { void* operator new(size_t size) { return (::operator new)(size); } bool PutImpl(StringField field, const std::string& value); + bool PutUniqueClientTag(const std::string& value); // Adjusts the successor and predecessor entries so that they no longer // refer to this entry. @@ -723,7 +731,8 @@ class Directory { EntryKernel* GetEntryByHandle(const int64 handle); EntryKernel* GetEntryByHandle(const int64 metahandle, ScopedKernelLock* lock); EntryKernel* GetEntryById(const Id& id); - EntryKernel* GetEntryByTag(const std::string& tag); + EntryKernel* GetEntryByServerTag(const std::string& tag); + EntryKernel* GetEntryByClientTag(const std::string& tag); EntryKernel* GetRootEntry(); bool ReindexId(EntryKernel* const entry, const Id& new_id); void ReindexParentId(EntryKernel* const entry, const Id& new_parent_id); @@ -845,6 +854,7 @@ class Directory { // processed |snapshot| failed, for example, due to no disk space. void HandleSaveChangesFailure(const SaveChangesSnapshot& snapshot); + // For new entry creation only void InsertEntry(EntryKernel* entry, ScopedKernelLock* lock); void InsertEntry(EntryKernel* entry); @@ -874,6 +884,13 @@ class Directory { // This index contains EntryKernels ordered by parent ID and metahandle. // It allows efficient lookup of the children of a given parent. typedef std::set<EntryKernel*, LessParentIdAndHandle> ParentIdChildIndex; + + // Contains both deleted and existing entries with tags. + // We can't store only existing tags because the client would create + // items that had a duplicated ID in the end, resulting in a DB key + // violation. ID reassociation would fail after an attempted commit. + typedef std::set<EntryKernel*, + LessField<StringField, UNIQUE_CLIENT_TAG> > ClientTagIndex; typedef std::vector<int64> MetahandlesToPurge; private: @@ -907,6 +924,7 @@ class Directory { MetahandlesIndex* metahandles_index; // Entries indexed by metahandle IdsIndex* ids_index; // Entries indexed by id ParentIdChildIndex* parent_id_child_index; + ClientTagIndex* client_tag_index; // So we don't have to create an EntryKernel every time we want to // look something up in an index. Needle in haystack metaphor. EntryKernel needle; diff --git a/chrome/browser/sync/syncable/syncable_columns.h b/chrome/browser/sync/syncable/syncable_columns.h index 56f0212..398a00f 100755 --- a/chrome/browser/sync/syncable/syncable_columns.h +++ b/chrome/browser/sync/syncable/syncable_columns.h @@ -50,7 +50,8 @@ static const ColumnSpec g_metas_columns[] = { // Strings {"non_unique_name", "varchar"}, {"server_non_unique_name", "varchar(255)"}, - {"singleton_tag", "varchar"}, + {"unique_server_tag", "varchar"}, + {"unique_client_tag", "varchar"}, ////////////////////////////////////// // Blobs. {"specifics", "blob"}, diff --git a/chrome/browser/sync/syncable/syncable_unittest.cc b/chrome/browser/sync/syncable/syncable_unittest.cc index 407508a..d30296a 100755 --- a/chrome/browser/sync/syncable/syncable_unittest.cc +++ b/chrome/browser/sync/syncable/syncable_unittest.cc @@ -34,6 +34,7 @@ #include "base/logging.h" #include "base/platform_thread.h" #include "base/scoped_ptr.h" +#include "base/scoped_temp_dir.h" #include "chrome/browser/sync/protocol/bookmark_specifics.pb.h" #include "chrome/browser/sync/syncable/directory_backing_store.h" #include "chrome/browser/sync/syncable/directory_manager.h" @@ -77,11 +78,24 @@ void ExpectDataFromExtendedAttributeEquals(BaseTransaction* trans, } } // namespace -TEST(Syncable, General) { - remove("SimpleTest.sqlite3"); +class SyncableGeneralTest : public testing::Test { + public: + virtual void SetUp() { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + db_path_ = temp_dir_.path().Append( + FILE_PATH_LITERAL("SyncableTest.sqlite3")); + } + + virtual void TearDown() { + } + protected: + ScopedTempDir temp_dir_; + FilePath db_path_; +}; + +TEST_F(SyncableGeneralTest, General) { Directory dir; - FilePath test_db(FILE_PATH_LITERAL("SimpleTest.sqlite3")); - dir.Open(test_db, "SimpleTest"); + dir.Open(db_path_, "SimpleTest"); int64 written_metahandle; const Id id = TestIdFactory::FromNumber(99); @@ -157,9 +171,85 @@ TEST(Syncable, General) { } dir.SaveChanges(); - remove("SimpleTest.sqlite3"); } +TEST_F(SyncableGeneralTest, ClientIndexRebuildsProperly) { + int64 written_metahandle; + TestIdFactory factory; + const Id id = factory.NewServerId(); + string name = "cheesepuffs"; + string tag = "dietcoke"; + + // Test creating a new meta entry. + { + Directory dir; + dir.Open(db_path_, "IndexTest"); + { + WriteTransaction wtrans(&dir, UNITTEST, __FILE__, __LINE__); + MutableEntry me(&wtrans, CREATE, wtrans.root_id(), name); + ASSERT_TRUE(me.good()); + me.Put(ID, id); + me.Put(BASE_VERSION, 1); + me.Put(UNIQUE_CLIENT_TAG, tag); + written_metahandle = me.Get(META_HANDLE); + } + dir.SaveChanges(); + } + + // The DB was closed. Now reopen it. This will cause index regeneration. + { + Directory dir; + dir.Open(db_path_, "IndexTest"); + + ReadTransaction trans(&dir, __FILE__, __LINE__); + Entry me(&trans, GET_BY_CLIENT_TAG, tag); + ASSERT_TRUE(me.good()); + EXPECT_EQ(me.Get(ID), id); + EXPECT_EQ(me.Get(BASE_VERSION), 1); + EXPECT_EQ(me.Get(UNIQUE_CLIENT_TAG), tag); + EXPECT_EQ(me.Get(META_HANDLE), written_metahandle); + } +} + +TEST_F(SyncableGeneralTest, ClientIndexRebuildsDeletedProperly) { + TestIdFactory factory; + const Id id = factory.NewServerId(); + string tag = "dietcoke"; + + // Test creating a deleted, unsynced, server meta entry. + { + Directory dir; + dir.Open(db_path_, "IndexTest"); + { + WriteTransaction wtrans(&dir, UNITTEST, __FILE__, __LINE__); + MutableEntry me(&wtrans, CREATE, wtrans.root_id(), "deleted"); + ASSERT_TRUE(me.good()); + me.Put(ID, id); + me.Put(BASE_VERSION, 1); + me.Put(UNIQUE_CLIENT_TAG, tag); + me.Put(IS_DEL, true); + me.Put(IS_UNSYNCED, true); // Or it might be purged. + } + dir.SaveChanges(); + } + + // The DB was closed. Now reopen it. This will cause index regeneration. + // Should still be present and valid in the client tag index. + { + Directory dir; + dir.Open(db_path_, "IndexTest"); + + ReadTransaction trans(&dir, __FILE__, __LINE__); + Entry me(&trans, GET_BY_CLIENT_TAG, tag); + ASSERT_TRUE(me.good()); + EXPECT_EQ(me.Get(ID), id); + EXPECT_EQ(me.Get(UNIQUE_CLIENT_TAG), tag); + EXPECT_TRUE(me.Get(IS_DEL)); + EXPECT_TRUE(me.Get(IS_UNSYNCED)); + } +} + + namespace { // A Directory whose backing store always fails SaveChanges by returning false. @@ -1131,5 +1221,100 @@ TEST_F(SyncableDirectoryTest, Bug1509232) { dir_.get()->SaveChanges(); } +class SyncableClientTagTest : public SyncableDirectoryTest { + public: + static const int kBaseVersion = 1; + const char* test_name_; + const char* test_tag_; + + SyncableClientTagTest() : test_name_("test_name"), test_tag_("dietcoke") {} + + bool CreateWithDefaultTag(Id id, bool deleted) { + return CreateWithTag(test_tag_, id, deleted); + } + + // Attempt to create an entry with a default tag. + bool CreateWithTag(const char* tag, Id id, bool deleted) { + WriteTransaction wtrans(dir_.get(), UNITTEST, __FILE__, __LINE__); + MutableEntry me(&wtrans, CREATE, wtrans.root_id(), test_name_); + CHECK(me.good()); + me.Put(ID, id); + if (id.ServerKnows()) { + me.Put(BASE_VERSION, kBaseVersion); + } + me.Put(IS_DEL, deleted); + me.Put(IS_UNSYNCED, true); + me.Put(IS_DIR, false); + return me.Put(UNIQUE_CLIENT_TAG, tag); + } + + // Verify an entry exists with the default tag. + void VerifyTag(Id id, bool deleted) { + // Should still be present and valid in the client tag index. + ReadTransaction trans(dir_.get(), __FILE__, __LINE__); + Entry me(&trans, GET_BY_CLIENT_TAG, test_tag_); + CHECK(me.good()); + EXPECT_EQ(me.Get(ID), id); + EXPECT_EQ(me.Get(UNIQUE_CLIENT_TAG), test_tag_); + EXPECT_EQ(me.Get(IS_DEL), deleted); + EXPECT_EQ(me.Get(IS_UNSYNCED), true); + } + + protected: + TestIdFactory factory_; +}; + +TEST_F(SyncableClientTagTest, TestClientTagClear) { + Id server_id = factory_.NewServerId(); + EXPECT_TRUE(CreateWithDefaultTag(server_id, false)); + { + WriteTransaction trans(dir_.get(), UNITTEST, __FILE__, __LINE__); + MutableEntry me(&trans, GET_BY_CLIENT_TAG, test_tag_); + EXPECT_TRUE(me.good()); + me.Put(UNIQUE_CLIENT_TAG, ""); + } + { + ReadTransaction trans(dir_.get(), __FILE__, __LINE__); + Entry by_tag(&trans, GET_BY_CLIENT_TAG, test_tag_); + EXPECT_FALSE(by_tag.good()); + + Entry by_id(&trans, GET_BY_ID, server_id); + EXPECT_TRUE(by_id.good()); + EXPECT_TRUE(by_id.Get(UNIQUE_CLIENT_TAG).empty()); + } +} + +TEST_F(SyncableClientTagTest, TestClientTagIndexServerId) { + Id server_id = factory_.NewServerId(); + EXPECT_TRUE(CreateWithDefaultTag(server_id, false)); + VerifyTag(server_id, false); +} + +TEST_F(SyncableClientTagTest, TestClientTagIndexClientId) { + Id client_id = factory_.NewLocalId(); + EXPECT_TRUE(CreateWithDefaultTag(client_id, false)); + VerifyTag(client_id, false); +} + +TEST_F(SyncableClientTagTest, TestDeletedClientTagIndexClientId) { + Id client_id = factory_.NewLocalId(); + EXPECT_TRUE(CreateWithDefaultTag(client_id, true)); + VerifyTag(client_id, true); +} + +TEST_F(SyncableClientTagTest, TestDeletedClientTagIndexServerId) { + Id server_id = factory_.NewServerId(); + EXPECT_TRUE(CreateWithDefaultTag(server_id, true)); + VerifyTag(server_id, true); +} + +TEST_F(SyncableClientTagTest, TestClientTagIndexDuplicateServer) { + EXPECT_TRUE(CreateWithDefaultTag(factory_.NewServerId(), true)); + EXPECT_FALSE(CreateWithDefaultTag(factory_.NewServerId(), true)); + EXPECT_FALSE(CreateWithDefaultTag(factory_.NewServerId(), false)); + EXPECT_FALSE(CreateWithDefaultTag(factory_.NewLocalId(), false)); + EXPECT_FALSE(CreateWithDefaultTag(factory_.NewLocalId(), true)); +} + } // namespace } // namespace syncable |