diff options
author | vishwath@google.com <vishwath@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-09 02:40:35 +0000 |
---|---|---|
committer | vishwath@google.com <vishwath@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-09 02:40:35 +0000 |
commit | 49c24fe8219c35d947e061c2d6aa102f8b04928a (patch) | |
tree | c9e1c6e436f4de1009e0428c5af3f3f5cb7f6c3d | |
parent | 01fda60d8a2ddcf7e317675364872fbe697fdb54 (diff) | |
download | chromium_src-49c24fe8219c35d947e061c2d6aa102f8b04928a.zip chromium_src-49c24fe8219c35d947e061c2d6aa102f8b04928a.tar.gz chromium_src-49c24fe8219c35d947e061c2d6aa102f8b04928a.tar.bz2 |
Changed DB to store node positions as Ordinals.
As part of the effort to move away from int64 based node positions,
changed the DB to store server_position_in_node as an ordinal
(represented by a varchar) instead. Also updated unittests and the
latest db version number (now at 81).
BUG=145412
Review URL: https://chromiumcodereview.appspot.com/10989063
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@160774 0039d316-1c4b-4281-b951-d872f2087c98
23 files changed, 578 insertions, 65 deletions
diff --git a/sync/engine/build_commit_command.cc b/sync/engine/build_commit_command.cc index 8e51ddb..bfa5b9c 100644 --- a/sync/engine/build_commit_command.cc +++ b/sync/engine/build_commit_command.cc @@ -22,6 +22,11 @@ #include "sync/syncable/write_transaction.h" #include "sync/util/time.h" +// TODO(vishwath): Remove this include after node positions have +// shifted to completely using Ordinals. +// See http://crbug.com/145412 . +#include "sync/internal_api/public/base/node_ordinal.h" + using std::set; using std::string; using std::vector; @@ -31,7 +36,7 @@ namespace syncer { using sessions::SyncSession; using syncable::Entry; using syncable::IS_DEL; -using syncable::SERVER_POSITION_IN_PARENT; +using syncable::SERVER_ORDINAL_IN_PARENT; using syncable::IS_UNAPPLIED_UPDATE; using syncable::IS_UNSYNCED; using syncable::Id; @@ -231,7 +236,7 @@ int64 BuildCommitCommand::FindAnchorPosition(syncable::IdField direction, syncable::GET_BY_ID, next_id); if (!next_entry.Get(IS_UNSYNCED) && !next_entry.Get(IS_UNAPPLIED_UPDATE)) { - return next_entry.Get(SERVER_POSITION_IN_PARENT); + return NodeOrdinalToInt64(next_entry.Get(SERVER_ORDINAL_IN_PARENT)); } next_id = next_entry.Get(direction); } diff --git a/sync/engine/process_commit_response_command.cc b/sync/engine/process_commit_response_command.cc index 2f84619..200a710 100644 --- a/sync/engine/process_commit_response_command.cc +++ b/sync/engine/process_commit_response_command.cc @@ -22,6 +22,11 @@ #include "sync/syncable/write_transaction.h" #include "sync/util/time.h" +// TODO(vishwath): Remove this include after node positions have +// shifted to completely using Ordinals. +// See http://crbug.com/145412 . +#include "sync/internal_api/public/base/node_ordinal.h" + using std::set; using std::string; using std::vector; @@ -45,7 +50,7 @@ using syncable::IS_UNSYNCED; using syncable::PARENT_ID; using syncable::SERVER_IS_DEL; using syncable::SERVER_PARENT_ID; -using syncable::SERVER_POSITION_IN_PARENT; +using syncable::SERVER_ORDINAL_IN_PARENT; using syncable::SERVER_VERSION; using syncable::SYNCER; using syncable::SYNCING; @@ -368,8 +373,9 @@ void ProcessCommitResponseCommand::UpdateServerFieldsAfterCommit( ProtoTimeToTime(committed_entry.mtime())); local_entry->Put(syncable::SERVER_CTIME, ProtoTimeToTime(committed_entry.ctime())); - local_entry->Put(syncable::SERVER_POSITION_IN_PARENT, - entry_response.position_in_parent()); + local_entry->Put(syncable::SERVER_ORDINAL_IN_PARENT, + Int64ToNodeOrdinal(entry_response.position_in_parent())); + // TODO(nick): The server doesn't set entry_response.server_parent_id in // practice; to update SERVER_PARENT_ID appropriately here we'd need to // get the post-commit ID of the parent indicated by @@ -417,7 +423,7 @@ void ProcessCommitResponseCommand::OverrideClientFieldsAfterCommit( if (entry_response.has_position_in_parent()) { // The SERVER_ field should already have been written. DCHECK_EQ(entry_response.position_in_parent(), - local_entry->Get(SERVER_POSITION_IN_PARENT)); + NodeOrdinalToInt64(local_entry->Get(SERVER_ORDINAL_IN_PARENT))); // We just committed successfully, so we assume that the position // value we got applies to the PARENT_ID we submitted. diff --git a/sync/engine/process_updates_command.cc b/sync/engine/process_updates_command.cc index de6be0f..0bf03f1 100644 --- a/sync/engine/process_updates_command.cc +++ b/sync/engine/process_updates_command.cc @@ -19,6 +19,11 @@ #include "sync/syncable/write_transaction.h" #include "sync/util/cryptographer.h" +// TODO(vishwath): Remove this include after node positions have +// shifted to completely using Ordinals. +// See http://crbug.com/145412 . +#include "sync/internal_api/public/base/node_ordinal.h" + using std::vector; namespace syncer { @@ -152,7 +157,8 @@ ServerUpdateProcessingResult ProcessUpdatesCommand::ProcessUpdate( (SyncableIdFromProto(update.parent_id_string()) == target_entry.Get(syncable::SERVER_PARENT_ID)) && (update.position_in_parent() == - target_entry.Get(syncable::SERVER_POSITION_IN_PARENT)) && + NodeOrdinalToInt64( + target_entry.Get(syncable::SERVER_ORDINAL_IN_PARENT))) && update.has_specifics() && update.specifics().has_encrypted() && !cryptographer->CanDecrypt(update.specifics().encrypted())) { sync_pb::EntitySpecifics prev_specifics = diff --git a/sync/engine/syncer.cc b/sync/engine/syncer.cc index af8e3a9..8e178cb 100644 --- a/sync/engine/syncer.cc +++ b/sync/engine/syncer.cc @@ -27,6 +27,11 @@ #include "sync/syncable/mutable_entry.h" #include "sync/syncable/syncable-inl.h" +// TODO(vishwath): Remove this include after node positions have +// shifted to completely using Ordinals. +// See http://crbug.com/145412 . +#include "sync/internal_api/public/base/node_ordinal.h" + using base::Time; using base::TimeDelta; using sync_pb::ClientCommand; @@ -43,7 +48,7 @@ using syncable::SERVER_IS_DIR; using syncable::SERVER_MTIME; using syncable::SERVER_NON_UNIQUE_NAME; using syncable::SERVER_PARENT_ID; -using syncable::SERVER_POSITION_IN_PARENT; +using syncable::SERVER_ORDINAL_IN_PARENT; using syncable::SERVER_SPECIFICS; using syncable::SERVER_VERSION; @@ -241,7 +246,7 @@ void CopyServerFields(syncable::Entry* src, syncable::MutableEntry* dest) { dest->Put(SERVER_IS_DEL, src->Get(SERVER_IS_DEL)); dest->Put(IS_UNAPPLIED_UPDATE, src->Get(IS_UNAPPLIED_UPDATE)); dest->Put(SERVER_SPECIFICS, src->Get(SERVER_SPECIFICS)); - dest->Put(SERVER_POSITION_IN_PARENT, src->Get(SERVER_POSITION_IN_PARENT)); + dest->Put(SERVER_ORDINAL_IN_PARENT, src->Get(SERVER_ORDINAL_IN_PARENT)); } void ClearServerData(syncable::MutableEntry* entry) { @@ -254,7 +259,7 @@ void ClearServerData(syncable::MutableEntry* entry) { entry->Put(SERVER_IS_DEL, false); entry->Put(IS_UNAPPLIED_UPDATE, false); entry->Put(SERVER_SPECIFICS, sync_pb::EntitySpecifics::default_instance()); - entry->Put(SERVER_POSITION_IN_PARENT, 0); + entry->Put(SERVER_ORDINAL_IN_PARENT, Int64ToNodeOrdinal(0)); } } // namespace syncer diff --git a/sync/engine/syncer_unittest.cc b/sync/engine/syncer_unittest.cc index c57def1..2519a6a 100644 --- a/sync/engine/syncer_unittest.cc +++ b/sync/engine/syncer_unittest.cc @@ -33,6 +33,7 @@ #include "sync/engine/traffic_recorder.h" #include "sync/internal_api/public/base/model_type.h" #include "sync/internal_api/public/engine/model_safe_worker.h" +#include "sync/internal_api/public/base/node_ordinal.h" #include "sync/protocol/bookmark_specifics.pb.h" #include "sync/protocol/nigori_specifics.pb.h" #include "sync/protocol/preference_specifics.pb.h" @@ -96,7 +97,7 @@ using syncable::PREV_ID; using syncable::BASE_SERVER_SPECIFICS; using syncable::SERVER_IS_DEL; using syncable::SERVER_PARENT_ID; -using syncable::SERVER_POSITION_IN_PARENT; +using syncable::SERVER_ORDINAL_IN_PARENT; using syncable::SERVER_SPECIFICS; using syncable::SERVER_VERSION; using syncable::UNIQUE_CLIENT_TAG; @@ -2224,7 +2225,7 @@ TEST_F(SyncerTest, CommitsUpdateDoesntAlterEntry) { SyncShareNudge(); syncable::Id id; int64 version; - int64 server_position_in_parent; + NodeOrdinal server_ordinal_in_parent; { syncable::ReadTransaction trans(FROM_HERE, directory()); Entry entry(&trans, syncable::GET_BY_HANDLE, entry_metahandle); @@ -2232,14 +2233,16 @@ TEST_F(SyncerTest, CommitsUpdateDoesntAlterEntry) { id = entry.Get(ID); EXPECT_TRUE(id.ServerKnows()); version = entry.Get(BASE_VERSION); - server_position_in_parent = entry.Get(SERVER_POSITION_IN_PARENT); + server_ordinal_in_parent = entry.Get(SERVER_ORDINAL_IN_PARENT); } sync_pb::SyncEntity* update = mock_server_->AddUpdateFromLastCommit(); EXPECT_EQ("Pete", update->name()); EXPECT_EQ(id.GetServerId(), update->id_string()); EXPECT_EQ(root_id_.GetServerId(), update->parent_id_string()); EXPECT_EQ(version, update->version()); - EXPECT_EQ(server_position_in_parent, update->position_in_parent()); + EXPECT_EQ( + NodeOrdinalToInt64(server_ordinal_in_parent), + update->position_in_parent()); SyncShareNudge(); { syncable::ReadTransaction trans(FROM_HERE, directory()); @@ -4490,7 +4493,9 @@ class SyncerPositionUpdateTest : public SyncerTest { Entry entry_with_id(&trans, GET_BY_ID, id); EXPECT_TRUE(entry_with_id.good()); EXPECT_EQ(prev_id, entry_with_id.Get(PREV_ID)); - EXPECT_EQ(i->first, entry_with_id.Get(SERVER_POSITION_IN_PARENT)); + EXPECT_EQ( + i->first, + NodeOrdinalToInt64(entry_with_id.Get(SERVER_ORDINAL_IN_PARENT))); if (next == position_map_.end()) { EXPECT_EQ(Id(), entry_with_id.Get(NEXT_ID)); } else { diff --git a/sync/engine/syncer_util.cc b/sync/engine/syncer_util.cc index 0dad3f8..cdc1b7a 100644 --- a/sync/engine/syncer_util.cc +++ b/sync/engine/syncer_util.cc @@ -29,6 +29,11 @@ #include "sync/util/cryptographer.h" #include "sync/util/time.h" +// TODO(vishwath): Remove this include after node positions have +// shifted to completely uing Ordinals. +// See http://crbug.com/145412 . +#include "sync/internal_api/public/base/node_ordinal.h" + namespace syncer { using syncable::BASE_VERSION; @@ -58,7 +63,7 @@ using syncable::SERVER_IS_DIR; using syncable::SERVER_MTIME; using syncable::SERVER_NON_UNIQUE_NAME; using syncable::SERVER_PARENT_ID; -using syncable::SERVER_POSITION_IN_PARENT; +using syncable::SERVER_ORDINAL_IN_PARENT; using syncable::SERVER_SPECIFICS; using syncable::SERVER_VERSION; using syncable::UNIQUE_CLIENT_TAG; @@ -351,7 +356,8 @@ void UpdateServerFieldsFromUpdate( target); } if (update.has_position_in_parent()) - target->Put(SERVER_POSITION_IN_PARENT, update.position_in_parent()); + target->Put(SERVER_ORDINAL_IN_PARENT, + Int64ToNodeOrdinal(update.position_in_parent())); target->Put(SERVER_IS_DEL, update.deleted()); // We only mark the entry as unapplied if its version is greater than the diff --git a/sync/syncable/directory.cc b/sync/syncable/directory.cc index 644a4e2..4ac17a5 100644 --- a/sync/syncable/directory.cc +++ b/sync/syncable/directory.cc @@ -8,6 +8,7 @@ #include "base/perftimer.h" #include "base/stl_util.h" #include "base/string_number_conversions.h" +#include "sync/internal_api/public/base/node_ordinal.h" #include "sync/internal_api/public/util/unrecoverable_error_handler.h" #include "sync/syncable/base_transaction.h" #include "sync/syncable/entry.h" @@ -50,10 +51,10 @@ bool ParentIdAndHandleIndexer::Comparator::operator() ( if (cmp != 0) return cmp < 0; - int64 a_position = a->ref(SERVER_POSITION_IN_PARENT); - int64 b_position = b->ref(SERVER_POSITION_IN_PARENT); - if (a_position != b_position) - return a_position < b_position; + const NodeOrdinal& a_position = a->ref(SERVER_ORDINAL_IN_PARENT); + const NodeOrdinal& b_position = b->ref(SERVER_ORDINAL_IN_PARENT); + if (!a_position.Equals(b_position)) + return a_position.LessThan(b_position); cmp = a->ref(ID).compare(b->ref(ID)); return cmp < 0; @@ -1132,8 +1133,11 @@ Id Directory::ComputePrevIdFromServerPosition( // Find the natural insertion point in the parent_id_child_index, and // work back from there, filtering out ineligible candidates. - ParentIdChildIndex::iterator sibling = LocateInParentChildIndex(lock, - parent_id, entry->ref(SERVER_POSITION_IN_PARENT), entry->ref(ID)); + ParentIdChildIndex::iterator sibling = LocateInParentChildIndex( + lock, + parent_id, + NodeOrdinalToInt64(entry->ref(SERVER_ORDINAL_IN_PARENT)), + entry->ref(ID)); ParentIdChildIndex::iterator first_sibling = GetParentChildIndexLowerBound(lock, parent_id); @@ -1176,7 +1180,8 @@ Directory::ParentIdChildIndex::iterator Directory::LocateInParentChildIndex( int64 position_in_parent, const Id& item_id_for_tiebreaking) { kernel_->needle.put(PARENT_ID, parent_id); - kernel_->needle.put(SERVER_POSITION_IN_PARENT, position_in_parent); + kernel_->needle.put(SERVER_ORDINAL_IN_PARENT, + Int64ToNodeOrdinal(position_in_parent)); kernel_->needle.put(ID, item_id_for_tiebreaking); return kernel_->parent_id_child_index->lower_bound(&kernel_->needle); } diff --git a/sync/syncable/directory_backing_store.cc b/sync/syncable/directory_backing_store.cc index 9b8e0db..fde0279 100644 --- a/sync/syncable/directory_backing_store.cc +++ b/sync/syncable/directory_backing_store.cc @@ -22,6 +22,7 @@ #include "sql/connection.h" #include "sql/statement.h" #include "sql/transaction.h" +#include "sync/internal_api/public/base/node_ordinal.h" #include "sync/protocol/bookmark_specifics.pb.h" #include "sync/protocol/sync.pb.h" #include "sync/syncable/syncable-inl.h" @@ -39,7 +40,7 @@ static const string::size_type kUpdateStatementBufferSize = 2048; // Increment this version whenever updating DB tables. extern const int32 kCurrentDBVersion; // Global visibility for our unittest. -const int32 kCurrentDBVersion = 80; +const int32 kCurrentDBVersion = 81; // Iterate over the fields of |entry| and bind each to |statement| for // updating. Returns the number of args bound. @@ -69,12 +70,17 @@ void BindFields(const EntryKernel& entry, entry.ref(static_cast<ProtoField>(i)).SerializeToString(&temp); statement->BindBlob(index++, temp.data(), temp.length()); } + for( ; i < ORDINAL_FIELDS_END; ++i) { + temp = entry.ref(static_cast<OrdinalField>(i)).ToInternalValue(); + statement->BindBlob(index++, temp.data(), temp.length()); + } } // The caller owns the returned EntryKernel*. Assumes the statement currently -// points to a valid row in the metas table. -EntryKernel* UnpackEntry(sql::Statement* statement) { - EntryKernel* kernel = new EntryKernel(); +// points to a valid row in the metas table. Returns NULL to indicate that +// it detected a corruption in the data on unpacking. +scoped_ptr<EntryKernel> UnpackEntry(sql::Statement* statement) { + scoped_ptr<EntryKernel> kernel(new EntryKernel()); DCHECK_EQ(statement->ColumnCount(), static_cast<int>(FIELD_COUNT)); int i = 0; for (i = BEGIN_FIELDS; i < INT64_FIELDS_END; ++i) { @@ -99,7 +105,21 @@ EntryKernel* UnpackEntry(sql::Statement* statement) { kernel->mutable_ref(static_cast<ProtoField>(i)).ParseFromArray( statement->ColumnBlob(i), statement->ColumnByteLength(i)); } - return kernel; + for( ; i < ORDINAL_FIELDS_END; ++i) { + std::string temp; + statement->ColumnBlobAsString(i, &temp); + NodeOrdinal unpacked_ord(temp); + + // Its safe to assume that an invalid ordinal is a sign that + // some external corruption has occurred. Return NULL to force + // a re-download of the sync data. + if(!unpacked_ord.IsValid()) { + DVLOG(1) << "Unpacked invalid ordinal. Signaling that the DB is corrupt"; + return scoped_ptr<EntryKernel>(NULL); + } + kernel->mutable_ref(static_cast<OrdinalField>(i)) = unpacked_ord; + } + return kernel.Pass(); } namespace { @@ -124,7 +144,7 @@ string ComposeCreateTableColumnSpecs() { 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) { + for (int i = BEGIN_FIELDS; i < FIELD_COUNT; ++i) { output->append(joiner); output->append(ColumnName(i)); joiner = ", "; @@ -323,6 +343,13 @@ bool DirectoryBackingStore::InitializeTables() { version_on_disk = 80; } + // Version 81 replaces the int64 server_position_in_parent_field + // with a blob server_ordinal_in_parent field. + if (version_on_disk == 80) { + if (MigrateVersion80To81()) + version_on_disk = 81; + } + // 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. @@ -428,8 +455,11 @@ bool DirectoryBackingStore::LoadEntries(MetahandlesIndex* entry_bucket) { sql::Statement s(db_->GetUniqueStatement(select.c_str())); while (s.Step()) { - EntryKernel *kernel = UnpackEntry(&s); - entry_bucket->insert(kernel); + scoped_ptr<EntryKernel> kernel = UnpackEntry(&s); + // A null kernel is evidence of external data corruption. + if (!kernel.get()) + return false; + entry_bucket->insert(kernel.release()); } return s.Succeeded(); } @@ -503,7 +533,7 @@ bool DirectoryBackingStore::SaveEntryToDB(const EntryKernel& entry) { values.append("VALUES "); const char* separator = "( "; int i = 0; - for (i = BEGIN_FIELDS; i < PROTO_FIELDS_END; ++i) { + for (i = BEGIN_FIELDS; i < FIELD_COUNT; ++i) { query.append(separator); values.append(separator); separator = ", "; @@ -968,6 +998,7 @@ bool DirectoryBackingStore::MigrateVersion78To79() { SetVersion(79); return true; } + bool DirectoryBackingStore::MigrateVersion79To80() { if (!db_->Execute( "ALTER TABLE share_info ADD COLUMN bag_of_chips BLOB")) @@ -982,6 +1013,36 @@ bool DirectoryBackingStore::MigrateVersion79To80() { return true; } +bool DirectoryBackingStore::MigrateVersion80To81() { + if(!db_->Execute( + "ALTER TABLE metas ADD COLUMN server_ordinal_in_parent BLOB")) + return false; + + sql::Statement get_positions(db_->GetUniqueStatement( + "SELECT metahandle, server_position_in_parent FROM metas")); + + sql::Statement put_ordinals(db_->GetUniqueStatement( + "UPDATE metas SET server_ordinal_in_parent = ?" + "WHERE metahandle = ?")); + + while(get_positions.Step()) { + int64 metahandle = get_positions.ColumnInt64(0); + int64 position = get_positions.ColumnInt64(1); + + const std::string& ordinal = Int64ToNodeOrdinal(position).ToInternalValue(); + put_ordinals.BindBlob(0, ordinal.data(), ordinal.length()); + put_ordinals.BindInt64(1, metahandle); + + if(!put_ordinals.Run()) + return false; + put_ordinals.Reset(true); + } + + SetVersion(81); + needs_column_refresh_ = true; + return true; +} + bool DirectoryBackingStore::CreateTables() { DVLOG(1) << "First run, creating tables"; // Create two little tables share_version and share_info @@ -1044,10 +1105,13 @@ bool DirectoryBackingStore::CreateTables() { const int64 now = TimeToProtoTime(base::Time::Now()); sql::Statement s(db_->GetUniqueStatement( "INSERT INTO metas " - "( id, metahandle, is_dir, ctime, mtime) " - "VALUES ( \"r\", 1, 1, ?, ?)")); + "( id, metahandle, is_dir, ctime, mtime, server_ordinal_in_parent) " + "VALUES ( \"r\", 1, 1, ?, ?, ?)")); s.BindInt64(0, now); s.BindInt64(1, now); + const std::string ord = + NodeOrdinal::CreateInitialOrdinal().ToInternalValue(); + s.BindBlob(2, ord.data(), ord.length()); if (!s.Run()) return false; diff --git a/sync/syncable/directory_backing_store.h b/sync/syncable/directory_backing_store.h index a441a86..21bc8c2 100644 --- a/sync/syncable/directory_backing_store.h +++ b/sync/syncable/directory_backing_store.h @@ -155,6 +155,7 @@ class DirectoryBackingStore : public base::NonThreadSafe { bool MigrateVersion77To78(); bool MigrateVersion78To79(); bool MigrateVersion79To80(); + bool MigrateVersion80To81(); scoped_ptr<sql::Connection> db_; sql::Statement save_entry_statement_; diff --git a/sync/syncable/directory_backing_store_unittest.cc b/sync/syncable/directory_backing_store_unittest.cc index 1864ba9..7174191 100644 --- a/sync/syncable/directory_backing_store_unittest.cc +++ b/sync/syncable/directory_backing_store_unittest.cc @@ -14,6 +14,7 @@ #include "base/string_number_conversions.h" #include "sql/connection.h" #include "sql/statement.h" +#include "sync/internal_api/public/base/node_ordinal.h" #include "sync/protocol/bookmark_specifics.pb.h" #include "sync/protocol/sync.pb.h" #include "sync/syncable/directory_backing_store.h" @@ -63,6 +64,8 @@ class MigrationTest : public testing::TestWithParam<int> { void SetUpVersion77Database(sql::Connection* connection); void SetUpVersion78Database(sql::Connection* connection); void SetUpVersion79Database(sql::Connection* connection); + void SetUpVersion80Database(sql::Connection* connection); + void SetUpVersion81Database(sql::Connection* connection); void SetUpCurrentDatabaseAndCheckVersion(sql::Connection* connection) { SetUpVersion77Database(connection); // Prepopulates data. @@ -1635,7 +1638,7 @@ void MigrationTest::SetUpVersion79Database(sql::Connection* connection) { ASSERT_TRUE(connection->BeginTransaction()); ASSERT_TRUE(connection->Execute( "CREATE TABLE share_version (id VARCHAR(128) primary key, data INT);" - "INSERT INTO 'share_version' VALUES('nick@chromium.org',78);" + "INSERT INTO 'share_version' VALUES('nick@chromium.org',79);" "CREATE TABLE models (model_id BLOB primary key, progress_marker BLOB, in" "itial_sync_ended BOOLEAN default 0);" "INSERT INTO 'models' VALUES(X'C2881000',X'0888810218B605',1);" @@ -1725,6 +1728,237 @@ void MigrationTest::SetUpVersion79Database(sql::Connection* connection) { ASSERT_TRUE(connection->CommitTransaction()); } +void MigrationTest::SetUpVersion80Database(sql::Connection* connection) { + ASSERT_TRUE(connection->is_open()); + ASSERT_TRUE(connection->BeginTransaction()); + ASSERT_TRUE(connection->Execute( + "CREATE TABLE share_version (id VARCHAR(128) primary key, data INT);" + "INSERT INTO 'share_version' VALUES('nick@chromium.org',80);" + "CREATE TABLE models (model_id BLOB primary key, progress_marker BLOB, in" + "itial_sync_ended BOOLEAN default 0);" + "INSERT INTO 'models' VALUES(X'C2881000',X'0888810218B605',1);" + "CREATE TABLE 'metas'(metahandle bigint primary key ON CONFLICT FAIL,base" + "_version bigint default -1,server_version bigint default 0,server_po" + "sition_in_parent bigint default 0,local_external_id bigint default 0" + ",mtime bigint default 0,server_mtime bigint default 0,ctime bigint d" + "efault 0,server_ctime bigint default 0,id varchar(255) default 'r',p" + "arent_id varchar(255) default 'r',server_parent_id varchar(255) defa" + "ult 'r',prev_id varchar(255) default 'r',next_id varchar(255) defaul" + "t 'r',is_unsynced bit default 0,is_unapplied_update bit default 0,is" + "_del bit default 0,is_dir bit default 0,server_is_dir bit default 0," + "server_is_del bit default 0,non_unique_name varchar,server_non_uniqu" + "e_name varchar(255),unique_server_tag varchar,unique_client_tag varc" + "har,specifics blob,server_specifics blob, base_server_specifics BLOB" + ");" + "INSERT INTO 'metas' VALUES(1,-1,0,0,0," META_PROTO_TIMES_VALS(1) ",'r','" + "r','r','r','r',0,0,0,1,0,0,NULL,NULL,NULL,NULL,X'',X'',NULL);" + "INSERT INTO 'metas' VALUES(2,669,669,-2097152,4," + META_PROTO_TIMES_VALS(2) ",'s_ID_2','s_ID_9','s_ID_9','s_ID_2','s_ID_" + "2',0,0,1,0,0,1,'Deleted Item','Deleted Item',NULL,NULL,X'C28810220A1" + "6687474703A2F2F7777772E676F6F676C652E636F6D2F12084141534741534741',X" + "'C28810260A17687474703A2F2F7777772E676F6F676C652E636F6D2F32120B41534" + "14447414447414447',NULL);" + "INSERT INTO 'metas' VALUES(4,681,681,-3145728,3," + META_PROTO_TIMES_VALS(4) ",'s_ID_4','s_ID_9','s_ID_9','s_ID_4','s_ID_" + "4',0,0,1,0,0,1,'Welcome to Chromium','Welcome to Chromium',NULL,NULL" + ",X'C28810350A31687474703A2F2F7777772E676F6F676C652E636F6D2F6368726F6" + "D652F696E746C2F656E2F77656C636F6D652E68746D6C1200',X'C28810350A31687" + "474703A2F2F7777772E676F6F676C652E636F6D2F6368726F6D652F696E746C2F656" + "E2F77656C636F6D652E68746D6C1200',NULL);" + "INSERT INTO 'metas' VALUES(5,677,677,1048576,7," + META_PROTO_TIMES_VALS(5) ",'s_ID_5','s_ID_9','s_ID_9','s_ID_5','s_ID_" + "5',0,0,1,0,0,1,'Google','Google',NULL,NULL,X'C28810220A16687474703A2" + "F2F7777772E676F6F676C652E636F6D2F12084147415347415347',X'C28810220A1" + "6687474703A2F2F7777772E676F6F676C652E636F6D2F12084147464447415347',N" + "ULL);" + "INSERT INTO 'metas' VALUES(6,694,694,-4194304,6," + META_PROTO_TIMES_VALS(6) ",'s_ID_6','s_ID_9','s_ID_9','r','r',0,0,0,1" + ",1,0,'The Internet','The Internet',NULL,NULL,X'C2881000',X'C2881000'" + ",NULL);" + "INSERT INTO 'metas' VALUES(7,663,663,1048576,0," + META_PROTO_TIMES_VALS(7) ",'s_ID_7','r','r','r','r',0,0,0,1,1,0,'Goog" + "le Chrome','Google Chrome','google_chrome',NULL,NULL,NULL,NULL);" + "INSERT INTO 'metas' VALUES(8,664,664,1048576,0," + META_PROTO_TIMES_VALS(8) ",'s_ID_8','s_ID_7','s_ID_7','r','r',0,0,0,1" + ",1,0,'Bookmarks','Bookmarks','google_chrome_bookmarks',NULL,X'C28810" + "00',X'C2881000',NULL);" + "INSERT INTO 'metas' VALUES(9,665,665,1048576,1," + META_PROTO_TIMES_VALS(9) ",'s_ID_9','s_ID_8','s_ID_8','r','s_ID_10',0" + ",0,0,1,1,0,'Bookmark Bar','Bookmark Bar','bookmark_bar',NULL,X'C2881" + "000',X'C2881000',NULL);" + "INSERT INTO 'metas' VALUES(10,666,666,2097152,2," + META_PROTO_TIMES_VALS(10) ",'s_ID_10','s_ID_8','s_ID_8','s_ID_9','r'," + "0,0,0,1,1,0,'Other Bookmarks','Other Bookmarks','other_bookmarks',NU" + "LL,X'C2881000',X'C2881000',NULL);" + "INSERT INTO 'metas' VALUES(11,683,683,-1048576,8," + META_PROTO_TIMES_VALS(11) ",'s_ID_11','s_ID_6','s_ID_6','r','s_ID_13'" + ",0,0,0,0,0,0,'Home (The Chromium Projects)','Home (The Chromium Proj" + "ects)',NULL,NULL,X'C28810220A18687474703A2F2F6465762E6368726F6D69756" + "D2E6F72672F1206414741545741',X'C28810290A1D687474703A2F2F6465762E636" + "8726F6D69756D2E6F72672F6F7468657212084146414756415346',NULL);" + "INSERT INTO 'metas' VALUES(12,685,685,0,9," + META_PROTO_TIMES_VALS(12) ",'s_ID_12','s_ID_6','s_ID_6','s_ID_13','s_" + "ID_14',0,0,0,1,1,0,'Extra Bookmarks','Extra Bookmarks',NULL,NULL,X'C" + "2881000',X'C2881000',NULL);" + "INSERT INTO 'metas' VALUES(13,687,687,-917504,10," + META_PROTO_TIMES_VALS(13) ",'s_ID_13','s_ID_6','s_ID_6','s_ID_11','s_" + "ID_12',0,0,0,0,0,0,'ICANN | Internet Corporation for Assigned Names " + "and Numbers','ICANN | Internet Corporation for Assigned Names and Nu" + "mbers',NULL,NULL,X'C28810240A15687474703A2F2F7777772E6963616E6E2E636" + "F6D2F120B504E474158463041414646',X'C28810200A15687474703A2F2F7777772" + "E6963616E6E2E636F6D2F120744414146415346',NULL);" + "INSERT INTO 'metas' VALUES(14,692,692,1048576,11," + META_PROTO_TIMES_VALS(14) ",'s_ID_14','s_ID_6','s_ID_6','s_ID_12','r'" + ",0,0,0,0,0,0,'The WebKit Open Source Project','The WebKit Open Sourc" + "e Project',NULL,NULL,X'C288101A0A12687474703A2F2F7765626B69742E6F726" + "72F1204504E4758',X'C288101C0A13687474703A2F2F7765626B69742E6F72672F7" + "81205504E473259',NULL);" + "CREATE TABLE 'share_info' (id TEXT primary key, name TEXT, store_birthda" + "y TEXT, db_create_version TEXT, db_create_time INT, next_id INT defa" + "ult -2, cache_guid TEXT , notification_state BLOB, bag_of_chips " + "blob);" + "INSERT INTO 'share_info' VALUES('nick@chromium.org','nick@chromium.org'," + "'c27e9f59-08ca-46f8-b0cc-f16a2ed778bb','Unknown',1263522064," + "-131078,'9010788312004066376x-6609234393368420856x',NULL, NULL);" + )); + ASSERT_TRUE(connection->CommitTransaction()); +} + + +// Helper definitions to create the version 81 DB tables. +namespace { + +const int V80_ROW_COUNT = 13; +const int64 V80_POSITIONS[V80_ROW_COUNT] = { + 0, + -2097152, + -3145728, + 1048576, + -4194304, + 1048576, + 1048576, + 1048576, + 2097152, + -1048576, + 0, + -917504, + 1048576 +}; + +std::string V81_Ordinal(int n) { + return Int64ToNodeOrdinal(V80_POSITIONS[n]).ToInternalValue(); +} + +} //namespace + +// Unlike the earlier versions, the rows for version 81 are generated +// programmatically to accurately handle unprintable characters for the +// server_ordinal_in_parent field. +void MigrationTest::SetUpVersion81Database(sql::Connection* connection) { + ASSERT_TRUE(connection->is_open()); + ASSERT_TRUE(connection->BeginTransaction()); + ASSERT_TRUE(connection->Execute( + "CREATE TABLE share_version (id VARCHAR(128) primary key, data INT);" + "INSERT INTO 'share_version' VALUES('nick@chromium.org',81);" + "CREATE TABLE models (model_id BLOB primary key, progress_marker BLOB, in" + "itial_sync_ended BOOLEAN default 0);" + "INSERT INTO 'models' VALUES(X'C2881000',X'0888810218B605',1);" + "CREATE TABLE 'metas'(metahandle bigint primary key ON CONFLICT FAIL,base" + "_version bigint default -1,server_version bigint default 0, " + "local_external_id bigint default 0" + ",mtime bigint default 0,server_mtime bigint default 0,ctime bigint d" + "efault 0,server_ctime bigint default 0,id varchar(255) default 'r',p" + "arent_id varchar(255) default 'r',server_parent_id varchar(255) defa" + "ult 'r',prev_id varchar(255) default 'r',next_id varchar(255) defaul" + "t 'r',is_unsynced bit default 0,is_unapplied_update bit default 0,is" + "_del bit default 0,is_dir bit default 0,server_is_dir bit default 0," + "server_is_del bit default 0,non_unique_name varchar,server_non_uniqu" + "e_name varchar(255),unique_server_tag varchar,unique_client_tag varc" + "har,specifics blob,server_specifics blob, base_server_specifics BLOB" + ", server_ordinal_in_parent blob);" + "CREATE TABLE 'share_info' (id TEXT primary key, name TEXT, store_birthda" + "y TEXT, db_create_version TEXT, db_create_time INT, next_id INT defa" + "ult -2, cache_guid TEXT , notification_state BLOB, bag_of_chips " + "blob);" + "INSERT INTO 'share_info' VALUES('nick@chromium.org','nick@chromium.org'," + "'c27e9f59-08ca-46f8-b0cc-f16a2ed778bb','Unknown',1263522064," + "-131078,'9010788312004066376x-6609234393368420856x',NULL, NULL);")); + + const char* insert_stmts[V80_ROW_COUNT] = { + "INSERT INTO 'metas' VALUES(1,-1,0,0," META_PROTO_TIMES_VALS(1) ",'r','" + "r','r','r','r',0,0,0,1,0,0,NULL,NULL,NULL,NULL,X'',X'',NULL,?);", + "INSERT INTO 'metas' VALUES(2,669,669,4," + META_PROTO_TIMES_VALS(2) ",'s_ID_2','s_ID_9','s_ID_9','s_ID_2','s_ID_" + "2',0,0,1,0,0,1,'Deleted Item','Deleted Item',NULL,NULL,X'C28810220A1" + "6687474703A2F2F7777772E676F6F676C652E636F6D2F12084141534741534741',X" + "'C28810260A17687474703A2F2F7777772E676F6F676C652E636F6D2F32120B41534" + "14447414447414447',NULL,?);", + "INSERT INTO 'metas' VALUES(4,681,681,3," + META_PROTO_TIMES_VALS(4) ",'s_ID_4','s_ID_9','s_ID_9','s_ID_4','s_ID_" + "4',0,0,1,0,0,1,'Welcome to Chromium','Welcome to Chromium',NULL,NULL" + ",X'C28810350A31687474703A2F2F7777772E676F6F676C652E636F6D2F6368726F6" + "D652F696E746C2F656E2F77656C636F6D652E68746D6C1200',X'C28810350A31687" + "474703A2F2F7777772E676F6F676C652E636F6D2F6368726F6D652F696E746C2F656" + "E2F77656C636F6D652E68746D6C1200',NULL,?);", + "INSERT INTO 'metas' VALUES(5,677,677,7," + META_PROTO_TIMES_VALS(5) ",'s_ID_5','s_ID_9','s_ID_9','s_ID_5','s_ID_" + "5',0,0,1,0,0,1,'Google','Google',NULL,NULL,X'C28810220A16687474703A2" + "F2F7777772E676F6F676C652E636F6D2F12084147415347415347',X'C28810220A1" + "6687474703A2F2F7777772E676F6F676C652E636F6D2F12084147464447415347',N" + "ULL,?);", + "INSERT INTO 'metas' VALUES(6,694,694,6," + META_PROTO_TIMES_VALS(6) ",'s_ID_6','s_ID_9','s_ID_9','r','r',0,0,0,1" + ",1,0,'The Internet','The Internet',NULL,NULL,X'C2881000',X'C2881000'" + ",NULL,?);", + "INSERT INTO 'metas' VALUES(7,663,663,0," + META_PROTO_TIMES_VALS(7) ",'s_ID_7','r','r','r','r',0,0,0,1,1,0,'Goog" + "le Chrome','Google Chrome','google_chrome',NULL,NULL,NULL,NULL,?);", + "INSERT INTO 'metas' VALUES(8,664,664,0," + META_PROTO_TIMES_VALS(8) ",'s_ID_8','s_ID_7','s_ID_7','r','r',0,0,0,1" + ",1,0,'Bookmarks','Bookmarks','google_chrome_bookmarks',NULL,X'C28810" + "00',X'C2881000',NULL,?);", + "INSERT INTO 'metas' VALUES(9,665,665,1," + META_PROTO_TIMES_VALS(9) ",'s_ID_9','s_ID_8','s_ID_8','r','s_ID_10',0" + ",0,0,1,1,0,'Bookmark Bar','Bookmark Bar','bookmark_bar',NULL,X'C2881" + "000',X'C2881000',NULL,?);", + "INSERT INTO 'metas' VALUES(10,666,666,2," + META_PROTO_TIMES_VALS(10) ",'s_ID_10','s_ID_8','s_ID_8','s_ID_9','r'," + "0,0,0,1,1,0,'Other Bookmarks','Other Bookmarks','other_bookmarks',NU" + "LL,X'C2881000',X'C2881000',NULL,?);", + "INSERT INTO 'metas' VALUES(11,683,683,8," + META_PROTO_TIMES_VALS(11) ",'s_ID_11','s_ID_6','s_ID_6','r','s_ID_13'" + ",0,0,0,0,0,0,'Home (The Chromium Projects)','Home (The Chromium Proj" + "ects)',NULL,NULL,X'C28810220A18687474703A2F2F6465762E6368726F6D69756" + "D2E6F72672F1206414741545741',X'C28810290A1D687474703A2F2F6465762E636" + "8726F6D69756D2E6F72672F6F7468657212084146414756415346',NULL,?);", + "INSERT INTO 'metas' VALUES(12,685,685,9," + META_PROTO_TIMES_VALS(12) ",'s_ID_12','s_ID_6','s_ID_6','s_ID_13','s_" + "ID_14',0,0,0,1,1,0,'Extra Bookmarks','Extra Bookmarks',NULL,NULL,X'C" + "2881000',X'C2881000',NULL,?);", + "INSERT INTO 'metas' VALUES(13,687,687,10," + META_PROTO_TIMES_VALS(13) ",'s_ID_13','s_ID_6','s_ID_6','s_ID_11','s_" + "ID_12',0,0,0,0,0,0,'ICANN | Internet Corporation for Assigned Names " + "and Numbers','ICANN | Internet Corporation for Assigned Names and Nu" + "mbers',NULL,NULL,X'C28810240A15687474703A2F2F7777772E6963616E6E2E636" + "F6D2F120B504E474158463041414646',X'C28810200A15687474703A2F2F7777772" + "E6963616E6E2E636F6D2F120744414146415346',NULL,?);", + "INSERT INTO 'metas' VALUES(14,692,692,11," + META_PROTO_TIMES_VALS(14) ",'s_ID_14','s_ID_6','s_ID_6','s_ID_12','r'" + ",0,0,0,0,0,0,'The WebKit Open Source Project','The WebKit Open Sourc" + "e Project',NULL,NULL,X'C288101A0A12687474703A2F2F7765626B69742E6F726" + "72F1204504E4758',X'C288101C0A13687474703A2F2F7765626B69742E6F72672F7" + "81205504E473259',NULL,?);" }; + + for (int i = 0; i < V80_ROW_COUNT; i++) { + sql::Statement s(connection->GetUniqueStatement(insert_stmts[i])); + std::string ord = V81_Ordinal(i); + s.BindBlob(0, ord.data(), ord.length()); + ASSERT_TRUE(s.Run()); + s.Reset(true); + } + ASSERT_TRUE(connection->CommitTransaction()); +} + TEST_F(DirectoryBackingStoreTest, MigrateVersion67To68) { sql::Connection connection; ASSERT_TRUE(connection.OpenInMemory()); @@ -2050,6 +2284,7 @@ TEST_F(DirectoryBackingStoreTest, MigrateVersion78To79) { STLElementDeleter<MetahandlesIndex> deleter(&entry_bucket); Directory::KernelLoadInfo load_info; + s.Clear(); ASSERT_TRUE(dbs->Load(&entry_bucket, &load_info)); EXPECT_LE(load_info.kernel_info.next_id, kInitialNextId - 65536); } @@ -2079,6 +2314,61 @@ TEST_F(DirectoryBackingStoreTest, MigrateVersion79To80) { EXPECT_EQ(serialized_chip_bag, load_info.kernel_info.bag_of_chips); } +TEST_F(DirectoryBackingStoreTest, MigrateVersion80To81) { + sql::Connection connection; + ASSERT_TRUE(connection.OpenInMemory()); + SetUpVersion80Database(&connection); + + sql::Statement s(connection.GetUniqueStatement( + "SELECT metahandle, server_position_in_parent " + "FROM metas WHERE unique_server_tag = 'google_chrome'")); + ASSERT_TRUE(s.Step()); + ASSERT_EQ(sql::COLUMN_TYPE_INTEGER, s.ColumnType(1)); + + scoped_ptr<TestDirectoryBackingStore> dbs( + new TestDirectoryBackingStore(GetUsername(), &connection)); + ASSERT_TRUE(dbs->MigrateVersion80To81()); + ASSERT_EQ(81, dbs->GetVersion()); + + // Test that ordinal values are preserved correctly. + sql::Statement new_s(connection.GetUniqueStatement( + "SELECT metahandle, server_ordinal_in_parent " + "FROM metas WHERE unique_server_tag = 'google_chrome'")); + ASSERT_TRUE(new_s.Step()); + ASSERT_EQ(sql::COLUMN_TYPE_BLOB, new_s.ColumnType(1)); + + std::string expected_ordinal = Int64ToNodeOrdinal(1048576).ToInternalValue(); + std::string actual_ordinal; + new_s.ColumnBlobAsString(1, &actual_ordinal); + ASSERT_EQ(expected_ordinal, actual_ordinal); +} + +TEST_F(DirectoryBackingStoreTest, DetectInvalidOrdinal) { + sql::Connection connection; + ASSERT_TRUE(connection.OpenInMemory()); + SetUpVersion81Database(&connection); + + scoped_ptr<TestDirectoryBackingStore> dbs( + new TestDirectoryBackingStore(GetUsername(), &connection)); + ASSERT_EQ(81, dbs->GetVersion()); + + // Insert row with bad ordinal. + const int64 now = TimeToProtoTime(base::Time::Now()); + sql::Statement s(connection.GetUniqueStatement( + "INSERT INTO metas " + "( id, metahandle, is_dir, ctime, mtime, server_ordinal_in_parent) " + "VALUES( \"c-invalid\", 9999, 1, ?, ?, \" \")")); + s.BindInt64(0, now); + s.BindInt64(1, now); + ASSERT_TRUE(s.Run()); + + // Trying to unpack this entry should signal that the DB is corrupted. + MetahandlesIndex entry_bucket; + Directory::KernelLoadInfo kernel_load_info; + ASSERT_EQ(FAILED_DATABASE_CORRUPT, + dbs->Load(&entry_bucket, &kernel_load_info)); +} + TEST_P(MigrationTest, ToCurrentVersion) { sql::Connection connection; ASSERT_TRUE(connection.OpenInMemory()); @@ -2122,6 +2412,9 @@ TEST_P(MigrationTest, ToCurrentVersion) { case 79: SetUpVersion79Database(&connection); break; + case 80: + SetUpVersion80Database(&connection); + break; default: // If you see this error, it may mean that you've increased the // database version number but you haven't finished adding unit tests diff --git a/sync/syncable/entry.cc b/sync/syncable/entry.cc index ba64299..5593426 100644 --- a/sync/syncable/entry.cc +++ b/sync/syncable/entry.cc @@ -6,7 +6,7 @@ #include <iomanip> -#include "net/base/escape.h" +#include "base/json/string_escape.h" #include "sync/syncable/base_transaction.h" #include "sync/syncable/blob.h" #include "sync/syncable/directory.h" @@ -127,9 +127,16 @@ std::ostream& operator<<(std::ostream& os, const Entry& entry) { os << g_metas_columns[i].name << ": " << field << ", "; } for ( ; i < PROTO_FIELDS_END; ++i) { + std::string escaped_str; + base::JsonDoubleQuote( + kernel->ref(static_cast<ProtoField>(i)).SerializeAsString(), + false, + &escaped_str); + os << g_metas_columns[i].name << ": " << escaped_str << ", "; + } + for ( ; i < ORDINAL_FIELDS_END; ++i) { os << g_metas_columns[i].name << ": " - << net::EscapePath( - kernel->ref(static_cast<ProtoField>(i)).SerializeAsString()) + << kernel->ref(static_cast<OrdinalField>(i)).ToDebugString() << ", "; } os << "TempFlags: "; diff --git a/sync/syncable/entry.h b/sync/syncable/entry.h index a755758..1a2609c 100644 --- a/sync/syncable/entry.h +++ b/sync/syncable/entry.h @@ -91,6 +91,10 @@ class Entry { DCHECK(kernel_); return kernel_->ref(field); } + inline const NodeOrdinal& Get(OrdinalField field) const { + DCHECK(kernel_); + return kernel_->ref(field); + } inline bool Get(BitTemp field) const { DCHECK(kernel_); return kernel_->ref(field); diff --git a/sync/syncable/entry_kernel.cc b/sync/syncable/entry_kernel.cc index 550fbb2..ab3de95 100644 --- a/sync/syncable/entry_kernel.cc +++ b/sync/syncable/entry_kernel.cc @@ -69,6 +69,10 @@ StringValue* IdToValue(const Id& id) { return id.ToValue(); } +StringValue* OrdinalToValue(const NodeOrdinal& ord) { + return Value::CreateStringValue(ord.ToDebugString()); +} + } // namespace DictionaryValue* EntryKernel::ToValue() const { @@ -123,6 +127,11 @@ DictionaryValue* EntryKernel::ToValue() const { &GetProtoFieldString, &EntitySpecificsToValue, PROTO_FIELDS_BEGIN, PROTO_FIELDS_END - 1); + // Ordinal fields + SetFieldValues(*this, kernel_info, + &GetOrdinalFieldString, &OrdinalToValue, + ORDINAL_FIELDS_BEGIN, ORDINAL_FIELDS_END - 1); + // Bit temps. SetFieldValues(*this, kernel_info, &GetBitTempString, &Value::CreateBooleanValue, diff --git a/sync/syncable/entry_kernel.h b/sync/syncable/entry_kernel.h index 383ffd7..e8c0924 100644 --- a/sync/syncable/entry_kernel.h +++ b/sync/syncable/entry_kernel.h @@ -8,6 +8,7 @@ #include "base/time.h" #include "base/values.h" #include "sync/internal_api/public/base/model_type.h" +#include "sync/internal_api/public/base/node_ordinal.h" #include "sync/internal_api/public/util/immutable.h" #include "sync/protocol/sync.pb.h" #include "sync/syncable/metahandle_set.h" @@ -21,8 +22,8 @@ namespace syncable { // - EntryKernel struct in this file // - syncable_columns.h // - syncable_enum_conversions{.h,.cc,_unittest.cc} -// - EntryKernel::EntryKernel(), EntryKernel::ToValue(), operator<< -// for Entry in syncable.cc +// - EntryKernel::EntryKernel(), EntryKernel::ToValue() in entry_kernel.cc +// - operator<< in Entry.cc // - BindFields() and UnpackEntry() in directory_backing_store.cc // - TestSimpleFieldsPreservedDuringSaveChanges in syncable_unittest.cc @@ -47,14 +48,8 @@ enum BaseVersion { enum Int64Field { SERVER_VERSION = BASE_VERSION + 1, - - // A numeric position value that indicates the relative ordering of - // this object among its siblings. - SERVER_POSITION_IN_PARENT, - LOCAL_EXTERNAL_ID, // ID of an item in the external local storage that this // entry is associated with. (such as bookmarks.js) - INT64_FIELDS_END }; @@ -143,9 +138,22 @@ enum ProtoField { }; enum { - FIELD_COUNT = PROTO_FIELDS_END, + PROTO_FIELDS_COUNT = PROTO_FIELDS_END - PROTO_FIELDS_BEGIN, + ORDINAL_FIELDS_BEGIN = PROTO_FIELDS_END +}; + +enum OrdinalField { + // An Ordinal that identifies the relative ordering of this object + // among its siblings. + SERVER_ORDINAL_IN_PARENT = ORDINAL_FIELDS_BEGIN, + ORDINAL_FIELDS_END +}; + +enum { + ORDINAL_FIELDS_COUNT = ORDINAL_FIELDS_END - ORDINAL_FIELDS_BEGIN, + FIELD_COUNT = ORDINAL_FIELDS_END - BEGIN_FIELDS, // Past this point we have temporaries, stored in memory only. - BEGIN_TEMPS = PROTO_FIELDS_END, + BEGIN_TEMPS = ORDINAL_FIELDS_END, BIT_TEMPS_BEGIN = BEGIN_TEMPS, }; @@ -160,9 +168,6 @@ enum { BIT_TEMPS_COUNT = BIT_TEMPS_END - BIT_TEMPS_BEGIN }; -enum { - PROTO_FIELDS_COUNT = PROTO_FIELDS_END - PROTO_FIELDS_BEGIN -}; struct EntryKernel { @@ -172,6 +177,7 @@ struct EntryKernel { int64 int64_fields[INT64_FIELDS_COUNT]; base::Time time_fields[TIME_FIELDS_COUNT]; Id id_fields[ID_FIELDS_COUNT]; + NodeOrdinal ordinal_fields[ORDINAL_FIELDS_COUNT]; std::bitset<BIT_FIELDS_COUNT> bit_fields; std::bitset<BIT_TEMPS_COUNT> bit_temps; @@ -239,6 +245,9 @@ struct EntryKernel { inline void put(ProtoField field, const sync_pb::EntitySpecifics& value) { specifics_fields[field - PROTO_FIELDS_BEGIN].CopyFrom(value); } + inline void put(OrdinalField field, const NodeOrdinal& value) { + ordinal_fields[field - ORDINAL_FIELDS_BEGIN] = value; + } inline void put(BitTemp field, bool value) { bit_temps[field - BIT_TEMPS_BEGIN] = value; } @@ -274,6 +283,9 @@ struct EntryKernel { inline const sync_pb::EntitySpecifics& ref(ProtoField field) const { return specifics_fields[field - PROTO_FIELDS_BEGIN]; } + inline const NodeOrdinal& ref(OrdinalField field) const { + return ordinal_fields[field - ORDINAL_FIELDS_BEGIN]; + } inline bool ref(BitTemp field) const { return bit_temps[field - BIT_TEMPS_BEGIN]; } @@ -288,6 +300,9 @@ struct EntryKernel { inline Id& mutable_ref(IdField field) { return id_fields[field - ID_FIELDS_BEGIN]; } + inline NodeOrdinal& mutable_ref(OrdinalField field) { + return ordinal_fields[field - ORDINAL_FIELDS_BEGIN]; + } ModelType GetServerModelType() const; diff --git a/sync/syncable/mutable_entry.cc b/sync/syncable/mutable_entry.cc index 869320f..5090ad1a9 100644 --- a/sync/syncable/mutable_entry.cc +++ b/sync/syncable/mutable_entry.cc @@ -5,6 +5,7 @@ #include "sync/syncable/mutable_entry.h" #include "base/memory/scoped_ptr.h" +#include "sync/internal_api/public/base/node_ordinal.h" #include "sync/syncable/directory.h" #include "sync/syncable/scoped_index_updater.h" #include "sync/syncable/scoped_kernel_lock.h" @@ -41,6 +42,7 @@ void MutableEntry::Init(WriteTransaction* trans, const Id& parent_id, kernel->put(MTIME, now); // We match the database defaults here kernel->put(BASE_VERSION, CHANGES_VERSION); + kernel->put(SERVER_ORDINAL_IN_PARENT, NodeOrdinal::CreateInitialOrdinal()); if (!trans->directory()->InsertEntry(trans, kernel.get())) { return; // We failed inserting, nothing more to do. } @@ -66,6 +68,7 @@ MutableEntry::MutableEntry(WriteTransaction* trans, CreateNewUpdateItem, kernel->put(ID, id); kernel->put(META_HANDLE, trans->directory_->NextMetahandle()); kernel->mark_dirty(trans->directory_->kernel_->dirty_metahandles); + kernel->put(SERVER_ORDINAL_IN_PARENT, NodeOrdinal::CreateInitialOrdinal()); kernel->put(IS_DEL, true); // We match the database defaults here kernel->put(BASE_VERSION, CHANGES_VERSION); @@ -146,13 +149,7 @@ bool MutableEntry::Put(Int64Field field, const int64& value) { write_transaction_->SaveOriginal(kernel_); if (kernel_->ref(field) != value) { ScopedKernelLock lock(dir()); - if (SERVER_POSITION_IN_PARENT == field) { - ScopedIndexUpdater<ParentIdAndHandleIndexer> updater(lock, kernel_, - dir()->kernel_->parent_id_child_index); - kernel_->put(field, value); - } else { - kernel_->put(field, value); - } + kernel_->put(field, value); kernel_->mark_dirty(dir()->kernel_->dirty_metahandles); } return true; @@ -190,6 +187,24 @@ bool MutableEntry::Put(IdField field, const Id& value) { return true; } +bool MutableEntry::Put(OrdinalField field, const NodeOrdinal& value) { + DCHECK(kernel_); + DCHECK(value.IsValid()); + write_transaction_->SaveOriginal(kernel_); + if(!kernel_->ref(field).Equals(value)) { + ScopedKernelLock lock(dir()); + if (SERVER_ORDINAL_IN_PARENT == field) { + ScopedIndexUpdater<ParentIdAndHandleIndexer> updater( + lock, kernel_, dir()->kernel_->parent_id_child_index); + kernel_->put(field, value); + } else { + kernel_->put(field, value); + } + kernel_->mark_dirty(dir()->kernel_->dirty_metahandles); + } + return true; +} + void MutableEntry::PutParentIdPropertyOnly(const Id& parent_id) { write_transaction_->SaveOriginal(kernel_); dir()->ReindexParentId(write_transaction(), kernel_, parent_id); diff --git a/sync/syncable/mutable_entry.h b/sync/syncable/mutable_entry.h index 60968e9..f01fcf6 100644 --- a/sync/syncable/mutable_entry.h +++ b/sync/syncable/mutable_entry.h @@ -5,6 +5,7 @@ #ifndef SYNC_SYNCABLE_MUTABLE_ENTRY_H_ #define SYNC_SYNCABLE_MUTABLE_ENTRY_H_ +#include "sync/internal_api/public/base/node_ordinal.h" #include "sync/syncable/entry.h" #include "sync/syncable/metahandle_set.h" @@ -49,6 +50,7 @@ class MutableEntry : public Entry { bool Put(Int64Field field, const int64& value); bool Put(TimeField field, const base::Time& value); bool Put(IdField field, const Id& value); + bool Put(OrdinalField field, const NodeOrdinal& value); // Do a simple property-only update if the PARENT_ID field. Use with caution. // diff --git a/sync/syncable/syncable_columns.h b/sync/syncable/syncable_columns.h index a6152c4..f5d48e0 100644 --- a/sync/syncable/syncable_columns.h +++ b/sync/syncable/syncable_columns.h @@ -16,14 +16,13 @@ struct ColumnSpec { const char* spec; }; -// Must be in exact same order as fields in syncable. +// Must be in exact same order as fields in entry_kernel.h. static const ColumnSpec g_metas_columns[] = { ////////////////////////////////////// // int64s {"metahandle", "bigint primary key ON CONFLICT FAIL"}, {"base_version", "bigint default " CHANGES_VERSION_STRING}, {"server_version", "bigint default 0"}, - {"server_position_in_parent", "bigint default 0"}, // This is the item ID that we store for the embedding application. {"local_external_id", "bigint default 0"}, // These timestamps are kept in the same format as that of the @@ -54,10 +53,13 @@ static const ColumnSpec g_metas_columns[] = { {"unique_server_tag", "varchar"}, {"unique_client_tag", "varchar"}, ////////////////////////////////////// - // Blobs. + // Blobs (serialized protos). {"specifics", "blob"}, {"server_specifics", "blob"}, - {"base_server_specifics", "blob"} + {"base_server_specifics", "blob"}, + ////////////////////////////////////// + // Blobs (ordinals). + {"server_ordinal_in_parent", "blob"}, }; // At least enforce that there are equal number of column names and fields. diff --git a/sync/syncable/syncable_enum_conversions.cc b/sync/syncable/syncable_enum_conversions.cc index 9a8b892..3fdd7a8 100644 --- a/sync/syncable/syncable_enum_conversions.cc +++ b/sync/syncable/syncable_enum_conversions.cc @@ -49,7 +49,6 @@ const char* GetInt64FieldString(Int64Field int64_field) { BASE_VERSION + 1, INT64_FIELDS_END - 1); switch (int64_field) { ENUM_CASE(SERVER_VERSION); - ENUM_CASE(SERVER_POSITION_IN_PARENT); ENUM_CASE(LOCAL_EXTERNAL_ID); case INT64_FIELDS_END: break; } @@ -148,6 +147,17 @@ const char* GetProtoFieldString(ProtoField proto_field) { return ""; } +const char* GetOrdinalFieldString(OrdinalField ordinal_field) { + ASSERT_ENUM_BOUNDS(SERVER_ORDINAL_IN_PARENT, SERVER_ORDINAL_IN_PARENT, + ORDINAL_FIELDS_BEGIN, ORDINAL_FIELDS_END - 1); + switch(ordinal_field) { + ENUM_CASE(SERVER_ORDINAL_IN_PARENT); + case ORDINAL_FIELDS_END: break; + } + NOTREACHED(); + return ""; +} + const char* GetBitTempString(BitTemp bit_temp) { ASSERT_ENUM_BOUNDS(SYNCING, SYNCING, BIT_TEMPS_BEGIN, BIT_TEMPS_END - 1); diff --git a/sync/syncable/syncable_enum_conversions.h b/sync/syncable/syncable_enum_conversions.h index 6127a33..5ab66f0 100644 --- a/sync/syncable/syncable_enum_conversions.h +++ b/sync/syncable/syncable_enum_conversions.h @@ -38,6 +38,8 @@ const char* GetStringFieldString(StringField string_field); const char* GetProtoFieldString(ProtoField proto_field); +const char* GetOrdinalFieldString(OrdinalField ordinal_field); + const char* GetBitTempString(BitTemp bit_temp); } // namespace syncable diff --git a/sync/syncable/syncable_enum_conversions_unittest.cc b/sync/syncable/syncable_enum_conversions_unittest.cc index 636d555..eac4a37 100644 --- a/sync/syncable/syncable_enum_conversions_unittest.cc +++ b/sync/syncable/syncable_enum_conversions_unittest.cc @@ -77,6 +77,11 @@ TEST_F(SyncableEnumConversionsTest, GetProtoFieldString) { GetProtoFieldString, PROTO_FIELDS_BEGIN, PROTO_FIELDS_END - 1); } +TEST_F(SyncableEnumConversionsTest, GetOrdinalFieldString) { + TestEnumStringFunction( + GetOrdinalFieldString, ORDINAL_FIELDS_BEGIN, ORDINAL_FIELDS_END - 1); +} + TEST_F(SyncableEnumConversionsTest, GetBitTempString) { TestEnumStringFunction( GetBitTempString, BIT_TEMPS_BEGIN, BIT_TEMPS_END - 1); diff --git a/sync/syncable/syncable_id.h b/sync/syncable/syncable_id.h index 95157ab..64fc7f6 100644 --- a/sync/syncable/syncable_id.h +++ b/sync/syncable/syncable_id.h @@ -10,6 +10,7 @@ #include <sstream> #include <string> +#include "base/memory/scoped_ptr.h" #include "base/hash_tables.h" class MockConnectionManager; @@ -111,7 +112,7 @@ class Id { static Id GetLeastIdForLexicographicComparison(); private: - friend EntryKernel* UnpackEntry(sql::Statement* statement); + friend scoped_ptr<EntryKernel> UnpackEntry(sql::Statement* statement); friend void BindFields(const EntryKernel& entry, sql::Statement* statement); friend std::ostream& operator<<(std::ostream& out, const Id& id); diff --git a/sync/syncable/syncable_unittest.cc b/sync/syncable/syncable_unittest.cc index f69347b..65cedc3 100644 --- a/sync/syncable/syncable_unittest.cc +++ b/sync/syncable/syncable_unittest.cc @@ -4,6 +4,7 @@ #include <string> +#include "base/basictypes.h" #include "base/compiler_specific.h" #include "base/file_path.h" #include "base/file_util.h" @@ -17,6 +18,7 @@ #include "base/test/values_test_util.h" #include "base/threading/platform_thread.h" #include "base/values.h" +#include "sync/internal_api/public/base/node_ordinal.h" #include "sync/protocol/bookmark_specifics.pb.h" #include "sync/syncable/directory_backing_store.h" #include "sync/syncable/directory_change_delegate.h" @@ -1366,6 +1368,39 @@ TEST_F(SyncableDirectoryTest, OldClientLeftUnsyncedDeletedLocalItem) { } } +TEST_F(SyncableDirectoryTest, OrdinalWithNullSurvivesSaveAndReload) { + TestIdFactory id_factory; + Id null_child_id; + const char null_cstr[] = "\0null\0test"; + std::string null_str(null_cstr, arraysize(null_cstr) - 1); + NodeOrdinal null_ord = NodeOrdinal(null_str); + + { + WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); + + MutableEntry parent(&trans, CREATE, id_factory.root(), "parent"); + parent.Put(IS_DIR, true); + parent.Put(IS_UNSYNCED, true); + + MutableEntry child(&trans, CREATE, parent.Get(ID), "child"); + child.Put(IS_UNSYNCED, true); + child.Put(SERVER_ORDINAL_IN_PARENT, null_ord); + + null_child_id = child.Get(ID); + } + + EXPECT_EQ(OPENED, SimulateSaveAndReloadDir()); + + { + ReadTransaction trans(FROM_HERE, dir_.get()); + + Entry null_ordinal_child(&trans, GET_BY_ID, null_child_id); + EXPECT_TRUE( + null_ord.Equals(null_ordinal_child.Get(SERVER_ORDINAL_IN_PARENT))); + } + +} + // An OnDirectoryBackingStore that can be set to always fail SaveChanges. class TestBackingStore : public OnDiskDirectoryBackingStore { public: @@ -1714,6 +1749,14 @@ TEST_F(OnDiskSyncableDirectoryTest, update_post_save.ref((ProtoField)i).SerializeAsString()) << "Blob field #" << i << " changed during save/load"; } + for ( ; i < ORDINAL_FIELDS_END; ++i) { + EXPECT_EQ(create_pre_save.ref((OrdinalField)i).ToInternalValue(), + create_post_save.ref((OrdinalField)i).ToInternalValue()) + << "Blob field #" << i << " changed during save/load"; + EXPECT_EQ(update_pre_save.ref((OrdinalField)i).ToInternalValue(), + update_post_save.ref((OrdinalField)i).ToInternalValue()) + << "Blob field #" << i << " changed during save/load"; + } } TEST_F(OnDiskSyncableDirectoryTest, TestSaveChangesFailure) { diff --git a/sync/test/test_directory_backing_store.h b/sync/test/test_directory_backing_store.h index 612e728..b0e230a 100644 --- a/sync/test/test_directory_backing_store.h +++ b/sync/test/test_directory_backing_store.h @@ -42,6 +42,8 @@ class TestDirectoryBackingStore : public DirectoryBackingStore { FRIEND_TEST_ALL_PREFIXES(DirectoryBackingStoreTest, MigrateVersion77To78); FRIEND_TEST_ALL_PREFIXES(DirectoryBackingStoreTest, MigrateVersion78To79); FRIEND_TEST_ALL_PREFIXES(DirectoryBackingStoreTest, MigrateVersion79To80); + FRIEND_TEST_ALL_PREFIXES(DirectoryBackingStoreTest, MigrateVersion80To81); + FRIEND_TEST_ALL_PREFIXES(DirectoryBackingStoreTest, DetectInvalidOrdinal); FRIEND_TEST_ALL_PREFIXES(DirectoryBackingStoreTest, ModelTypeIds); FRIEND_TEST_ALL_PREFIXES(DirectoryBackingStoreTest, Corruption); FRIEND_TEST_ALL_PREFIXES(DirectoryBackingStoreTest, DeleteEntries); |