summaryrefslogtreecommitdiffstats
path: root/chrome/browser/sync/syncable
diff options
context:
space:
mode:
authorchron@google.com <chron@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2010-02-09 22:00:06 +0000
committerchron@google.com <chron@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2010-02-09 22:00:06 +0000
commit7377db59c9a1b6d5b3724acee85fb5171ddc37a4 (patch)
tree5b8b14e1a16be3e1db553a6dff96c4ee6689eecc /chrome/browser/sync/syncable
parent0cf9160a53a0dbd523656858a36e3bbeeac76b25 (diff)
downloadchromium_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-xchrome/browser/sync/syncable/directory_backing_store.cc27
-rw-r--r--chrome/browser/sync/syncable/directory_backing_store.h2
-rwxr-xr-xchrome/browser/sync/syncable/directory_backing_store_unittest.cc177
-rwxr-xr-xchrome/browser/sync/syncable/syncable.cc99
-rwxr-xr-xchrome/browser/sync/syncable/syncable.h28
-rwxr-xr-xchrome/browser/sync/syncable/syncable_columns.h3
-rwxr-xr-xchrome/browser/sync/syncable/syncable_unittest.cc195
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