Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(49)

Side by Side Diff: third_party/WebKit/LayoutTests/storage/indexeddb/rename-index.html

Issue 2276593002: Support renaming of IndexedDB indexes and object stores. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Most renaming tests pass. Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 <!DOCTYPE html>
2 <title>IndexedDB: index renaming support</title>
3 <script src='../../resources/testharness.js'></script>
4 <link rel="help"
5 href="https://w3c.github.io/IndexedDB/#dom-idbindex-name">
6 <link rel="author" href="pwnall@chromium.org" title="Victor Costan">
7 <script src='../../resources/testharnessreport.js'></script>
8 <script>
9
10 // Returns an IndexedDB database name likely to be unique to the test case.
11 const databaseName = function(testCase) {
12 return 'db' + self.location.pathname + '-' + testCase.name;
13 };
14
15 // Creates an EventWatcher covering all the events that can be issued by
16 // IndexedDB requests and transactions.
17 const requestWatcher = function(testCase, request) {
18 return new EventWatcher(testCase, request,
19 ['error', 'success', 'upgradeneeded']);
20 };
21
22 // Migrates an IndexedDB database whose name is unique for the test case.
23 //
24 // setupCallback will be called during a versionchange transaction, and will be
25 // given the created database and the versionchange transaction.
26 //
27 // Returns a promise that resolves to an IndexedDB database. The caller must
28 // close the database.
29 const migrateDatabase = function(testCase, newVersion, setupCallback) {
30 // We cannot use eventWatcher.wait_for('upgradeneeded') here, because
31 // the versionchange transaction auto-commits before the Promise's then
32 // callback gets called.
33 return new Promise((resolve, reject) => {
34 const request = indexedDB.open(databaseName(testCase), newVersion);
35 request.onupgradeneeded = event => {
36 const eventWatcher = requestWatcher(testCase, request);
37 const database = event.target.result;
38 const transaction = event.target.transaction;
39 setupCallback(database, transaction);
40 resolve(eventWatcher.wait_for('success'));
41 };
42 request.onerror = event => reject(event.target.error);
43 }).then(event => event.target.result);
44 };
45
46 // Creates an IndexedDB database whose name is unique for the test case.
47 //
48 // setupCallback will be called during a versionchange transaction, and will be
49 // given the created database and the versionchange transaction.
50 //
51 // Returns a promise that resolves to an IndexedDB database. The caller must
52 // close the database.
53 const createDatabase = function(testCase, setupCallback) {
54 const request = indexedDB.deleteDatabase(databaseName(testCase));
55 const eventWatcher = requestWatcher(testCase, request);
56
57 return eventWatcher.wait_for('success').then(event =>
58 migrateDatabase(testCase, 1, setupCallback));
59 };
60
61 // Creates a 'books' object store whose contents closely resembles the first
62 // example in the IndexedDB specification.
63 const createBooksStore = function(testCase, database) {
64 const store = database.createObjectStore('books',
65 { keyPath: 'isbn', autoIncrement: true });
66 store.createIndex('by_author', 'author');
67 store.createIndex('by_title', 'title', { unique: true });
68 store.put({ title: 'Quarry Memories', author: 'Fred', isbn: 123456 });
jsbell 2016/09/02 22:57:36 Maybe make a global `const SAMPLE_DATA = [ ... ]`
pwnall 2016/09/03 06:01:19 Done. I called it BOOKS_RECORD_DATA. Please let m
69 store.put({ title: 'Water Buffaloes', author: 'Fred', isbn: 234567 });
70 store.put({ title: 'Bedrock Nights', author: 'Barney', isbn: 345678 });
71 return store;
72 };
73
74 // Verifies that an object store's index matches the index used to create the
75 // by_author books store in the test database's version 1.
76 //
77 // The errorMessage is used if the assertions fail. It can state that the
78 // IndexedDB implementation being tested is incorrect, or that the testing code
79 // is using it incorrectly.
80 const checkAuthorIndexContents = function(testCase, index, errorMessage) {
81 const request = index.get('Barney');
jsbell 2016/09/02 22:57:36 ... and then use the SAMPLE_DATA[0]'s values rathe
pwnall 2016/09/03 06:01:19 Done.
82 const eventWatcher = requestWatcher(testCase, request);
83 return eventWatcher.wait_for('success').then(() => {
84 const result = request.result;
85 assert_equals(result.isbn, 345678, errorMessage);
86 assert_equals(result.title, 'Bedrock Nights', errorMessage);
87 });
88 };
89
90 // Verifies that an object store's index matches the index used to create the
91 // by_title books store in the test database's version 1.
92 //
93 // The errorMessage is used if the assertions fail. It can state that the
94 // IndexedDB implementation being tested is incorrect, or that the testing code
95 // is using it incorrectly.
96 const checkTitleIndexContents = function(testCase, index, errorMessage) {
97 const request = index.get('Bedrock Nights');
jsbell 2016/09/02 22:57:36 ditto
pwnall 2016/09/03 06:01:19 Done.
98 const eventWatcher = requestWatcher(testCase, request);
99 return eventWatcher.wait_for('success').then(() => {
100 const result = request.result;
101 assert_equals(result.author, 'Barney', errorMessage);
102 assert_equals(result.isbn, 345678, errorMessage);
103 });
104 };
105
106 promise_test(testCase => {
107 let authorIndex = null, authorIndex2 = null;
108 let renamedAuthorIndex = null, renamedAuthorIndex2 = null;
109 return createDatabase(testCase, (database, transaction) => {
110 const store = createBooksStore(testCase, database);
111 authorIndex = store.index('by_author');
112 }).then(database => {
113 const transaction = database.transaction('books', 'readonly');
114 const store = transaction.objectStore('books');
115 assert_array_equals(
116 store.indexNames, ['by_author', 'by_title'],
117 'Test setup should have created two indexes');
118 authorIndex2 = store.index('by_author');
119 return checkAuthorIndexContents(
120 testCase, authorIndex2,
121 'The index content checks should pass before any renaming').then(
122 () => database.close());
123 }).then(() => migrateDatabase(testCase, 2, (database, transaction) => {
124 const store = transaction.objectStore('books');
125 renamedAuthorIndex = store.index('by_author');
126 renamedAuthorIndex.name = 'renamed_by_author';
127
128 assert_equals(
129 renamedAuthorIndex.name, 'renamed_by_author',
130 'IDBIndex name should change immediately after a rename');
131 assert_array_equals(
132 store.indexNames, ['by_title', 'renamed_by_author'],
133 'IDBObjectStore.indexNames should immediately reflect the rename');
134 assert_equals(
135 store.index('renamed_by_author'), renamedAuthorIndex,
136 'IDBObjectStore.index should return the renamed index store when ' +
137 'queried using the new name immediately after the rename');
138 assert_throws(
139 'NotFoundError', () => store.index('by_author'),
140 'IDBObjectStore.index should throw when queried using the renamed' +
141 "index's old name immediately after the rename");
142 })).then(database => {
143 const transaction = database.transaction('books', 'readonly');
144 const store = transaction.objectStore('books');
145 assert_array_equals(
146 store.indexNames, ['by_title', 'renamed_by_author'],
147 'IDBObjectStore.indexNames should still reflect the rename after ' +
148 'the versionchange transaction commits');
149 renamedAuthorIndex2 = store.index('renamed_by_author');
150 return checkAuthorIndexContents(
151 testCase, renamedAuthorIndex2,
152 'Renaming an index should not change its contents').then(
153 () => database.close());
154 }).then(() => {
155 assert_equals(
156 authorIndex.name, 'by_author',
157 'IDBIndex obtained before the rename transaction should not ' +
158 'reflect the rename');
159 assert_equals(
160 authorIndex2.name, 'by_author',
161 'IDBIndex obtained before the rename transaction should not ' +
162 'reflect the rename');
163 assert_equals(
164 renamedAuthorIndex.name, 'renamed_by_author',
165 'IDBIndex used in the rename transaction should keep reflecting ' +
166 'the new name after the transaction is committed');
167 assert_equals(
168 renamedAuthorIndex2.name, 'renamed_by_author',
169 'IDBIndex obtained after the rename transaction should reflect ' +
170 'the new name');
171 });
172 }, 'IndexedDB index rename in new transaction');
173
174 promise_test(testCase => {
175 let renamedAuthorIndex = null, renamedAuthorIndex2 = null;
176 return createDatabase(testCase, (database, transaction) => {
177 const store = createBooksStore(testCase, database);
178 renamedAuthorIndex = store.index('by_author');
179 renamedAuthorIndex.name = 'renamed_by_author';
180
181 assert_equals(
182 renamedAuthorIndex.name, 'renamed_by_author',
183 'IDBIndex name should change immediately after a rename');
184 assert_array_equals(
185 store.indexNames, ['by_title', 'renamed_by_author'],
186 'IDBObjectStore.indexNames should immediately reflect the rename');
187 assert_equals(
188 store.index('renamed_by_author'), renamedAuthorIndex,
189 'IDBObjectStore.index should return the renamed index store when ' +
190 'queried using the new name immediately after the rename');
191 assert_throws(
192 'NotFoundError', () => store.index('by_author'),
193 'IDBObjectStore.index should throw when queried using the renamed' +
194 "index's old name immediately after the rename");
195 }).then(database => {
196 const transaction = database.transaction('books', 'readonly');
197 const store = transaction.objectStore('books');
198 assert_array_equals(
199 store.indexNames, ['by_title', 'renamed_by_author'],
200 'IDBObjectStore.indexNames should still reflect the rename after ' +
201 'the versionchange transaction commits');
202 renamedAuthorIndex2 = store.index('renamed_by_author');
203 return checkAuthorIndexContents(
204 testCase, renamedAuthorIndex2,
205 'Renaming an index should not change its contents').then(
206 () => database.close());
207 }).then(() => {
208 assert_equals(
209 renamedAuthorIndex.name, 'renamed_by_author',
210 'IDBIndex used in the rename transaction should keep reflecting ' +
211 'the new name after the transaction is committed');
212 assert_equals(
213 renamedAuthorIndex2.name, 'renamed_by_author',
214 'IDBIndex obtained after the rename transaction should reflect ' +
215 'the new name');
216 });
217 }, 'IndexedDB index rename in the transaction where it is created');
218
219 promise_test(testCase => {
220 return createDatabase(testCase, (database, transaction) => {
221 createBooksStore(testCase, database);
222 }).then(database => {
223 database.close();
224 }).then(() => migrateDatabase(testCase, 2, (database, transaction) => {
225 const store = transaction.objectStore('books');
226 const index = store.index('by_author');
227 store.deleteIndex('by_author');
228 assert_throws(
229 'InvalidStateError', () => index.name = 'renamed_by_author');
230 })).then(database => database.close());
231 }, 'IndexedDB deleted index rename throws');
232
233 promise_test(testCase => {
234 return createDatabase(testCase, (database, transaction) => {
235 createBooksStore(testCase, database);
236 }).then(database => {
237 const transaction = database.transaction('books', 'readonly');
238 const store = transaction.objectStore('books');
239 const index = store.index('by_author');
240
241 assert_throws(
242 'InvalidStateError', () => index.name = 'renamed_by_author');
243 database.close();
244 });
245 }, 'IndexedDB index rename throws in a readonly transaction');
246
247 promise_test(testCase => {
248 return createDatabase(testCase, (database, transaction) => {
249 createBooksStore(testCase, database);
250 }).then(database => {
251 const transaction = database.transaction('books', 'readwrite');
252 const store = transaction.objectStore('books');
253 const index = store.index('by_author');
254
255 assert_throws(
256 'InvalidStateError', () => index.name = 'renamed_by_author');
257 database.close();
258 });
259 }, 'IndexedDB index rename throws in a readwrite transaction');
260
261 promise_test(testCase => {
262 let authorIndex = null;
263 return createDatabase(testCase, (database, transaction) => {
264 const store = createBooksStore(testCase, database);
265 authorIndex = store.index('by_author');
266 }).then(database => {
267 assert_throws(
268 'TransactionInactiveError',
269 () => authorIndex.name = 'renamed_by_author');
270 database.close();
271 });
272 }, 'IndexedDB index rename throws in an inactive transaction');
273
274 promise_test(testCase => {
275 return createDatabase(testCase, (database, transaction) => {
276 createBooksStore(testCase, database);
277 }).then(database => {
278 database.close();
279 }).then(() => migrateDatabase(testCase, 2, (database, transaction) => {
280 const store = transaction.objectStore('books');
281 const index = store.index('by_author');
282 index.name = 'by_author';
283 assert_array_equals(
284 store.indexNames, ['by_author', 'by_title'],
285 'Renaming an index to the same name should not change the ' +
286 "index's IDBObjectStore.indexNames");
287 })).then(database => {
288 const transaction = database.transaction('books', 'readonly');
289 const store = transaction.objectStore('books');
290 assert_array_equals(
291 store.indexNames, ['by_author', 'by_title'],
292 'Committing a transaction that renames a store to the same name ' +
293 "should not change the index's IDBObjectStore.indexNames");
294 const index = store.index('by_author');
295 return checkAuthorIndexContents(
296 testCase, index,
297 'Committing a transaction that renames an index to the same name ' +
298 "should not change the index's contents").then(
299 () => database.close());
300 });
301 }, 'IndexedDB index rename to the same name succeeds');
302
303 promise_test(testCase => {
304 return createDatabase(testCase, (database, transaction) => {
305 createBooksStore(testCase, database);
306 }).then(database => {
307 database.close();
308 }).then(() => migrateDatabase(testCase, 2, (database, transaction) => {
309 const store = transaction.objectStore('books');
310 const index = store.index('by_author');
311
312 assert_throws('ConstraintError', () => index.name = 'by_title');
313 assert_array_equals(
314 store.indexNames, ['by_author', 'by_title'],
315 'An index rename that throws an exception should not change the ' +
316 "index's IDBObjectStore.indexNames");
317 })).then(database => {
318 const transaction = database.transaction('books', 'readonly');
319 const store = transaction.objectStore('books');
320 assert_array_equals(
321 store.indexNames, ['by_author', 'by_title'],
322 'Committing a transaction with a failed store rename attempt ' +
323 "should not change the index's IDBObjectStore.indexNames");
324 const index = store.index('by_author');
325 return checkAuthorIndexContents(
326 testCase, index,
327 'Committing a transaction with a failed rename attempt should not' +
328 "change the index's contents").then(() => database.close());
329 });
330 }, 'IndexedDB index rename to the name of another index throws');
331
332 promise_test(testCase => {
333 return createDatabase(testCase, (database, transaction) => {
334 createBooksStore(testCase, database);
335 }).then(database => {
336 database.close();
337 }).then(() => migrateDatabase(testCase, 2, (database, transaction) => {
338 const store = transaction.objectStore('books');
339 const index = store.index('by_author');
340 store.deleteIndex('by_title');
341 index.name = 'by_title';
342 assert_array_equals(
343 store.indexNames, ['by_title'],
344 'IDBObjectStore.indexNames should immediately reflect the rename');
345 })).then(database => {
346 const transaction = database.transaction('books', 'readonly');
347 const store = transaction.objectStore('books');
348 assert_array_equals(
349 store.indexNames, ['by_title'],
350 'IDBObjectStore.indexNames should still reflect the rename after ' +
351 'the versionchange transaction commits');
352 const index = store.index('by_title');
353 return checkAuthorIndexContents(
354 testCase, index,
355 'Renaming an index should not change its contents').then(
356 () => database.close());
357 });
358 }, 'IndexedDB index rename to the name of a deleted index succeeds');
359
360 promise_test(testCase => {
361 return createDatabase(testCase, (database, transaction) => {
362 createBooksStore(testCase, database);
363 }).then(database => {
364 database.close();
365 }).then(() => migrateDatabase(testCase, 2, (database, transaction) => {
366 const store = transaction.objectStore('books');
367 store.index('by_author').name = 'tmp';
368 store.index('by_title').name = 'by_author';
369 store.index('tmp').name = 'by_title';
370 assert_array_equals(
371 store.indexNames, ['by_author', 'by_title'],
372 'IDBObjectStore.indexNames should reflect the swap immediately ' +
373 'after the renames');
374 return checkTitleIndexContents(
375 testCase, store.index('by_title'),
376 'Renaming an index should not change its contents');
377 })).then(database => {
378 const transaction = database.transaction('books', 'readonly');
379 const store = transaction.objectStore('books');
380 assert_array_equals(
381 store.indexNames, ['by_author', 'by_title'],
382 'IDBObjectStore.indexNames should still reflect the swap after ' +
383 'the versionchange transaction commits');
384 const index = store.index('by_title');
385 return checkAuthorIndexContents(
386 testCase, index,
387 'Renaming an index should not change its contents').then(
388 () => database.close());
389 });
390 }, 'IndexedDB index swapping via renames succeeds');
391
392 promise_test(testCase => {
393 return createDatabase(testCase, (database, transaction) => {
394 createBooksStore(testCase, database);
395 }).then(database => {
396 database.close();
397 }).then(() => migrateDatabase(testCase, 2, (database, transaction) => {
398 const store = transaction.objectStore('books');
399 const index = store.index('by_author');
400
401 index.name = 42;
402 assert_equals(index.name, '42',
403 'IDBIndex name should change immediately after a rename to a ' +
404 'number');
405 assert_array_equals(
406 store.indexNames, ['42', 'by_title'],
407 'IDBObjectStore.indexNames should immediately reflect the ' +
408 'stringifying rename');
409
410 index.name = true;
411 assert_equals(index.name, 'true',
412 'IDBIndex name should change immediately after a rename to a ' +
413 'boolean');
414
415 index.name = {};
416 assert_equals(index.name, '[object Object]',
417 'IDBIndex name should change immediately after a rename to an ' +
418 'object');
419
420 index.name = () => null;
421 assert_equals(index.name, '() => null',
422 'IDBIndex name should change immediately after a rename to a ' +
423 'function');
424
425 index.name = undefined;
426 assert_equals(index.name, 'undefined',
427 'IDBIndex name should change immediately after a rename to ' +
428 'undefined');
429 })).then(database => {
430 const transaction = database.transaction('books', 'readonly');
431 const store = transaction.objectStore('books');
432 assert_array_equals(
433 store.indexNames, ['by_title', 'undefined'],
434 'IDBObjectStore.indexNames should reflect the last rename ' +
435 'after the versionchange transaction commits');
436 const index = store.index('undefined');
437 return checkAuthorIndexContents(
438 testCase, index,
439 'Renaming an index should not change its contents').then(
440 () => database.close());
441 });
442 }, 'IndexedDB object store rename stringifies non-string names');
443
444 promise_test(testCase => {
445 return createDatabase(testCase, (database, transaction) => {
446 createBooksStore(testCase, database);
447 }).then(database => {
448 database.close();
449 }).then(() => migrateDatabase(testCase, 2, (database, transaction) => {
450 const store = transaction.objectStore('books');
451 const index = store.index('by_author');
452
453 assert_throws(
454 { name: 'Custom stringifying error'},
455 () => {
456 index.name = {
457 toString: () => { throw { name: 'Custom stringifying error'}; }
458 };
459 }, 'IDBObjectStore rename should re-raise toString() exception');
460 assert_array_equals(
461 store.indexNames, ['by_author', 'by_title'],
462 'An index rename that throws an exception should not change the ' +
463 "index's IDBObjectStore.indexNames");
464 })).then(database => {
465 const transaction = database.transaction('books', 'readonly');
466 const store = transaction.objectStore('books');
467 assert_array_equals(
468 store.indexNames, ['by_author', 'by_title'],
469 'Committing a transaction with a failed store rename attempt ' +
470 "should not change the index's IDBObjectStore.indexNames");
471 const index = store.index('by_author');
472 return checkAuthorIndexContents(
473 testCase, index,
474 'Committing a transaction with a failed rename attempt should not' +
475 "change the index's contents").then(() => database.close());
476 });
477 }, 'IndexedDB object store rename handles exceptions when stringifying names');
478
479 for (let escapedName of ['', '\\u0000', '\\uDC00\\uD800']) ((escapedName) => {
480 const name = JSON.parse('"' + escapedName + '"');
481 promise_test(testCase => {
482 return createDatabase(testCase, (database, transaction) => {
483 createBooksStore(testCase, database);
484 }).then(database => {
485 database.close();
486 }).then(() => migrateDatabase(testCase, 2, (database, transaction) => {
487 const store = transaction.objectStore('books');
488 const index = store.index('by_author');
489
490 index.name = name;
491 assert_equals(index.name, name,
492 'IDBIndex name should change immediately after the rename');
493 assert_array_equals(
494 store.indexNames, [name, 'by_title'].sort(),
495 'IDBObjectStore.indexNames should immediately reflect the rename');
496 })).then(database => {
497 const transaction = database.transaction('books', 'readonly');
498 const store = transaction.objectStore('books');
499 assert_array_equals(
500 store.indexNames, [name, 'by_title'].sort(),
501 'IDBObjectStore.indexNames should reflect the rename ' +
502 'after the versionchange transaction commits');
503 const index = store.index(name);
504 return checkAuthorIndexContents(
505 testCase, index,
506 'Renaming an index should not change its contents').then(
507 () => database.close());
508 });
509 }, 'IndexedDB object store can be renamed to "' + escapedName + '"');
510 })(escapedName);
511
512 promise_test(testCase => {
513 const dbName = databaseName(testCase);
514 let authorIndex = null, authorIndex2 = null;
515 return createDatabase(testCase, (database, transaction) => {
516 const store = createBooksStore(testCase, database);
517 }).then(database => {
518 database.close();
519 }).then(() => new Promise((resolve, reject) => {
520 const request = indexedDB.open(dbName, 2);
521 request.onupgradeneeded = event => {
522 const database = event.target.result;
523 const transaction = event.target.transaction;
524 const store = transaction.objectStore('books');
525 authorIndex = store.index('by_author');
526 authorIndex.name = 'renamed_by_author';
527 request.onerror = event => {
528 event.preventDefault();
529 resolve(event);
530 }
531 transaction.abort();
532
533 assert_equals(
534 authorIndex.name, 'by_author',
535 'IDBIndex.name should not reflect the rename anymore ' +
536 'immediately after transaction.abort() returns');
537 assert_array_equals(
538 store.indexNames, ['by_author', 'by_title'],
539 'IDBObjectStore.indexNames should not reflect the rename ' +
540 'anymore immediately after transaction.abort() returns');
541 };
542 request.onerror = event => reject(event.target.error);
543 request.onsuccess = () => reject(new Error(
544 'indexedDB.open was not supposed to succeed'));
545 })).then(event => {
546 assert_equals(authorIndex.name, 'by_author',
547 'IDBIndex.name should not reflect the rename anymore ' +
548 'after the versionchange transaction is aborted');
549
550 const request = indexedDB.open(dbName, 1);
551 return requestWatcher(testCase, request).wait_for('success');
552 }).then(event => {
553 const database = event.target.result;
554 const transaction = database.transaction('books', 'readonly');
555 const store = transaction.objectStore('books');
556 assert_array_equals(
557 store.indexNames, ['by_author', 'by_title'],
558 'IDBDatabase.objectStoreNames should not reflect the rename ' +
559 'after the versionchange transaction is aborted');
560
561 authorIndex2 = store.index('by_author');
562 return checkAuthorIndexContents(
563 testCase, authorIndex2,
564 'Aborting an index rename transaction should not change the ' +
565 "index's records").then(() => database.close());
566 }).then(() => {
567 assert_equals(
568 authorIndex.name, 'by_author',
569 'IDBIndex used in aborted rename transaction should not reflect ' +
570 'the rename after the transaction is aborted');
571 assert_equals(authorIndex2.name, 'by_author',
572 'IDBIndex obtained after an aborted rename transaction should ' +
573 'not reflect the rename');
574 });
575 }, 'IndexedDB object store rename in aborted transaction');
576 </script>
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698