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..87bc8838bc2ca17c62e5d57046895295dbfa63b7 100644 |
--- a/content/browser/indexed_db/indexed_db_backing_store.cc |
+++ b/content/browser/indexed_db/indexed_db_backing_store.cc |
@@ -3174,6 +3174,20 @@ bool IndexedDBBackingStore::Cursor::Continue(const IndexedDBKey* key, |
const IndexedDBKey* primary_key, |
IteratorState next_state, |
leveldb::Status* s) { |
+ DCHECK(!key || next_state == SEEK); |
+ |
+ 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 +3195,120 @@ 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) { |
+ std::string leveldb_key = |
+ primary_key ? EncodeKey(*key, *primary_key) : EncodeKey(*key); |
+ *s = iterator_->Seek(leveldb_key); |
+ if (!s->ok()) |
+ return false; |
+ // Cursor is at the next value already; don't advance it again below. |
+ 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. |
+ // TODO(jsbell): Given the Seek() optimization above, this should |
+ // not be necessary. Verify and remove. |
+ if (key) { |
+ if (primary_key && current_key_->Equals(*key) && |
+ this->primary_key().IsLessThan(*primary_key)) |
+ continue; |
+ if (current_key_->IsLessThan(*key)) |
continue; |
+ } |
+ |
+ // Cursor is now positioned at a non-stale record in range. |
+ |
+ // "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; |
+ |
+ // 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,52 +3320,74 @@ bool IndexedDBBackingStore::Cursor::Continue(const IndexedDBKey* key, |
continue; |
} |
+ // If seeking to a key (or key and primary key), continue until found. |
+ // TODO(jsbell): If Seek() optimization is added above, remove this. |
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; |
} |
+ // Cursor is now positioned at a non-stale record in range. |
+ |
if (cursor_options_.unique) { |
- if (previous_key.IsValid() && current_key_->Equals(previous_key)) { |
- // We should never be able to walk forward all the way |
- // to the previous key. |
- DCHECK(!last_duplicate_key.IsValid()); |
+ // If entry is a duplicate, keep going. Although the cursor should be |
+ // positioned at the first duplicate already, new duplicates may have |
+ // been inserted since the cursor was last iterated, and should be |
+ // skipped to maintain "unique" iteration. |
+ if (previous_key.IsValid() && current_key_->Equals(previous_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; |
} |
- if (!forward) { |
- if (!last_duplicate_key.IsValid()) { |
- last_duplicate_key = *current_key_; |
- continue; |
- } |
+ // If we're still seeing duplicates, keep going. |
+ if (last_duplicate_key.Equals(*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; |
- } |
+ // 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() || !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; |
} |
+ |
+ 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; |
} |