Index: sync/syncable/directory.cc |
diff --git a/sync/syncable/directory.cc b/sync/syncable/directory.cc |
index 82eb709cf52c4988ba032c60e5b9883fab21e5bb..623260ef685a7815b1ad4a057e2c019c9732da16 100644 |
--- a/sync/syncable/directory.cc |
+++ b/sync/syncable/directory.cc |
@@ -4,11 +4,11 @@ |
#include "sync/syncable/directory.h" |
+#include "base/base64.h" |
#include "base/debug/trace_event.h" |
-#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/base/unique_position.h" |
#include "sync/internal_api/public/util/unrecoverable_error_handler.h" |
#include "sync/syncable/base_transaction.h" |
#include "sync/syncable/entry.h" |
@@ -17,6 +17,7 @@ |
#include "sync/syncable/on_disk_directory_backing_store.h" |
#include "sync/syncable/read_transaction.h" |
#include "sync/syncable/scoped_index_updater.h" |
+#include "sync/syncable/scoped_parent_child_index_updater.h" |
#include "sync/syncable/syncable-inl.h" |
#include "sync/syncable/syncable_changes_version.h" |
#include "sync/syncable/syncable_util.h" |
@@ -36,7 +37,6 @@ void InitializeIndexEntry(EntryKernel* entry, |
index->insert(entry); |
} |
} |
- |
} |
// static |
@@ -44,29 +44,6 @@ bool ClientTagIndexer::ShouldInclude(const EntryKernel* a) { |
return !a->ref(UNIQUE_CLIENT_TAG).empty(); |
} |
-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; |
- |
- 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; |
-} |
- |
-// static |
-bool ParentIdAndHandleIndexer::ShouldInclude(const EntryKernel* a) { |
- // 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(); |
-} |
- |
// static |
const FilePath::CharType Directory::kSyncDatabaseFilename[] = |
FILE_PATH_LITERAL("SyncData.sqlite3"); |
@@ -111,7 +88,7 @@ Directory::Kernel::Kernel( |
name(name), |
metahandles_index(new Directory::MetahandlesIndex), |
ids_index(new Directory::IdsIndex), |
- parent_id_child_index(new Directory::ParentIdChildIndex), |
+ parent_child_index(new ParentChildIndex), |
client_tag_index(new Directory::ClientTagIndex), |
unsynced_metahandles(new MetahandleSet), |
dirty_metahandles(new MetahandleSet), |
@@ -126,11 +103,12 @@ Directory::Kernel::Kernel( |
DCHECK(transaction_observer.IsInitialized()); |
} |
+ |
Directory::Kernel::~Kernel() { |
delete unsynced_metahandles; |
delete dirty_metahandles; |
delete metahandles_to_purge; |
- delete parent_id_child_index; |
+ delete parent_child_index; |
delete client_tag_index; |
delete ids_index; |
STLDeleteElements(metahandles_index); |
@@ -176,8 +154,8 @@ void Directory::InitializeIndices() { |
MetahandlesIndex::iterator it = kernel_->metahandles_index->begin(); |
for (; it != kernel_->metahandles_index->end(); ++it) { |
EntryKernel* entry = *it; |
- InitializeIndexEntry<ParentIdAndHandleIndexer>(entry, |
- kernel_->parent_id_child_index); |
+ if (ParentChildIndex::ShouldInclude(entry)) |
+ kernel_->parent_child_index->Insert(entry); |
InitializeIndexEntry<IdIndexer>(entry, kernel_->ids_index); |
InitializeIndexEntry<ClientTagIndexer>(entry, kernel_->client_tag_index); |
const int64 metahandle = entry->ref(META_HANDLE); |
@@ -356,8 +334,8 @@ bool Directory::InsertEntry(WriteTransaction* trans, |
trans)) |
return false; |
- if (!entry->ref(IS_DEL)) { |
- if (!SyncAssert(kernel_->parent_id_child_index->insert(entry).second, |
+ if (ParentChildIndex::ShouldInclude(entry)) { |
+ if (!SyncAssert(kernel_->parent_child_index->Insert(entry), |
FROM_HERE, |
error, |
trans)) { |
@@ -388,8 +366,8 @@ bool Directory::ReindexId(WriteTransaction* trans, |
{ |
// 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); |
+ ScopedParentChildIndexUpdater updater_b(lock, entry, |
+ kernel_->parent_child_index); |
entry->put(ID, new_id); |
} |
return true; |
@@ -402,8 +380,8 @@ bool Directory::ReindexParentId(WriteTransaction* trans, |
{ |
// Update the indices that depend on the PARENT_ID field. |
- ScopedIndexUpdater<ParentIdAndHandleIndexer> index_updater(lock, entry, |
- kernel_->parent_id_child_index); |
+ ScopedParentChildIndexUpdater index_updater(lock, entry, |
+ kernel_->parent_child_index); |
entry->put(PARENT_ID, new_parent_id); |
} |
return true; |
@@ -535,7 +513,7 @@ bool Directory::VacuumAfterSaveChanges(const SaveChangesSnapshot& snapshot) { |
// Might not be in it |
num_erased = kernel_->client_tag_index->erase(entry); |
DCHECK_EQ(entry->ref(UNIQUE_CLIENT_TAG).empty(), !num_erased); |
- if (!SyncAssert(!kernel_->parent_id_child_index->count(entry), |
+ if (!SyncAssert(!kernel_->parent_child_index->Contains(entry), |
FROM_HERE, |
"Deleted entry still present", |
(&trans))) |
@@ -567,8 +545,6 @@ bool Directory::PurgeEntriesWithTypeIn(ModelTypeSet types) { |
// Note the dance around incrementing |it|, since we sometimes erase(). |
if ((IsRealDataType(local_type) && types.Has(local_type)) || |
(IsRealDataType(server_type) && types.Has(server_type))) { |
- if (!UnlinkEntryFromOrder(*it, &trans, &lock, DATA_TYPE_PURGE)) |
- return false; |
int64 handle = (*it)->ref(META_HANDLE); |
kernel_->metahandles_to_purge->insert(handle); |
@@ -584,8 +560,8 @@ bool Directory::PurgeEntriesWithTypeIn(ModelTypeSet types) { |
num_erased = |
kernel_->unapplied_update_metahandles[server_type].erase(handle); |
DCHECK_EQ(entry->ref(IS_UNAPPLIED_UPDATE), num_erased > 0); |
- num_erased = kernel_->parent_id_child_index->erase(entry); |
- DCHECK_EQ(entry->ref(IS_DEL), !num_erased); |
+ if (ParentChildIndex::ShouldInclude(entry)) |
+ kernel_->parent_child_index->Remove(entry); |
kernel_->metahandles_index->erase(it++); |
delete entry; |
} else { |
@@ -1010,83 +986,13 @@ void Directory::SetInvariantCheckLevel(InvariantCheckLevel check_level) { |
invariant_check_level_ = check_level; |
} |
-bool Directory::UnlinkEntryFromOrder(EntryKernel* entry, |
- WriteTransaction* trans, |
- ScopedKernelLock* lock, |
- UnlinkReason unlink_reason) { |
- if (!SyncAssert(!trans || this == trans->directory(), |
- FROM_HERE, |
- "Transaction not pointing to the right directory", |
- trans)) |
- return false; |
- Id old_previous = entry->ref(PREV_ID); |
- Id old_next = entry->ref(NEXT_ID); |
- |
- entry->put(NEXT_ID, entry->ref(ID)); |
- entry->put(PREV_ID, entry->ref(ID)); |
- entry->mark_dirty(kernel_->dirty_metahandles); |
- |
- if (!old_previous.IsRoot()) { |
- if (old_previous == old_next) { |
- // Note previous == next doesn't imply previous == next == Get(ID). We |
- // could have prev==next=="c-XX" and Get(ID)=="sX..." if an item was added |
- // and deleted before receiving the server ID in the commit response. |
- if (!SyncAssert( |
- (old_next == entry->ref(ID)) || !old_next.ServerKnows(), |
- FROM_HERE, |
- "Encounteered inconsistent entry while deleting", |
- trans)) { |
- return false; |
- } |
- return true; // Done if we were already self-looped (hence unlinked). |
- } |
- EntryKernel* previous_entry = GetEntryById(old_previous, lock); |
- ModelType type = GetModelTypeFromSpecifics(entry->ref(SPECIFICS)); |
- // TODO(tim): Multiple asserts here for bug 101039 investigation. |
- if (type == AUTOFILL) { |
- if (!SyncAssert(previous_entry != NULL, |
- FROM_HERE, |
- "Could not find previous autofill entry", |
- trans)) { |
- return false; |
- } |
- } else { |
- if (!SyncAssert(previous_entry != NULL, |
- FROM_HERE, |
- "Could not find previous entry", |
- trans)) { |
- return false; |
- } |
- } |
- if (unlink_reason == NODE_MANIPULATION) |
- trans->SaveOriginal(previous_entry); |
- previous_entry->put(NEXT_ID, old_next); |
- previous_entry->mark_dirty(kernel_->dirty_metahandles); |
- } |
- |
- if (!old_next.IsRoot()) { |
- EntryKernel* next_entry = GetEntryById(old_next, lock); |
- if (!SyncAssert(next_entry != NULL, |
- FROM_HERE, |
- "Could not find next entry", |
- trans)) { |
- return false; |
- } |
- if (unlink_reason == NODE_MANIPULATION) |
- trans->SaveOriginal(next_entry); |
- next_entry->put(PREV_ID, old_previous); |
- next_entry->mark_dirty(kernel_->dirty_metahandles); |
- } |
- return true; |
-} |
- |
int64 Directory::NextMetahandle() { |
ScopedKernelLock lock(this); |
int64 metahandle = (kernel_->next_metahandle)++; |
return metahandle; |
} |
-// Always returns a client ID that is the string representation of a negative |
+// Always returns a client ID that is a string representation of a negative |
// number. |
Id Directory::NextId() { |
int64 result; |
@@ -1101,171 +1007,160 @@ Id Directory::NextId() { |
bool Directory::HasChildren(BaseTransaction* trans, const Id& id) { |
ScopedKernelLock lock(this); |
- return (GetPossibleFirstChild(lock, id) != NULL); |
+ return kernel_->parent_child_index->GetChildren(id) != NULL; |
} |
-bool Directory::GetFirstChildId(BaseTransaction* trans, |
- const Id& parent_id, |
- Id* first_child_id) { |
+Id Directory::GetFirstChildId(BaseTransaction* trans, |
+ const EntryKernel* parent) { |
+ DCHECK(parent); |
+ DCHECK(parent->ref(IS_DIR)); |
+ |
ScopedKernelLock lock(this); |
- EntryKernel* entry = GetPossibleFirstChild(lock, parent_id); |
- if (!entry) { |
- *first_child_id = Id(); |
- return true; |
- } |
+ const ChildSet* children = |
+ kernel_->parent_child_index->GetChildren(parent->ref(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); |
- if (!entry) { |
- *first_child_id = Id(); |
- return false; |
- } |
- } |
- *first_child_id = entry->ref(ID); |
- return true; |
+ // We're expected to return root if there are no children. |
+ if (!children) |
+ return Id(); |
+ |
+ return (*children->begin())->ref(ID); |
} |
-bool Directory::GetLastChildIdForTest( |
- BaseTransaction* trans, const Id& parent_id, Id* last_child_id) { |
+syncable::Id Directory::GetPredecessorId(EntryKernel* e) { |
ScopedKernelLock lock(this); |
- EntryKernel* entry = GetPossibleFirstChild(lock, parent_id); |
- if (!entry) { |
- *last_child_id = Id(); |
- return true; |
- } |
- // 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); |
- if (!entry) { |
- *last_child_id = Id(); |
- return false; |
- } |
+ DCHECK(ParentChildIndex::ShouldInclude(e)); |
+ const ChildSet* children = |
+ kernel_->parent_child_index->GetChildren(e->ref(PARENT_ID)); |
+ DCHECK(children && !children->empty()); |
+ ChildSet::const_iterator i = children->find(e); |
+ DCHECK(i != children->end()); |
+ |
+ if (i == children->begin()) { |
+ return Id(); |
+ } else { |
+ i--; |
+ return (*i)->ref(ID); |
} |
- |
- *last_child_id = entry->ref(ID); |
- return true; |
} |
-Id Directory::ComputePrevIdFromServerPosition( |
- const EntryKernel* entry, |
- const syncable::Id& parent_id) { |
+syncable::Id Directory::GetSuccessorId(EntryKernel* e) { |
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, |
- NodeOrdinalToInt64(entry->ref(SERVER_ORDINAL_IN_PARENT)), |
- entry->ref(ID)); |
- ParentIdChildIndex::iterator first_sibling = |
- GetParentChildIndexLowerBound(lock, parent_id); |
- |
- while (sibling != first_sibling) { |
- --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; |
+ DCHECK(ParentChildIndex::ShouldInclude(e)); |
+ const ChildSet* children = |
+ kernel_->parent_child_index->GetChildren(e->ref(PARENT_ID)); |
+ DCHECK(children && !children->empty()); |
+ ChildSet::const_iterator i = children->find(e); |
+ DCHECK(i != children->end()); |
+ |
+ i++; |
+ if (i == children->end()) { |
+ return Id(); |
+ } else { |
+ return (*i)->ref(ID); |
+ } |
+} |
- // 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; |
+// TODO(rlarocque): Remove all support for placing ShouldMaintainPosition() |
+// items as siblings of items that do not maintain postions. It is required |
+// only for tests. |
+void Directory::PutPredecessor(EntryKernel* e, EntryKernel* predecessor) { |
+ DCHECK(!e->ref(IS_DEL)); |
+ if (!ShouldMaintainPosition(GetModelTypeFromSpecifics(e->ref(SPECIFICS)))) { |
+ DCHECK(!e->ref(UNIQUE_POSITION).IsValid()); |
+ return; |
+ } |
+ std::string suffix = e->ref(UNIQUE_BOOKMARK_TAG); |
+ DCHECK(!suffix.empty()); |
- // 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; |
+ // Remove our item from the list and remember to re-add it later. |
+ ScopedKernelLock lock(this); |
+ ScopedParentChildIndexUpdater updater(lock, e, kernel_->parent_child_index); |
+ |
+ // Note: The ScopedParentChildIndexUpdater will update this set for us as we |
+ // leave this function. |
+ const ChildSet* children = |
+ kernel_->parent_child_index->GetChildren(e->ref(PARENT_ID)); |
+ |
+ if (!children) { |
+ // This parent currently has no other children. |
+ DCHECK(predecessor->ref(ID).IsRoot()); |
+ UniquePosition pos = UniquePosition::InitialPosition(suffix); |
+ e->put(UNIQUE_POSITION, pos); |
+ return; |
+ } |
+ |
+ if (predecessor->ref(ID).IsRoot()) { |
+ // We have at least one sibling, and we're inserting to the left of them. |
+ UniquePosition successor_pos = (*children->begin())->ref(UNIQUE_POSITION); |
+ |
+ if (!successor_pos.IsValid()) { |
+ // If all our successors are of non-positionable types, just create an |
+ // initial position. We arbitrarily choose to sort invalid positions to |
+ // the right of the valid positions. |
+ // |
+ // We really shoulndn't need to support this. See TODO above. |
+ UniquePosition pos = UniquePosition::InitialPosition(suffix); |
+ e->put(UNIQUE_POSITION, pos); |
+ return; |
} |
- return candidate->ref(ID); |
+ |
+ DCHECK(!children->empty()); |
+ UniquePosition pos = UniquePosition::Before(successor_pos, suffix); |
+ e->put(UNIQUE_POSITION, pos); |
+ return; |
} |
- // This item will be the first in the sibling order. |
- return Id(); |
-} |
-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_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); |
-} |
+ // We can't support placing an item after an invalid position. Fortunately, |
+ // the tests don't exercise this particular case. We should not support |
+ // siblings with invalid positions at all. See TODO above. |
+ DCHECK(predecessor->ref(UNIQUE_POSITION).IsValid()); |
+ |
+ ChildSet::iterator i = children->find(predecessor); |
+ DCHECK(i != children->end()); |
+ |
+ ++i; |
+ if (i == children->end()) { |
+ // Inserting at the end of the list. |
+ UniquePosition pos = UniquePosition::After( |
+ predecessor->ref(UNIQUE_POSITION), |
+ suffix); |
+ e->put(UNIQUE_POSITION, pos); |
+ return; |
+ } |
-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()); |
-} |
+ EntryKernel* successor = *i; |
+ |
+ // Another mixed valid and invalid position case. This one could be supported |
+ // in theory, but we're trying to deprecate support for siblings with and |
+ // without valid positions. See TODO above. |
+ DCHECK(successor->ref(UNIQUE_POSITION).IsValid()); |
-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()); |
+ // Finally, the normal case: inserting between two elements. |
+ UniquePosition pos = UniquePosition::Between( |
+ predecessor->ref(UNIQUE_POSITION), |
+ successor->ref(UNIQUE_POSITION), |
+ suffix); |
+ e->put(UNIQUE_POSITION, pos); |
+ return; |
} |
+// TODO(rlarocque): Avoid this indirection. Just return the set. |
void Directory::AppendChildHandles(const ScopedKernelLock& lock, |
const Id& parent_id, |
Directory::ChildHandles* result) { |
- typedef ParentIdChildIndex::iterator iterator; |
- CHECK(result); |
- for (iterator i = GetParentChildIndexLowerBound(lock, parent_id), |
- end = GetParentChildIndexUpperBound(lock, parent_id); |
- i != end; ++i) { |
+ const ChildSet* children = |
+ kernel_->parent_child_index->GetChildren(parent_id); |
+ if (!children) |
+ return; |
+ |
+ for (ChildSet::iterator i = children->begin(); i != children->end(); ++i) { |
DCHECK_EQ(parent_id, (*i)->ref(PARENT_ID)); |
result->push_back((*i)->ref(META_HANDLE)); |
} |
} |
-EntryKernel* Directory::GetPossibleFirstChild( |
- const ScopedKernelLock& lock, const Id& parent_id) { |
- // 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)) { |
- return entry; |
- } |
- } |
- // There were no children in the linked list. |
- return NULL; |
-} |
- |
ScopedKernelLock::ScopedKernelLock(const Directory* dir) |
: scoped_lock_(dir->kernel_->mutex), dir_(const_cast<Directory*>(dir)) { |
} |