Index: content/browser/indexed_db/indexed_db_backing_store.cc |
diff --git a/content/browser/indexed_db/indexed_db_backing_store.cc b/content/browser/indexed_db/indexed_db_backing_store.cc |
index 58d02532a36c3d2a4929da40665965485b3abcd6..56cf9f34e9db7027e19bc7513523b8456cc8d4ab 100644 |
--- a/content/browser/indexed_db/indexed_db_backing_store.cc |
+++ b/content/browser/indexed_db/indexed_db_backing_store.cc |
@@ -3174,6 +3174,18 @@ bool IndexedDBBackingStore::Cursor::Continue(const IndexedDBKey* key, |
const IndexedDBKey* primary_key, |
IteratorState next_state, |
leveldb::Status* s) { |
+ if (cursor_options_.forward) |
+ return ContinueNext(key, primary_key, next_state, s); |
+ else |
+ return ContinuePrevious(key, primary_key, next_state, s); |
+} |
+ |
+bool IndexedDBBackingStore::Cursor::ContinueNext( |
+ const IndexedDBKey* key, |
+ const IndexedDBKey* primary_key, |
+ IteratorState next_state, |
+ leveldb::Status* s) { |
+ DCHECK(cursor_options_.forward); |
DCHECK(!key || key->IsValid()); |
DCHECK(!primary_key || primary_key->IsValid()); |
*s = leveldb::Status::OK(); |
@@ -3181,62 +3193,119 @@ bool IndexedDBBackingStore::Cursor::Continue(const IndexedDBKey* key, |
// TODO(alecflett): avoid a copy here? |
IndexedDBKey previous_key = current_key_ ? *current_key_ : IndexedDBKey(); |
- // When iterating with PrevNoDuplicate, spec requires that the |
- // value we yield for each key is the first duplicate in forwards |
- // order. |
- IndexedDBKey last_duplicate_key; |
- |
- bool forward = cursor_options_.forward; |
- bool first_iteration_forward = forward; |
- bool flipped = false; |
+ // Optimization: if seeking to a particular key (or key and primary key), |
+ // skip the cursor forward rather than iterating it. |
+ if (next_state == SEEK && key) { |
jsbell
2015/06/09 18:52:19
It should never be the case that key is specified
jsbell
2015/06/10 17:51:57
Done.
|
+ std::string leveldb_key; |
+ if (primary_key) |
jsbell
2015/06/09 18:52:19
Clearer as ternary (?:) operation?
jsbell
2015/06/10 17:51:57
Done.
|
+ leveldb_key = EncodeKey(*key, *primary_key); |
+ else |
+ leveldb_key = EncodeKey(*key); |
+ *s = iterator_->Seek(leveldb_key); |
+ if (!s->ok()) |
+ return false; |
+ // Cursor may be at the next value already; don't advance it again below. |
cmumford
2015/06/09 17:06:24
Is it a maybe, or is it for sure at the next value
jsbell
2015/06/10 17:51:58
Done.
(It may be positioned at a stale entry and
|
+ next_state = READY; |
+ } |
for (;;) { |
+ // Only advance the cursor if it was not set to position already, |
+ // either because it is newly opened (and positioned at start of range) |
+ // or skipped forward by continue with a specific key. |
if (next_state == SEEK) { |
- // TODO(jsbell): Optimize seeking for reverse cursors as well. |
- if (first_iteration_forward && key) { |
- first_iteration_forward = false; |
- std::string leveldb_key; |
- if (primary_key) { |
- leveldb_key = EncodeKey(*key, *primary_key); |
- } else { |
- leveldb_key = EncodeKey(*key); |
- } |
- *s = iterator_->Seek(leveldb_key); |
- } else if (forward) { |
- *s = iterator_->Next(); |
- } else { |
- *s = iterator_->Prev(); |
- } |
+ *s = iterator_->Next(); |
if (!s->ok()) |
return false; |
} else { |
- next_state = SEEK; // for subsequent iterations |
+ next_state = SEEK; |
} |
- if (!iterator_->IsValid()) { |
- if (!forward && last_duplicate_key.IsValid()) { |
- // We need to walk forward because we hit the end of |
- // the data. |
- forward = true; |
- flipped = true; |
- continue; |
- } |
- |
+ // TODO(jsbell): Can we DCHECK iterator_->IsValid() up top? |
+ // Fail if we've run out of data or gone past the cursor's bounds. |
+ if (!iterator_->IsValid() || IsPastBounds()) |
return false; |
+ |
+ // TODO(jsbell): Document why this might be false. When do we ever not |
+ // seek into the range before starting cursor iteration? |
+ if (!HaveEnteredRange()) |
+ continue; |
+ |
+ // The row may not load because there's a stale entry in the |
+ // index. If no error then not fatal. |
+ if (!LoadCurrentRow(s)) { |
+ if (!s->ok()) |
+ return false; |
+ continue; |
} |
- if (IsPastBounds()) { |
- if (!forward && last_duplicate_key.IsValid()) { |
- // We need to walk forward because now we're beyond the |
- // bounds defined by the cursor. |
- forward = true; |
- flipped = true; |
+ // If seeking to a key (or key and primary key), continue until found. |
jsbell
2015/06/09 18:52:19
Due to the Seek() optimization above, this block s
jsbell
2015/06/10 17:51:57
Added comment. Will run tests w/ DCHECK and remove
|
+ if (key) { |
+ if (primary_key && current_key_->Equals(*key) && |
+ this->primary_key().IsLessThan(*primary_key)) |
continue; |
+ if (current_key_->IsLessThan(*key)) |
+ continue; |
+ } |
+ |
jsbell
2015/06/09 18:52:19
Add a comment here:
// Cursor is now positioned a
jsbell
2015/06/10 17:51:57
Done.
|
+ // "Unique" cursors should continue seeking until a new key value is seen. |
+ if (cursor_options_.unique && previous_key.IsValid() && |
+ current_key_->Equals(previous_key)) { |
+ continue; |
+ } |
+ |
+ break; |
+ } |
+ |
+ return true; |
+} |
+ |
+bool IndexedDBBackingStore::Cursor::ContinuePrevious( |
+ const IndexedDBKey* key, |
+ const IndexedDBKey* primary_key, |
+ IteratorState next_state, |
+ leveldb::Status* s) { |
+ DCHECK(!cursor_options_.forward); |
+ DCHECK(!key || key->IsValid()); |
+ DCHECK(!primary_key || primary_key->IsValid()); |
+ *s = leveldb::Status::OK(); |
+ |
+ // TODO(alecflett): avoid a copy here? |
+ IndexedDBKey previous_key = current_key_ ? *current_key_ : IndexedDBKey(); |
+ |
+ // When iterating with PrevNoDuplicate, spec requires that the value we |
+ // yield for each key is the *first* duplicate in forwards order. We do this |
+ // by remembering the *last* duplicate seen (implicitly, the first value |
+ // seen with a new key), continuing until another new key is seen, then |
+ // reversing until we find a key equal to the *last* duplicate. |
+ IndexedDBKey last_duplicate_key; |
+ bool find_first_duplicate = false; |
jsbell
2015/06/09 15:47:32
Renamed from "flipped"
|
+ |
+ // TODO(jsbell): Optimize continuing to a specific key (or key and primary |
+ // key) for reverse cursors as well. See Seek() optimization at the start |
+ // of ContinueNext() for an example. |
+ |
+ for (;;) { |
+ if (next_state == SEEK) { |
+ *s = iterator_->Prev(); |
+ if (!s->ok()) |
+ return false; |
+ } else { |
+ next_state = SEEK; // for subsequent iterations |
+ } |
+ |
+ // If we've run out of data or gone past the cursor's bounds. |
+ if (!iterator_->IsValid() || IsPastBounds()) { |
+ if (last_duplicate_key.IsValid()) { |
+ // Walk the cursor forward to the first duplicate. |
+ find_first_duplicate = true; |
+ break; |
} |
return false; |
} |
+ // TODO(jsbell): Document why this might be false. When do we ever not |
+ // seek into the range before starting cursor iteration? |
if (!HaveEnteredRange()) |
continue; |
@@ -3248,20 +3317,13 @@ bool IndexedDBBackingStore::Cursor::Continue(const IndexedDBKey* key, |
continue; |
} |
+ // If seeking to a key (or key and primary key), continue until found. |
if (key) { |
- if (forward) { |
- if (primary_key && current_key_->Equals(*key) && |
- this->primary_key().IsLessThan(*primary_key)) |
- continue; |
- if (!flipped && current_key_->IsLessThan(*key)) |
- continue; |
- } else { |
- if (primary_key && key->Equals(*current_key_) && |
- primary_key->IsLessThan(this->primary_key())) |
- continue; |
- if (key->IsLessThan(*current_key_)) |
- continue; |
- } |
+ if (primary_key && key->Equals(*current_key_) && |
+ primary_key->IsLessThan(this->primary_key())) |
+ continue; |
+ if (key->IsLessThan(*current_key_)) |
+ continue; |
} |
jsbell
2015/06/09 18:52:19
Add comment here:
// Cursor is now positioned at
jsbell
2015/06/10 17:51:57
Done.
|
if (cursor_options_.unique) { |
@@ -3272,28 +3334,57 @@ bool IndexedDBBackingStore::Cursor::Continue(const IndexedDBKey* key, |
continue; |
} |
- if (!forward) { |
- if (!last_duplicate_key.IsValid()) { |
- last_duplicate_key = *current_key_; |
- continue; |
- } |
+ // If we've found a new key, remember it and keep going. |
+ if (!last_duplicate_key.IsValid()) { |
+ last_duplicate_key = *current_key_; |
+ continue; |
+ } |
- // We need to walk forward because we hit the boundary |
- // between key ranges. |
- if (!last_duplicate_key.Equals(*current_key_)) { |
- forward = true; |
- flipped = true; |
- continue; |
- } |
+ // If we're still seeing duplicates, keep going. |
+ if (last_duplicate_key.Equals(*current_key_)) |
+ continue; |
+ |
+ // Walk the cursor forward to the first duplicate. |
+ find_first_duplicate = true; |
+ } |
+ break; |
+ } |
+ |
+ // TODO(jsbell): Rather than iterating, stash the last leveldb key of the |
+ // last plausible result, then Seek() the cursor directly to that and |
+ // LoadCurrentRow(). |
+ |
+ if (find_first_duplicate) { |
+ DCHECK_EQ(next_state, SEEK); |
+ DCHECK(cursor_options_.unique); |
+ for (;;) { |
+ *s = iterator_->Next(); |
+ if (!s->ok()) |
cmumford
2015/06/09 17:06:24
What about:
if (!s->ok() || !iterator_->IsValid()
jsbell
2015/06/10 17:51:57
Done.
|
+ return false; |
+ |
+ if (!iterator_->IsValid() || IsPastBounds()) |
+ return false; |
+ |
+ // TODO(jsbell): Document why this might be false. When do we ever not |
+ // seek into the range before starting cursor iteration? |
+ if (!HaveEnteredRange()) |
+ continue; |
+ |
+ // The row may not load because there's a stale entry in the |
+ // index. If no error then not fatal. |
+ if (!LoadCurrentRow(s)) { |
+ if (!s->ok()) |
+ return false; |
continue; |
} |
+ |
jsbell
2015/06/09 15:47:32
I removed two clauses here from when we were pairi
|
+ break; |
} |
- break; |
} |
DCHECK(!last_duplicate_key.IsValid() || |
- (forward && last_duplicate_key.Equals(*current_key_))); |
+ (find_first_duplicate && last_duplicate_key.Equals(*current_key_))); |
return true; |
} |