Chromium Code Reviews| Index: chrome/browser/sync/syncable/syncable.cc |
| diff --git a/chrome/browser/sync/syncable/syncable.cc b/chrome/browser/sync/syncable/syncable.cc |
| index 2a8a83e5773ee3e055b9b8d46221d0d952ce50ab..17e64cc6b51b18b57cc52bf26f14ee47b364d0bc 100644 |
| --- a/chrome/browser/sync/syncable/syncable.cc |
| +++ b/chrome/browser/sync/syncable/syncable.cc |
| @@ -126,6 +126,15 @@ class ScopedIndexUpdater { |
| typename Index<Indexer>::Set* const index_; |
| }; |
| +// Helper function to add an item to the index, if it ought to be added. |
| +template<typename Indexer> |
| +void InitializeIndexEntry(EntryKernel* entry, |
| + typename Index<Indexer>::Set* index) { |
| + if (Indexer::ShouldInclude(entry)) { |
| + index->insert(entry); |
| + } |
| +} |
| + |
| } // namespace |
| /////////////////////////////////////////////////////////////////////////// |
| @@ -136,22 +145,27 @@ bool ClientTagIndexer::ShouldInclude(const EntryKernel* a) { |
| return !a->ref(UNIQUE_CLIENT_TAG).empty(); |
| } |
| -class ParentIdAndHandleIndexer::Comparator { |
| - public: |
| - bool operator() (const syncable::EntryKernel* a, |
| - const syncable::EntryKernel* b) const { |
| - int cmp = a->ref(PARENT_ID).compare(b->ref(PARENT_ID)); |
| - if (cmp != 0) |
| - return cmp < 0; |
| +bool ParentIdAndHandleIndexer::Comparator::operator() ( |
| + const syncable::EntryKernel* a, |
| + const syncable::EntryKernel* b) const { |
| + int cmp = a->ref(PARENT_ID).compare(b->ref(PARENT_ID)); |
| + if (cmp != 0) |
| + return cmp < 0; |
| - // Meta handles are immutable per entry so this is ideal. |
| - return a->ref(META_HANDLE) < b->ref(META_HANDLE); |
| - } |
| -}; |
| + 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; |
| + |
| + cmp = a->ref(ID).compare(b->ref(ID)); |
| + return cmp < 0; |
| +} |
| // static |
| bool ParentIdAndHandleIndexer::ShouldInclude(const EntryKernel* a) { |
| - return !a->ref(IS_DEL); |
| + // This index excludes deleted items and the root item. The root |
| + // item is excluded so that it doesn't show up as a child of itself. |
| + return !a->ref(IS_DEL) && !a->ref(ID).IsRoot(); |
| } |
| /////////////////////////////////////////////////////////////////////////// |
| @@ -263,12 +277,10 @@ void Directory::InitializeIndices() { |
| MetahandlesIndex::iterator it = kernel_->metahandles_index->begin(); |
| for (; it != kernel_->metahandles_index->end(); ++it) { |
| EntryKernel* entry = *it; |
| - if (!entry->ref(IS_DEL)) |
| - kernel_->parent_id_child_index->insert(entry); |
| - kernel_->ids_index->insert(entry); |
| - if (!entry->ref(UNIQUE_CLIENT_TAG).empty()) { |
| - kernel_->client_tag_index->insert(entry); |
| - } |
| + InitializeIndexEntry<ParentIdAndHandleIndexer>(entry, |
| + kernel_->parent_id_child_index); |
| + InitializeIndexEntry<IdIndexer>(entry, kernel_->ids_index); |
| + InitializeIndexEntry<ClientTagIndexer>(entry, kernel_->client_tag_index); |
| if (entry->ref(IS_UNSYNCED)) |
| kernel_->unsynced_metahandles->insert(entry->ref(META_HANDLE)); |
| if (entry->ref(IS_UNAPPLIED_UPDATE)) |
| @@ -381,62 +393,17 @@ EntryKernel* Directory::GetEntryByHandle(int64 metahandle, |
| return NULL; |
| } |
| -// An interface to specify the details of which children |
| -// GetChildHandles() is looking for. |
| -// TODO(chron): Clean this up into one function to get child handles |
| -struct PathMatcher { |
| - explicit PathMatcher(const Id& parent_id) : parent_id_(parent_id) { } |
| - virtual ~PathMatcher() { } |
| - |
| - typedef Directory::ParentIdChildIndex Index; |
| - virtual Index::iterator lower_bound(Index* index) = 0; |
| - virtual Index::iterator upper_bound(Index* index) = 0; |
| - const Id parent_id_; |
| - EntryKernel needle_; |
| -}; |
| - |
| -// Matches all children. |
| -// TODO(chron): Unit test this by itself |
| -struct AllPathsMatcher : public PathMatcher { |
| - explicit AllPathsMatcher(const Id& parent_id) : PathMatcher(parent_id) { |
| - } |
| - |
| - virtual Index::iterator lower_bound(Index* index) { |
| - needle_.put(PARENT_ID, parent_id_); |
| - needle_.put(META_HANDLE, std::numeric_limits<int64>::min()); |
| - return index->lower_bound(&needle_); |
| - } |
| - |
| - virtual Index::iterator upper_bound(Index* index) { |
| - needle_.put(PARENT_ID, parent_id_); |
| - needle_.put(META_HANDLE, std::numeric_limits<int64>::max()); |
| - return index->upper_bound(&needle_); |
| - } |
| -}; |
| - |
| void Directory::GetChildHandles(BaseTransaction* trans, const Id& parent_id, |
| Directory::ChildHandles* result) { |
| - AllPathsMatcher matcher(parent_id); |
| - return GetChildHandlesImpl(trans, parent_id, &matcher, result); |
| -} |
| - |
| -void Directory::GetChildHandlesImpl(BaseTransaction* trans, const Id& parent_id, |
| - PathMatcher* matcher, |
| - Directory::ChildHandles* result) { |
| CHECK(this == trans->directory()); |
| result->clear(); |
| { |
| ScopedKernelLock lock(this); |
| - // This index is sorted by parent id and metahandle. |
| - ParentIdChildIndex* const index = kernel_->parent_id_child_index; |
| typedef ParentIdChildIndex::iterator iterator; |
| - for (iterator i = matcher->lower_bound(index), |
| - end = matcher->upper_bound(index); i != end; ++i) { |
| - // root's parent_id is NULL in the db but 0 in memory, so |
| - // have avoid listing the root as its own child. |
| - if ((*i)->ref(ID) == (*i)->ref(PARENT_ID)) |
| - continue; |
| + for (iterator i = GetParentChildIndexLowerBound(lock, parent_id), |
| + end = GetParentChildIndexUpperBound(lock, parent_id); |
| + i != end; ++i) { |
| result->push_back((*i)->ref(META_HANDLE)); |
| } |
| } |
| @@ -489,6 +456,8 @@ bool Directory::ReindexId(EntryKernel* const entry, const Id& new_id) { |
| { |
| // Update the indices that depend on the ID field. |
| ScopedIndexUpdater<IdIndexer> updater_a(lock, entry, kernel_->ids_index); |
| + ScopedIndexUpdater<ParentIdAndHandleIndexer> updater_b(lock, entry, |
| + kernel_->parent_id_child_index); |
| entry->put(ID, new_id); |
| } |
| return true; |
| @@ -1234,6 +1203,10 @@ Directory* Entry::dir() const { |
| return basetrans_->directory(); |
| } |
| +Id Entry::ComputePrevIdFromServerPosition(const Id& parent_id) const { |
| + return dir()->ComputePrevIdFromServerPosition(kernel_, parent_id); |
| +} |
| + |
| const string& Entry::Get(StringField field) const { |
| DCHECK(kernel_); |
| return kernel_->ref(field); |
| @@ -1372,13 +1345,21 @@ bool MutableEntry::PutIsDel(bool is_del) { |
| if (!is_del) |
| PutPredecessor(Id()); // Restores position to the 0th index. |
| + |
| return true; |
| } |
| bool MutableEntry::Put(Int64Field field, const int64& value) { |
| DCHECK(kernel_); |
| if (kernel_->ref(field) != value) { |
| - kernel_->put(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_->mark_dirty(dir()->kernel_->dirty_metahandles); |
| } |
| return true; |
| @@ -1606,34 +1587,109 @@ Id Directory::NextId() { |
| return Id::CreateFromClientString(base::Int64ToString(result)); |
| } |
| -Id Directory::GetChildWithNullIdField(IdField field, |
| - BaseTransaction* trans, |
| - const Id& parent_id) { |
| - // This query is O(number of children), which should be acceptable |
| - // when this method is used as the first step in enumerating the children of |
| - // a node. But careless use otherwise could potentially result in |
| - // O((number of children)^2) performance. |
| - ChildHandles child_handles; |
| - GetChildHandles(trans, parent_id, &child_handles); |
| - ChildHandles::const_iterator it; |
| - for (it = child_handles.begin(); it != child_handles.end(); ++it) { |
| - Entry e(trans, GET_BY_HANDLE, *it); |
| - CHECK(e.good()); |
| - if (e.Get(field).IsRoot()) |
| - return e.Get(ID); |
| - } |
| - |
| - return Id(); |
| -} |
| - |
| Id Directory::GetFirstChildId(BaseTransaction* trans, |
| const Id& parent_id) { |
| - return GetChildWithNullIdField(PREV_ID, trans, parent_id); |
| + ScopedKernelLock lock(this); |
| + // We can use the server positional ordering as a hint because it's generally |
| + // in sync with the local (linked-list) positional ordering, and we have an |
| + // index on it. |
| + ParentIdChildIndex::iterator candidate = |
| + GetParentChildIndexLowerBound(lock, parent_id); |
| + ParentIdChildIndex::iterator end_range = |
| + GetParentChildIndexUpperBound(lock, parent_id); |
| + for (; candidate != end_range; ++candidate) { |
| + EntryKernel* entry = *candidate; |
| + // Filter out self-looped items, which are temporarily not in the child |
| + // ordering. |
| + if (entry->ref(PREV_ID).IsRoot() || |
| + entry->ref(PREV_ID) != entry->ref(NEXT_ID)) { |
| + // Walk to the front of the list; the server position ordering |
| + // is commonly identical to the linked-list ordering, but pending |
| + // unsynced or unapplied items may diverge. |
| + while (!entry->ref(PREV_ID).IsRoot()) { |
| + entry = GetEntryById(entry->ref(PREV_ID), &lock); |
| + } |
| + return entry->ref(ID); |
| + } |
| + } |
| + // There were no children in the linked list. |
| + return Id(); |
| } |
| Id Directory::GetLastChildId(BaseTransaction* trans, |
| const Id& parent_id) { |
| - return GetChildWithNullIdField(NEXT_ID, trans, parent_id); |
| + ScopedKernelLock lock(this); |
| + // We can use the server positional ordering as a hint because it's generally |
| + // in sync with the local (linked-list) positional ordering, and we have an |
| + // index on it. |
| + ParentIdChildIndex::iterator begin_range = |
| + GetParentChildIndexLowerBound(lock, parent_id); |
| + ParentIdChildIndex::iterator candidate = |
| + GetParentChildIndexUpperBound(lock, parent_id); |
|
Nicolas Zea
2011/03/02 22:58:02
The first candidate will actually have parent_id+1
ncarter (slow)
2011/03/02 23:58:16
It's subtle, but I think it's okay. The pattern u
|
| + |
| + while (begin_range != candidate) { |
| + --candidate; |
| + EntryKernel* entry = *candidate; |
| + |
| + // Filter out self-looped items, which are temporarily not in the child |
| + // ordering. |
| + if (entry->ref(NEXT_ID).IsRoot() || |
| + entry->ref(NEXT_ID) != entry->ref(PREV_ID)) { |
| + // Walk to the back of the list; the server position ordering |
| + // is commonly identical to the linked-list ordering, but pending |
| + // unsynced or unapplied items may diverge. |
| + while (!entry->ref(NEXT_ID).IsRoot()) |
| + entry = GetEntryById(entry->ref(NEXT_ID), &lock); |
| + return entry->ref(ID); |
| + } |
| + } |
| + // There were no children in the linked list. |
| + return Id(); |
| +} |
| + |
| +Id Directory::ComputePrevIdFromServerPosition( |
| + const EntryKernel* entry, |
| + const syncable::Id& parent_id) { |
| + ScopedKernelLock lock(this); |
| + |
| + // 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 first_sibling = |
| + GetParentChildIndexLowerBound(lock, parent_id); |
| + |
| + while (sibling != first_sibling) { |
|
Nicolas Zea
2011/03/02 22:58:02
I think this prevents the possibility of having th
ncarter (slow)
2011/03/02 23:58:16
I think it works okay for the same reason as the p
|
| + --sibling; |
| + EntryKernel* candidate = *sibling; |
| + |
| + // The item itself should never be in the range under consideration. |
| + DCHECK_NE(candidate->ref(META_HANDLE), entry->ref(META_HANDLE)); |
| + |
| + // Ignore unapplied updates -- they might not even be server-siblings. |
| + if (candidate->ref(IS_UNAPPLIED_UPDATE)) |
| + continue; |
| + |
| + // We can't trust the SERVER_ fields of unsynced items, but they are |
| + // potentially legitimate local predecessors. In the case where |
| + // |update_item| and an unsynced item wind up in the same insertion |
| + // position, we need to choose how to order them. The following check puts |
| + // the unapplied update first; removing it would put the unsynced item(s) |
| + // first. |
| + if (candidate->ref(IS_UNSYNCED)) |
| + continue; |
| + |
| + // Skip over self-looped items, which are not valid predecessors. This |
| + // shouldn't happen in practice, but is worth defending against. |
| + if (candidate->ref(PREV_ID) == candidate->ref(NEXT_ID) && |
| + !candidate->ref(PREV_ID).IsRoot()) { |
| + NOTREACHED(); |
| + continue; |
| + } |
| + return candidate->ref(ID); |
| + } |
| + // This item will be the first in the sibling order. |
| + return Id(); |
| } |
| bool IsLegalNewParent(BaseTransaction* trans, const Id& entry_id, |
| @@ -1701,4 +1757,35 @@ std::ostream& operator<<(std::ostream& s, const Blob& blob) { |
| return s << std::dec; |
| } |
| +Directory::ParentIdChildIndex::iterator Directory::LocateInParentChildIndex( |
| + const ScopedKernelLock& lock, |
| + const Id& parent_id, |
| + 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(ID, item_id_for_tiebreaking); |
| + return kernel_->parent_id_child_index->lower_bound(&kernel_->needle); |
| +} |
| + |
| +Directory::ParentIdChildIndex::iterator |
| +Directory::GetParentChildIndexLowerBound(const ScopedKernelLock& lock, |
| + const Id& parent_id) { |
| + // Peg the parent ID, and use the least values for the remaining |
| + // index variables. |
| + return LocateInParentChildIndex(lock, parent_id, |
| + std::numeric_limits<int64>::min(), |
| + Id::GetLeastIdForLexicographicComparison()); |
| +} |
| + |
| + |
| +Directory::ParentIdChildIndex::iterator |
| +Directory::GetParentChildIndexUpperBound(const ScopedKernelLock& lock, |
| + const Id& parent_id) { |
| + // The upper bound of |parent_id|'s range is the lower |
| + // bound of |++parent_id|'s range. |
| + return GetParentChildIndexLowerBound(lock, |
| + parent_id.GetLexicographicSuccessor()); |
| +} |
| + |
| } // namespace syncable |