Reviewed by Tony Gentilcore.
IndexedDB: Support mutating cursors on top of LevelDB
https://bugs.webkit.org/show_bug.cgi?id=61615
New test for adding new entries to an objecet store while running a
cursor over it.
* storage/indexeddb/mutating-cursor-expected.txt: Added.
* storage/indexeddb/mutating-cursor.html: Added.
2011-05-27 Hans Wennborg <hans@chromium.org>
Reviewed by Tony Gentilcore.
IndexedDB: Support mutating cursors on top of LevelDB
https://bugs.webkit.org/show_bug.cgi?id=61615
We need to support the case where a new node is added to the tree in a
transaction after the TreeIterator has covered the whole tree.
Since this is done lazily, i.e. we set a flag that the tree might have
changed, and act upon it later, some members need to be mutable,
because we might need to re-seek the tree iterator in a const function.
Test: storage/indexeddb/mutating-cursor.html
storage/indexeddb/mozilla/cursor-mutation-objectstore-only.html (existing)
* platform/leveldb/LevelDBTransaction.cpp:
(WebCore::LevelDBTransaction::set):
(WebCore::LevelDBTransaction::TreeIterator::reset):
(WebCore::LevelDBTransaction::TreeIterator::~TreeIterator):
(WebCore::LevelDBTransaction::TreeIterator::TreeIterator):
(WebCore::LevelDBTransaction::TransactionIterator::TransactionIterator):
(WebCore::LevelDBTransaction::TransactionIterator::~TransactionIterator):
(WebCore::LevelDBTransaction::TransactionIterator::next):
(WebCore::LevelDBTransaction::TransactionIterator::prev):
(WebCore::LevelDBTransaction::TransactionIterator::key):
(WebCore::LevelDBTransaction::TransactionIterator::value):
(WebCore::LevelDBTransaction::TransactionIterator::treeChanged):
(WebCore::LevelDBTransaction::TransactionIterator::refreshTreeIterator):
(WebCore::LevelDBTransaction::registerIterator):
(WebCore::LevelDBTransaction::unregisterIterator):
(WebCore::LevelDBTransaction::notifyIteratorsOfTreeChange):
* platform/leveldb/LevelDBTransaction.h:
* storage/IDBFactoryBackendImpl.cpp:
(WebCore::IDBFactoryBackendImpl::open):
* storage/IDBLevelDBBackingStore.cpp:
(WebCore::IDBLevelDBBackingStore::open):
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@87508
268f45cc-cd09-0410-ab3c-
d52691b4dbfc
+2011-05-27 Hans Wennborg <hans@chromium.org>
+
+ Reviewed by Tony Gentilcore.
+
+ IndexedDB: Support mutating cursors on top of LevelDB
+ https://bugs.webkit.org/show_bug.cgi?id=61615
+
+ New test for adding new entries to an objecet store while running a
+ cursor over it.
+
+ * storage/indexeddb/mutating-cursor-expected.txt: Added.
+ * storage/indexeddb/mutating-cursor.html: Added.
+
2011-05-27 Csaba Osztrogonác <ossy@webkit.org>
[Qt][Mac] Daily gardening.
--- /dev/null
+Test mutating an IndexedDB's objectstore from a cursor.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+webkitIndexedDB.open('mutating-cursor')
+db = event.target.result
+db.setVersion('1')
+setVersionSuccess():
+trans = event.target.result
+PASS trans !== null is true
+Deleted all object stores.
+objectStore = db.createObjectStore('store')
+objectStore.add(1, 1).onerror = unexpectedErrorCallback
+objectStore.add(2, 2).onerror = unexpectedErrorCallback
+objectStore.add(3, 3).onerror = unexpectedErrorCallback
+objectStore.add(4, 4).onerror = unexpectedErrorCallback
+openForwardCursor()
+trans = db.transaction([], webkitIDBTransaction.READ_WRITE)
+trans.objectStore('store')
+objectStore.openCursor()
+forwardCursor()
+1
+PASS cursor.key is cursorSteps
+PASS cursor.value is cursorSteps
+event.target.source.add(5, 5)
+cursor.continue()
+forwardCursor()
+2
+PASS cursor.key is cursorSteps
+PASS cursor.value is cursorSteps
+cursor.continue()
+forwardCursor()
+3
+PASS cursor.key is cursorSteps
+PASS cursor.value is cursorSteps
+cursor.continue()
+forwardCursor()
+4
+PASS cursor.key is cursorSteps
+PASS cursor.value is cursorSteps
+cursor.continue()
+forwardCursor()
+5
+PASS cursor.key is cursorSteps
+PASS cursor.value is cursorSteps
+cursor.continue()
+forwardCursor()
+PASS cursorSteps is 5
+forwardCursorComplete()
+openReverseCursor()
+trans = db.transaction([], webkitIDBTransaction.READ_WRITE)
+trans.objectStore('store')
+objectStore.openCursor(null, webkitIDBCursor.PREV)
+reverseCursor()
+5
+PASS cursor.key is cursorSteps
+PASS cursor.value is cursorSteps
+cursor.continue()
+reverseCursor()
+4
+PASS cursor.key is cursorSteps
+PASS cursor.value is cursorSteps
+cursor.continue()
+reverseCursor()
+3
+PASS cursor.key is cursorSteps
+PASS cursor.value is cursorSteps
+cursor.continue()
+reverseCursor()
+2
+PASS cursor.key is cursorSteps
+PASS cursor.value is cursorSteps
+event.target.source.add(0, 0)
+cursor.continue()
+reverseCursor()
+1
+PASS cursor.key is cursorSteps
+PASS cursor.value is cursorSteps
+cursor.continue()
+reverseCursor()
+0
+PASS cursor.key is cursorSteps
+PASS cursor.value is cursorSteps
+cursor.continue()
+reverseCursor()
+PASS cursorSteps is 0
+reverseCursorComplete()
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
--- /dev/null
+<html>
+<head>
+<link rel="stylesheet" href="../../fast/js/resources/js-test-style.css">
+<script src="../../fast/js/resources/js-test-pre.js"></script>
+<script src="../../fast/js/resources/js-test-post-function.js"></script>
+<script src="resources/shared.js"></script>
+</head>
+<body>
+<p id="description"></p>
+<div id="console"></div>
+<script>
+
+description("Test mutating an IndexedDB's objectstore from a cursor.");
+if (window.layoutTestController)
+ layoutTestController.waitUntilDone();
+
+test();
+
+function test()
+{
+ request = evalAndLog("webkitIndexedDB.open('mutating-cursor')");
+ request.onsuccess = openSuccess;
+ request.onerror = unexpectedErrorCallback;
+}
+
+function openSuccess()
+{
+ var db = evalAndLog("db = event.target.result");
+
+ request = evalAndLog("db.setVersion('1')");
+ request.onsuccess = setVersionSuccess;
+ request.onerror = unexpectedErrorCallback;
+}
+
+function setVersionSuccess()
+{
+ debug("setVersionSuccess():");
+ window.trans = evalAndLog("trans = event.target.result");
+ shouldBeTrue("trans !== null");
+ trans.onabort = unexpectedAbortCallback;
+ trans.oncomplete = openForwardCursor;
+
+ deleteAllObjectStores(db);
+
+ var objectStore = evalAndLog("objectStore = db.createObjectStore('store')");
+ evalAndLog("objectStore.add(1, 1).onerror = unexpectedErrorCallback");
+ evalAndLog("objectStore.add(2, 2).onerror = unexpectedErrorCallback");
+ evalAndLog("objectStore.add(3, 3).onerror = unexpectedErrorCallback");
+ evalAndLog("objectStore.add(4, 4).onerror = unexpectedErrorCallback");
+}
+
+function openForwardCursor()
+{
+ debug("openForwardCursor()");
+ evalAndLog("trans = db.transaction([], webkitIDBTransaction.READ_WRITE)");
+ trans.onabort = unexpectedAbortCallback;
+ trans.oncomplete = forwardCursorComplete;
+
+ window.objectStore = evalAndLog("trans.objectStore('store')");
+ request = evalAndLog("objectStore.openCursor()");
+ request.onsuccess = forwardCursor;
+ request.onerror = unexpectedErrorCallback;
+ window.cursorSteps = 0;
+}
+
+function forwardCursor()
+{
+ debug("forwardCursor()");
+ window.cursor = event.target.result;
+
+ if (event.target.result == null) {
+ shouldBe("cursorSteps", "5");
+
+ // Let the transaction finish.
+ return;
+ }
+
+ debug(++cursorSteps);
+ shouldBe("cursor.key", "cursorSteps");
+ shouldBe("cursor.value", "cursorSteps");
+
+ if (cursorSteps == 1) {
+ request = evalAndLog("event.target.source.add(5, 5)");
+ request.onsuccess = function() { evalAndLog("cursor.continue()"); }
+ request.onerror = unexpectedErrorCallback;
+ } else {
+ evalAndLog("cursor.continue()");
+ }
+}
+
+function forwardCursorComplete()
+{
+ debug("forwardCursorComplete()");
+ openReverseCursor()
+}
+
+function openReverseCursor()
+{
+ debug("openReverseCursor()");
+ evalAndLog("trans = db.transaction([], webkitIDBTransaction.READ_WRITE)");
+ trans.onabort = unexpectedAbortCallback;
+ trans.oncomplete = reverseCursorComplete;
+
+ window.objectStore = evalAndLog("trans.objectStore('store')");
+ request = evalAndLog("objectStore.openCursor(null, webkitIDBCursor.PREV)");
+ request.onsuccess = reverseCursor;
+ request.onerror = unexpectedErrorCallback;
+ window.cursorSteps = 6;
+}
+
+function reverseCursor()
+{
+ debug("reverseCursor()");
+ window.cursor = event.target.result;
+
+ if (event.target.result == null) {
+ shouldBe("cursorSteps", "0");
+
+ // Let the transaction finish.
+ return;
+ }
+
+ debug(--cursorSteps);
+ shouldBe("cursor.key", "cursorSteps");
+ shouldBe("cursor.value", "cursorSteps");
+
+ if (cursorSteps == 2) {
+ request = evalAndLog("event.target.source.add(0, 0)");
+ request.onsuccess = function() { evalAndLog("cursor.continue()"); }
+ request.onerror = unexpectedErrorCallback;
+ } else {
+ evalAndLog("cursor.continue()");
+ }
+}
+
+function reverseCursorComplete()
+{
+ debug("reverseCursorComplete()");
+ done();
+}
+
+var successfullyParsed = true;
+
+</script>
+</body>
+</html>
+2011-05-27 Hans Wennborg <hans@chromium.org>
+
+ Reviewed by Tony Gentilcore.
+
+ IndexedDB: Support mutating cursors on top of LevelDB
+ https://bugs.webkit.org/show_bug.cgi?id=61615
+
+ We need to support the case where a new node is added to the tree in a
+ transaction after the TreeIterator has covered the whole tree.
+
+ Since this is done lazily, i.e. we set a flag that the tree might have
+ changed, and act upon it later, some members need to be mutable,
+ because we might need to re-seek the tree iterator in a const function.
+
+ Test: storage/indexeddb/mutating-cursor.html
+ storage/indexeddb/mozilla/cursor-mutation-objectstore-only.html (existing)
+
+ * platform/leveldb/LevelDBTransaction.cpp:
+ (WebCore::LevelDBTransaction::set):
+ (WebCore::LevelDBTransaction::TreeIterator::reset):
+ (WebCore::LevelDBTransaction::TreeIterator::~TreeIterator):
+ (WebCore::LevelDBTransaction::TreeIterator::TreeIterator):
+ (WebCore::LevelDBTransaction::TransactionIterator::TransactionIterator):
+ (WebCore::LevelDBTransaction::TransactionIterator::~TransactionIterator):
+ (WebCore::LevelDBTransaction::TransactionIterator::next):
+ (WebCore::LevelDBTransaction::TransactionIterator::prev):
+ (WebCore::LevelDBTransaction::TransactionIterator::key):
+ (WebCore::LevelDBTransaction::TransactionIterator::value):
+ (WebCore::LevelDBTransaction::TransactionIterator::treeChanged):
+ (WebCore::LevelDBTransaction::TransactionIterator::refreshTreeIterator):
+ (WebCore::LevelDBTransaction::registerIterator):
+ (WebCore::LevelDBTransaction::unregisterIterator):
+ (WebCore::LevelDBTransaction::notifyIteratorsOfTreeChange):
+ * platform/leveldb/LevelDBTransaction.h:
+ * storage/IDBFactoryBackendImpl.cpp:
+ (WebCore::IDBFactoryBackendImpl::open):
+ * storage/IDBLevelDBBackingStore.cpp:
+ (WebCore::IDBLevelDBBackingStore::open):
+
2011-05-27 Sujin Park <sujjin.park@gmail.com>
Unreviewed, buildfix if --no-javascript-debugger.
node->deleted = deleted;
if (newNode)
- resetIterators();
+ notifyIteratorsOfTreeChange();
return true;
}
void LevelDBTransaction::TreeIterator::reset()
{
- // FIXME: Be lazy: set a flag and do the actual reset next time we use the iterator.
- if (isValid()) {
- m_iterator.start_iter(*m_tree, m_key, TreeType::EQUAL);
- ASSERT(isValid());
- }
+ ASSERT(isValid());
+ m_iterator.start_iter(*m_tree, m_key, TreeType::EQUAL);
+ ASSERT(isValid());
}
LevelDBTransaction::TreeIterator::~TreeIterator()
{
- m_transaction->unregisterIterator(this);
}
LevelDBTransaction::TreeIterator::TreeIterator(LevelDBTransaction* transaction)
: m_tree(&transaction->m_tree)
, m_transaction(transaction)
{
- transaction->registerIterator(this);
}
PassOwnPtr<LevelDBTransaction::TransactionIterator> LevelDBTransaction::TransactionIterator::create(PassRefPtr<LevelDBTransaction> transaction)
, m_dbIterator(m_transaction->m_db->createIterator())
, m_current(0)
, m_direction(kForward)
+ , m_treeChanged(false)
+{
+ m_transaction->registerIterator(this);
+}
+
+LevelDBTransaction::TransactionIterator::~TransactionIterator()
{
+ m_transaction->unregisterIterator(this);
}
bool LevelDBTransaction::TransactionIterator::isValid() const
void LevelDBTransaction::TransactionIterator::next()
{
ASSERT(isValid());
+ if (m_treeChanged)
+ refreshTreeIterator();
if (m_direction != kForward) {
// Ensure the non-current iterator is positioned after key().
void LevelDBTransaction::TransactionIterator::prev()
{
ASSERT(isValid());
+ if (m_treeChanged)
+ refreshTreeIterator();
if (m_direction != kReverse) {
// Ensure the non-current iterator is positioned before key().
LevelDBSlice LevelDBTransaction::TransactionIterator::key() const
{
ASSERT(isValid());
+ if (m_treeChanged)
+ refreshTreeIterator();
return m_current->key();
}
LevelDBSlice LevelDBTransaction::TransactionIterator::value() const
{
ASSERT(isValid());
+ if (m_treeChanged)
+ refreshTreeIterator();
return m_current->value();
}
+void LevelDBTransaction::TransactionIterator::treeChanged()
+{
+ m_treeChanged = true;
+}
+
+void LevelDBTransaction::TransactionIterator::refreshTreeIterator() const
+{
+ ASSERT(m_treeChanged);
+
+ if (m_treeIterator->isValid()) {
+ m_treeIterator->reset();
+ return;
+ }
+
+ if (m_dbIterator->isValid()) {
+ ASSERT(!m_treeIterator->isValid());
+
+ // There could be new nodes in the tree that we should iterate over.
+
+ if (m_direction == kForward) {
+ // Try to seek tree iterator to something greater than the db iterator.
+ m_treeIterator->seek(m_dbIterator->key());
+ if (m_treeIterator->isValid() && !m_comparator->compare(m_treeIterator->key(), m_dbIterator->key()))
+ m_treeIterator->next(); // If equal, take another step so the tree iterator is strictly greater.
+ } else {
+ // If going backward, seek to a key less than the db iterator.
+ ASSERT(m_direction == kReverse);
+ m_treeIterator->seek(m_dbIterator->key());
+ if (m_treeIterator->isValid())
+ m_treeIterator->prev();
+ }
+ }
+
+ m_treeChanged = false;
+}
+
void LevelDBTransaction::TransactionIterator::handleConflictsAndDeletes()
{
bool loop = true;
m_current = largest;
}
-void LevelDBTransaction::registerIterator(TreeIterator* iterator)
+void LevelDBTransaction::registerIterator(TransactionIterator* iterator)
{
- ASSERT(!m_treeIterators.contains(iterator));
- m_treeIterators.add(iterator);
+ ASSERT(!m_iterators.contains(iterator));
+ m_iterators.add(iterator);
}
-void LevelDBTransaction::unregisterIterator(TreeIterator* iterator)
+void LevelDBTransaction::unregisterIterator(TransactionIterator* iterator)
{
- ASSERT(m_treeIterators.contains(iterator));
- m_treeIterators.remove(iterator);
+ ASSERT(m_iterators.contains(iterator));
+ m_iterators.remove(iterator);
}
-void LevelDBTransaction::resetIterators()
+void LevelDBTransaction::notifyIteratorsOfTreeChange()
{
- for (HashSet<TreeIterator*>::iterator i = m_treeIterators.begin(); i != m_treeIterators.end(); ++i) {
- TreeIterator* treeIterator = *i;
- treeIterator->reset();
+ for (HashSet<TransactionIterator*>::iterator i = m_iterators.begin(); i != m_iterators.end(); ++i) {
+ TransactionIterator* transactionIterator = *i;
+ transactionIterator->treeChanged();
}
}
class TransactionIterator : public LevelDBIterator {
public:
- ~TransactionIterator() { };
+ ~TransactionIterator();
static PassOwnPtr<TransactionIterator> create(PassRefPtr<LevelDBTransaction>);
virtual bool isValid() const;
virtual void prev();
virtual LevelDBSlice key() const;
virtual LevelDBSlice value() const;
+ void treeChanged();
private:
TransactionIterator(PassRefPtr<LevelDBTransaction>);
void handleConflictsAndDeletes();
void setCurrentIteratorToSmallestKey();
void setCurrentIteratorToLargestKey();
+ void refreshTreeIterator() const;
RefPtr<LevelDBTransaction> m_transaction;
const LevelDBComparator* m_comparator;
- OwnPtr<TreeIterator> m_treeIterator;
+ mutable OwnPtr<TreeIterator> m_treeIterator;
OwnPtr<LevelDBIterator> m_dbIterator;
LevelDBIterator* m_current;
kReverse
};
Direction m_direction;
+ mutable bool m_treeChanged;
};
bool set(const LevelDBSlice& key, const Vector<char>& value, bool deleted);
void clearTree();
- void registerIterator(TreeIterator*);
- void unregisterIterator(TreeIterator*);
- void resetIterators();
+ void registerIterator(TransactionIterator*);
+ void unregisterIterator(TransactionIterator*);
+ void notifyIteratorsOfTreeChange();
LevelDBDatabase* m_db;
const LevelDBComparator* m_comparator;
TreeType m_tree;
bool m_finished;
- HashSet<TreeIterator*> m_treeIterators;
+ HashSet<TransactionIterator*> m_iterators;
};
}