Title: [101648] trunk
Revision
101648
Author
h...@chromium.org
Date
2011-12-01 03:20:58 -0800 (Thu, 01 Dec 2011)

Log Message

IndexedDB: Fix reverse cursor with non-existent upper bound
https://bugs.webkit.org/show_bug.cgi?id=73220

Reviewed by Tony Chang.

Source/WebCore:

The code previously did not properly handle the case where the
specified upper bound for a reverse cursor did not exist.

Test: storage/indexeddb/cursor-reverse-bug.html

* storage/IDBLevelDBBackingStore.cpp:
(WebCore::findGreatestKeyLessThanOrEqual):
(WebCore::IDBLevelDBBackingStore::openObjectStoreCursor):
(WebCore::IDBLevelDBBackingStore::openIndexKeyCursor):
(WebCore::IDBLevelDBBackingStore::openIndexCursor):

LayoutTests:

Add test to check that opening a reverse cursor works even when the
key specified as the upper bound does not exist.

* storage/indexeddb/cursor-reverse-bug-expected.txt: Added.
* storage/indexeddb/cursor-reverse-bug.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (101647 => 101648)


--- trunk/LayoutTests/ChangeLog	2011-12-01 11:14:58 UTC (rev 101647)
+++ trunk/LayoutTests/ChangeLog	2011-12-01 11:20:58 UTC (rev 101648)
@@ -1,3 +1,16 @@
+2011-11-28  Hans Wennborg  <h...@chromium.org>
+
+        IndexedDB: Fix reverse cursor with non-existent upper bound
+        https://bugs.webkit.org/show_bug.cgi?id=73220
+
+        Reviewed by Tony Chang.
+
+        Add test to check that opening a reverse cursor works even when the
+        key specified as the upper bound does not exist.
+
+        * storage/indexeddb/cursor-reverse-bug-expected.txt: Added.
+        * storage/indexeddb/cursor-reverse-bug.html: Added.
+
 2011-12-01  Hayato Ito  <hay...@chromium.org>
 
         Unreviewed.  Chromium rebaselines after r101638.

Added: trunk/LayoutTests/storage/indexeddb/cursor-reverse-bug-expected.txt (0 => 101648)


--- trunk/LayoutTests/storage/indexeddb/cursor-reverse-bug-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/cursor-reverse-bug-expected.txt	2011-12-01 11:20:58 UTC (rev 101648)
@@ -0,0 +1,109 @@
+Test IndexedDB keys ordering and readback from cursors.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
+PASS indexedDB == null is false
+IDBCursor = window.IDBCursor || window.webkitIDBCursor;
+PASS IDBCursor == null is false
+IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction
+PASS IDBTransaction == null is false
+IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange
+PASS IDBKeyRange == null is false
+
+openreq = indexedDB.open('cursor-reverse-bug')
+db = openreq.result
+verreq = db.setVersion('1')
+Deleted all object stores.
+store = db.createObjectStore('store')
+store.createIndex('index', '')
+
+populating store...
+trans = db.transaction('store', IDBTransaction.READ_WRITE)
+store = trans.objectStore('store');
+store.put(1, 1)
+store.put(2, 2)
+store.put(3, 3)
+testCursor()
+trans = db.transaction('store', IDBTransaction.READ_ONLY)
+store = trans.objectStore('store');
+index = store.index('index');
+upperBound: 7 open: false expected: 3
+storeReq = store.openCursor(IDBKeyRange.upperBound(test.upperBound, test.open), IDBCursor.PREV)
+indexReq = index.openCursor(IDBKeyRange.upperBound(test.upperBound, test.open), IDBCursor.PREV)
+indexKeyReq = index.openKeyCursor(IDBKeyRange.upperBound(test.upperBound, test.open), IDBCursor.PREV)
+cursor = event.target.result
+PASS cursor.key is test.expected
+PASS cursor.value is test.expected
+PASS cursor.primaryKey is test.expected
+cursor = event.target.result
+PASS cursor.key is test.expected
+PASS cursor.value is test.expected
+PASS cursor.primaryKey is test.expected
+cursor = event.target.result
+PASS cursor.key is test.expected
+PASS cursor.primaryKey is test.expected
+testCursor()
+trans = db.transaction('store', IDBTransaction.READ_ONLY)
+store = trans.objectStore('store');
+index = store.index('index');
+upperBound: 7 open: true expected: 3
+storeReq = store.openCursor(IDBKeyRange.upperBound(test.upperBound, test.open), IDBCursor.PREV)
+indexReq = index.openCursor(IDBKeyRange.upperBound(test.upperBound, test.open), IDBCursor.PREV)
+indexKeyReq = index.openKeyCursor(IDBKeyRange.upperBound(test.upperBound, test.open), IDBCursor.PREV)
+cursor = event.target.result
+PASS cursor.key is test.expected
+PASS cursor.value is test.expected
+PASS cursor.primaryKey is test.expected
+cursor = event.target.result
+PASS cursor.key is test.expected
+PASS cursor.value is test.expected
+PASS cursor.primaryKey is test.expected
+cursor = event.target.result
+PASS cursor.key is test.expected
+PASS cursor.primaryKey is test.expected
+testCursor()
+trans = db.transaction('store', IDBTransaction.READ_ONLY)
+store = trans.objectStore('store');
+index = store.index('index');
+upperBound: 3 open: false expected: 3
+storeReq = store.openCursor(IDBKeyRange.upperBound(test.upperBound, test.open), IDBCursor.PREV)
+indexReq = index.openCursor(IDBKeyRange.upperBound(test.upperBound, test.open), IDBCursor.PREV)
+indexKeyReq = index.openKeyCursor(IDBKeyRange.upperBound(test.upperBound, test.open), IDBCursor.PREV)
+cursor = event.target.result
+PASS cursor.key is test.expected
+PASS cursor.value is test.expected
+PASS cursor.primaryKey is test.expected
+cursor = event.target.result
+PASS cursor.key is test.expected
+PASS cursor.value is test.expected
+PASS cursor.primaryKey is test.expected
+cursor = event.target.result
+PASS cursor.key is test.expected
+PASS cursor.primaryKey is test.expected
+testCursor()
+trans = db.transaction('store', IDBTransaction.READ_ONLY)
+store = trans.objectStore('store');
+index = store.index('index');
+upperBound: 3 open: true expected: 2
+storeReq = store.openCursor(IDBKeyRange.upperBound(test.upperBound, test.open), IDBCursor.PREV)
+indexReq = index.openCursor(IDBKeyRange.upperBound(test.upperBound, test.open), IDBCursor.PREV)
+indexKeyReq = index.openKeyCursor(IDBKeyRange.upperBound(test.upperBound, test.open), IDBCursor.PREV)
+cursor = event.target.result
+PASS cursor.key is test.expected
+PASS cursor.value is test.expected
+PASS cursor.primaryKey is test.expected
+cursor = event.target.result
+PASS cursor.key is test.expected
+PASS cursor.value is test.expected
+PASS cursor.primaryKey is test.expected
+cursor = event.target.result
+PASS cursor.key is test.expected
+PASS cursor.primaryKey is test.expected
+testCursor()
+No more tests.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Property changes on: trunk/LayoutTests/storage/indexeddb/cursor-reverse-bug-expected.txt
___________________________________________________________________

Added: svn:eol-style

Added: trunk/LayoutTests/storage/indexeddb/cursor-reverse-bug.html (0 => 101648)


--- trunk/LayoutTests/storage/indexeddb/cursor-reverse-bug.html	                        (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/cursor-reverse-bug.html	2011-12-01 11:20:58 UTC (rev 101648)
@@ -0,0 +1,120 @@
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+<p id="description"></p>
+<div id="console"></div>
+<script>
+
+description("Test IndexedDB keys ordering and readback from cursors.");
+if (window.layoutTestController)
+    layoutTestController.waitUntilDone();
+
+function test()
+{
+    evalAndLog("indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;");
+    shouldBeFalse("indexedDB == null");
+    evalAndLog("IDBCursor = window.IDBCursor || window.webkitIDBCursor;");
+    shouldBeFalse("IDBCursor == null");
+    evalAndLog("IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction");
+    shouldBeFalse("IDBTransaction == null");
+    evalAndLog("IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange");
+    shouldBeFalse("IDBKeyRange == null");
+
+    prepareDatabase();
+}
+
+function prepareDatabase()
+{
+    debug("");
+    evalAndLog("openreq = indexedDB.open('cursor-reverse-bug')");
+    openreq._onerror_ = unexpectedErrorCallback;
+    openreq._onsuccess_ = function() {
+        evalAndLog("db = openreq.result");
+        evalAndLog("verreq = db.setVersion('1')");
+        verreq._onerror_ = unexpectedErrorCallback;
+        verreq._onsuccess_ = function() {
+            deleteAllObjectStores(db);
+            store = evalAndLog("store = db.createObjectStore('store')");
+            evalAndLog("store.createIndex('index', '')");
+            verreq.result._oncomplete_ = populateStore;
+        };
+    };
+}
+
+function populateStore()
+{
+    debug("");
+    debug("populating store...");
+    evalAndLog("trans = db.transaction('store', IDBTransaction.READ_WRITE)");
+    evalAndLog("store = trans.objectStore('store');");
+    trans._onerror_ = unexpectedErrorCallback;
+    trans._onabort_ = unexpectedAbortCallback;
+
+    evalAndLog("store.put(1, 1)");
+    evalAndLog("store.put(2, 2)");
+    evalAndLog("store.put(3, 3)");
+    trans._oncomplete_ = testCursor;
+}
+
+var tests = [
+                {upperBound: 7, open: false, expected: 3},
+                {upperBound: 7, open: true,  expected: 3},
+                {upperBound: 3, open: false, expected: 3},
+                {upperBound: 3, open: true,  expected: 2}
+            ];
+
+function testCursor()
+{
+    debug("testCursor()");
+
+    if (tests.length === 0) {
+        debug("No more tests.");
+        done();
+        return;
+    }
+
+    test = tests.shift();
+
+    evalAndLog("trans = db.transaction('store', IDBTransaction.READ_ONLY)");
+    trans._onerror_ = unexpectedErrorCallback;
+    trans._onabort_ = unexpectedAbortCallback;
+    trans._oncomplete_ = testCursor;
+    evalAndLog("store = trans.objectStore('store');");
+    evalAndLog("index = store.index('index');");
+
+    var testFunction = function() {
+        evalAndLog("cursor = event.target.result");
+        if (cursor === null) {
+            debug("cursor should not be null");
+            fail();
+            return;
+        }
+
+        shouldBe("cursor.key", "test.expected");
+        if ("value" in cursor)
+            shouldBe("cursor.value", "test.expected");
+        shouldBe("cursor.primaryKey", "test.expected");
+
+        // Let the transaction finish.
+    }
+
+    debug("upperBound: " + test.upperBound + " open: " + test.open + " expected: " + test.expected);
+    storeReq = evalAndLog("storeReq = store.openCursor(IDBKeyRange.upperBound(test.upperBound, test.open), IDBCursor.PREV)");
+    storeReq._onsuccess_ = testFunction;
+
+    indexReq = evalAndLog("indexReq = index.openCursor(IDBKeyRange.upperBound(test.upperBound, test.open), IDBCursor.PREV)");
+    indexReq._onsuccess_ = testFunction;
+
+    indexKeyReq = evalAndLog("indexKeyReq = index.openKeyCursor(IDBKeyRange.upperBound(test.upperBound, test.open), IDBCursor.PREV)");
+    indexKeyReq._onsuccess_ = testFunction;
+}
+
+test();
+
+</script>
+</body>
+</html>
+
Property changes on: trunk/LayoutTests/storage/indexeddb/cursor-reverse-bug.html
___________________________________________________________________

Added: svn:eol-style

Modified: trunk/Source/WebCore/ChangeLog (101647 => 101648)


--- trunk/Source/WebCore/ChangeLog	2011-12-01 11:14:58 UTC (rev 101647)
+++ trunk/Source/WebCore/ChangeLog	2011-12-01 11:20:58 UTC (rev 101648)
@@ -1,3 +1,21 @@
+2011-11-28  Hans Wennborg  <h...@chromium.org>
+
+        IndexedDB: Fix reverse cursor with non-existent upper bound
+        https://bugs.webkit.org/show_bug.cgi?id=73220
+
+        Reviewed by Tony Chang.
+
+        The code previously did not properly handle the case where the
+        specified upper bound for a reverse cursor did not exist.
+
+        Test: storage/indexeddb/cursor-reverse-bug.html
+
+        * storage/IDBLevelDBBackingStore.cpp:
+        (WebCore::findGreatestKeyLessThanOrEqual):
+        (WebCore::IDBLevelDBBackingStore::openObjectStoreCursor):
+        (WebCore::IDBLevelDBBackingStore::openIndexKeyCursor):
+        (WebCore::IDBLevelDBBackingStore::openIndexCursor):
+
 2011-11-29  Hans Wennborg  <h...@chromium.org>
 
         IndexedDB: Cursor pre-fetching

Modified: trunk/Source/WebCore/storage/IDBLevelDBBackingStore.cpp (101647 => 101648)


--- trunk/Source/WebCore/storage/IDBLevelDBBackingStore.cpp	2011-12-01 11:14:58 UTC (rev 101647)
+++ trunk/Source/WebCore/storage/IDBLevelDBBackingStore.cpp	2011-12-01 11:20:58 UTC (rev 101648)
@@ -838,7 +838,7 @@
     return m_currentTransaction->put(indexDataKey, data);
 }
 
-static bool findGreatestKeyLessThan(LevelDBTransaction* transaction, const Vector<char>& target, Vector<char>& foundKey)
+static bool findGreatestKeyLessThanOrEqual(LevelDBTransaction* transaction, const Vector<char>& target, Vector<char>& foundKey)
 {
     OwnPtr<LevelDBIterator> it = transaction->createIterator();
     it->seek(target);
@@ -849,14 +849,20 @@
             return false;
     }
 
-    while (compareIndexKeys(it->key(), target) >= 0) {
+    while (compareIndexKeys(it->key(), target) > 0) {
         it->prev();
         if (!it->isValid())
             return false;
     }
 
-    foundKey.clear();
-    foundKey.append(it->key().begin(), it->key().end() - it->key().begin());
+    do {
+        foundKey.clear();
+        foundKey.append(it->key().begin(), it->key().end() - it->key().begin());
+
+        // There can be several index keys that compare equal. We want the last one.
+        it->next();
+    } while (it->isValid() && !compareIndexKeys(it->key(), target));
+
     return true;
 }
 
@@ -1312,23 +1318,6 @@
 
 }
 
-static bool findLastIndexKeyEqualTo(LevelDBTransaction* transaction, const Vector<char>& target, Vector<char>& foundKey)
-{
-    OwnPtr<LevelDBIterator> it = transaction->createIterator();
-    it->seek(target);
-
-    if (!it->isValid())
-        return false;
-
-    while (it->isValid() && !compareIndexKeys(it->key(), target)) {
-        foundKey.clear();
-        foundKey.append(it->key().begin(), it->key().end() - it->key().begin());
-        it->next();
-    }
-
-    return true;
-}
-
 PassRefPtr<IDBBackingStore::Cursor> IDBLevelDBBackingStore::openObjectStoreCursor(int64_t databaseId, int64_t objectStoreId, const IDBKeyRange* range, IDBCursor::Direction direction)
 {
     ASSERT(m_currentTransaction);
@@ -1349,16 +1338,31 @@
 
     if (!upperBound) {
         cursorOptions.highKey = ObjectStoreDataKey::encode(databaseId, objectStoreId, maxIDBKey());
-        cursorOptions.highOpen = true; // Not included.
 
-        if (!cursorOptions.forward) { // We need a key that exists.
-            if (!findGreatestKeyLessThan(m_currentTransaction.get(), cursorOptions.highKey, cursorOptions.highKey))
+        if (cursorOptions.forward)
+            cursorOptions.highOpen = true; // Not included.
+        else {
+            // We need a key that exists.
+            if (!findGreatestKeyLessThanOrEqual(m_currentTransaction.get(), cursorOptions.highKey, cursorOptions.highKey))
                 return 0;
             cursorOptions.highOpen = false;
         }
     } else {
         cursorOptions.highKey = ObjectStoreDataKey::encode(databaseId, objectStoreId, *range->upper());
         cursorOptions.highOpen = range->upperOpen();
+
+        if (!cursorOptions.forward) {
+            // For reverse cursors, we need a key that exists.
+            Vector<char> foundHighKey;
+            if (!findGreatestKeyLessThanOrEqual(m_currentTransaction.get(), cursorOptions.highKey, foundHighKey))
+                return 0;
+
+            // If the target key should not be included, but we end up with a smaller key, we should include that.
+            if (cursorOptions.highOpen && compareIndexKeys(foundHighKey, cursorOptions.highKey) < 0)
+                cursorOptions.highOpen = false;
+
+            cursorOptions.highKey = foundHighKey;
+        }
     }
 
     RefPtr<ObjectStoreCursorImpl> cursor = ObjectStoreCursorImpl::create(m_currentTransaction.get(), cursorOptions);
@@ -1390,15 +1394,23 @@
         cursorOptions.highOpen = false; // Included.
 
         if (!cursorOptions.forward) { // We need a key that exists.
-            if (!findGreatestKeyLessThan(m_currentTransaction.get(), cursorOptions.highKey, cursorOptions.highKey))
+            if (!findGreatestKeyLessThanOrEqual(m_currentTransaction.get(), cursorOptions.highKey, cursorOptions.highKey))
                 return 0;
             cursorOptions.highOpen = false;
         }
     } else {
         cursorOptions.highKey = IndexDataKey::encode(databaseId, objectStoreId, indexId, *range->upper());
-        if (!findLastIndexKeyEqualTo(m_currentTransaction.get(), cursorOptions.highKey, cursorOptions.highKey)) // Seek to the *last* key in the set of non-unique keys.
+        cursorOptions.highOpen = range->upperOpen();
+
+        Vector<char> foundHighKey;
+        if (!findGreatestKeyLessThanOrEqual(m_currentTransaction.get(), cursorOptions.highKey, foundHighKey)) // Seek to the *last* key in the set of non-unique keys.
             return 0;
-        cursorOptions.highOpen = range->upperOpen();
+
+        // If the target key should not be included, but we end up with a smaller key, we should include that.
+        if (cursorOptions.highOpen && compareIndexKeys(foundHighKey, cursorOptions.highKey) < 0)
+            cursorOptions.highOpen = false;
+
+        cursorOptions.highKey = foundHighKey;
     }
 
     RefPtr<IndexKeyCursorImpl> cursor = IndexKeyCursorImpl::create(m_currentTransaction.get(), cursorOptions);
@@ -1430,15 +1442,23 @@
         cursorOptions.highOpen = false; // Included.
 
         if (!cursorOptions.forward) { // We need a key that exists.
-            if (!findGreatestKeyLessThan(m_currentTransaction.get(), cursorOptions.highKey, cursorOptions.highKey))
+            if (!findGreatestKeyLessThanOrEqual(m_currentTransaction.get(), cursorOptions.highKey, cursorOptions.highKey))
                 return 0;
             cursorOptions.highOpen = false;
         }
     } else {
         cursorOptions.highKey = IndexDataKey::encode(databaseId, objectStoreId, indexId, *range->upper());
-        if (!findLastIndexKeyEqualTo(m_currentTransaction.get(), cursorOptions.highKey, cursorOptions.highKey)) // Seek to the *last* key in the set of non-unique keys.
+        cursorOptions.highOpen = range->upperOpen();
+
+        Vector<char> foundHighKey;
+        if (!findGreatestKeyLessThanOrEqual(m_currentTransaction.get(), cursorOptions.highKey, foundHighKey)) // Seek to the *last* key in the set of non-unique keys.
             return 0;
-        cursorOptions.highOpen = range->upperOpen();
+
+        // If the target key should not be included, but we end up with a smaller key, we should include that.
+        if (cursorOptions.highOpen && compareIndexKeys(foundHighKey, cursorOptions.highKey) < 0)
+            cursorOptions.highOpen = false;
+
+        cursorOptions.highKey = foundHighKey;
     }
 
     RefPtr<IndexCursorImpl> cursor = IndexCursorImpl::create(m_currentTransaction.get(), cursorOptions);
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to