2011-02-09 Hans Wennborg <hans@chromium.org>
authorhans@chromium.org <hans@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 9 Feb 2011 10:33:31 +0000 (10:33 +0000)
committerhans@chromium.org <hans@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 9 Feb 2011 10:33:31 +0000 (10:33 +0000)
        Reviewed by Jeremy Orlow.

        IndexedDB: Cursors should skip deleted entries
        https://bugs.webkit.org/show_bug.cgi?id=53690

        Cursors should skip over entries that have been deleted
        since the cursor was opened.

        * storage/indexeddb/cursor-skip-deleted-expected.txt: Added.
        * storage/indexeddb/cursor-skip-deleted.html: Added.
2011-02-09  Hans Wennborg  <hans@chromium.org>

        Reviewed by Jeremy Orlow.

        IndexedDB: Cursors should skip deleted entries
        https://bugs.webkit.org/show_bug.cgi?id=53690

        Add test to check that the cursor skips deleted entries.

        Test: storage/indexeddb/cursor-skip-deleted.html

        * storage/IDBCursorBackendImpl.cpp:
        (WebCore::IDBCursorBackendImpl::currentRowExists):
        (WebCore::IDBCursorBackendImpl::continueFunctionInternal):
        * storage/IDBCursorBackendImpl.h:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@78035 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/storage/indexeddb/cursor-skip-deleted-expected.txt [new file with mode: 0644]
LayoutTests/storage/indexeddb/cursor-skip-deleted.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/storage/IDBCursorBackendImpl.cpp
Source/WebCore/storage/IDBCursorBackendImpl.h

index 78d0ca6..0b068a0 100644 (file)
@@ -1,3 +1,16 @@
+2011-02-09  Hans Wennborg  <hans@chromium.org>
+
+        Reviewed by Jeremy Orlow.
+
+        IndexedDB: Cursors should skip deleted entries
+        https://bugs.webkit.org/show_bug.cgi?id=53690
+
+        Cursors should skip over entries that have been deleted
+        since the cursor was opened.
+
+        * storage/indexeddb/cursor-skip-deleted-expected.txt: Added.
+        * storage/indexeddb/cursor-skip-deleted.html: Added.
+
 2011-02-09  Ilya Tikhonovsky  <loislo@chromium.org>
 
         Unreviewed.
diff --git a/LayoutTests/storage/indexeddb/cursor-skip-deleted-expected.txt b/LayoutTests/storage/indexeddb/cursor-skip-deleted-expected.txt
new file mode 100644 (file)
index 0000000..f1bb3f7
--- /dev/null
@@ -0,0 +1,185 @@
+Test IndexedDB's cursor skips deleted entries.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+webkitIndexedDB.open('name')
+PASS 'onsuccess' in result is true
+PASS 'onerror' in result is true
+PASS 'readyState' in result is true
+An event should fire shortly...
+
+Success event fired:
+PASS 'result' in event is true
+PASS 'code' in event is false
+PASS 'message' in event is false
+PASS 'source' in event is true
+PASS event.source != null is true
+PASS 'onsuccess' in event.target is true
+PASS 'onerror' in event.target is true
+PASS 'readyState' in event.target is true
+PASS event.target.readyState is event.target.DONE
+
+db = event.result
+db.setVersion('new version')
+PASS 'onsuccess' in result is true
+PASS 'onerror' in result is true
+PASS 'readyState' in result is true
+An event should fire shortly...
+
+setVersionSuccess():
+Success event fired:
+PASS 'result' in event is true
+PASS 'code' in event is false
+PASS 'message' in event is false
+PASS 'source' in event is true
+PASS event.source != null is true
+PASS 'onsuccess' in event.target is true
+PASS 'onerror' in event.target is true
+PASS 'readyState' in event.target is true
+PASS event.target.readyState is event.target.DONE
+
+trans = event.result
+PASS trans !== null is true
+Deleted all object stores.
+createAndPopulateObjectStore():
+objectStore = db.createObjectStore('store', {keyPath: 'id'})
+objectStore.createIndex('nameIndex', 'name')
+
+resetObjectStore():
+
+basicCursorTest()
+trans = db.transaction([], webkitIDBTransaction.READ_WRITE)
+
+testCursor():
+trans.objectStore('store').openCursor(webkitIDBKeyRange.lowerBound(0))
+PASS 'onsuccess' in result is true
+PASS 'onerror' in result is true
+PASS 'readyState' in result is true
+An event should fire shortly...
+
+0: Alpha
+1: Bravo
+request = trans.objectStore('store').delete(0)
+2: Charlie
+request = trans.objectStore('store').delete(25)
+3: Delta
+request = trans.objectStore('store').delete(5)
+request = trans.objectStore('store').delete(6)
+request = trans.objectStore('store').delete(7)
+4: Echo
+8: India
+9: Juliet
+10: Kilo
+request = trans.objectStore('store').delete(10)
+11: Lima
+12: Mike
+request = trans.objectStore('store').delete(13)
+14: Oscar
+15: Papa
+request = trans.objectStore('store').delete(14)
+16: Quebec
+17: Romeo
+18: Sierra
+19: Tango
+20: Uniform
+request = trans.objectStore('store').delete(17)
+request = trans.objectStore('store').delete(18)
+21: Victor
+22: Whiskey
+23: X-ray
+24: Yankee
+
+
+resetObjectStore():
+
+reverseCursorTest():
+
+testCursor():
+trans.objectStore('store').openCursor(webkitIDBKeyRange.lowerBound(0), webkitIDBCursor.PREV)
+PASS 'onsuccess' in result is true
+PASS 'onerror' in result is true
+PASS 'readyState' in result is true
+An event should fire shortly...
+
+25: Zulu
+24: Yankee
+request = trans.objectStore('store').delete(25)
+23: X-ray
+request = trans.objectStore('store').delete(0)
+22: Whiskey
+request = trans.objectStore('store').delete(20)
+request = trans.objectStore('store').delete(19)
+request = trans.objectStore('store').delete(18)
+21: Victor
+17: Romeo
+16: Quebec
+15: Papa
+request = trans.objectStore('store').delete(15)
+14: Oscar
+13: November
+request = trans.objectStore('store').delete(12)
+11: Lima
+10: Kilo
+request = trans.objectStore('store').delete(11)
+9: Juliet
+8: India
+7: Hotel
+6: Golf
+5: Foxtrot
+request = trans.objectStore('store').delete(7)
+request = trans.objectStore('store').delete(8)
+4: Echo
+3: Delta
+2: Charlie
+1: Bravo
+
+
+resetObjectStore():
+
+indexCursorTest():
+
+testCursor():
+trans.objectStore('store').index('nameIndex').openCursor(webkitIDBKeyRange.lowerBound('Alpha'))
+PASS 'onsuccess' in result is true
+PASS 'onerror' in result is true
+PASS 'readyState' in result is true
+An event should fire shortly...
+
+0: Alpha
+1: Bravo
+request = trans.objectStore('store').delete(0)
+2: Charlie
+request = trans.objectStore('store').delete(25)
+3: Delta
+request = trans.objectStore('store').delete(5)
+request = trans.objectStore('store').delete(6)
+request = trans.objectStore('store').delete(7)
+4: Echo
+8: India
+9: Juliet
+10: Kilo
+request = trans.objectStore('store').delete(10)
+11: Lima
+12: Mike
+request = trans.objectStore('store').delete(13)
+14: Oscar
+15: Papa
+request = trans.objectStore('store').delete(14)
+16: Quebec
+17: Romeo
+18: Sierra
+19: Tango
+20: Uniform
+request = trans.objectStore('store').delete(17)
+request = trans.objectStore('store').delete(18)
+21: Victor
+22: Whiskey
+23: X-ray
+24: Yankee
+
+transactionComplete():
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/storage/indexeddb/cursor-skip-deleted.html b/LayoutTests/storage/indexeddb/cursor-skip-deleted.html
new file mode 100644 (file)
index 0000000..a97c4af
--- /dev/null
@@ -0,0 +1,218 @@
+<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 IndexedDB's cursor skips deleted entries.");
+if (window.layoutTestController)
+    layoutTestController.waitUntilDone();
+
+var names = ['Alpha', 'Bravo', 'Charlie', 'Delta', 'Echo', 'Foxtrot', 'Golf',
+             'Hotel', 'India', 'Juliet', 'Kilo', 'Lima', 'Mike', 'November',
+             'Oscar', 'Papa', 'Quebec', 'Romeo', 'Sierra', 'Tango', 'Uniform',
+             'Victor', 'Whiskey', 'X-ray', 'Yankee', 'Zulu'];
+
+test();
+
+function test()
+{
+    result = evalAndLog("webkitIndexedDB.open('name')");
+    verifyResult(result);
+    result.onsuccess = openSuccess;
+    result.onerror = unexpectedErrorCallback;
+}
+
+function openSuccess()
+{
+    verifySuccessEvent(event);
+    var db = evalAndLog("db = event.result");
+
+    result = evalAndLog("db.setVersion('new version')");
+    verifyResult(result);
+    result.onsuccess = setVersionSuccess;
+    result.onerror = unexpectedErrorCallback;
+}
+
+function setVersionSuccess()
+{
+    debug("setVersionSuccess():");
+    verifySuccessEvent(event);
+    window.trans = evalAndLog("trans = event.result");
+    shouldBeTrue("trans !== null");
+    trans.onabort = unexpectedAbortCallback;
+    trans.oncomplete = basicCursorTest;
+
+    deleteAllObjectStores(db, createAndPopulateObjectStore);
+}
+
+function createAndPopulateObjectStore()
+{
+    debug("createAndPopulateObjectStore():");
+
+    var objectStore = evalAndLog("objectStore = db.createObjectStore('store', {keyPath: 'id'})");
+    evalAndLog("objectStore.createIndex('nameIndex', 'name')");
+    resetObjectStore(function() {});
+}
+
+var silentErrorHandler = function() { event.preventDefault(); }
+
+function resetObjectStore(callback)
+{
+    debug("\nresetObjectStore():");
+    if (callback === undefined)
+        callback = function () {};
+
+    var objectStore = trans.objectStore('store');
+    for (var i = 0; i < names.length; i++)
+        objectStore.delete(i).onerror = silentErrorHandler;
+    for (var i = 0; i < names.length; i++)
+        objectStore.add({id: i, name: names[i]}).onerror = unexpectedErrorCallback;
+
+    debug("");
+    callback();
+}
+
+function contains(arr, obj)
+{
+    for (var i = 0; i < arr.length; i++) {
+        if (arr[i] == obj)
+            return true;
+    }
+    return false;
+}
+
+var cursor;
+var deleted;
+var seen;
+
+function testCursor(deleteList, createCursorCommand, callback)
+{
+    debug("\ntestCursor():");
+    deleted = [];
+    seen = [];
+
+    // Create the cursor.
+    request = evalAndLog(createCursorCommand);
+    verifyResult(request);
+
+    request.onerror = unexpectedErrorCallback;
+    request.onsuccess = function () {
+        if (event.result == null) {
+            // Make sure we have seen every non-deleted item.
+            for (var i = 0; i < names.length; i++) {
+                if (contains(deleted, i))
+                    continue;
+
+                if (!contains(seen, i))
+                    testFailed("Cursor did not see item with id: " + i);
+            }
+
+            // Make sure we used every rule in |deleteList|.
+            for (var i = 0; i < deleteList.length; i++) {
+                if (!contains(seen, deleteList[i].id))
+                    testFailed("deleteList rule with id: " + deleteList[i].id + " was never used.");
+            }
+
+            debug("");
+            callback();
+            return;
+        }
+
+        cursor = event.result;
+        debug(event.result.value.id + ": " + event.result.value.name);
+        seen.push(event.result.value.id);
+
+        // Make sure we don't see any deleted items.
+        if (contains(deleted, event.result.value.id))
+            testFailed("Cursor hit previously deleted element.");
+
+        for (var i = 0; i < deleteList.length; i++) {
+            if (event.result.value.id == deleteList[i].id) {
+                // Delete objects targeted by this id.
+                var targets = deleteList[i].targets;
+                for (var j = 0; j < targets.length; j++) {
+                    deleted.push(targets[j]);
+                    request = evalAndLog("request = trans.objectStore('store').delete(" + targets[j] + ")");
+                    request.onerror = unexpectedErrorCallback;
+                    if (j == targets.length - 1)
+                        request.onsuccess = function() { cursor.continue(); }
+                }
+                return;
+            }
+        }
+
+        cursor.continue();
+    }
+}
+
+function basicCursorTest()
+{
+    debug("basicCursorTest()");
+
+    evalAndLog("trans = db.transaction([], webkitIDBTransaction.READ_WRITE)");
+    trans.onabort = unexpectedAbortCallback;
+    trans.oncomplete = transactionComplete;
+
+    var deletes = [{id: 1, targets: [0]},
+                   {id: 2, targets: [names.length - 1]},
+                   {id: 3, targets: [5,6,7]},
+                   {id: 10, targets: [10]},
+                   {id: 12, targets: [13]},
+                   {id: 15, targets: [14]},
+                   {id: 20, targets: [17,18]}
+                   ];
+
+    testCursor(deletes, "trans.objectStore('store').openCursor(webkitIDBKeyRange.lowerBound(0))", function() { resetObjectStore(reverseCursorTest); });
+}
+
+function reverseCursorTest()
+{
+    debug("reverseCursorTest():");
+
+    var deletes = [{id: 24, targets: [names.length - 1]},
+                   {id: 23, targets: [0]},
+                   {id: 22, targets: [20, 19, 18]},
+                   {id: 15, targets: [15]},
+                   {id: 13, targets: [12]},
+                   {id: 10, targets: [11]},
+                   {id: 5, targets: [7,8]}
+                   ];
+
+
+    testCursor(deletes, "trans.objectStore('store').openCursor(webkitIDBKeyRange.lowerBound(0), webkitIDBCursor.PREV)", function() { resetObjectStore(indexCursorTest); });
+}
+
+function indexCursorTest()
+{
+    debug("indexCursorTest():");
+
+    var deletes = [{id: 1, targets: [0]},
+                   {id: 2, targets: [names.length - 1]},
+                   {id: 3, targets: [5,6,7]},
+                   {id: 10, targets: [10]},
+                   {id: 12, targets: [13]},
+                   {id: 15, targets: [14]},
+                   {id: 20, targets: [17,18]}
+                   ];
+
+    testCursor(deletes, "trans.objectStore('store').index('nameIndex').openCursor(webkitIDBKeyRange.lowerBound('Alpha'))", function() { });
+}
+
+function transactionComplete()
+{
+    debug("transactionComplete():");
+    done();
+}
+
+var successfullyParsed = true;
+
+</script>
+</body>
+</html>
index 066173a..e901267 100644 (file)
@@ -1,3 +1,19 @@
+2011-02-09  Hans Wennborg  <hans@chromium.org>
+
+        Reviewed by Jeremy Orlow.
+
+        IndexedDB: Cursors should skip deleted entries
+        https://bugs.webkit.org/show_bug.cgi?id=53690
+
+        Add test to check that the cursor skips deleted entries.
+
+        Test: storage/indexeddb/cursor-skip-deleted.html
+
+        * storage/IDBCursorBackendImpl.cpp:
+        (WebCore::IDBCursorBackendImpl::currentRowExists):
+        (WebCore::IDBCursorBackendImpl::continueFunctionInternal):
+        * storage/IDBCursorBackendImpl.h:
+
 2011-02-08  Pavel Podivilov  <podivilov@chromium.org>
 
         Reviewed by Yury Semikhatsky.
index fcfcf3a..0e72449 100644 (file)
@@ -98,6 +98,18 @@ void IDBCursorBackendImpl::continueFunction(PassRefPtr<IDBKey> prpKey, PassRefPt
         ec = IDBDatabaseException::NOT_ALLOWED_ERR;
 }
 
+bool IDBCursorBackendImpl::currentRowExists()
+{
+    String sql = m_currentIDBKeyValue ? "SELECT id FROM IndexData WHERE id = ?" : "SELECT id FROM ObjectStoreData WHERE id = ?";
+    SQLiteStatement statement(m_database->db(), sql);
+
+    bool ok = statement.prepare() == SQLResultOk;
+    ASSERT_UNUSED(ok, ok);
+
+    statement.bindInt64(1, m_currentId);
+    return statement.step() == SQLResultRow;
+}
+
 void IDBCursorBackendImpl::continueFunctionInternal(ScriptExecutionContext*, PassRefPtr<IDBCursorBackendImpl> prpCursor, PassRefPtr<IDBKey> prpKey, PassRefPtr<IDBCallbacks> callbacks)
 {
     RefPtr<IDBCursorBackendImpl> cursor = prpCursor;
@@ -116,6 +128,10 @@ void IDBCursorBackendImpl::continueFunctionInternal(ScriptExecutionContext*, Pas
         RefPtr<IDBKey> oldKey = cursor->m_currentKey;
         cursor->loadCurrentRow();
 
+        // Skip if this entry has been deleted from the object store.
+        if (!cursor->currentRowExists())
+            continue;
+
         // If a key was supplied, we must loop until we find that key (or hit the end).
         if (key && !key->isEqual(cursor->m_currentKey.get()))
             continue;
index 919f153..f459139 100644 (file)
@@ -65,6 +65,7 @@ public:
 private:
     IDBCursorBackendImpl(IDBSQLiteDatabase*, PassRefPtr<IDBKeyRange>, IDBCursor::Direction, PassOwnPtr<SQLiteStatement> query, bool isSerializedScriptValueCursor, IDBTransactionBackendInterface*, IDBObjectStoreBackendInterface*);
 
+    bool currentRowExists();
     void loadCurrentRow();
     SQLiteDatabase& database() const;