// META: title=IDBObjectStore.createIndex() // META: global=window,worker // META: script=resources/support.js 'use strict'; async_test(t => { let db; let open_rq = createdb(t); open_rq.onupgradeneeded = function (e) { db = e.target.result; let objStore = db.createObjectStore("store"); let index = objStore.createIndex("index", "indexedProperty", { unique: true }); assert_true(index instanceof IDBIndex, "IDBIndex"); assert_equals(index.name, "index", "name"); assert_equals(index.objectStore, objStore, "objectStore"); assert_equals(index.keyPath, "indexedProperty", "keyPath"); assert_true(index.unique, "unique"); assert_false(index.multiEntry, "multiEntry"); t.done(); }; }, "Returns an IDBIndex and the properties are set correctly"); async_test(t => { let db, aborted, record = { indexedProperty: "bar" }; let open_rq = createdb(t); open_rq.onupgradeneeded = function (e) { db = e.target.result; let txn = e.target.transaction, objStore = db.createObjectStore("store"); objStore.add(record, 1); objStore.add(record, 2); let index = objStore.createIndex("index", "indexedProperty", { unique: true }); assert_true(index instanceof IDBIndex, "IDBIndex"); e.target.transaction.onabort = t.step_func(function (e) { aborted = true; assert_equals(e.type, "abort", "event type"); }); db.onabort = function (e) { assert_true(aborted, "transaction.abort event has fired"); t.done(); }; e.target.transaction.oncomplete = fail(t, "got complete, expected abort"); }; }, "Attempt to create an index that requires unique values on an object store already contains duplicates"); async_test(t => { let db, aborted; let open_rq = createdb(t); open_rq.onupgradeneeded = function (e) { db = e.target.result; let txn = e.target.transaction, objStore = db.createObjectStore("store", { keyPath: 'key' }); for (let i = 0; i < 100; i++) objStore.add({ key: "key_" + i, indexedProperty: "indexed_" + i }); let idx = objStore.createIndex("index", "indexedProperty") idx.get('indexed_99').onsuccess = t.step_func(function (e) { assert_equals(e.target.result.key, 'key_99', 'key'); }); idx.get('indexed_9').onsuccess = t.step_func(function (e) { assert_equals(e.target.result.key, 'key_9', 'key'); }); } open_rq.onsuccess = function () { t.done(); } }, "The index is usable right after being made"); async_test(t => { let db, events = []; let open_rq = createdb(t); open_rq.onupgradeneeded = function (e) { db = e.target.result; e.target.transaction.oncomplete = log("transaction.complete"); let txn = e.target.transaction, objStore = db.createObjectStore("store"); let rq_add1 = objStore.add({ animal: "Unicorn" }, 1); rq_add1.onsuccess = log("rq_add1.success"); rq_add1.onerror = log("rq_add1.error"); objStore.createIndex("index", "animal", { unique: true }); let rq_add2 = objStore.add({ animal: "Unicorn" }, 2); rq_add2.onsuccess = log("rq_add2.success"); rq_add2.onerror = function (e) { log("rq_add2.error")(e); e.preventDefault(); e.stopPropagation(); } objStore.deleteIndex("index"); let rq_add3 = objStore.add({ animal: "Unicorn" }, 3); rq_add3.onsuccess = log("rq_add3.success"); rq_add3.onerror = log("rq_add3.error"); } open_rq.onsuccess = function (e) { log("open_rq.success")(e); assert_array_equals(events, ["rq_add1.success", "rq_add2.error: ConstraintError", "rq_add3.success", "transaction.complete", "open_rq.success"], "events"); t.done(); } function log(msg) { return function (e) { if (e && e.target && e.target.error) events.push(msg + ": " + e.target.error.name); else events.push(msg); }; } }, "Event ordering for a later deleted index"); async_test(t => { let db, aborted; let open_rq = createdb(t); open_rq.onupgradeneeded = function (e) { db = e.target.result; let txn = e.target.transaction, objStore = db.createObjectStore("store"); for (let i = 0; i < 5; i++) objStore.add("object_" + i, i); let rq = objStore.createIndex("index", "") rq.onerror = function () { assert_unreached("error: " + rq.error.name); } rq.onsuccess = function () { } objStore.index("index") .get('object_4') .onsuccess = t.step_func(function (e) { assert_equals(e.target.result, 'object_4', 'result'); }); } open_rq.onsuccess = function () { t.done(); } }, "Empty keyPath"); async_test(t => { // Transaction may fire window.onerror in some implementations. setup({ allow_uncaught_exception: true }); let db, events = []; let open_rq = createdb(t); open_rq.onupgradeneeded = function (e) { db = e.target.result; db.onerror = log("db.error"); db.onabort = log("db.abort"); e.target.transaction.onabort = log("transaction.abort") e.target.transaction.onerror = log("transaction.error") e.target.transaction.oncomplete = log("transaction.complete") let txn = e.target.transaction, objStore = db.createObjectStore("store"); let rq_add1 = objStore.add({ animal: "Unicorn" }, 1); rq_add1.onsuccess = log("rq_add1.success"); rq_add1.onerror = log("rq_add1.error"); let rq_add2 = objStore.add({ animal: "Unicorn" }, 2); rq_add2.onsuccess = log("rq_add2.success"); rq_add2.onerror = log("rq_add2.error"); objStore.createIndex("index", "animal", { unique: true }) let rq_add3 = objStore.add({ animal: "Unicorn" }, 3); rq_add3.onsuccess = log("rq_add3.success"); rq_add3.onerror = log("rq_add3.error"); } open_rq.onerror = function (e) { log("open_rq.error")(e); assert_array_equals(events, ["rq_add1.success", "rq_add2.success", "rq_add3.error: AbortError", "transaction.error: AbortError", "db.error: AbortError", "transaction.abort: ConstraintError", "db.abort: ConstraintError", "open_rq.error: AbortError"], "events"); t.done(); } function log(msg) { return function (e) { if (e && e.target && e.target.error) events.push(msg + ": " + e.target.error.name); else events.push(msg); }; } }, "Event order when unique constraint is triggered"); async_test(t => { setup({ allow_uncaught_exception: true }); let db, events = []; const open_rq = createdb(t); open_rq.onupgradeneeded = function (e) { db = e.target.result; let txn = e.target.transaction; db.onerror = log("db.error"); db.onabort = log("db.abort"); txn.onabort = log("transaction.abort") txn.onerror = log("transaction.error") txn.oncomplete = log("transaction.complete") let objStore = db.createObjectStore("store"); let rq_add1 = objStore.add({ animal: "Unicorn" }, 1); rq_add1.onsuccess = log("rq_add1.success"); rq_add1.onerror = log("rq_add1.error"); objStore.createIndex("index", "animal", { unique: true }) let rq_add2 = objStore.add({ animal: "Unicorn" }, 2); rq_add2.onsuccess = log("rq_add2.success"); rq_add2.onerror = log("rq_add2.error"); let rq_add3 = objStore.add({ animal: "Horse" }, 3); rq_add3.onsuccess = log("rq_add3.success"); rq_add3.onerror = log("rq_add3.error"); } open_rq.onerror = function (e) { log("open_rq.error")(e); assert_array_equals(events, ["rq_add1.success", "rq_add2.error: ConstraintError", "transaction.error: ConstraintError", "db.error: ConstraintError", "rq_add3.error: AbortError", "transaction.error: AbortError", "db.error: AbortError", "transaction.abort: ConstraintError", "db.abort: ConstraintError", "open_rq.error: AbortError"], "events"); t.done(); } function log(msg) { return function (e) { if (e && e.target && e.target.error) events.push(msg + ": " + e.target.error.name); else events.push(msg); }; } }, "Event ordering for ConstraintError on request"); async_test(t => { let db, now = new Date(), mar18 = new Date(1111111111111), ar = ["Yay", 2, -Infinity], num = 1337; const open_rq = createdb(t); open_rq.onupgradeneeded = function (e) { db = e.target.result; let txn = e.target.transaction, objStore = db.createObjectStore("store", { keyPath: 'key' }); objStore.add({ key: "now", i: now }); objStore.add({ key: "mar18", i: mar18 }); objStore.add({ key: "array", i: ar }); objStore.add({ key: "number", i: num }); let idx = objStore.createIndex("index", "i") idx.get(now).onsuccess = t.step_func(function (e) { assert_equals(e.target.result.key, 'now', 'key'); assert_equals(e.target.result.i.getTime(), now.getTime(), 'getTime'); }); idx.get(mar18).onsuccess = t.step_func(function (e) { assert_equals(e.target.result.key, 'mar18', 'key'); assert_equals(e.target.result.i.getTime(), mar18.getTime(), 'getTime'); }); idx.get(ar).onsuccess = t.step_func(function (e) { assert_equals(e.target.result.key, 'array', 'key'); assert_array_equals(e.target.result.i, ar, 'array is the same'); }); idx.get(num).onsuccess = t.step_func(function (e) { assert_equals(e.target.result.key, 'number', 'key'); assert_equals(e.target.result.i, num, 'number is the same'); }); } open_rq.onsuccess = function () { t.done(); } }, "Index can be valid keys"); async_test(t => { let db; const open_rq = createdb(t); open_rq.onupgradeneeded = function (e) { db = e.target.result let store = db.createObjectStore("store") for (let i = 0; i < 5; i++) store.add({ idx: "object_" + i }, i) store.createIndex("", "idx") store.index("") .get('object_4') .onsuccess = t.step_func(function (e) { assert_equals(e.target.result.idx, 'object_4', 'result') }) assert_equals(store.indexNames[0], "", "indexNames[0]") assert_equals(store.indexNames.length, 1, "indexNames.length") } open_rq.onsuccess = function () { let store = db.transaction("store", "readonly").objectStore("store") assert_equals(store.indexNames[0], "", "indexNames[0]") assert_equals(store.indexNames.length, 1, "indexNames.length") t.done() } }, "IDBObjectStore.createIndex() - empty name"); async_test(t => { const open_rq = createdb(t); open_rq.onupgradeneeded = function (e) { let db = e.target.result; let ostore = db.createObjectStore("store"); ostore.createIndex("a", "a"); assert_throws_dom("ConstraintError", function () { ostore.createIndex("a", "a"); }); t.done(); } }, "If an index with the name name already exists in this object store, the implementation must throw a DOMException of type ConstraintError"); async_test(t => { const open_rq = createdb(t); open_rq.onupgradeneeded = function (e) { let db = e.target.result; let ostore = db.createObjectStore("store"); assert_throws_dom("SyntaxError", function () { ostore.createIndex("ab", "."); }); t.done(); } }, "If keyPath is not a valid key path, the implementation must throw a DOMException of type SyntaxError"); async_test(t => { let db, ostore; let open_rq = createdb(t); open_rq.onupgradeneeded = function (event) { db = event.target.result; ostore = db.createObjectStore("store"); db.deleteObjectStore("store"); } open_rq.onsuccess = function (event) { t.step(function () { assert_throws_dom("InvalidStateError", function () { ostore.createIndex("index", "indexedProperty"); }); }); t.done(); } }, "If the object store has been deleted, the implementation must throw a DOMException of type InvalidStateError"); async_test(t => { let db; const open_rq = createdb(t); open_rq.onupgradeneeded = function (event) { db = event.target.result; db.createObjectStore("store"); } open_rq.onsuccess = function (event) { let txn = db.transaction("store", "readwrite"); let ostore = txn.objectStore("store"); t.step(function () { assert_throws_dom("InvalidStateError", function () { ostore.createIndex("index", "indexedProperty"); }); }); t.done(); } }, "Operate out versionchange throw InvalidStateError"); /* IndexedDB: Exception Order of IDBObjectStore.createIndex() */ indexeddb_test( function (t, db, txn) { let store = db.createObjectStore("s"); }, function (t, db) { let txn = db.transaction("s", "readonly"); let store = txn.objectStore("s"); txn.oncomplete = function () { assert_throws_dom("InvalidStateError", function () { store.createIndex("index", "foo"); }); t.done(); }; }, "InvalidStateError(Incorrect mode) vs. TransactionInactiveError. Mode check should precede state check of the transaction." ); let gDeletedObjectStore; indexeddb_test( function (t, db, txn) { gDeletedObjectStore = db.createObjectStore("s"); db.deleteObjectStore("s"); txn.oncomplete = function () { assert_throws_dom("InvalidStateError", function () { gDeletedObjectStore.createIndex("index", "foo"); }); t.done(); }; }, null, "InvalidStateError(Deleted ObjectStore) vs. TransactionInactiveError. Deletion check should precede transaction-state check." ); indexeddb_test( function (t, db, txn) { let store = db.createObjectStore("s"); store.createIndex("index", "foo"); txn.oncomplete = function () { assert_throws_dom("TransactionInactiveError", function () { store.createIndex("index", "foo"); }); t.done(); }; }, null, "TransactionInactiveError vs. ConstraintError. Transaction-state check should precede index name check." ); indexeddb_test( function (t, db) { let store = db.createObjectStore("s"); store.createIndex("index", "foo"); assert_throws_dom("ConstraintError", function () { store.createIndex("index", "invalid key path"); }); assert_throws_dom("ConstraintError", function () { store.createIndex("index", ["invalid key path 1", "invalid key path 2"]); }); t.done(); }, null, "ConstraintError vs. SyntaxError. Index name check should precede syntax check of the key path" ); indexeddb_test( function (t, db) { let store = db.createObjectStore("s"); assert_throws_dom("SyntaxError", function () { store.createIndex("index", ["invalid key path 1", "invalid key path 2"], { multiEntry: true }); }); t.done(); }, null, "SyntaxError vs. InvalidAccessError. Syntax check should precede multiEntry check of the key path." ); /* AutoIncrement in Compound Index */ indexeddb_test( function (t, db, txn) { // No auto-increment let store = db.createObjectStore("Store1", { keyPath: "id" }); store.createIndex("CompoundKey", ["num", "id"]); // Add data store.put({ id: 1, num: 100 }); }, function (t, db) { let store = db.transaction("Store1", "readwrite").objectStore("Store1"); store.openCursor().onsuccess = t.step_func(function (e) { let item = e.target.result.value; store.index("CompoundKey").get([item.num, item.id]).onsuccess = t.step_func(function (e) { assert_equals(e.target.result ? e.target.result.num : null, 100, 'Expected 100.'); t.done(); }); }); }, "Explicit Primary Key" ); indexeddb_test( function (t, db, txn) { // Auto-increment let store = db.createObjectStore("Store2", { keyPath: "id", autoIncrement: true }); store.createIndex("CompoundKey", ["num", "id"]); // Add data store.put({ num: 100 }); }, function (t, db) { let store = db.transaction("Store2", "readwrite").objectStore("Store2"); store.openCursor().onsuccess = t.step_func(function (e) { let item = e.target.result.value; store.index("CompoundKey").get([item.num, item.id]).onsuccess = t.step_func(function (e) { assert_equals(e.target.result ? e.target.result.num : null, 100, 'Expected 100.'); t.done(); }); }); }, "Auto-Increment Primary Key" ); indexeddb_test( function (t, db, txn) { // Auto-increment let store = db.createObjectStore("Store3", { keyPath: "id", autoIncrement: true }); store.createIndex("CompoundKey", ["num", "id", "other"]); let num = 100; // Add data to Store3 - valid keys // Objects will be stored in Store3 and keys will get added // to the CompoundKeys index. store.put({ num: num++, other: 0 }); store.put({ num: num++, other: [0] }); // Add data - missing key // Objects will be stored in Store3 but keys won't get added to // the CompoundKeys index because the 'other' keypath doesn't // resolve to a value. store.put({ num: num++ }); // Add data to Store3 - invalid keys // Objects will be stored in Store3 but keys won't get added to // the CompoundKeys index because the 'other' property values // aren't valid keys. store.put({ num: num++, other: null }); store.put({ num: num++, other: {} }); store.put({ num: num++, other: [null] }); store.put({ num: num++, other: [{}] }); }, function (t, db) { let store = db.transaction("Store3", "readwrite").objectStore("Store3"); const keys = []; let count; store.count().onsuccess = t.step_func(e => { count = e.target.result; }); store.index("CompoundKey").openCursor().onsuccess = t.step_func(function (e) { const cursor = e.target.result; if (cursor !== null) { keys.push(cursor.key); cursor.continue(); return; } // Done iteration, check results. assert_equals(count, 7, 'Expected all 7 records to be stored.'); assert_equals(keys.length, 2, 'Expected exactly two index entries.'); assert_array_equals(keys[0], [100, 1, 0]); assert_object_equals(keys[1], [101, 2, [0]]); t.done(); }); }, "Auto-Increment Primary Key - invalid key values elsewhere" );