IndexedDB: Support Array-type key paths
authorjsbell@chromium.org <jsbell@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 22 May 2012 18:46:07 +0000 (18:46 +0000)
committerjsbell@chromium.org <jsbell@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 22 May 2012 18:46:07 +0000 (18:46 +0000)
https://bugs.webkit.org/show_bug.cgi?id=84207

Reviewed by Tony Chang.

Source/WebCore:

Implement IDB spec behavior that key paths can be arrays of strings; when
evaluated, these yield arrays of keys, providing compound key support. Also
changes exception types to match the spec.

Test: storage/indexeddb/keypath-arrays.html
Test: storage/indexeddb/keypath-basics.html

* Modules/indexeddb/IDBDatabase.cpp:
(WebCore::IDBDatabase::createObjectStore): Look for both string and array in option dict,
throw exceptions for forbidden combinations of key generator and key paths.
* Modules/indexeddb/IDBObjectStore.cpp:
(WebCore::IDBObjectStore::createIndex): Handle special cases (accepted and forbidden).
(WebCore):
* Modules/indexeddb/IDBObjectStore.h:
(IDBObjectStore):
(WebCore::IDBObjectStore::createIndex): Overloads to satisfy IDL overloads and optional dicts.
* Modules/indexeddb/IDBObjectStore.idl: Add DOMString array overload.
* bindings/v8/IDBBindingUtilities.cpp: Implement spec logic for evaluating array key paths.
(WebCore::createIDBKeyFromSerializedValueAndKeyPath):
(WebCore):

LayoutTests:

* storage/indexeddb/keypath-arrays-expected.txt: Added.
* storage/indexeddb/keypath-arrays.html: Added.
* storage/indexeddb/keypath-basics-expected.txt:
* storage/indexeddb/mozilla/create-objectstore-basics-expected.txt:
* storage/indexeddb/mozilla/resources/create-objectstore-basics.js:
(cleanDatabase):
* storage/indexeddb/objectStore-required-arguments-expected.txt:
* storage/indexeddb/resources/keypath-arrays.js: Added.
(test.request.onsuccess):
(test):
(openSuccess.request.onsuccess):
(openSuccess):
(testKeyPaths.checkStore.request.onsuccess):
(testKeyPaths.checkStore):
(testKeyPaths.checkIndex.request.onsuccess):
(testKeyPaths.checkIndex):
(testKeyPaths):
* storage/indexeddb/resources/keypath-basics.js:

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

15 files changed:
LayoutTests/ChangeLog
LayoutTests/storage/indexeddb/keypath-arrays-expected.txt [new file with mode: 0644]
LayoutTests/storage/indexeddb/keypath-arrays.html [new file with mode: 0644]
LayoutTests/storage/indexeddb/keypath-basics-expected.txt
LayoutTests/storage/indexeddb/mozilla/create-objectstore-basics-expected.txt
LayoutTests/storage/indexeddb/mozilla/resources/create-objectstore-basics.js
LayoutTests/storage/indexeddb/objectStore-required-arguments-expected.txt
LayoutTests/storage/indexeddb/resources/keypath-arrays.js [new file with mode: 0644]
LayoutTests/storage/indexeddb/resources/keypath-basics.js
Source/WebCore/ChangeLog
Source/WebCore/Modules/indexeddb/IDBDatabase.cpp
Source/WebCore/Modules/indexeddb/IDBObjectStore.cpp
Source/WebCore/Modules/indexeddb/IDBObjectStore.h
Source/WebCore/Modules/indexeddb/IDBObjectStore.idl
Source/WebCore/bindings/v8/IDBBindingUtilities.cpp

index 8a5e4ab..3e29e20 100644 (file)
@@ -1,3 +1,29 @@
+2012-05-22  Joshua Bell  <jsbell@chromium.org>
+
+        IndexedDB: Support Array-type key paths
+        https://bugs.webkit.org/show_bug.cgi?id=84207
+
+        Reviewed by Tony Chang.
+
+        * storage/indexeddb/keypath-arrays-expected.txt: Added.
+        * storage/indexeddb/keypath-arrays.html: Added.
+        * storage/indexeddb/keypath-basics-expected.txt:
+        * storage/indexeddb/mozilla/create-objectstore-basics-expected.txt:
+        * storage/indexeddb/mozilla/resources/create-objectstore-basics.js:
+        (cleanDatabase):
+        * storage/indexeddb/objectStore-required-arguments-expected.txt:
+        * storage/indexeddb/resources/keypath-arrays.js: Added.
+        (test.request.onsuccess):
+        (test):
+        (openSuccess.request.onsuccess):
+        (openSuccess):
+        (testKeyPaths.checkStore.request.onsuccess):
+        (testKeyPaths.checkStore):
+        (testKeyPaths.checkIndex.request.onsuccess):
+        (testKeyPaths.checkIndex):
+        (testKeyPaths):
+        * storage/indexeddb/resources/keypath-basics.js:
+
 2012-05-22  Abhishek Arya  <inferno@chromium.org>
 
         Assertion failure (toRenderBox() called on a RenderInline) beneath RenderBlock::blockBeforeWithinSelectionRoot()
diff --git a/LayoutTests/storage/indexeddb/keypath-arrays-expected.txt b/LayoutTests/storage/indexeddb/keypath-arrays-expected.txt
new file mode 100644 (file)
index 0000000..7de63fd
--- /dev/null
@@ -0,0 +1,41 @@
+Test IndexedDB Array-type keyPaths
+
+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;
+
+indexedDB.deleteDatabase('keypath-arrays')
+indexedDB.open('keypath-arrays')
+
+openSuccess():
+db = event.target.result
+request = db.setVersion('1')
+store = db.createObjectStore('store', {keyPath: ['a', 'b']})
+store.createIndex('index', ['c', 'd'])
+Expecting exception from db.createObjectStore('store-with-generator', {keyPath: ['a', 'b'], autoIncrement: true})
+PASS Exception was thrown.
+PASS code is DOMException.INVALID_ACCESS_ERR
+Expecting exception from store.createIndex('index-multientry', ['e', 'f'], {multiEntry: true})
+PASS Exception was thrown.
+PASS code is DOMException.NOT_SUPPORTED_ERR
+
+testKeyPaths():
+transaction = db.transaction(['store'], 'readwrite')
+store = transaction.objectStore('store')
+index = store.index('index')
+
+request = store.put({a: 1, b: 2, c: 3, d: 4})
+request = store.openCursor()
+cursor = request.result
+PASS cursor is non-null.
+PASS JSON.stringify(cursor.key) is "[1,2]"
+request = index.openCursor()
+cursor = request.result
+PASS cursor is non-null.
+PASS JSON.stringify(cursor.primaryKey) is "[1,2]"
+PASS JSON.stringify(cursor.key) is "[3,4]"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/storage/indexeddb/keypath-arrays.html b/LayoutTests/storage/indexeddb/keypath-arrays.html
new file mode 100644 (file)
index 0000000..460ec19
--- /dev/null
@@ -0,0 +1,10 @@
+<html>
+<head>
+<script src="../../fast/js/resources/js-test-pre.js"></script>
+<script src="resources/shared.js"></script>
+</head>
+<body>
+<script src="resources/keypath-arrays.js"></script>
+<script src="../../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
index 8629463..4d5c970 100644 (file)
@@ -8,6 +8,8 @@ indexedDB = self.indexedDB || self.webkitIndexedDB || self.mozIndexedDB || self.
 indexedDB.open(name)
 db = event.target.result
 request = db.setVersion('1')
+
+testValidKeyPaths():
 Deleted all object stores.
 store = db.createObjectStore('name')
 PASS store.keyPath is null
@@ -37,129 +39,98 @@ PASS store.keyPath is 'foo.bar.baz'
 index = store.createIndex('name', 'foo.bar.baz')
 PASS index.keyPath is 'foo.bar.baz'
 Deleted all object stores.
+
+testInvalidKeyPaths():
 Deleted all object stores.
-globalKeyPath = '[]'
-Expecting exception from db.createObjectStore('name', {keyPath: globalKeyPath})
+
+Object store key path may not be empty or an array if autoIncrement is true
+Expecting exception from store = db.createObjectStore('storeName', {autoIncrement: true, keyPath: ''})
 PASS Exception was thrown.
-PASS code is IDBDatabaseException.NON_TRANSIENT_ERR
+PASS code is DOMException.INVALID_ACCESS_ERR
 Deleted all object stores.
-globalKeyPath = '["foo"]'
-Expecting exception from db.createObjectStore('name', {keyPath: globalKeyPath})
+Expecting exception from store = db.createObjectStore('storeName', {autoIncrement: true, keyPath: []})
 PASS Exception was thrown.
-PASS code is IDBDatabaseException.NON_TRANSIENT_ERR
+PASS code is DOMException.INVALID_ACCESS_ERR
 Deleted all object stores.
-globalKeyPath = '["foo", "bar"]'
-Expecting exception from db.createObjectStore('name', {keyPath: globalKeyPath})
+Expecting exception from store = db.createObjectStore('storeName', {autoIncrement: true, keyPath: ['a']})
 PASS Exception was thrown.
-PASS code is IDBDatabaseException.NON_TRANSIENT_ERR
+PASS code is DOMException.INVALID_ACCESS_ERR
 Deleted all object stores.
-globalKeyPath = '["", ""]'
-Expecting exception from db.createObjectStore('name', {keyPath: globalKeyPath})
+Expecting exception from store = db.createObjectStore('storeName', {autoIncrement: true, keyPath: ['']})
 PASS Exception was thrown.
-PASS code is IDBDatabaseException.NON_TRANSIENT_ERR
+PASS code is DOMException.INVALID_ACCESS_ERR
 Deleted all object stores.
-globalKeyPath = '[1.0, 2.0]'
-Expecting exception from db.createObjectStore('name', {keyPath: globalKeyPath})
+
+Key paths which are never valid:
+Expecting exception from db.createObjectStore('name', {keyPath: ' '})
 PASS Exception was thrown.
-PASS code is IDBDatabaseException.NON_TRANSIENT_ERR
-Deleted all object stores.
-globalKeyPath = '[["foo"]]'
-Expecting exception from db.createObjectStore('name', {keyPath: globalKeyPath})
+PASS code is DOMException.SYNTAX_ERR
+Expecting exception from db.createObjectStore('name').createIndex('name', ' ')
 PASS Exception was thrown.
-PASS code is IDBDatabaseException.NON_TRANSIENT_ERR
+PASS code is DOMException.SYNTAX_ERR
 Deleted all object stores.
-globalKeyPath = '["foo", ["bar"]]'
-Expecting exception from db.createObjectStore('name', {keyPath: globalKeyPath})
+Expecting exception from db.createObjectStore('name', {keyPath: 'foo '})
 PASS Exception was thrown.
-PASS code is IDBDatabaseException.NON_TRANSIENT_ERR
-Deleted all object stores.
-globalKeyPath = ' '
-Expecting exception from db.createObjectStore('name', {keyPath: globalKeyPath})
+PASS code is DOMException.SYNTAX_ERR
+Expecting exception from db.createObjectStore('name').createIndex('name', 'foo ')
 PASS Exception was thrown.
-PASS code is IDBDatabaseException.NON_TRANSIENT_ERR
+PASS code is DOMException.SYNTAX_ERR
 Deleted all object stores.
-globalKeyPath = 'foo '
-Expecting exception from db.createObjectStore('name', {keyPath: globalKeyPath})
+Expecting exception from db.createObjectStore('name', {keyPath: 'foo bar'})
 PASS Exception was thrown.
-PASS code is IDBDatabaseException.NON_TRANSIENT_ERR
-Deleted all object stores.
-globalKeyPath = 'foo bar'
-Expecting exception from db.createObjectStore('name', {keyPath: globalKeyPath})
+PASS code is DOMException.SYNTAX_ERR
+Expecting exception from db.createObjectStore('name').createIndex('name', 'foo bar')
 PASS Exception was thrown.
-PASS code is IDBDatabaseException.NON_TRANSIENT_ERR
+PASS code is DOMException.SYNTAX_ERR
 Deleted all object stores.
-globalKeyPath = 'foo. bar'
-Expecting exception from db.createObjectStore('name', {keyPath: globalKeyPath})
+Expecting exception from db.createObjectStore('name', {keyPath: 'foo. bar'})
 PASS Exception was thrown.
-PASS code is IDBDatabaseException.NON_TRANSIENT_ERR
-Deleted all object stores.
-globalKeyPath = 'foo .bar'
-Expecting exception from db.createObjectStore('name', {keyPath: globalKeyPath})
+PASS code is DOMException.SYNTAX_ERR
+Expecting exception from db.createObjectStore('name').createIndex('name', 'foo. bar')
 PASS Exception was thrown.
-PASS code is IDBDatabaseException.NON_TRANSIENT_ERR
+PASS code is DOMException.SYNTAX_ERR
 Deleted all object stores.
-globalKeyPath = 'foo..bar'
-Expecting exception from db.createObjectStore('name', {keyPath: globalKeyPath})
+Expecting exception from db.createObjectStore('name', {keyPath: 'foo .bar'})
 PASS Exception was thrown.
-PASS code is IDBDatabaseException.NON_TRANSIENT_ERR
-Deleted all object stores.
-globalKeyPath = '+foo'
-Expecting exception from db.createObjectStore('name', {keyPath: globalKeyPath})
+PASS code is DOMException.SYNTAX_ERR
+Expecting exception from db.createObjectStore('name').createIndex('name', 'foo .bar')
 PASS Exception was thrown.
-PASS code is IDBDatabaseException.NON_TRANSIENT_ERR
+PASS code is DOMException.SYNTAX_ERR
 Deleted all object stores.
-globalKeyPath = 'foo%'
-Expecting exception from db.createObjectStore('name', {keyPath: globalKeyPath})
+Expecting exception from db.createObjectStore('name', {keyPath: 'foo..bar'})
 PASS Exception was thrown.
-PASS code is IDBDatabaseException.NON_TRANSIENT_ERR
-Deleted all object stores.
-globalKeyPath = ' '
-store = db.createObjectStore('storeName')
-Expecting exception from store.createIndex('name', globalKeyPath)
+PASS code is DOMException.SYNTAX_ERR
+Expecting exception from db.createObjectStore('name').createIndex('name', 'foo..bar')
 PASS Exception was thrown.
-PASS code is IDBDatabaseException.NON_TRANSIENT_ERR
+PASS code is DOMException.SYNTAX_ERR
 Deleted all object stores.
-globalKeyPath = 'foo '
-store = db.createObjectStore('storeName')
-Expecting exception from store.createIndex('name', globalKeyPath)
+Expecting exception from db.createObjectStore('name', {keyPath: '+foo'})
 PASS Exception was thrown.
-PASS code is IDBDatabaseException.NON_TRANSIENT_ERR
-Deleted all object stores.
-globalKeyPath = 'foo bar'
-store = db.createObjectStore('storeName')
-Expecting exception from store.createIndex('name', globalKeyPath)
+PASS code is DOMException.SYNTAX_ERR
+Expecting exception from db.createObjectStore('name').createIndex('name', '+foo')
 PASS Exception was thrown.
-PASS code is IDBDatabaseException.NON_TRANSIENT_ERR
+PASS code is DOMException.SYNTAX_ERR
 Deleted all object stores.
-globalKeyPath = 'foo. bar'
-store = db.createObjectStore('storeName')
-Expecting exception from store.createIndex('name', globalKeyPath)
+Expecting exception from db.createObjectStore('name', {keyPath: 'foo%'})
 PASS Exception was thrown.
-PASS code is IDBDatabaseException.NON_TRANSIENT_ERR
-Deleted all object stores.
-globalKeyPath = 'foo .bar'
-store = db.createObjectStore('storeName')
-Expecting exception from store.createIndex('name', globalKeyPath)
+PASS code is DOMException.SYNTAX_ERR
+Expecting exception from db.createObjectStore('name').createIndex('name', 'foo%')
 PASS Exception was thrown.
-PASS code is IDBDatabaseException.NON_TRANSIENT_ERR
+PASS code is DOMException.SYNTAX_ERR
 Deleted all object stores.
-globalKeyPath = 'foo..bar'
-store = db.createObjectStore('storeName')
-Expecting exception from store.createIndex('name', globalKeyPath)
+Expecting exception from db.createObjectStore('name', {keyPath: '1'})
 PASS Exception was thrown.
-PASS code is IDBDatabaseException.NON_TRANSIENT_ERR
-Deleted all object stores.
-globalKeyPath = '+foo'
-store = db.createObjectStore('storeName')
-Expecting exception from store.createIndex('name', globalKeyPath)
+PASS code is DOMException.SYNTAX_ERR
+Expecting exception from db.createObjectStore('name').createIndex('name', '1')
 PASS Exception was thrown.
-PASS code is IDBDatabaseException.NON_TRANSIENT_ERR
+PASS code is DOMException.SYNTAX_ERR
 Deleted all object stores.
-globalKeyPath = 'foo%'
-store = db.createObjectStore('storeName')
-Expecting exception from store.createIndex('name', globalKeyPath)
+Expecting exception from db.createObjectStore('name', {keyPath: '1.0'})
+PASS Exception was thrown.
+PASS code is DOMException.SYNTAX_ERR
+Expecting exception from db.createObjectStore('name').createIndex('name', '1.0')
 PASS Exception was thrown.
-PASS code is IDBDatabaseException.NON_TRANSIENT_ERR
+PASS code is DOMException.SYNTAX_ERR
 Deleted all object stores.
 PASS successfullyParsed is true
 
index d400822..1641192 100644 (file)
@@ -13,53 +13,43 @@ objectStore = db.createObjectStore(info.name, info.options);
 PASS objectStore.name is info.name
 PASS objectStore.indexNames.length is 0
 PASS event.target.transaction.db is db
-PASS event.target.transaction.readyState is IDBTransaction.LOADING
-PASS event.target.transaction.mode is 'versionchange'
+PASS event.target.transaction.mode is "versionchange"
 objectStore = db.createObjectStore(info.name, info.options);
 PASS objectStore.name is info.name
 PASS objectStore.indexNames.length is 0
 PASS event.target.transaction.db is db
-PASS event.target.transaction.readyState is IDBTransaction.LOADING
-PASS event.target.transaction.mode is 'versionchange'
+PASS event.target.transaction.mode is "versionchange"
 objectStore = db.createObjectStore(info.name, info.options);
 PASS objectStore.name is info.name
 PASS objectStore.indexNames.length is 0
 PASS event.target.transaction.db is db
-PASS event.target.transaction.readyState is IDBTransaction.LOADING
-PASS event.target.transaction.mode is 'versionchange'
+PASS event.target.transaction.mode is "versionchange"
+Expecting exception from objectStore = db.createObjectStore(info.name, info.options)
+PASS Exception was thrown.
+PASS code is DOMException.INVALID_ACCESS_ERR
 objectStore = db.createObjectStore(info.name, info.options);
 PASS objectStore.name is info.name
 PASS objectStore.indexNames.length is 0
 PASS event.target.transaction.db is db
-PASS event.target.transaction.readyState is IDBTransaction.LOADING
-PASS event.target.transaction.mode is 'versionchange'
-objectStore = db.createObjectStore(info.name, info.options);
-PASS objectStore.name is info.name
-PASS objectStore.indexNames.length is 0
-PASS event.target.transaction.db is db
-PASS event.target.transaction.readyState is IDBTransaction.LOADING
-PASS event.target.transaction.mode is 'versionchange'
+PASS event.target.transaction.mode is "versionchange"
 objectStore = db.createObjectStore(info.name, info.options);
 PASS objectStore.name is info.name
 PASS objectStore.keyPath is info.options.keyPath
 PASS objectStore.indexNames.length is 0
 PASS event.target.transaction.db is db
-PASS event.target.transaction.readyState is IDBTransaction.LOADING
-PASS event.target.transaction.mode is 'versionchange'
+PASS event.target.transaction.mode is "versionchange"
 objectStore = db.createObjectStore(info.name, info.options);
 PASS objectStore.name is info.name
 PASS objectStore.keyPath is info.options.keyPath
 PASS objectStore.indexNames.length is 0
 PASS event.target.transaction.db is db
-PASS event.target.transaction.readyState is IDBTransaction.LOADING
-PASS event.target.transaction.mode is 'versionchange'
+PASS event.target.transaction.mode is "versionchange"
 objectStore = db.createObjectStore(info.name, info.options);
 PASS objectStore.name is info.name
 PASS objectStore.keyPath is info.options.keyPath
 PASS objectStore.indexNames.length is 0
 PASS event.target.transaction.db is db
-PASS event.target.transaction.readyState is IDBTransaction.LOADING
-PASS event.target.transaction.mode is 'versionchange'
+PASS event.target.transaction.mode is "versionchange"
 PASS successfullyParsed is true
 
 TEST COMPLETE
index b9921b5..0bac0d5 100644 (file)
@@ -39,7 +39,7 @@ function cleanDatabase()
         { name: "1", options: { autoIncrement: true } },
         { name: "2", options: { autoIncrement: false } },
         { name: "3", options: { keyPath: "" } },
-        { name: "4", options: { keyPath: "", autoIncrement: true } },
+        { name: "4", options: { keyPath: "", autoIncrement: true }, fail: true },
         { name: "5", options: { keyPath: "", autoIncrement: false } },
         { name: "6", options: { keyPath: "foo" } },
         { name: "7", options: { keyPath: "foo", autoIncrement: false } },
@@ -49,18 +49,21 @@ function cleanDatabase()
     for (var index in objectStoreInfo) {
         index = parseInt(index);
         info = objectStoreInfo[index];
-        objectStore = evalAndLog("objectStore = db.createObjectStore(info.name, info.options);");
-        shouldBe("objectStore.name", "info.name");
-        if (info.options && info.options.keyPath) {
-            shouldBe("objectStore.keyPath", "info.options.keyPath");
+        if (!info.fail) {
+            objectStore = evalAndLog("objectStore = db.createObjectStore(info.name, info.options);");
+            shouldBe("objectStore.name", "info.name");
+            if (info.options && info.options.keyPath) {
+                shouldBe("objectStore.keyPath", "info.options.keyPath");
+            }
+            shouldBe("objectStore.indexNames.length", "0");
+            shouldBe("event.target.transaction.db", "db");
+            shouldBeEqualToString("event.target.transaction.mode", "versionchange");
+        } else {
+            evalAndExpectException("objectStore = db.createObjectStore(info.name, info.options)", "DOMException.INVALID_ACCESS_ERR");
         }
-        shouldBe("objectStore.indexNames.length", "0");
-        shouldBe("event.target.transaction.db", "db");
-        shouldBe("event.target.transaction.readyState", "IDBTransaction.LOADING");
-        shouldBe("event.target.transaction.mode", "'versionchange'");
     }
 
     finishJSTest();
 }
 
-test();
\ No newline at end of file
+test();
index 044bbe1..26133cb 100644 (file)
@@ -14,8 +14,8 @@ PASS objectStore.put(); threw exception TypeError: Not enough arguments.
 PASS objectStore.add(); threw exception TypeError: Not enough arguments.
 PASS objectStore.delete(); threw exception TypeError: Type error.
 PASS objectStore.get(); threw exception TypeError: Type error.
-PASS objectStore.createIndex(); threw exception TypeError: Not enough arguments.
-PASS objectStore.createIndex('foo'); threw exception TypeError: Not enough arguments.
+PASS objectStore.createIndex(); threw exception TypeError: Type error.
+PASS objectStore.createIndex('foo'); threw exception TypeError: Type error.
 PASS objectStore.index(); threw exception TypeError: Not enough arguments.
 PASS objectStore.deleteIndex(); threw exception TypeError: Not enough arguments.
 PASS successfullyParsed is true
diff --git a/LayoutTests/storage/indexeddb/resources/keypath-arrays.js b/LayoutTests/storage/indexeddb/resources/keypath-arrays.js
new file mode 100644 (file)
index 0000000..8baba81
--- /dev/null
@@ -0,0 +1,81 @@
+if (this.importScripts) {
+    importScripts('../../../fast/js/resources/js-test-pre.js');
+    importScripts('shared.js');
+}
+
+description("Test IndexedDB Array-type keyPaths");
+
+function test()
+{
+    removeVendorPrefixes();
+
+    request = evalAndLog("indexedDB.deleteDatabase('keypath-arrays')");
+    request.onerror = unexpectedErrorCallback;
+    request.onsuccess = function () {
+        request = evalAndLog("indexedDB.open('keypath-arrays')");
+        request.onerror = unexpectedErrorCallback;
+        request.onsuccess = openSuccess;
+    };
+}
+
+function openSuccess()
+{
+    debug("");
+    debug("openSuccess():");
+    db = evalAndLog("db = event.target.result");
+    request = evalAndLog("request = db.setVersion('1')");
+    request.onerror = unexpectedErrorCallback;
+    request.onsuccess = function () {
+        transaction = request.result;
+        transaction.onabort = unexpectedAbortCallback;
+        evalAndLog("store = db.createObjectStore('store', {keyPath: ['a', 'b']})");
+        evalAndLog("store.createIndex('index', ['c', 'd'])");
+
+        evalAndExpectException("db.createObjectStore('store-with-generator', {keyPath: ['a', 'b'], autoIncrement: true})", "DOMException.INVALID_ACCESS_ERR");
+        evalAndExpectException("store.createIndex('index-multientry', ['e', 'f'], {multiEntry: true})", "DOMException.NOT_SUPPORTED_ERR");
+
+        transaction.oncomplete = testKeyPaths;
+    };
+}
+
+function testKeyPaths()
+{
+    debug("");
+    debug("testKeyPaths():");
+
+    transaction = evalAndLog("transaction = db.transaction(['store'], 'readwrite')");
+    transaction.onabort = unexpectedAbortCallback;
+    evalAndLog("store = transaction.objectStore('store')");
+    evalAndLog("index = store.index('index')");
+
+    debug("");
+    evalAndLog("request = store.put({a: 1, b: 2, c: 3, d: 4})");
+    request.onerror = unexpectedErrorCallback;
+    checkStore();
+
+    function checkStore() {
+        evalAndLog("request = store.openCursor()");
+        request.onerror = unexpectedErrorCallback;
+        request.onsuccess = function () {
+            evalAndLog("cursor = request.result");
+            shouldBeNonNull("cursor");
+            shouldBeEqualToString("JSON.stringify(cursor.key)", "[1,2]");
+            checkIndex();
+        };
+    };
+
+    function checkIndex() {
+        evalAndLog("request = index.openCursor()");
+        request.onerror = unexpectedErrorCallback;
+        request.onsuccess = function () {
+            evalAndLog("cursor = request.result");
+            shouldBeNonNull("cursor");
+            shouldBeEqualToString("JSON.stringify(cursor.primaryKey)", "[1,2]");
+            shouldBeEqualToString("JSON.stringify(cursor.key)", "[3,4]");
+        };
+    };
+
+    transaction.oncomplete = finishJSTest;
+}
+
+test();
index e6b2606..1769986 100644 (file)
@@ -25,6 +25,8 @@ function openSuccess()
 
 function testValidKeyPaths()
 {
+    debug("");
+    debug("testValidKeyPaths():");
     deleteAllObjectStores(db);
 
     evalAndLog("store = db.createObjectStore('name')");
@@ -52,30 +54,24 @@ function testValidKeyPaths()
 
 function testInvalidKeyPaths()
 {
+    debug("");
+    debug("testInvalidKeyPaths():");
     deleteAllObjectStores(db);
 
-    testKeyPaths = ['[]', '["foo"]', '["foo", "bar"]', '["", ""]', '[1.0, 2.0]', '[["foo"]]', '["foo", ["bar"]]'];
+    debug("");
+    debug("Object store key path may not be empty or an array if autoIncrement is true");
+    testKeyPaths = ["''", "[]", "['a']", "['']"];
     testKeyPaths.forEach(function (keyPath) {
-        globalKeyPath = keyPath;
-        debug("globalKeyPath = '" + globalKeyPath + "'");
-        evalAndExpectException("db.createObjectStore('name', {keyPath: globalKeyPath})", "IDBDatabaseException.NON_TRANSIENT_ERR");
+        store = evalAndExpectException("store = db.createObjectStore('storeName', {autoIncrement: true, keyPath: " + keyPath + "})", "DOMException.INVALID_ACCESS_ERR");
         deleteAllObjectStores(db);
     });
 
-    testKeyPaths = [' ', 'foo ', 'foo bar', 'foo. bar', 'foo .bar', 'foo..bar', '+foo', 'foo%'];
+    debug("");
+    debug("Key paths which are never valid:");
+    testKeyPaths = ["' '", "'foo '", "'foo bar'", "'foo. bar'", "'foo .bar'", "'foo..bar'", "'+foo'", "'foo%'", "'1'", "'1.0'"];
     testKeyPaths.forEach(function (keyPath) {
-        globalKeyPath = keyPath;
-        debug("globalKeyPath = '" + globalKeyPath + "'");
-        evalAndExpectException("db.createObjectStore('name', {keyPath: globalKeyPath})", "IDBDatabaseException.NON_TRANSIENT_ERR");
-        deleteAllObjectStores(db);
-    });
-
-    testKeyPaths = [' ', 'foo ', 'foo bar', 'foo. bar', 'foo .bar', 'foo..bar', '+foo', 'foo%'];
-    testKeyPaths.forEach(function (keyPath) {
-        globalKeyPath = keyPath;
-        debug("globalKeyPath = '" + globalKeyPath + "'");
-        store = evalAndLog("store = db.createObjectStore('storeName')");
-        evalAndExpectException("store.createIndex('name', globalKeyPath)", "IDBDatabaseException.NON_TRANSIENT_ERR");
+        evalAndExpectException("db.createObjectStore('name', {keyPath: " + keyPath + "})", "DOMException.SYNTAX_ERR");
+        evalAndExpectException("db.createObjectStore('name').createIndex('name', " + keyPath + ")", "DOMException.SYNTAX_ERR");
         deleteAllObjectStores(db);
     });
 
index 0a21e2e..84de860 100644 (file)
@@ -1,3 +1,31 @@
+2012-05-22  Joshua Bell  <jsbell@chromium.org>
+
+        IndexedDB: Support Array-type key paths
+        https://bugs.webkit.org/show_bug.cgi?id=84207
+
+        Reviewed by Tony Chang.
+
+        Implement IDB spec behavior that key paths can be arrays of strings; when 
+        evaluated, these yield arrays of keys, providing compound key support. Also
+        changes exception types to match the spec.
+
+        Test: storage/indexeddb/keypath-arrays.html
+        Test: storage/indexeddb/keypath-basics.html
+
+        * Modules/indexeddb/IDBDatabase.cpp:
+        (WebCore::IDBDatabase::createObjectStore): Look for both string and array in option dict,
+        throw exceptions for forbidden combinations of key generator and key paths.
+        * Modules/indexeddb/IDBObjectStore.cpp:
+        (WebCore::IDBObjectStore::createIndex): Handle special cases (accepted and forbidden).
+        (WebCore):
+        * Modules/indexeddb/IDBObjectStore.h:
+        (IDBObjectStore):
+        (WebCore::IDBObjectStore::createIndex): Overloads to satisfy IDL overloads and optional dicts.
+        * Modules/indexeddb/IDBObjectStore.idl: Add DOMString array overload.
+        * bindings/v8/IDBBindingUtilities.cpp: Implement spec logic for evaluating array key paths.
+        (WebCore::createIDBKeyFromSerializedValueAndKeyPath):
+        (WebCore):
+
 2012-05-22  Martin Robinson  <mrobinson@igalia.com>
 
         [GTK] REGRESSION(r116135): Keys that confirm composition trigger a default action
index eb8d863..74816b8 100644 (file)
@@ -95,18 +95,26 @@ PassRefPtr<IDBObjectStore> IDBDatabase::createObjectStore(const String& name, co
     IDBKeyPath keyPath;
     if (!options.isUndefinedOrNull()) {
         String keyPathString;
-        if (options.getWithUndefinedOrNullCheck("keyPath", keyPathString))
+        Vector<String> keyPathArray;
+        if (options.get("keyPath", keyPathArray))
+            keyPath = IDBKeyPath(keyPathArray);
+        else if (options.getWithUndefinedOrNullCheck("keyPath", keyPathString))
             keyPath = IDBKeyPath(keyPathString);
     }
 
     if (!keyPath.isNull() && !keyPath.isValid()) {
-        ec = IDBDatabaseException::NON_TRANSIENT_ERR;
+        ec = SYNTAX_ERR;
         return 0;
     }
 
     bool autoIncrement = false;
-    options.get("autoIncrement", autoIncrement);
-    // FIXME: Look up evictable and pass that on as well.
+    if (!options.isUndefinedOrNull())
+        options.get("autoIncrement", autoIncrement);
+
+    if (autoIncrement && ((keyPath.type() == IDBKeyPath::StringType && keyPath.string().isEmpty()) || keyPath.type() == IDBKeyPath::ArrayType)) {
+        ec = INVALID_ACCESS_ERR;
+        return 0;
+    }
 
     RefPtr<IDBObjectStoreBackendInterface> objectStoreBackend = m_backend->createObjectStore(name, keyPath, autoIncrement, m_versionChangeTransaction->backend(), ec);
     if (!objectStoreBackend) {
index 5ae2646..04eba50 100644 (file)
@@ -214,11 +214,20 @@ PassRefPtr<IDBIndex> IDBObjectStore::createIndex(const String& name, const Strin
     return createIndex(name, IDBKeyPath(keyPath), options, ec);
 }
 
+PassRefPtr<IDBIndex> IDBObjectStore::createIndex(const String& name, PassRefPtr<DOMStringList> keyPath, const Dictionary& options, ExceptionCode& ec)
+{
+    // FIXME: Binding code for DOMString[] should not match null. http://webkit.org/b/84217
+    if (!keyPath)
+        return createIndex(name, IDBKeyPath("null"), options, ec);
+    return createIndex(name, IDBKeyPath(*keyPath), options, ec);
+}
+
+
 PassRefPtr<IDBIndex> IDBObjectStore::createIndex(const String& name, const IDBKeyPath& keyPath, const Dictionary& options, ExceptionCode& ec)
 {
     IDB_TRACE("IDBObjectStore::createIndex");
     if (!keyPath.isValid()) {
-        ec = IDBDatabaseException::NON_TRANSIENT_ERR;
+        ec = SYNTAX_ERR;
         return 0;
     }
 
@@ -228,7 +237,10 @@ PassRefPtr<IDBIndex> IDBObjectStore::createIndex(const String& name, const IDBKe
     bool multiEntry = false;
     options.get("multiEntry", multiEntry);
 
-    // FIXME: When Array-type keyPaths are supported, throw exception if keyPath is Array and multiEntry is true.
+    if (keyPath.type() == IDBKeyPath::ArrayType && multiEntry) {
+        ec = NOT_SUPPORTED_ERR;
+        return 0;
+    }
 
     RefPtr<IDBIndexBackendInterface> indexBackend = m_backend->createIndex(name, keyPath, unique, multiEntry, m_transaction->backend(), ec);
     ASSERT(!indexBackend != !ec); // If we didn't get an index, we should have gotten an exception code. And vice versa.
index 9b28a74..e11fbd0 100644 (file)
@@ -62,10 +62,8 @@ public:
     IDBTransaction* transaction() const;
     bool autoIncrement() const;
 
-    // FIXME: Try to modify the code generator so this is unneeded.
     PassRefPtr<IDBRequest> add(ScriptExecutionContext* context, PassRefPtr<SerializedScriptValue> value, ExceptionCode& ec) { return add(context, value, 0, ec);  }
     PassRefPtr<IDBRequest> put(ScriptExecutionContext* context, PassRefPtr<SerializedScriptValue> value, ExceptionCode& ec) { return put(context, value, 0, ec);  }
-    PassRefPtr<IDBIndex> createIndex(const String& name, const String& keyPath, ExceptionCode& ec) { return createIndex(name, keyPath, Dictionary(), ec); }
     PassRefPtr<IDBRequest> openCursor(ScriptExecutionContext* context, ExceptionCode& ec) { return openCursor(context, static_cast<IDBKeyRange*>(0), ec); } 
     PassRefPtr<IDBRequest> openCursor(ScriptExecutionContext* context, PassRefPtr<IDBKeyRange> keyRange, ExceptionCode& ec) { return openCursor(context, keyRange, IDBCursor::directionNext(), ec); } 
     PassRefPtr<IDBRequest> openCursor(ScriptExecutionContext* context, PassRefPtr<IDBKey> key, ExceptionCode& ec) { return openCursor(context, key, IDBCursor::directionNext(), ec); } 
@@ -78,7 +76,11 @@ public:
     PassRefPtr<IDBRequest> deleteFunction(ScriptExecutionContext*, PassRefPtr<IDBKey> key, ExceptionCode&);
     PassRefPtr<IDBRequest> clear(ScriptExecutionContext*, ExceptionCode&);
 
+    // FIXME: Try to modify the code generator so this duplication is unneeded.
     PassRefPtr<IDBIndex> createIndex(const String& name, const String& keyPath, const Dictionary&, ExceptionCode&);
+    PassRefPtr<IDBIndex> createIndex(const String& name, const String& keyPath, ExceptionCode& ec) { return createIndex(name, keyPath, Dictionary(), ec); }
+    PassRefPtr<IDBIndex> createIndex(const String& name, PassRefPtr<DOMStringList> keyPath, const Dictionary&, ExceptionCode&);
+    PassRefPtr<IDBIndex> createIndex(const String& name, PassRefPtr<DOMStringList> keyPath, ExceptionCode& ec) { return createIndex(name, keyPath, Dictionary(), ec); }
     PassRefPtr<IDBIndex> createIndex(const String&, const IDBKeyPath&, const Dictionary&, ExceptionCode&);
 
     PassRefPtr<IDBIndex> index(const String& name, ExceptionCode&);
index e59cbbc..2b58e03 100644 (file)
@@ -59,6 +59,8 @@ module storage {
         [CallWith=ScriptExecutionContext] IDBRequest openCursor(in IDBKey key, in [Optional] unsigned short direction)
             raises (IDBDatabaseException);
 
+        IDBIndex createIndex(in DOMString name, in DOMString[] keyPath, in [Optional] Dictionary options)
+            raises (IDBDatabaseException);
         IDBIndex createIndex(in DOMString name, in DOMString keyPath, in [Optional] Dictionary options)
             raises (IDBDatabaseException);
         IDBIndex index(in DOMString name)
index 15b43e9..d316ad7 100644 (file)
@@ -153,13 +153,11 @@ v8::Handle<v8::Value> ensureNthValueOnKeyPath(v8::Handle<v8::Value>& rootValue,
 
 } // anonymous namespace
 
-PassRefPtr<IDBKey> createIDBKeyFromSerializedValueAndKeyPath(PassRefPtr<SerializedScriptValue> value, const IDBKeyPath& keyPath)
+static PassRefPtr<IDBKey> createIDBKeyFromSerializedValueAndKeyPath(PassRefPtr<SerializedScriptValue> value, const String& keyPath)
 {
-    IDB_TRACE("createIDBKeyFromSerializedValueAndKeyPath");
-    ASSERT(keyPath.type() == IDBKeyPath::StringType);
     Vector<String> keyPathElements;
     IDBKeyPathParseError error;
-    IDBParseKeyPath(keyPath.string(), keyPathElements, error);
+    IDBParseKeyPath(keyPath, keyPathElements, error);
     ASSERT(error == IDBKeyPathParseErrorNone);
 
     V8AuxiliaryContext context;
@@ -170,6 +168,30 @@ PassRefPtr<IDBKey> createIDBKeyFromSerializedValueAndKeyPath(PassRefPtr<Serializ
     return createIDBKeyFromValue(v8Key);
 }
 
+
+PassRefPtr<IDBKey> createIDBKeyFromSerializedValueAndKeyPath(PassRefPtr<SerializedScriptValue> prpValue, const IDBKeyPath& keyPath)
+{
+    IDB_TRACE("createIDBKeyFromSerializedValueAndKeyPath");
+    ASSERT(!keyPath.isNull());
+
+    RefPtr<SerializedScriptValue> value = prpValue;
+
+    if (keyPath.type() == IDBKeyPath::ArrayType) {
+        IDBKey::KeyArray result;
+        const Vector<String>& array = keyPath.array();
+        for (size_t i = 0; i < array.size(); ++i) {
+            RefPtr<IDBKey> key = createIDBKeyFromSerializedValueAndKeyPath(value, array[i]);
+            if (!key)
+                return 0;
+            result.append(key);
+        }
+        return IDBKey::createArray(result);
+    }
+
+    ASSERT(keyPath.type() == IDBKeyPath::StringType);
+    return createIDBKeyFromSerializedValueAndKeyPath(value, keyPath.string());
+}
+
 PassRefPtr<SerializedScriptValue> injectIDBKeyIntoSerializedValue(PassRefPtr<IDBKey> key, PassRefPtr<SerializedScriptValue> value, const IDBKeyPath& keyPath)
 {
     IDB_TRACE("injectIDBKeyIntoSerializedValue");