Commit f319ea71 authored by commit-queue@webkit.org's avatar commit-queue@webkit.org
Browse files

IndexedDB chooses wrong record on PREV_NO_DUPLICATE index cursor

https://bugs.webkit.org/show_bug.cgi?id=60746

Patch by Alec Flett <alecflett@chromium.org> on 2012-04-17
Reviewed by Ojan Vafai.

Source/WebCore:

When iterating backwards with PREV_NO_DUPLICATE, keep walking past
the 'prev' key until it changes, then walk forward one time. This
covers the object store and index cases.

Test: storage/indexeddb/mozilla/index-prev-no-duplicate.html

* Modules/indexeddb/IDBLevelDBBackingStore.cpp:
(WebCore):

LayoutTests:

Test for PREV_NO_DUPLICATE.

* storage/indexeddb/cursor-prev-no-duplicate.html: Added.
* storage/indexeddb/cursor-prev-no-duplicate.txt: Added.
* storage/indexeddb/mozilla/index-prev-no-duplicate-expected.txt: Added.
* storage/indexeddb/mozilla/index-prev-no-duplicate.html: Added.
* storage/indexeddb/resources/cursor-prev-no-duplicate.js: Added.
(prepareDatabase.openreq.onsuccess.verreq.onsuccess):
(prepareDatabase.openreq.onsuccess):
(prepareDatabase):
(populateStore):
(runAllTests):
(waitFor):
(complete.fireCallback):
(complete):
(testFarRangeCursor):
(makeOpenKeyCursor):
(runTest.trans.oncomplete):
(runTest.testFunction):
(runTest):

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@114464 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 4823073c
2012-04-17 Alec Flett <alecflett@chromium.org>
IndexedDB chooses wrong record on PREV_NO_DUPLICATE index cursor
https://bugs.webkit.org/show_bug.cgi?id=60746
Reviewed by Ojan Vafai.
Test for PREV_NO_DUPLICATE.
* storage/indexeddb/cursor-prev-no-duplicate.html: Added.
* storage/indexeddb/cursor-prev-no-duplicate.txt: Added.
* storage/indexeddb/mozilla/index-prev-no-duplicate-expected.txt: Added.
* storage/indexeddb/mozilla/index-prev-no-duplicate.html: Added.
* storage/indexeddb/resources/cursor-prev-no-duplicate.js: Added.
(prepareDatabase.openreq.onsuccess.verreq.onsuccess):
(prepareDatabase.openreq.onsuccess):
(prepareDatabase):
(populateStore):
(runAllTests):
(waitFor):
(complete.fireCallback):
(complete):
(testFarRangeCursor):
(makeOpenKeyCursor):
(runTest.trans.oncomplete):
(runTest.testFunction):
(runTest):
2012-04-17 Yuta Kitamura <yutak@chromium.org>
 
Unreviewed, unskip some WebSocket worker tests that should not be skipped.
Test IndexedDB behavior when iterating backwards with and without NO_DUPLICATE
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
indexedDB = self.indexedDB || self.webkitIndexedDB || self.mozIndexedDB || self.msIndexedDB || self.OIndexedDB;
openreq = indexedDB.open('cursor-prev-no-duplicate')
db = openreq.result
verreq = db.setVersion('1')
Deleted all object stores.
store = db.createObjectStore('store')
store.createIndex('index', 'sorted')
populating store...
trans = db.transaction('store', IDBTransaction.READ_WRITE)
store = trans.objectStore('store');
store.put({sorted: 3, value: 111}, 1)
store.put({sorted: 2, value: 222}, 2)
store.put({sorted: 1, value: 333}, 3)
store.put({sorted: 10, value: 444}, 17)
store.put({sorted: 10, value: 555}, 16)
store.put({sorted: 10, value: 666}, 15)
testFarRangeCursor: upper bound is well out of range, results always the same, whether open or closed
storeReq = store.openCursor(IDBKeyRange.upperBound(7, false), 2)
Testing: store.openCursor(IDBKeyRange.upperBound(7, false), 2)
PASS cursor.key is 3
PASS cursor.value.value is 333
storeReq = store.openCursor(IDBKeyRange.upperBound(7, true), 2)
DONE
Testing: store.openCursor(IDBKeyRange.upperBound(7, true), 2)
PASS cursor.key is 3
PASS cursor.value.value is 333
storeReq = index.openCursor(IDBKeyRange.upperBound(7, false), 2)
DONE
Testing: index.openCursor(IDBKeyRange.upperBound(7, false), 2)
PASS cursor.key is 3
PASS cursor.value.value is 111
PASS cursor.primaryKey is 1
storeReq = index.openCursor(IDBKeyRange.upperBound(7, true), 2)
DONE
Testing: index.openCursor(IDBKeyRange.upperBound(7, true), 2)
PASS cursor.key is 3
PASS cursor.value.value is 111
PASS cursor.primaryKey is 1
storeReq = index.openKeyCursor(IDBKeyRange.upperBound(7, false), 2)
DONE
Testing: index.openKeyCursor(IDBKeyRange.upperBound(7, false), 2)
PASS cursor.key is 3
PASS cursor.primaryKey is 1
storeReq = index.openKeyCursor(IDBKeyRange.upperBound(7, true), 2)
DONE
Testing: index.openKeyCursor(IDBKeyRange.upperBound(7, true), 2)
PASS cursor.key is 3
PASS cursor.primaryKey is 1
storeReq = store.openCursor(IDBKeyRange.upperBound(3, false), 2)
DONE
Testing: store.openCursor(IDBKeyRange.upperBound(3, false), 2)
PASS cursor.key is 3
PASS cursor.value.value is 333
storeReq = store.openCursor(IDBKeyRange.upperBound(3, true), 2)
DONE
Testing: store.openCursor(IDBKeyRange.upperBound(3, true), 2)
PASS cursor.key is 2
PASS cursor.value.value is 222
storeReq = index.openCursor(IDBKeyRange.upperBound(3, false), 2)
DONE
Testing: index.openCursor(IDBKeyRange.upperBound(3, false), 2)
PASS cursor.key is 3
PASS cursor.value.value is 111
PASS cursor.primaryKey is 1
storeReq = index.openCursor(IDBKeyRange.upperBound(3, true), 2)
DONE
Testing: index.openCursor(IDBKeyRange.upperBound(3, true), 2)
PASS cursor.key is 2
PASS cursor.value.value is 222
PASS cursor.primaryKey is 2
storeReq = index.openKeyCursor(IDBKeyRange.upperBound(3, false), 2)
DONE
Testing: index.openKeyCursor(IDBKeyRange.upperBound(3, false), 2)
PASS cursor.key is 3
PASS cursor.primaryKey is 1
storeReq = index.openKeyCursor(IDBKeyRange.upperBound(3, true), 2)
DONE
Testing: index.openKeyCursor(IDBKeyRange.upperBound(3, true), 2)
PASS cursor.key is 2
PASS cursor.primaryKey is 2
testNoDuplicate: there are 3 values, but we should return always the first one
storeReq = store.openCursor(IDBKeyRange.upperBound(15, false), 3)
DONE
Testing: store.openCursor(IDBKeyRange.upperBound(15, false), 3)
PASS cursor.key is 15
PASS cursor.value.value is 666
PASS cursor.primaryKey is 15
storeReq = index.openCursor(IDBKeyRange.upperBound(15, false), 3)
DONE
Testing: index.openCursor(IDBKeyRange.upperBound(15, false), 3)
PASS cursor.key is 10
PASS cursor.value.value is 666
PASS cursor.primaryKey is 15
storeReq = index.openKeyCursor(IDBKeyRange.upperBound(15, false), 3)
DONE
Testing: index.openKeyCursor(IDBKeyRange.upperBound(15, false), 3)
PASS cursor.key is 10
PASS cursor.primaryKey is 15
PASS successfullyParsed is true
TEST COMPLETE
<html>
<head>
<script src="../../fast/js/resources/js-test-pre.js"></script>
<script src="resources/shared.js"></script>
</head>
<body>
<script src="resources/cursor-prev-no-duplicate.js"></script>
<script src="../../fast/js/resources/js-test-post.js"></script>
</body>
</html>
Test IndexedDB: iterating backwards through an index, skipping duplicates
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
IDBDatabaseException = window.IDBDatabaseException || window.webkitIDBDatabaseException;
PASS IDBDatabaseException == null is false
IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;
PASS IDBTransaction == null is false
IDBCursor = window.IDBCursor || window.webkitIDBCursor;
PASS IDBCursor == null is false
IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange;
PASS IDBKeyRange == null is false
indexedDB.open(name, description)
openSuccess():
db = event.target.result
request = db.setVersion('1')
Deleted all object stores.
objectStore = db.createObjectStore(objectStoreName);
First, add all our data to the object store.
request = objectStore.add(objectStoreData[i].value, objectStoreData[i].key);
request = objectStore.add(objectStoreData[i].value, objectStoreData[i].key);
request = objectStore.add(objectStoreData[i].value, objectStoreData[i].key);
request = objectStore.add(objectStoreData[i].value, objectStoreData[i].key);
request = objectStore.add(objectStoreData[i].value, objectStoreData[i].key);
request = objectStore.add(objectStoreData[i].value, objectStoreData[i].key);
request = objectStore.add(objectStoreData[i].value, objectStoreData[i].key);
request = objectStore.add(objectStoreData[i].value, objectStoreData[i].key);
request = objectStore.add(objectStoreData[i].value, objectStoreData[i].key);
Now create the indexes.
objectStore.createIndex(indexData[i].name, indexData[i].keyPath, indexData[i].options);
objectStore.createIndex(indexData[i].name, indexData[i].keyPath, indexData[i].options);
objectStore.createIndex(indexData[i].name, indexData[i].keyPath, indexData[i].options);
testPrev()
trans = db.transaction(objectStoreName)
objectStore = trans.objectStore(objectStoreName);
keyIndex = 8;
request = objectStore.index('height').openCursor(null, IDBCursor.PREV);
cursor = event.target.result;
PASS cursor.key is 73
PASS cursor.primaryKey is '237-23-7740'
PASS cursor.value.name is 'Sam'
PASS cursor.value.height is 73
PASS cursor.value.weight is 110
cursor.continue();
keyIndex--;
=> 7
cursor = event.target.result;
PASS cursor.key is 73
PASS cursor.primaryKey is '237-23-7734'
PASS cursor.value.name is 'Ron'
PASS cursor.value.height is 73
PASS cursor.value.weight is 180
cursor.continue();
keyIndex--;
=> 6
cursor = event.target.result;
PASS cursor.key is 65
PASS cursor.primaryKey is '237-23-7739'
PASS cursor.value.name is 'Jef'
PASS cursor.value.height is 65
PASS cursor.value.weight is 120
cursor.continue();
keyIndex--;
=> 5
cursor = event.target.result;
PASS cursor.key is 65
PASS cursor.primaryKey is '237-23-7738'
PASS cursor.value.name is 'Leo'
PASS cursor.value.height is 65
PASS cursor.value.weight is 180
cursor.continue();
keyIndex--;
=> 4
cursor = event.target.result;
PASS cursor.key is 65
PASS cursor.primaryKey is '237-23-7737'
PASS cursor.value.name is 'Pat'
PASS cursor.value.height is 65
PASS cursor.value.weight is 100
cursor.continue();
keyIndex--;
=> 3
cursor = event.target.result;
PASS cursor.key is 65
PASS cursor.primaryKey is '237-23-7736'
PASS cursor.value.name is 'Joe'
PASS cursor.value.height is 65
PASS cursor.value.weight is 150
cursor.continue();
keyIndex--;
=> 2
cursor = event.target.result;
PASS cursor.key is 60
PASS cursor.primaryKey is '237-23-7732'
PASS cursor.value.name is 'Bob'
PASS cursor.value.height is 60
PASS cursor.value.weight is 120
cursor.continue();
keyIndex--;
=> 1
cursor = event.target.result;
PASS cursor.key is 58
PASS cursor.primaryKey is '237-23-7735'
PASS cursor.value.name is 'Sue'
PASS cursor.value.height is 58
PASS cursor.value.weight is 130
cursor.continue();
keyIndex--;
=> 0
cursor = event.target.result;
PASS cursor.key is 52
PASS cursor.primaryKey is '237-23-7733'
PASS cursor.value.name is 'Ann'
PASS cursor.value.height is 52
PASS cursor.value.weight is 110
cursor.continue();
keyIndex--;
=> -1
cursor = event.target.result;
No cursor: null
PASS keyIndex is -1
testPrevNoDuplicate()
objectStore = db.transaction(objectStoreName).objectStore(objectStoreName);
keyIndex = 8;
request = objectStore.index('height').openCursor(null, IDBCursor.PREV_NO_DUPLICATE);
cursor = event.target.result;
keyIndex -= 1
=> Entering with keyIndex = 7
PASS cursor.key is 73
PASS cursor.primaryKey is '237-23-7734'
PASS cursor.value.name is 'Ron'
PASS cursor.value.height is 73
PASS cursor.value.weight is 180
cursor.continue();
keyIndex--;
cursor = event.target.result;
keyIndex -= 3;
=> Entering with keyIndex = 3
PASS cursor.key is 65
PASS cursor.primaryKey is '237-23-7736'
PASS cursor.value.name is 'Joe'
PASS cursor.value.height is 65
PASS cursor.value.weight is 150
cursor.continue();
keyIndex--;
cursor = event.target.result;
=> Entering with keyIndex = 2
PASS cursor.key is 60
PASS cursor.primaryKey is '237-23-7732'
PASS cursor.value.name is 'Bob'
PASS cursor.value.height is 60
PASS cursor.value.weight is 120
cursor.continue();
keyIndex--;
cursor = event.target.result;
=> Entering with keyIndex = 1
PASS cursor.key is 58
PASS cursor.primaryKey is '237-23-7735'
PASS cursor.value.name is 'Sue'
PASS cursor.value.height is 58
PASS cursor.value.weight is 130
cursor.continue();
keyIndex--;
cursor = event.target.result;
=> Entering with keyIndex = 0
PASS cursor.key is 52
PASS cursor.primaryKey is '237-23-7733'
PASS cursor.value.name is 'Ann'
PASS cursor.value.height is 52
PASS cursor.value.weight is 110
cursor.continue();
keyIndex--;
cursor = event.target.result;
=> Entering with keyIndex = -1
No cursor: null
PASS keyIndex is -1
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE html>
<!--
original test: http://mxr.mozilla.org/mozilla2.0/source/dom/indexedDB/test/test_indexes.html?force=1
license of original test:
" Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ "
-->
<html>
<head>
<script src="../../../fast/js/resources/js-test-pre.js"></script>
<script>
var jsTestIsAsync = true;
</script>
<script src="../resources/shared.js"></script>
</head>
<body>
<script>
description("Test IndexedDB: iterating backwards through an index, skipping duplicates");
if (window.layoutTestController)
layoutTestController.waitUntilDone();
function test()
{
indexedDB = evalAndLog("indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;");
shouldBeFalse("indexedDB == null");
IDBDatabaseException = evalAndLog("IDBDatabaseException = window.IDBDatabaseException || window.webkitIDBDatabaseException;");
shouldBeFalse("IDBDatabaseException == null");
IDBTransaction = evalAndLog("IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;");
shouldBeFalse("IDBTransaction == null");
IDBCursor = evalAndLog("IDBCursor = window.IDBCursor || window.webkitIDBCursor;");
shouldBeFalse("IDBCursor == null");
IDBKeyRange = evalAndLog("IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange;");
shouldBeFalse("IDBKeyRange == null");
name = window.location.pathname;
description = "My Test Database";
request = evalAndLog("indexedDB.open(name, description)");
request.onsuccess = openSuccess;
request.onerror = unexpectedErrorCallback;
}
function openSuccess()
{
debug("openSuccess():");
db = evalAndLog("db = event.target.result");
objectStoreName = "People";
objectStoreData = [
{ key: "237-23-7732", value: { name: "Bob", height: 60, weight: 120 } },
{ key: "237-23-7733", value: { name: "Ann", height: 52, weight: 110 } },
{ key: "237-23-7734", value: { name: "Ron", height: 73, weight: 180 } },
{ key: "237-23-7735", value: { name: "Sue", height: 58, weight: 130 } },
{ key: "237-23-7736", value: { name: "Joe", height: 65, weight: 150 } },
{ key: "237-23-7737", value: { name: "Pat", height: 65, weight: 100 } },
{ key: "237-23-7738", value: { name: "Leo", height: 65, weight: 180 } },
{ key: "237-23-7739", value: { name: "Jef", height: 65, weight: 120 } },
{ key: "237-23-7740", value: { name: "Sam", height: 73, weight: 110 } },
];
indexData = [
{ name: "name", keyPath: "name", options: { unique: true } },
{ name: "height", keyPath: "height", options: { } },
{ name: "weight", keyPath: "weight", options: { unique: false } }
];
objectStoreDataHeightSort = [
{ key: "237-23-7733", value: { name: "Ann", height: 52, weight: 110 } },
{ key: "237-23-7735", value: { name: "Sue", height: 58, weight: 130 } },
{ key: "237-23-7732", value: { name: "Bob", height: 60, weight: 120 } },
{ key: "237-23-7736", value: { name: "Joe", height: 65, weight: 150 } },
{ key: "237-23-7737", value: { name: "Pat", height: 65, weight: 100 } },
{ key: "237-23-7738", value: { name: "Leo", height: 65, weight: 180 } },
{ key: "237-23-7739", value: { name: "Jef", height: 65, weight: 120 } },
{ key: "237-23-7734", value: { name: "Ron", height: 73, weight: 180 } },
{ key: "237-23-7740", value: { name: "Sam", height: 73, weight: 110 } },
];
request = evalAndLog("request = db.setVersion('1')");
request.onsuccess = createAndPopulateObjectStore;
request.onerror = unexpectedErrorCallback;
}
function createAndPopulateObjectStore()
{
deleteAllObjectStores(db);
objectStore = evalAndLog("objectStore = db.createObjectStore(objectStoreName);");
debug("First, add all our data to the object store.");
addedData = 0;
for (i in objectStoreData) {
request = evalAndLog("request = objectStore.add(objectStoreData[i].value, objectStoreData[i].key);");
request.onerror = unexpectedErrorCallback;
request.onsuccess = function(event)
{
if (++addedData == objectStoreData.length) {
createIndexes();
}
}
}
}
function createIndexes()
{
debug("Now create the indexes.");
for (i in indexData) {
evalAndLog("objectStore.createIndex(indexData[i].name, indexData[i].keyPath, indexData[i].options);");
}
testPrev();
}
function testPrev()
{
debug("testPrev()");
trans = evalAndLog("trans = db.transaction(objectStoreName)");
objectStore = evalAndLog("objectStore = trans.objectStore(objectStoreName);");
keyIndex = evalAndLog("keyIndex = 8;");
// first try with just PREV
request = evalAndLog("request = objectStore.index('height').openCursor(null, IDBCursor.PREV);");
request.onerror = unexpectedErrorCallback;
request.onsuccess = function(event)
{
cursor = evalAndLog("cursor = event.target.result;");
if (cursor) {
shouldBe("cursor.key", "" + objectStoreDataHeightSort[keyIndex].value.height);
shouldBe("cursor.primaryKey", "'" + objectStoreDataHeightSort[keyIndex].key + "'");
shouldBe("cursor.value.name", "'" + objectStoreDataHeightSort[keyIndex].value.name + "'");
shouldBe("cursor.value.height", "" + objectStoreDataHeightSort[keyIndex].value.height);
if ('weight' in cursor.value) {
shouldBe("cursor.value.weight", "" + objectStoreDataHeightSort[keyIndex].value.weight);
}
evalAndLog("cursor.continue();");
evalAndLog("keyIndex--;");
debug(" => " + keyIndex);
}
else {
debug("No cursor: " + cursor);
shouldBe("keyIndex", "-1");
}
};
trans.oncomplete = testPrevNoDuplicate;
}
function testPrevNoDuplicate()
{
debug("testPrevNoDuplicate()");
objectStore = evalAndLog("objectStore = db.transaction(objectStoreName).objectStore(objectStoreName);");
keyIndex = evalAndLog("keyIndex = 8;");
request = evalAndLog("request = objectStore.index('height').openCursor(null, IDBCursor.PREV_NO_DUPLICATE);");
request.onerror = unexpectedErrorCallback;
request.onsuccess = function (event)
{
cursor = evalAndLog("cursor = event.target.result;");
if (keyIndex == 8) {
evalAndLog("keyIndex -= 1");
}
if (keyIndex == 6) {
evalAndLog("keyIndex -= 3;");
}
debug(" => Entering with keyIndex = " + keyIndex);
if (cursor) {
shouldBe("cursor.key", "" + objectStoreDataHeightSort[keyIndex].value.height);
shouldBe("cursor.primaryKey", "'" + objectStoreDataHeightSort[keyIndex].key + "'");
shouldBe("cursor.value.name", "'" + objectStoreDataHeightSort[keyIndex].value.name + "'");
shouldBe("cursor.value.height", "" + objectStoreDataHeightSort[keyIndex].value.height);
if ('weight' in cursor.value) {
shouldBe("cursor.value.weight", "" + objectStoreDataHeightSort[keyIndex].value.weight);
}
evalAndLog("cursor.continue();");
evalAndLog("keyIndex--;");
}
else {
debug("No cursor: " + cursor);
shouldBe("keyIndex", "-1");
finishJSTest();
}
}
}
var successfullyParsed = true;
test();
</script>
<script src="../../../fast/js/resources/js-test-post.js"></script>
</body>
</html>
if (this.importScripts) {
importScripts('../../../fast/js/resources/js-test-pre.js');
importScripts('shared.js');
}
description("Test IndexedDB behavior when iterating backwards with and without NO_DUPLICATE");
function test()
{
removeVendorPrefixes();
prepareDatabase();
}
function prepareDatabase()
{
debug("");
evalAndLog("openreq = indexedDB.open('cursor-prev-no-duplicate')");
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', 'sorted')");
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({sorted: 3, value: 111}, 1)");