summaryrefslogtreecommitdiffstats
path: root/chrome/browser/sync/syncable
diff options
context:
space:
mode:
authornick@chromium.org <nick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-01-27 16:08:08 +0000
committernick@chromium.org <nick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-01-27 16:08:08 +0000
commit3273dceb702908e7a2e7c13488d0bdfcdce2148b (patch)
tree30aa669e4d6312122cb98527317f58614d89c629 /chrome/browser/sync/syncable
parent395608dc4edab1f468a2bcf9189d3c34b87f919b (diff)
downloadchromium_src-3273dceb702908e7a2e7c13488d0bdfcdce2148b.zip
chromium_src-3273dceb702908e7a2e7c13488d0bdfcdce2148b.tar.gz
chromium_src-3273dceb702908e7a2e7c13488d0bdfcdce2148b.tar.bz2
In the sync database, use protobuf-based storage. Drop the old
bookmark-only columns. Add getters and setters for BookmarkSpecifics to syncapi as well as syncable entries. Make the datatype be a required property when creating a syncapi node. Add a datatype for the 'google chrome' top level folder. Add database migrations from version 67 to the new schema. Add infrastructure to support migrations generically. Add unit tests for the migrations. Pull a new version of the protobuf library to pick up a fix for a bug that this change exposed (I upstreamed the fix). Fix some example code in the sql helpers so that it would actually compile. BUG=29899,30041 TEST=New unit tests for migrations: unit tests are based on actual database dumps. Additionally, I manually tested 2-client sync using combos of old-protocol servers, new-protocol servers, and initial database versions v67, v68, and v0 (new client). I manually verified that add/edit/delete works in these combination cases. Afterwards I verified (by inspecting the sync databases) that the ModelTypes are consistent across the various migration/protocol paths. Review URL: http://codereview.chromium.org/554066 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@37253 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/sync/syncable')
-rwxr-xr-xchrome/browser/sync/syncable/directory_backing_store.cc309
-rw-r--r--chrome/browser/sync/syncable/directory_backing_store.h38
-rwxr-xr-xchrome/browser/sync/syncable/directory_backing_store_unittest.cc523
-rw-r--r--chrome/browser/sync/syncable/model_type.h16
-rwxr-xr-xchrome/browser/sync/syncable/syncable.cc87
-rwxr-xr-xchrome/browser/sync/syncable/syncable.h62
-rwxr-xr-xchrome/browser/sync/syncable/syncable_columns.h10
-rwxr-xr-xchrome/browser/sync/syncable/syncable_unittest.cc20
8 files changed, 951 insertions, 114 deletions
diff --git a/chrome/browser/sync/syncable/directory_backing_store.cc b/chrome/browser/sync/syncable/directory_backing_store.cc
index 6df61be..4cdf7c2 100755
--- a/chrome/browser/sync/syncable/directory_backing_store.cc
+++ b/chrome/browser/sync/syncable/directory_backing_store.cc
@@ -14,7 +14,9 @@
#include "base/hash_tables.h"
#include "base/logging.h"
+#include "chrome/browser/sync/protocol/bookmark_specifics.pb.h"
#include "chrome/browser/sync/protocol/service_constants.h"
+#include "chrome/browser/sync/protocol/sync.pb.h"
#include "chrome/browser/sync/syncable/syncable-inl.h"
#include "chrome/browser/sync/syncable/syncable_columns.h"
#include "chrome/browser/sync/util/crypto_helpers.h"
@@ -36,13 +38,7 @@ namespace syncable {
static const string::size_type kUpdateStatementBufferSize = 2048;
// Increment this version whenever updating DB tables.
-static const int32 kCurrentDBVersion = 68;
-
-static void RegisterPathNameCollate(sqlite3* dbhandle) {
- const int collate = SQLITE_UTF8;
- CHECK(SQLITE_OK == sqlite3_create_collation(dbhandle, "PATHNAME", collate,
- NULL, &ComparePathNames16));
-}
+extern const int32 kCurrentDBVersion = 69; // Extern only for our unittest.
namespace {
@@ -58,12 +54,13 @@ int ExecQuery(sqlite3* dbhandle, const char* query) {
return result;
}
-} // namespace
-
-static string GenerateCacheGUID() {
+string GenerateCacheGUID() {
return Generate128BitRandomHexString();
}
+} // namespace
+
+
// Iterate over the fields of |entry| and bind each to |statement| for
// updating. Returns the number of args bound.
int BindFields(const EntryKernel& entry, SQLStatement* statement) {
@@ -81,11 +78,10 @@ int BindFields(const EntryKernel& entry, SQLStatement* statement) {
for ( ; i < STRING_FIELDS_END; ++i) {
statement->bind_string(index++, entry.ref(static_cast<StringField>(i)));
}
- for ( ; i < BLOB_FIELDS_END; ++i) {
- uint8* blob = entry.ref(static_cast<BlobField>(i)).empty() ?
- NULL : const_cast<uint8*>(&entry.ref(static_cast<BlobField>(i)).at(0));
- statement->bind_blob(index++, blob,
- entry.ref(static_cast<BlobField>(i)).size());
+ std::string temp;
+ for ( ; i < PROTO_FIELDS_END; ++i) {
+ entry.ref(static_cast<ProtoField>(i)).SerializeToString(&temp);
+ statement->bind_blob(index++, temp.data(), temp.length());
}
return index;
}
@@ -110,12 +106,12 @@ EntryKernel* UnpackEntry(SQLStatement* statement) {
result->put(static_cast<BitField>(i), (0 != statement->column_int(i)));
}
for ( ; i < STRING_FIELDS_END; ++i) {
- result->put(static_cast<StringField>(i),
+ result->put(static_cast<StringField>(i),
statement->column_string(i));
}
- for ( ; i < BLOB_FIELDS_END; ++i) {
- statement->column_blob_as_vector(
- i, &result->mutable_ref(static_cast<BlobField>(i)));
+ for ( ; i < PROTO_FIELDS_END; ++i) {
+ result->mutable_ref(static_cast<ProtoField>(i)).ParseFromArray(
+ statement->column_blob(i), statement->column_bytes(i));
}
ZeroFields(result, i);
} else {
@@ -125,8 +121,11 @@ EntryKernel* UnpackEntry(SQLStatement* statement) {
return result;
}
-static string ComposeCreateTableColumnSpecs(const ColumnSpec* begin,
- const ColumnSpec* end) {
+namespace {
+
+string ComposeCreateTableColumnSpecs() {
+ const ColumnSpec* begin = g_metas_columns;
+ const ColumnSpec* end = g_metas_columns + arraysize(g_metas_columns);
string query;
query.reserve(kUpdateStatementBufferSize);
char separator = '(';
@@ -141,6 +140,18 @@ static string ComposeCreateTableColumnSpecs(const ColumnSpec* begin,
return query;
}
+void AppendColumnList(std::string* output) {
+ const char* joiner = " ";
+ // Be explicit in SELECT order to match up with UnpackEntry.
+ for (int i = BEGIN_FIELDS; i < BEGIN_FIELDS + FIELD_COUNT; ++i) {
+ output->append(joiner);
+ output->append(ColumnName(i));
+ joiner = ", ";
+ }
+}
+
+} // namespace
+
///////////////////////////////////////////////////////////////////////////////
// DirectoryBackingStore implementation.
@@ -149,7 +160,8 @@ DirectoryBackingStore::DirectoryBackingStore(const string& dir_name,
: load_dbhandle_(NULL),
save_dbhandle_(NULL),
dir_name_(dir_name),
- backing_filepath_(backing_filepath) {
+ backing_filepath_(backing_filepath),
+ needs_column_refresh_(false) {
}
DirectoryBackingStore::~DirectoryBackingStore() {
@@ -182,7 +194,6 @@ bool DirectoryBackingStore::OpenAndConfigureHandleHelper(
}
}
sqlite3_busy_timeout(*handle, kDirectoryBackingStoreBusyTimeoutMs);
- RegisterPathNameCollate(*handle);
#if defined(OS_WIN)
// Do not index this file. Scanning can occur every time we close the file,
// which causes long delays in SQLite's file locking.
@@ -200,8 +211,7 @@ bool DirectoryBackingStore::OpenAndConfigureHandleHelper(
DirOpenResult DirectoryBackingStore::Load(MetahandlesIndex* entry_bucket,
ExtendedAttributes* xattrs_bucket,
Directory::KernelLoadInfo* kernel_load_info) {
- DCHECK(load_dbhandle_ == NULL);
- if (!OpenAndConfigureHandleHelper(&load_dbhandle_))
+ if (!BeginLoad())
return FAILED_OPEN_DATABASE;
DirOpenResult result = InitializeTables();
@@ -213,10 +223,18 @@ DirOpenResult DirectoryBackingStore::Load(MetahandlesIndex* entry_bucket,
LoadExtendedAttributes(xattrs_bucket);
LoadInfo(kernel_load_info);
+ EndLoad();
+ return OPENED;
+}
+
+bool DirectoryBackingStore::BeginLoad() {
+ DCHECK(load_dbhandle_ == NULL);
+ return OpenAndConfigureHandleHelper(&load_dbhandle_);
+}
+
+void DirectoryBackingStore::EndLoad() {
sqlite3_close(load_dbhandle_);
load_dbhandle_ = NULL; // No longer used.
-
- return OPENED;
}
bool DirectoryBackingStore::SaveChanges(
@@ -273,25 +291,38 @@ DirOpenResult DirectoryBackingStore::InitializeTables() {
if (SQLITE_OK != transaction.BeginExclusive()) {
return FAILED_DISK_FULL;
}
- int version_on_disk = 0;
+ int version_on_disk = GetVersion();
int last_result = SQLITE_OK;
- if (DoesSqliteTableExist(load_dbhandle_, "share_version")) {
- SQLStatement version_query;
- version_query.prepare(load_dbhandle_, "SELECT data from share_version");
- last_result = version_query.step();
- if (SQLITE_ROW == last_result) {
- version_on_disk = version_query.column_int(0);
- }
- last_result = version_query.reset();
+ // Upgrade from version 67. Version 67 was widely distributed as the original
+ // Bookmark Sync release. Version 68 removed unique naming.
+ if (version_on_disk == 67) {
+ if (MigrateVersion67To68())
+ version_on_disk = 68;
+ }
+ // Version 69 introduced additional datatypes.
+ if (version_on_disk == 68) {
+ if (MigrateVersion68To69())
+ version_on_disk = 69;
}
+
+ // 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.
+ if (version_on_disk == kCurrentDBVersion && needs_column_refresh_) {
+ if (!RefreshColumns())
+ version_on_disk = 0;
+ }
+
+ // A final, alternative catch-all migration to simply re-sync everything.
if (version_on_disk != kCurrentDBVersion) {
if (version_on_disk > kCurrentDBVersion) {
transaction.Rollback();
return FAILED_NEWER_VERSION;
}
+ // Fallback (re-sync everything) migration path.
LOG(INFO) << "Old/null sync database, version " << version_on_disk;
- // Delete the existing database (if any), and create a freshone.
+ // Delete the existing database (if any), and create a fresh one.
if (SQLITE_OK == last_result) {
DropAllTables();
if (SQLITE_DONE == CreateTables()) {
@@ -323,17 +354,40 @@ DirOpenResult DirectoryBackingStore::InitializeTables() {
return FAILED_DISK_FULL;
}
+bool DirectoryBackingStore::RefreshColumns() {
+ DCHECK(needs_column_refresh_);
+
+ // Create a new table named temp_metas.
+ SafeDropTable("temp_metas");
+ if (CreateMetasTable(true) != SQLITE_DONE)
+ return false;
+
+ // Populate temp_metas from metas.
+ std::string query = "INSERT INTO temp_metas (";
+ AppendColumnList(&query);
+ query.append(") SELECT ");
+ AppendColumnList(&query);
+ query.append(" FROM metas");
+ if (ExecQuery(load_dbhandle_, query.c_str()) != SQLITE_DONE)
+ return false;
+
+ // Drop metas.
+ SafeDropTable("metas");
+
+ // Rename temp_metas -> metas.
+ int result = ExecQuery(load_dbhandle_,
+ "ALTER TABLE temp_metas RENAME TO metas");
+ if (result != SQLITE_DONE)
+ return false;
+ needs_column_refresh_ = false;
+ return true;
+}
+
void DirectoryBackingStore::LoadEntries(MetahandlesIndex* entry_bucket) {
string select;
select.reserve(kUpdateStatementBufferSize);
- select.append("SELECT");
- const char* joiner = " ";
- // Be explicit in SELECT order to match up with UnpackEntry.
- for (int i = BEGIN_FIELDS; i < BEGIN_FIELDS + FIELD_COUNT; ++i) {
- select.append(joiner);
- select.append(ColumnName(i));
- joiner = ", ";
- }
+ select.append("SELECT ");
+ AppendColumnList(&select);
select.append(" FROM metas ");
SQLStatement statement;
statement.prepare(load_dbhandle_, select.c_str());
@@ -402,7 +456,7 @@ bool DirectoryBackingStore::SaveEntryToDB(const EntryKernel& entry) {
values.append("VALUES ");
const char* separator = "( ";
int i = 0;
- for (i = BEGIN_FIELDS; i < BLOB_FIELDS_END; ++i) {
+ for (i = BEGIN_FIELDS; i < PROTO_FIELDS_END; ++i) {
query.append(separator);
values.append(separator);
separator = ", ";
@@ -517,11 +571,162 @@ int DirectoryBackingStore::CreateExtendedAttributeTable() {
void DirectoryBackingStore::DropAllTables() {
SafeDropTable("metas");
+ SafeDropTable("temp_metas");
SafeDropTable("share_info");
SafeDropTable("share_version");
SafeDropTable("extended_attributes");
+ needs_column_refresh_ = false;
+}
+
+bool DirectoryBackingStore::MigrateToSpecifics(
+ const char* old_columns,
+ const char* specifics_column,
+ void (*handler_function)(SQLStatement* old_value_query,
+ int old_value_column,
+ sync_pb::EntitySpecifics* mutable_new_value)) {
+ std::string query_sql = StringPrintf("SELECT metahandle, %s, %s FROM metas",
+ specifics_column, old_columns);
+ std::string update_sql = StringPrintf(
+ "UPDATE metas SET %s = ? WHERE metahandle = ?", specifics_column);
+ SQLStatement query;
+ query.prepare(load_dbhandle_, query_sql.c_str());
+ while (query.step() == SQLITE_ROW) {
+ int64 metahandle = query.column_int64(0);
+ std::string new_value_bytes;
+ query.column_blob_as_string(1, &new_value_bytes);
+ sync_pb::EntitySpecifics new_value;
+ new_value.ParseFromString(new_value_bytes);
+ handler_function(&query, 2, &new_value);
+ new_value.SerializeToString(&new_value_bytes);
+
+ SQLStatement update;
+ update.prepare(load_dbhandle_, update_sql.data(), update_sql.length());
+ update.bind_blob(0, new_value_bytes.data(), new_value_bytes.length());
+ update.bind_int64(1, metahandle);
+ if (update.step() != SQLITE_DONE) {
+ NOTREACHED();
+ return false;
+ }
+ }
+ return true;
+}
+
+bool DirectoryBackingStore::AddColumn(const ColumnSpec* column) {
+ SQLStatement add_column;
+ std::string sql = StringPrintf("ALTER TABLE metas ADD COLUMN %s %s",
+ column->name, column->spec);
+ add_column.prepare(load_dbhandle_, sql.c_str());
+ return add_column.step() == SQLITE_DONE;
+}
+
+bool DirectoryBackingStore::SetVersion(int version) {
+ SQLStatement statement;
+ statement.prepare(load_dbhandle_, "UPDATE share_version SET data = ?");
+ statement.bind_int(0, version);
+ return statement.step() == SQLITE_DONE;
+}
+
+int DirectoryBackingStore::GetVersion() {
+ if (!DoesSqliteTableExist(load_dbhandle_, "share_version"))
+ return 0;
+ SQLStatement version_query;
+ version_query.prepare(load_dbhandle_, "SELECT data from share_version");
+ if (SQLITE_ROW != version_query.step())
+ return 0;
+ int value = version_query.column_int(0);
+ if (version_query.reset() != SQLITE_OK)
+ return 0;
+ return value;
+}
+
+bool DirectoryBackingStore::MigrateVersion67To68() {
+ // This change simply removed three columns:
+ // string NAME
+ // string UNSANITIZED_NAME
+ // string SERVER_NAME
+ // No data migration is necessary, but we should do a column refresh.
+ SetVersion(68);
+ needs_column_refresh_ = true;
+ return true;
+}
+
+namespace {
+
+// Callback passed to MigrateToSpecifics for the v68->v69 migration. See
+// MigrateVersion68To69().
+void EncodeBookmarkURLAndFavicon(SQLStatement* old_value_query,
+ int old_value_column,
+ sync_pb::EntitySpecifics* mutable_new_value) {
+ // Extract data from the column trio we expect.
+ bool old_is_bookmark_object = old_value_query->column_bool(old_value_column);
+ std::string old_url = old_value_query->column_string(old_value_column + 1);
+ std::string old_favicon;
+ old_value_query->column_blob_as_string(old_value_column + 2, &old_favicon);
+ bool old_is_dir = old_value_query->column_bool(old_value_column + 3);
+
+ if (old_is_bookmark_object) {
+ sync_pb::BookmarkSpecifics* bookmark_data =
+ mutable_new_value->MutableExtension(sync_pb::bookmark);
+ if (!old_is_dir) {
+ bookmark_data->set_url(old_url);
+ bookmark_data->set_favicon(old_favicon);
+ }
+ }
}
+} // namespace
+
+bool DirectoryBackingStore::MigrateVersion68To69() {
+ // In Version 68, there were columns on table 'metas':
+ // string BOOKMARK_URL
+ // string SERVER_BOOKMARK_URL
+ // blob BOOKMARK_FAVICON
+ // blob SERVER_BOOKMARK_FAVICON
+ // In version 69, these columns went away in favor of storing
+ // a serialized EntrySpecifics protobuf in the columns:
+ // protobuf blob SPECIFICS
+ // protobuf blob SERVER_SPECIFICS
+ // For bookmarks, EntrySpecifics is extended as per
+ // bookmark_specifics.proto. This migration converts bookmarks from the
+ // former scheme to the latter scheme.
+
+ // First, add the two new columns to the schema.
+ if (!AddColumn(&g_metas_columns[SPECIFICS]))
+ return false;
+ if (!AddColumn(&g_metas_columns[SERVER_SPECIFICS]))
+ return false;
+
+ // Next, fold data from the old columns into the new protobuf columns.
+ if (!MigrateToSpecifics(("is_bookmark_object, bookmark_url, "
+ "bookmark_favicon, is_dir"),
+ "specifics",
+ &EncodeBookmarkURLAndFavicon)) {
+ return false;
+ }
+ if (!MigrateToSpecifics(("server_is_bookmark_object, "
+ "server_bookmark_url, "
+ "server_bookmark_favicon, "
+ "server_is_dir"),
+ "server_specifics",
+ &EncodeBookmarkURLAndFavicon)) {
+ return false;
+ }
+
+ // Lastly, fix up the "Google Chrome" folder, which is of the TOP_LEVEL_FOLDER
+ // ModelType: it shouldn't have BookmarkSpecifics.
+ SQLStatement clear_permanent_items;
+ clear_permanent_items.prepare(load_dbhandle_,
+ "UPDATE metas SET specifics = NULL, server_specifics = NULL WHERE "
+ "singleton_tag IN ('google_chrome')");
+ if (clear_permanent_items.step() != SQLITE_DONE)
+ return false;
+
+ SetVersion(69);
+ needs_column_refresh_ = true; // Trigger deletion of old columns.
+ return true;
+}
+
+
int DirectoryBackingStore::CreateTables() {
LOG(INFO) << "First run, creating tables";
// Create two little tables share_version and share_info
@@ -579,9 +784,7 @@ int DirectoryBackingStore::CreateTables() {
if (result != SQLITE_DONE)
return result;
// Create the big metas table.
- string query = "CREATE TABLE metas " + ComposeCreateTableColumnSpecs
- (g_metas_columns, g_metas_columns + arraysize(g_metas_columns));
- result = ExecQuery(load_dbhandle_, query.c_str());
+ result = CreateMetasTable(false);
if (result != SQLITE_DONE)
return result;
{
@@ -610,4 +813,12 @@ sqlite3* DirectoryBackingStore::LazyGetSaveHandle() {
return save_dbhandle_;
}
+int DirectoryBackingStore::CreateMetasTable(bool is_temporary) {
+ const char* name = is_temporary ? "temp_metas" : "metas";
+ string query = "CREATE TABLE ";
+ query.append(name);
+ query.append(ComposeCreateTableColumnSpecs());
+ return ExecQuery(load_dbhandle_, query.c_str());
+}
+
} // namespace syncable
diff --git a/chrome/browser/sync/syncable/directory_backing_store.h b/chrome/browser/sync/syncable/directory_backing_store.h
index a3fa5093..a4294ae 100644
--- a/chrome/browser/sync/syncable/directory_backing_store.h
+++ b/chrome/browser/sync/syncable/directory_backing_store.h
@@ -11,12 +11,19 @@
#include "base/file_path.h"
#include "chrome/browser/sync/syncable/dir_open_result.h"
#include "chrome/browser/sync/syncable/syncable.h"
+#include "testing/gtest/include/gtest/gtest_prod.h" // For FRIEND_TEST
extern "C" {
struct sqlite3;
struct sqlite3_stmt;
}
+class SQLStatement;
+
+namespace sync_pb {
+class EntitySpecifics;
+}
+
namespace syncable {
struct ColumnSpec;
@@ -68,10 +75,18 @@ class DirectoryBackingStore {
virtual bool SaveChanges(const Directory::SaveChangesSnapshot& snapshot);
private:
+ FRIEND_TEST(DirectoryBackingStoreTest, MigrateVersion67To68);
+ FRIEND_TEST(DirectoryBackingStoreTest, MigrateVersion68To69);
+ FRIEND_TEST(MigrationTest, ToCurrentVersion);
+
// General Directory initialization and load helpers.
DirOpenResult InitializeTables();
// Returns an sqlite return code, usually SQLITE_DONE.
int CreateTables();
+ // Create 'metas' or 'temp_metas' depending on value of is_temporary.
+ // Returns an sqlite return code, SQLITE_DONE on success.
+ int CreateMetasTable(bool is_temporary);
+
int CreateExtendedAttributeTable();
// We don't need to load any synced and applied deleted entries, we can
// in fact just purge them forever on startup.
@@ -98,6 +113,9 @@ class DirectoryBackingStore {
// said handle. Returns true on success, false if the sqlite open operation
// did not succeed.
bool OpenAndConfigureHandleHelper(sqlite3** handle) const;
+ // Initialize and destroy load_dbhandle_. Broken out for testing.
+ bool BeginLoad();
+ void EndLoad();
// Lazy creation of save_dbhandle_ for use by SaveChanges code path.
sqlite3* LazyGetSaveHandle();
@@ -105,6 +123,22 @@ class DirectoryBackingStore {
// Drop all tables in preparation for reinitialization.
void DropAllTables();
+ // Migration utilities.
+ bool AddColumn(const ColumnSpec* column);
+ bool RefreshColumns();
+ bool SetVersion(int version);
+ int GetVersion();
+ bool MigrateToSpecifics(const char* old_columns,
+ const char* specifics_column,
+ void (*handler_function)(
+ SQLStatement* old_value_query,
+ int old_value_column,
+ sync_pb::EntitySpecifics* mutable_new_value));
+
+ // Individual version migrations.
+ bool MigrateVersion67To68();
+ bool MigrateVersion68To69();
+
// The handle to our sqlite on-disk store for initialization and loading, and
// for saving changes periodically via SaveChanges, respectively.
// TODO(timsteele): We should only have one handle here. The reason we need
@@ -117,6 +151,10 @@ class DirectoryBackingStore {
std::string dir_name_;
FilePath backing_filepath_;
+ // Set to true if migration left some old columns around that need to be
+ // discarded.
+ bool needs_column_refresh_;
+
DISALLOW_COPY_AND_ASSIGN(DirectoryBackingStore);
};
diff --git a/chrome/browser/sync/syncable/directory_backing_store_unittest.cc b/chrome/browser/sync/syncable/directory_backing_store_unittest.cc
new file mode 100755
index 0000000..2bdded7
--- /dev/null
+++ b/chrome/browser/sync/syncable/directory_backing_store_unittest.cc
@@ -0,0 +1,523 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include <string>
+
+#include "app/sql/connection.h"
+#include "app/sql/statement.h"
+#include "app/sql/transaction.h"
+#include "base/file_path.h"
+#include "base/scoped_ptr.h"
+#include "base/scoped_temp_dir.h"
+#include "base/stl_util-inl.h"
+#include "chrome/browser/sync/protocol/bookmark_specifics.pb.h"
+#include "chrome/browser/sync/protocol/sync.pb.h"
+#include "chrome/browser/sync/syncable/directory_backing_store.h"
+#include "chrome/browser/sync/syncable/directory_manager.h"
+#include "chrome/browser/sync/syncable/syncable-inl.h"
+#include "chrome/browser/sync/syncable/syncable.h"
+#include "chrome/common/sqlite_utils.h"
+#include "testing/gtest/include/gtest/gtest-param-test.h"
+
+namespace syncable {
+
+extern const int32 kCurrentDBVersion;
+
+class MigrationTest : public testing::TestWithParam<int> {
+ public:
+ virtual void SetUp() {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ }
+
+ protected:
+ std::string GetUsername() {
+ return "nick@chromium.org";
+ }
+
+ FilePath GetDatabasePath() {
+ return temp_dir_.path().Append(
+ DirectoryManager::GetSyncDataDatabaseFilename());
+ }
+ void SetUpVersion67Database();
+ void SetUpVersion68Database();
+
+ private:
+ ScopedTempDir temp_dir_;
+};
+
+class DirectoryBackingStoreTest : public MigrationTest {};
+
+void MigrationTest::SetUpVersion67Database() {
+ // This is a version 67 database dump whose contents were backformed from
+ // the contents of the version 68 database dump (the v68 migration was
+ // actually written first).
+ 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,"
+ "name varchar(255), " /* COLLATE PATHNAME, */
+ "unsanitized_name varchar(255)," /* COLLATE PATHNAME, */
+ "non_unique_name varchar,"
+ "server_name varchar(255)," /* COLLATE PATHNAME */
+ "server_non_unique_name varchar,"
+ "bookmark_url varchar,server_bookmark_url varchar,"
+ "singleton_tag varchar,bookmark_favicon blob,"
+ "server_bookmark_favicon 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,NULL,NULL,NULL);"
+ "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',NULL,'Deleted Item','Deleted Item','Deleted Item',"
+ "'http://www.google.com/','http://www.google.com/2',NULL,'AASGASGA',"
+ "'ASADGADGADG');"
+ "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',NULL,'Welcome to Chromium',"
+ "'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);"
+ "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',NULL,'Google','Google','Google','http://www.google.com/',"
+ "'http://www.google.com/',NULL,'AGASGASG','AGFDGASG');"
+ "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',NULL,'The Internet','The Internet',"
+ "'The Internet',NULL,NULL,NULL,NULL,NULL);"
+ "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',NULL,'Google Chrome','Google Chrome',"
+ "'Google Chrome',NULL,NULL,'google_chrome',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',"
+ "NULL,'Bookmarks','Bookmarks','Bookmarks',NULL,NULL,"
+ "'google_chrome_bookmarks',NULL,NULL);"
+ "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',NULL,'Bookmark Bar','Bookmark Bar','Bookmark Bar',"
+ "NULL,NULL,'bookmark_bar',NULL,NULL);"
+ "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',NULL,'Other Bookmarks','Other Bookmarks',"
+ "'Other Bookmarks',NULL,NULL,'other_bookmarks',"
+ "NULL,NULL);"
+ "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)',NULL,'Home (The Chromium Projects)',"
+ "'Home (The Chromium Projects)','Home (The Chromium Projects)',"
+ "'http://dev.chromium.org/','http://dev.chromium.org/other',NULL,"
+ "'AGATWA','AFAGVASF');"
+ "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',NULL,'Extra Bookmarks','Extra Bookmarks',"
+ "'Extra Bookmarks',NULL,NULL,NULL,NULL,NULL);"
+ "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',"
+ "'ICANN | Internet Corporation for Assigned Names and Numbers',"
+ "'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');"
+ "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',NULL,"
+ "'The WebKit Open Source Project','The WebKit Open Source Project',"
+ "'The WebKit Open Source Project','http://webkit.org/',"
+ "'http://webkit.org/x',NULL,'PNGX','PNG2Y');"
+ "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',68);"));
+ ASSERT_TRUE(connection.CommitTransaction());
+}
+
+void MigrationTest::SetUpVersion68Database() {
+ // This sets up an actual version 68 database dump. The IDs were
+ // canonicalized to be less huge, and the favicons were overwritten
+ // with random junk so that they didn't contain any unprintable
+ // characters. A few server URLs were tweaked so that they'd be
+ // different from the local URLs. Lastly, the custom collation on
+ // the server_non_unique_name column was removed.
+ 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);"
+ "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);"
+ "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');"
+ "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);"
+ "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');"
+ "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);"
+ "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);"
+ "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);"
+ "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);"
+ "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);"
+ "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');"
+ "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);"
+ "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');"
+ "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');"
+ "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',68);"));
+ ASSERT_TRUE(connection.CommitTransaction());
+}
+
+TEST_F(DirectoryBackingStoreTest, MigrateVersion67To68) {
+ SetUpVersion67Database();
+
+ {
+ sql::Connection connection;
+ ASSERT_TRUE(connection.Open(GetDatabasePath()));
+
+ // Columns existing befor version 67.
+ ASSERT_TRUE(connection.DoesColumnExist("metas", "name"));
+ ASSERT_TRUE(connection.DoesColumnExist("metas", "unsanitized_name"));
+ ASSERT_TRUE(connection.DoesColumnExist("metas", "server_name"));
+ }
+
+ scoped_ptr<DirectoryBackingStore> dbs(
+ new DirectoryBackingStore(GetUsername(), GetDatabasePath()));
+
+ dbs->BeginLoad();
+ ASSERT_FALSE(dbs->needs_column_refresh_);
+ ASSERT_TRUE(dbs->MigrateVersion67To68());
+ ASSERT_EQ(68, dbs->GetVersion());
+ dbs->EndLoad();
+ ASSERT_TRUE(dbs->needs_column_refresh_);
+}
+
+TEST_F(DirectoryBackingStoreTest, MigrateVersion68To69) {
+ SetUpVersion68Database();
+
+ scoped_ptr<DirectoryBackingStore> dbs(
+ new DirectoryBackingStore(GetUsername(), GetDatabasePath()));
+
+ dbs->BeginLoad();
+ ASSERT_FALSE(dbs->needs_column_refresh_);
+ ASSERT_TRUE(dbs->MigrateVersion68To69());
+ ASSERT_EQ(69, dbs->GetVersion());
+ dbs->EndLoad();
+ ASSERT_TRUE(dbs->needs_column_refresh_);
+
+ sql::Connection connection;
+ ASSERT_TRUE(connection.Open(GetDatabasePath()));
+ ASSERT_TRUE(connection.DoesColumnExist("metas", "specifics"));
+ ASSERT_TRUE(connection.DoesColumnExist("metas", "server_specifics"));
+ sql::Statement s(connection.GetUniqueStatement("SELECT non_unique_name,"
+ "is_del, is_dir, id, specifics, server_specifics FROM metas "
+ "WHERE metahandle = 2"));
+ ASSERT_TRUE(s.Step());
+ ASSERT_EQ("Deleted Item", s.ColumnString(0));
+ ASSERT_TRUE(s.ColumnBool(1));
+ ASSERT_FALSE(s.ColumnBool(2));
+ ASSERT_EQ("s_ID_2", s.ColumnString(3));
+ sync_pb::EntitySpecifics specifics;
+ specifics.ParseFromArray(s.ColumnBlob(4), s.ColumnByteLength(4));
+ ASSERT_TRUE(specifics.HasExtension(sync_pb::bookmark));
+ ASSERT_EQ("http://www.google.com/",
+ specifics.GetExtension(sync_pb::bookmark).url());
+ ASSERT_EQ("AASGASGA", specifics.GetExtension(sync_pb::bookmark).favicon());
+ specifics.ParseFromArray(s.ColumnBlob(5), s.ColumnByteLength(5));
+ ASSERT_TRUE(specifics.HasExtension(sync_pb::bookmark));
+ ASSERT_EQ("http://www.google.com/2",
+ specifics.GetExtension(sync_pb::bookmark).url());
+ ASSERT_EQ("ASADGADGADG", specifics.GetExtension(sync_pb::bookmark).favicon());
+ ASSERT_FALSE(s.Step());
+}
+
+TEST_P(MigrationTest, ToCurrentVersion) {
+ switch (GetParam()) {
+ case 67:
+ SetUpVersion67Database();
+ break;
+ case 68:
+ SetUpVersion68Database();
+ break;
+ default:
+ FAIL() << "Need to supply database dump for version " << GetParam();
+ }
+
+ scoped_ptr<DirectoryBackingStore> dbs(
+ new DirectoryBackingStore(GetUsername(), GetDatabasePath()));
+
+ dbs->BeginLoad();
+ ASSERT_TRUE(OPENED == dbs->InitializeTables());
+ ASSERT_FALSE(dbs->needs_column_refresh_);
+ ASSERT_EQ(kCurrentDBVersion, dbs->GetVersion());
+
+ {
+ sql::Connection connection;
+ ASSERT_TRUE(connection.Open(GetDatabasePath()));
+
+ // Columns deleted in Version 67.
+ ASSERT_FALSE(connection.DoesColumnExist("metas", "name"));
+ ASSERT_FALSE(connection.DoesColumnExist("metas", "unsanitized_name"));
+ ASSERT_FALSE(connection.DoesColumnExist("metas", "server_name"));
+
+ // Columns added in Version 68.
+ ASSERT_TRUE(connection.DoesColumnExist("metas", "specifics"));
+ ASSERT_TRUE(connection.DoesColumnExist("metas", "server_specifics"));
+
+ // Columns deleted in Version 68.
+ ASSERT_FALSE(connection.DoesColumnExist("metas", "is_bookmark_object"));
+ ASSERT_FALSE(connection.DoesColumnExist("metas",
+ "server_is_bookmark_object"));
+ ASSERT_FALSE(connection.DoesColumnExist("metas", "bookmark_favicon"));
+ ASSERT_FALSE(connection.DoesColumnExist("metas", "bookmark_url"));
+ ASSERT_FALSE(connection.DoesColumnExist("metas", "server_bookmark_url"));
+ }
+
+ MetahandlesIndex index;
+ dbs->LoadEntries(&index);
+ dbs->EndLoad();
+
+ MetahandlesIndex::iterator it = index.begin();
+ ASSERT_TRUE(it != index.end());
+ ASSERT_EQ(1, (*it)->ref(META_HANDLE));
+ EXPECT_TRUE((*it)->ref(ID).IsRoot());
+
+ ASSERT_TRUE(++it != index.end()) << "Upgrade destroyed database contents.";
+ ASSERT_EQ(2, (*it)->ref(META_HANDLE));
+ EXPECT_TRUE((*it)->ref(IS_DEL));
+ EXPECT_TRUE((*it)->ref(SERVER_IS_DEL));
+ EXPECT_TRUE((*it)->ref(SPECIFICS).HasExtension(sync_pb::bookmark));
+ EXPECT_TRUE((*it)->ref(SERVER_SPECIFICS).HasExtension(sync_pb::bookmark));
+ EXPECT_EQ("http://www.google.com/",
+ (*it)->ref(SPECIFICS).GetExtension(sync_pb::bookmark).url());
+ EXPECT_EQ("AASGASGA",
+ (*it)->ref(SPECIFICS).GetExtension(sync_pb::bookmark).favicon());
+ EXPECT_EQ("http://www.google.com/2",
+ (*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("Deleted Item", (*it)->ref(NON_UNIQUE_NAME));
+ EXPECT_EQ("Deleted Item", (*it)->ref(SERVER_NON_UNIQUE_NAME));
+
+ ASSERT_TRUE(++it != index.end());
+ ASSERT_EQ(4, (*it)->ref(META_HANDLE));
+ EXPECT_TRUE((*it)->ref(IS_DEL));
+ EXPECT_TRUE((*it)->ref(SERVER_IS_DEL));
+
+ ASSERT_TRUE(++it != index.end());
+ ASSERT_EQ(5, (*it)->ref(META_HANDLE));
+ EXPECT_TRUE((*it)->ref(IS_DEL));
+ EXPECT_TRUE((*it)->ref(SERVER_IS_DEL));
+
+ ASSERT_TRUE(++it != index.end());
+ ASSERT_EQ(6, (*it)->ref(META_HANDLE));
+ EXPECT_TRUE((*it)->ref(IS_DIR));
+ EXPECT_TRUE((*it)->ref(SERVER_IS_DIR));
+ EXPECT_FALSE(
+ (*it)->ref(SPECIFICS).GetExtension(sync_pb::bookmark).has_url());
+ EXPECT_FALSE(
+ (*it)->ref(SERVER_SPECIFICS).GetExtension(sync_pb::bookmark).has_url());
+ EXPECT_FALSE(
+ (*it)->ref(SPECIFICS).GetExtension(sync_pb::bookmark).has_favicon());
+ EXPECT_FALSE((*it)->ref(SERVER_SPECIFICS).
+ GetExtension(sync_pb::bookmark).has_favicon());
+
+ ASSERT_TRUE(++it != index.end());
+ ASSERT_EQ(7, (*it)->ref(META_HANDLE));
+ EXPECT_EQ("google_chrome", (*it)->ref(SINGLETON_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_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_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(10, (*it)->ref(META_HANDLE));
+ EXPECT_FALSE((*it)->ref(IS_DEL));
+ EXPECT_TRUE((*it)->ref(SPECIFICS).HasExtension(sync_pb::bookmark));
+ EXPECT_TRUE((*it)->ref(SERVER_SPECIFICS).HasExtension(sync_pb::bookmark));
+ EXPECT_FALSE((*it)->ref(SPECIFICS).GetExtension(sync_pb::bookmark).has_url());
+ EXPECT_FALSE(
+ (*it)->ref(SPECIFICS).GetExtension(sync_pb::bookmark).has_favicon());
+ EXPECT_FALSE(
+ (*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(NON_UNIQUE_NAME));
+ EXPECT_EQ("Other Bookmarks", (*it)->ref(SERVER_NON_UNIQUE_NAME));
+
+ ASSERT_TRUE(++it != index.end());
+ ASSERT_EQ(11, (*it)->ref(META_HANDLE));
+ EXPECT_FALSE((*it)->ref(IS_DEL));
+ EXPECT_FALSE((*it)->ref(IS_DIR));
+ EXPECT_TRUE((*it)->ref(SPECIFICS).HasExtension(sync_pb::bookmark));
+ EXPECT_TRUE((*it)->ref(SERVER_SPECIFICS).HasExtension(sync_pb::bookmark));
+ EXPECT_EQ("http://dev.chromium.org/",
+ (*it)->ref(SPECIFICS).GetExtension(sync_pb::bookmark).url());
+ EXPECT_EQ("AGATWA",
+ (*it)->ref(SPECIFICS).GetExtension(sync_pb::bookmark).favicon());
+ EXPECT_EQ("http://dev.chromium.org/other",
+ (*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("Home (The Chromium Projects)", (*it)->ref(NON_UNIQUE_NAME));
+ EXPECT_EQ("Home (The Chromium Projects)", (*it)->ref(SERVER_NON_UNIQUE_NAME));
+
+ ASSERT_TRUE(++it != index.end());
+ ASSERT_EQ(12, (*it)->ref(META_HANDLE));
+ EXPECT_FALSE((*it)->ref(IS_DEL));
+ EXPECT_TRUE((*it)->ref(IS_DIR));
+ EXPECT_EQ("Extra Bookmarks", (*it)->ref(NON_UNIQUE_NAME));
+ EXPECT_EQ("Extra Bookmarks", (*it)->ref(SERVER_NON_UNIQUE_NAME));
+ EXPECT_TRUE((*it)->ref(SPECIFICS).HasExtension(sync_pb::bookmark));
+ EXPECT_TRUE((*it)->ref(SERVER_SPECIFICS).HasExtension(sync_pb::bookmark));
+ EXPECT_FALSE(
+ (*it)->ref(SPECIFICS).GetExtension(sync_pb::bookmark).has_url());
+ EXPECT_FALSE(
+ (*it)->ref(SERVER_SPECIFICS).GetExtension(sync_pb::bookmark).has_url());
+ EXPECT_FALSE(
+ (*it)->ref(SPECIFICS).GetExtension(sync_pb::bookmark).has_favicon());
+ EXPECT_FALSE((*it)->ref(SERVER_SPECIFICS).
+ GetExtension(sync_pb::bookmark).has_favicon());
+
+ ASSERT_TRUE(++it != index.end());
+ ASSERT_EQ(13, (*it)->ref(META_HANDLE));
+
+ ASSERT_TRUE(++it != index.end());
+ ASSERT_EQ(14, (*it)->ref(META_HANDLE));
+
+ ASSERT_TRUE(++it == index.end());
+
+ STLDeleteElements(&index);
+}
+
+INSTANTIATE_TEST_CASE_P(DirectoryBackingStore, MigrationTest,
+ testing::Range(67, kCurrentDBVersion));
+
+} // namespace syncable
diff --git a/chrome/browser/sync/syncable/model_type.h b/chrome/browser/sync/syncable/model_type.h
index 7bd9826..cc00f4c4 100644
--- a/chrome/browser/sync/syncable/model_type.h
+++ b/chrome/browser/sync/syncable/model_type.h
@@ -2,6 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+// Enumerate the various item subtypes that are supported by sync.
+// Each sync object is expected to have an immutable object type.
+// An object's type is inferred from the type of data it holds.
+
#ifndef CHROME_BROWSER_SYNC_SYNCABLE_MODEL_TYPE_H_
#define CHROME_BROWSER_SYNC_SYNCABLE_MODEL_TYPE_H_
@@ -10,9 +14,14 @@
namespace syncable {
enum ModelType {
- UNSPECIFIED = 0,
- BOOKMARKS,
- MODEL_TYPE_COUNT
+ UNSPECIFIED, // Object type unknown. Objects may transition through
+ // the unknown state during their initial creation, before
+ // their properties are set. After deletion, object types
+ // are generally preserved.
+ TOP_LEVEL_FOLDER, // A permanent folder whose children may be of mixed
+ // datatypes (e.g. the "Google Chrome" folder).
+ BOOKMARKS, // A bookmark folder or a bookmark URL object.
+ MODEL_TYPE_COUNT,
};
inline ModelType ModelTypeFromInt(int i) {
@@ -24,4 +33,3 @@ inline ModelType ModelTypeFromInt(int i) {
} // namespace syncable
#endif // CHROME_BROWSER_SYNC_SYNCABLE_MODEL_TYPE_H_
-
diff --git a/chrome/browser/sync/syncable/syncable.cc b/chrome/browser/sync/syncable/syncable.cc
index 6816f9c..b8c66b7 100755
--- a/chrome/browser/sync/syncable/syncable.cc
+++ b/chrome/browser/sync/syncable/syncable.cc
@@ -35,6 +35,7 @@
#include "base/time.h"
#include "chrome/browser/sync/engine/syncer.h"
#include "chrome/browser/sync/engine/syncer_util.h"
+#include "chrome/browser/sync/protocol/bookmark_specifics.pb.h"
#include "chrome/browser/sync/protocol/service_constants.h"
#include "chrome/browser/sync/syncable/directory_backing_store.h"
#include "chrome/browser/sync/syncable/directory_manager.h"
@@ -396,8 +397,8 @@ void ZeroFields(EntryKernel* entry, int first_field) {
entry->mutable_ref(static_cast<IdField>(i)).Clear();
for ( ; i < BIT_FIELDS_END; ++i)
entry->put(static_cast<BitField>(i), false);
- if (i < BLOB_FIELDS_END)
- i = BLOB_FIELDS_END;
+ if (i < PROTO_FIELDS_END)
+ i = PROTO_FIELDS_END;
}
void Directory::InsertEntry(EntryKernel* entry) {
@@ -998,6 +999,51 @@ void Entry::DeleteAllExtendedAttributes(WriteTransaction *trans) {
dir()->DeleteAllExtendedAttributes(trans, kernel_->ref(META_HANDLE));
}
+syncable::ModelType Entry::GetServerModelType() const {
+ if (Get(SERVER_SPECIFICS).HasExtension(sync_pb::bookmark))
+ return BOOKMARKS;
+ if (IsRoot())
+ 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))
+ return TOP_LEVEL_FOLDER;
+
+ // Otherwise, we don't have a server type yet. That should only happen
+ // if the item is an uncommitted locally created item.
+ // It's possible we'll need to relax these checks in the future; they're
+ // just here for now as a safety measure.
+ DCHECK(Get(IS_UNSYNCED));
+ DCHECK(Get(SERVER_VERSION) == 0);
+ DCHECK(Get(SERVER_IS_DEL));
+ // Note: can't enforce !Get(ID).ServerKnows() here because that could
+ // actually happen if we hit AttemptReuniteLostCommitResponses.
+ return UNSPECIFIED;
+}
+
+// Note: keep this consistent with GetModelType in syncproto.h!
+syncable::ModelType Entry::GetModelType() const {
+ if (Get(SPECIFICS).HasExtension(sync_pb::bookmark))
+ return BOOKMARKS;
+ if (IsRoot())
+ 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))
+ return TOP_LEVEL_FOLDER;
+
+ // Otherwise, we don't have a local type yet. That should only happen
+ // if the item doesn't exist locally -- like if it's a new update item.
+ // It's possible we'll need to relax these checks in the future; they're
+ // just here for now as a safety measure.
+ DCHECK(Get(ID).ServerKnows());
+ DCHECK(Get(IS_UNAPPLIED_UPDATE));
+ DCHECK(!Get(IS_UNSYNCED));
+ DCHECK(Get(BASE_VERSION) == CHANGES_VERSION);
+ DCHECK(Get(IS_DEL));
+ return UNSPECIFIED;
+}
+
///////////////////////////////////////////////////////////////////////////
// MutableEntry
@@ -1176,9 +1222,6 @@ void MutableEntry::UnlinkFromOrder() {
}
bool MutableEntry::PutPredecessor(const Id& predecessor_id) {
- // TODO(ncarter): Maybe there should be an independent HAS_POSITION bit?
- if (!Get(IS_BOOKMARK_OBJECT))
- return true;
UnlinkFromOrder();
if (Get(IS_DEL)) {
@@ -1186,6 +1229,15 @@ bool MutableEntry::PutPredecessor(const Id& predecessor_id) {
return true;
}
+ // TODO(ncarter): It should be possible to not maintain position for
+ // non-bookmark items. However, we'd need to robustly handle all possible
+ // permutations of setting IS_DEL and the SPECIFICS to identify the
+ // object type; or else, we'd need to add a ModelType to the
+ // MutableEntry's Create ctor.
+ // if (!ShouldMaintainPosition()) {
+ // return false;
+ // }
+
// This is classic insert-into-doubly-linked-list from CS 101 and your last
// job interview. An "IsRoot" Id signifies the head or tail.
Id successor_id;
@@ -1356,22 +1408,22 @@ inline FastDump& operator<<(FastDump& dump, const DumpColon&) {
std::ostream& operator<<(std::ostream& stream, const syncable::Entry& entry) {
// Using ostreams directly here is dreadfully slow, because a mutex is
// acquired for every <<. Users noticed it spiking CPU.
- using syncable::BitField;
- using syncable::BitTemp;
- using syncable::BlobField;
- using syncable::EntryKernel;
- using syncable::g_metas_columns;
- using syncable::IdField;
- using syncable::Int64Field;
- using syncable::StringField;
using syncable::BEGIN_FIELDS;
using syncable::BIT_FIELDS_END;
using syncable::BIT_TEMPS_BEGIN;
using syncable::BIT_TEMPS_END;
- using syncable::BLOB_FIELDS_END;
- using syncable::INT64_FIELDS_END;
+ using syncable::BitField;
+ using syncable::BitTemp;
+ using syncable::EntryKernel;
using syncable::ID_FIELDS_END;
+ using syncable::INT64_FIELDS_END;
+ using syncable::IdField;
+ using syncable::Int64Field;
+ using syncable::PROTO_FIELDS_END;
+ using syncable::ProtoField;
using syncable::STRING_FIELDS_END;
+ using syncable::StringField;
+ using syncable::g_metas_columns;
int i;
FastDump s(&stream);
@@ -1393,9 +1445,10 @@ std::ostream& operator<<(std::ostream& stream, const syncable::Entry& entry) {
const string& field = kernel->ref(static_cast<StringField>(i));
s << g_metas_columns[i].name << colon << field << separator;
}
- for ( ; i < BLOB_FIELDS_END; ++i) {
+ for ( ; i < PROTO_FIELDS_END; ++i) {
s << g_metas_columns[i].name << colon
- << kernel->ref(static_cast<BlobField>(i)) << separator;
+ << kernel->ref(static_cast<ProtoField>(i)).SerializeAsString()
+ << separator;
}
s << "TempFlags: ";
for ( ; i < BIT_TEMPS_END; ++i) {
diff --git a/chrome/browser/sync/syncable/syncable.h b/chrome/browser/sync/syncable/syncable.h
index 05b3052..0fe199e 100755
--- a/chrome/browser/sync/syncable/syncable.h
+++ b/chrome/browser/sync/syncable/syncable.h
@@ -24,6 +24,7 @@
#include "chrome/browser/sync/syncable/directory_event.h"
#include "chrome/browser/sync/syncable/path_name_cmp.h"
#include "chrome/browser/sync/syncable/syncable_id.h"
+#include "chrome/browser/sync/syncable/model_type.h"
#include "chrome/browser/sync/util/dbgq.h"
#include "chrome/browser/sync/util/event_sys.h"
#include "chrome/browser/sync/util/row_iterator.h"
@@ -116,12 +117,8 @@ enum IsDelField {
enum BitField {
IS_DIR = IS_DEL + 1,
- IS_BOOKMARK_OBJECT,
-
SERVER_IS_DIR,
SERVER_IS_DEL,
- SERVER_IS_BOOKMARK_OBJECT,
-
BIT_FIELDS_END
};
@@ -135,9 +132,6 @@ enum StringField {
NON_UNIQUE_NAME = STRING_FIELDS_BEGIN,
// The server version of |NON_UNIQUE_NAME|.
SERVER_NON_UNIQUE_NAME,
- // For bookmark entries, the URL of the bookmark.
- BOOKMARK_URL,
- SERVER_BOOKMARK_URL,
// 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
@@ -148,27 +142,25 @@ enum StringField {
enum {
STRING_FIELDS_COUNT = STRING_FIELDS_END - STRING_FIELDS_BEGIN,
- BLOB_FIELDS_BEGIN = STRING_FIELDS_END
+ PROTO_FIELDS_BEGIN = STRING_FIELDS_END
};
// From looking at the sqlite3 docs, it's not directly stated, but it
// seems the overhead for storing a NULL blob is very small.
-enum BlobField {
- // For bookmark entries, the favicon data. These will be NULL for
- // non-bookmark items.
- BOOKMARK_FAVICON = BLOB_FIELDS_BEGIN,
- SERVER_BOOKMARK_FAVICON,
- BLOB_FIELDS_END,
+enum ProtoField {
+ SPECIFICS = PROTO_FIELDS_BEGIN,
+ SERVER_SPECIFICS,
+ PROTO_FIELDS_END,
};
enum {
- BLOB_FIELDS_COUNT = BLOB_FIELDS_END - BLOB_FIELDS_BEGIN
+ PROTO_FIELDS_COUNT = PROTO_FIELDS_END - PROTO_FIELDS_BEGIN
};
enum {
- FIELD_COUNT = BLOB_FIELDS_END,
+ FIELD_COUNT = PROTO_FIELDS_END,
// Past this point we have temporaries, stored in memory only.
- BEGIN_TEMPS = BLOB_FIELDS_END,
+ BEGIN_TEMPS = PROTO_FIELDS_END,
BIT_TEMPS_BEGIN = BEGIN_TEMPS,
};
@@ -225,7 +217,7 @@ typedef std::set<std::string> AttributeKeySet;
struct EntryKernel {
private:
std::string string_fields[STRING_FIELDS_COUNT];
- Blob blob_fields[BLOB_FIELDS_COUNT];
+ sync_pb::EntitySpecifics specifics_fields[PROTO_FIELDS_COUNT];
int64 int64_fields[INT64_FIELDS_COUNT];
Id id_fields[ID_FIELDS_COUNT];
std::bitset<BIT_FIELDS_COUNT> bit_fields;
@@ -269,8 +261,8 @@ struct EntryKernel {
inline void put(StringField field, const std::string& value) {
string_fields[field - STRING_FIELDS_BEGIN] = value;
}
- inline void put(BlobField field, const Blob& value) {
- blob_fields[field - BLOB_FIELDS_BEGIN] = value;
+ inline void put(ProtoField field, const sync_pb::EntitySpecifics& value) {
+ specifics_fields[field - PROTO_FIELDS_BEGIN].CopyFrom(value);
}
inline void put(BitTemp field, bool value) {
bit_temps[field - BIT_TEMPS_BEGIN] = value;
@@ -301,8 +293,8 @@ struct EntryKernel {
inline const std::string& ref(StringField field) const {
return string_fields[field - STRING_FIELDS_BEGIN];
}
- inline const Blob& ref(BlobField field) const {
- return blob_fields[field - BLOB_FIELDS_BEGIN];
+ inline const sync_pb::EntitySpecifics& ref(ProtoField field) const {
+ return specifics_fields[field - PROTO_FIELDS_BEGIN];
}
inline bool ref(BitTemp field) const {
return bit_temps[field - BIT_TEMPS_BEGIN];
@@ -312,8 +304,8 @@ struct EntryKernel {
inline std::string& mutable_ref(StringField field) {
return string_fields[field - STRING_FIELDS_BEGIN];
}
- inline Blob& mutable_ref(BlobField field) {
- return blob_fields[field - BLOB_FIELDS_BEGIN];
+ inline sync_pb::EntitySpecifics& mutable_ref(ProtoField field) {
+ return specifics_fields[field - PROTO_FIELDS_BEGIN];
}
inline Id& mutable_ref(IdField field) {
return id_fields[field - ID_FIELDS_BEGIN];
@@ -369,7 +361,7 @@ class Entry {
return kernel_->ref(field);
}
const std::string& Get(StringField field) const;
- inline Blob Get(BlobField field) const {
+ inline const sync_pb::EntitySpecifics& Get(ProtoField field) const {
DCHECK(kernel_);
return kernel_->ref(field);
}
@@ -378,6 +370,15 @@ class Entry {
return kernel_->ref(field);
}
+ ModelType GetServerModelType() const;
+ ModelType GetModelType() const;
+
+ // If this returns false, we shouldn't bother maintaining
+ // a position value (sibling ordering) for this item.
+ bool ShouldMaintainPosition() const {
+ return GetModelType() == BOOKMARKS;
+ }
+
inline bool ExistsOnClientBecauseNameIsNonEmpty() const {
DCHECK(kernel_);
return !kernel_->ref(NON_UNIQUE_NAME).empty();
@@ -444,8 +445,15 @@ class MutableEntry : public Entry {
bool Put(StringField field, const std::string& value);
bool Put(BaseVersion field, int64 value);
- inline bool Put(BlobField field, const Blob& value) {
- return PutField(field, value);
+ inline bool Put(ProtoField field, const sync_pb::EntitySpecifics& value) {
+ DCHECK(kernel_);
+ // TODO(ncarter): This is unfortunately heavyweight. Can we do
+ // better?
+ if (kernel_->ref(field).SerializeAsString() != value.SerializeAsString()) {
+ kernel_->put(field, value);
+ kernel_->mark_dirty();
+ }
+ return true;
}
inline bool Put(BitField field, bool value) {
return PutField(field, value);
diff --git a/chrome/browser/sync/syncable/syncable_columns.h b/chrome/browser/sync/syncable/syncable_columns.h
index 92caf64..56f0212 100755
--- a/chrome/browser/sync/syncable/syncable_columns.h
+++ b/chrome/browser/sync/syncable/syncable_columns.h
@@ -44,21 +44,17 @@ static const ColumnSpec g_metas_columns[] = {
{"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"},
//////////////////////////////////////
// Strings
{"non_unique_name", "varchar"},
- {"server_non_unique_name", "varchar(255) COLLATE PATHNAME"},
- {"bookmark_url", "varchar"},
- {"server_bookmark_url", "varchar"},
+ {"server_non_unique_name", "varchar(255)"},
{"singleton_tag", "varchar"},
//////////////////////////////////////
// Blobs.
- {"bookmark_favicon", "blob"},
- {"server_bookmark_favicon", "blob"},
+ {"specifics", "blob"},
+ {"server_specifics", "blob"},
};
// At least enforce that there are equal number of column names and fields.
diff --git a/chrome/browser/sync/syncable/syncable_unittest.cc b/chrome/browser/sync/syncable/syncable_unittest.cc
index a03028d..407508a 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 "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"
#include "chrome/browser/sync/util/closure.h"
@@ -626,7 +627,6 @@ TEST_F(SyncableDirectoryTest, TestSimpleFieldsPreservedDuringSaveChanges) {
EntryKernel create_pre_save, update_pre_save;
EntryKernel create_post_save, update_post_save;
string create_name = "Create";
- string favicon_bytes = "PNG";
{
WriteTransaction trans(dir_.get(), UNITTEST, __FILE__, __LINE__);
@@ -634,10 +634,10 @@ TEST_F(SyncableDirectoryTest, TestSimpleFieldsPreservedDuringSaveChanges) {
MutableEntry update(&trans, CREATE_NEW_UPDATE_ITEM, update_id);
create.Put(IS_UNSYNCED, true);
update.Put(IS_UNAPPLIED_UPDATE, true);
- syncable::Blob fav(favicon_bytes.data(),
- favicon_bytes.data() + favicon_bytes.size());
- create.Put(BOOKMARK_FAVICON, fav);
- update.Put(BOOKMARK_FAVICON, fav);
+ sync_pb::EntitySpecifics specifics;
+ specifics.MutableExtension(sync_pb::bookmark)->set_favicon("PNG");
+ specifics.MutableExtension(sync_pb::bookmark)->set_url("http://nowhere");
+ create.Put(SPECIFICS, specifics);
create_pre_save = create.GetKernelCopy();
update_pre_save = update.GetKernelCopy();
create_id = create.Get(ID);
@@ -690,12 +690,12 @@ TEST_F(SyncableDirectoryTest, TestSimpleFieldsPreservedDuringSaveChanges) {
update_post_save.ref((StringField)i))
<< "String field #" << i << " changed during save/load";
}
- for ( ; i < BLOB_FIELDS_END; ++i) {
- EXPECT_EQ(create_pre_save.ref((BlobField)i),
- create_post_save.ref((BlobField)i))
+ for ( ; i < PROTO_FIELDS_END; ++i) {
+ EXPECT_EQ(create_pre_save.ref((ProtoField)i).SerializeAsString(),
+ create_post_save.ref((ProtoField)i).SerializeAsString())
<< "Blob field #" << i << " changed during save/load";
- EXPECT_EQ(update_pre_save.ref((BlobField)i),
- update_post_save.ref((BlobField)i))
+ EXPECT_EQ(update_pre_save.ref((ProtoField)i).SerializeAsString(),
+ update_post_save.ref((ProtoField)i).SerializeAsString())
<< "Blob field #" << i << " changed during save/load";
}
}