OLD | NEW |
(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 src='resources/rename-common.js'></script> |
| 9 <script> |
| 10 |
| 11 promise_test(testCase => { |
| 12 let authorIndex = null, authorIndex2 = null; |
| 13 let renamedAuthorIndex = null, renamedAuthorIndex2 = null; |
| 14 return createDatabase(testCase, (database, transaction) => { |
| 15 const store = createBooksStore(testCase, database); |
| 16 authorIndex = store.index('by_author'); |
| 17 }).then(database => { |
| 18 const transaction = database.transaction('books', 'readonly'); |
| 19 const store = transaction.objectStore('books'); |
| 20 assert_array_equals( |
| 21 store.indexNames, ['by_author', 'by_title'], |
| 22 'Test setup should have created two indexes'); |
| 23 authorIndex2 = store.index('by_author'); |
| 24 return checkAuthorIndexContents( |
| 25 testCase, authorIndex2, |
| 26 'The index should have the expected contents before any renaming'). |
| 27 then(() => database.close()); |
| 28 }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { |
| 29 const store = transaction.objectStore('books'); |
| 30 renamedAuthorIndex = store.index('by_author'); |
| 31 renamedAuthorIndex.name = 'renamed_by_author'; |
| 32 |
| 33 assert_equals( |
| 34 renamedAuthorIndex.name, 'renamed_by_author', |
| 35 'IDBIndex name should change immediately after a rename'); |
| 36 assert_array_equals( |
| 37 store.indexNames, ['by_title', 'renamed_by_author'], |
| 38 'IDBObjectStore.indexNames should immediately reflect the rename'); |
| 39 assert_equals( |
| 40 store.index('renamed_by_author'), renamedAuthorIndex, |
| 41 'IDBObjectStore.index should return the renamed index store when ' + |
| 42 'queried using the new name immediately after the rename'); |
| 43 assert_throws( |
| 44 'NotFoundError', () => store.index('by_author'), |
| 45 'IDBObjectStore.index should throw when queried using the renamed' + |
| 46 "index's old name immediately after the rename"); |
| 47 })).then(database => { |
| 48 const transaction = database.transaction('books', 'readonly'); |
| 49 const store = transaction.objectStore('books'); |
| 50 assert_array_equals( |
| 51 store.indexNames, ['by_title', 'renamed_by_author'], |
| 52 'IDBObjectStore.indexNames should still reflect the rename after ' + |
| 53 'the versionchange transaction commits'); |
| 54 renamedAuthorIndex2 = store.index('renamed_by_author'); |
| 55 return checkAuthorIndexContents( |
| 56 testCase, renamedAuthorIndex2, |
| 57 'Renaming an index should not change its contents').then( |
| 58 () => database.close()); |
| 59 }).then(() => { |
| 60 assert_equals( |
| 61 authorIndex.name, 'by_author', |
| 62 'IDBIndex obtained before the rename transaction should not ' + |
| 63 'reflect the rename'); |
| 64 assert_equals( |
| 65 authorIndex2.name, 'by_author', |
| 66 'IDBIndex obtained before the rename transaction should not ' + |
| 67 'reflect the rename'); |
| 68 assert_equals( |
| 69 renamedAuthorIndex.name, 'renamed_by_author', |
| 70 'IDBIndex used in the rename transaction should keep reflecting ' + |
| 71 'the new name after the transaction is committed'); |
| 72 assert_equals( |
| 73 renamedAuthorIndex2.name, 'renamed_by_author', |
| 74 'IDBIndex obtained after the rename transaction should reflect ' + |
| 75 'the new name'); |
| 76 }); |
| 77 }, 'IndexedDB index rename in new transaction'); |
| 78 |
| 79 promise_test(testCase => { |
| 80 let renamedAuthorIndex = null, renamedAuthorIndex2 = null; |
| 81 return createDatabase(testCase, (database, transaction) => { |
| 82 const store = createBooksStore(testCase, database); |
| 83 renamedAuthorIndex = store.index('by_author'); |
| 84 renamedAuthorIndex.name = 'renamed_by_author'; |
| 85 |
| 86 assert_equals( |
| 87 renamedAuthorIndex.name, 'renamed_by_author', |
| 88 'IDBIndex name should change immediately after a rename'); |
| 89 assert_array_equals( |
| 90 store.indexNames, ['by_title', 'renamed_by_author'], |
| 91 'IDBObjectStore.indexNames should immediately reflect the rename'); |
| 92 assert_equals( |
| 93 store.index('renamed_by_author'), renamedAuthorIndex, |
| 94 'IDBObjectStore.index should return the renamed index store when ' + |
| 95 'queried using the new name immediately after the rename'); |
| 96 assert_throws( |
| 97 'NotFoundError', () => store.index('by_author'), |
| 98 'IDBObjectStore.index should throw when queried using the renamed' + |
| 99 "index's old name immediately after the rename"); |
| 100 }).then(database => { |
| 101 const transaction = database.transaction('books', 'readonly'); |
| 102 const store = transaction.objectStore('books'); |
| 103 assert_array_equals( |
| 104 store.indexNames, ['by_title', 'renamed_by_author'], |
| 105 'IDBObjectStore.indexNames should still reflect the rename after ' + |
| 106 'the versionchange transaction commits'); |
| 107 renamedAuthorIndex2 = store.index('renamed_by_author'); |
| 108 return checkAuthorIndexContents( |
| 109 testCase, renamedAuthorIndex2, |
| 110 'Renaming an index should not change its contents').then( |
| 111 () => database.close()); |
| 112 }).then(() => { |
| 113 assert_equals( |
| 114 renamedAuthorIndex.name, 'renamed_by_author', |
| 115 'IDBIndex used in the rename transaction should keep reflecting ' + |
| 116 'the new name after the transaction is committed'); |
| 117 assert_equals( |
| 118 renamedAuthorIndex2.name, 'renamed_by_author', |
| 119 'IDBIndex obtained after the rename transaction should reflect ' + |
| 120 'the new name'); |
| 121 }); |
| 122 }, 'IndexedDB index rename in the transaction where it is created'); |
| 123 |
| 124 promise_test(testCase => { |
| 125 return createDatabase(testCase, (database, transaction) => { |
| 126 createBooksStore(testCase, database); |
| 127 }).then(database => { |
| 128 database.close(); |
| 129 }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { |
| 130 const store = transaction.objectStore('books'); |
| 131 const index = store.index('by_author'); |
| 132 store.deleteIndex('by_author'); |
| 133 assert_throws( |
| 134 'InvalidStateError', () => index.name = 'renamed_by_author'); |
| 135 })).then(database => database.close()); |
| 136 }, 'IndexedDB deleted index rename throws'); |
| 137 |
| 138 promise_test(testCase => { |
| 139 return createDatabase(testCase, (database, transaction) => { |
| 140 createBooksStore(testCase, database); |
| 141 }).then(database => { |
| 142 const transaction = database.transaction('books', 'readonly'); |
| 143 const store = transaction.objectStore('books'); |
| 144 const index = store.index('by_author'); |
| 145 |
| 146 assert_throws( |
| 147 'InvalidStateError', () => index.name = 'renamed_by_author'); |
| 148 database.close(); |
| 149 }); |
| 150 }, 'IndexedDB index rename throws in a readonly transaction'); |
| 151 |
| 152 promise_test(testCase => { |
| 153 return createDatabase(testCase, (database, transaction) => { |
| 154 createBooksStore(testCase, database); |
| 155 }).then(database => { |
| 156 const transaction = database.transaction('books', 'readwrite'); |
| 157 const store = transaction.objectStore('books'); |
| 158 const index = store.index('by_author'); |
| 159 |
| 160 assert_throws( |
| 161 'InvalidStateError', () => index.name = 'renamed_by_author'); |
| 162 database.close(); |
| 163 }); |
| 164 }, 'IndexedDB index rename throws in a readwrite transaction'); |
| 165 |
| 166 promise_test(testCase => { |
| 167 let authorIndex = null; |
| 168 return createDatabase(testCase, (database, transaction) => { |
| 169 const store = createBooksStore(testCase, database); |
| 170 authorIndex = store.index('by_author'); |
| 171 }).then(database => { |
| 172 assert_throws( |
| 173 'TransactionInactiveError', |
| 174 () => authorIndex.name = 'renamed_by_author'); |
| 175 database.close(); |
| 176 }); |
| 177 }, 'IndexedDB index rename throws in an inactive transaction'); |
| 178 |
| 179 promise_test(testCase => { |
| 180 return createDatabase(testCase, (database, transaction) => { |
| 181 createBooksStore(testCase, database); |
| 182 }).then(database => { |
| 183 database.close(); |
| 184 }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { |
| 185 const store = transaction.objectStore('books'); |
| 186 const index = store.index('by_author'); |
| 187 index.name = 'by_author'; |
| 188 assert_array_equals( |
| 189 store.indexNames, ['by_author', 'by_title'], |
| 190 'Renaming an index to the same name should not change the ' + |
| 191 "index's IDBObjectStore.indexNames"); |
| 192 })).then(database => { |
| 193 const transaction = database.transaction('books', 'readonly'); |
| 194 const store = transaction.objectStore('books'); |
| 195 assert_array_equals( |
| 196 store.indexNames, ['by_author', 'by_title'], |
| 197 'Committing a transaction that renames a store to the same name ' + |
| 198 "should not change the index's IDBObjectStore.indexNames"); |
| 199 const index = store.index('by_author'); |
| 200 return checkAuthorIndexContents( |
| 201 testCase, index, |
| 202 'Committing a transaction that renames an index to the same name ' + |
| 203 "should not change the index's contents").then( |
| 204 () => database.close()); |
| 205 }); |
| 206 }, 'IndexedDB index rename to the same name succeeds'); |
| 207 |
| 208 promise_test(testCase => { |
| 209 return createDatabase(testCase, (database, transaction) => { |
| 210 createBooksStore(testCase, database); |
| 211 }).then(database => { |
| 212 database.close(); |
| 213 }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { |
| 214 const store = transaction.objectStore('books'); |
| 215 const index = store.index('by_author'); |
| 216 |
| 217 assert_throws('ConstraintError', () => index.name = 'by_title'); |
| 218 assert_array_equals( |
| 219 store.indexNames, ['by_author', 'by_title'], |
| 220 'An index rename that throws an exception should not change the ' + |
| 221 "index's IDBObjectStore.indexNames"); |
| 222 })).then(database => { |
| 223 const transaction = database.transaction('books', 'readonly'); |
| 224 const store = transaction.objectStore('books'); |
| 225 assert_array_equals( |
| 226 store.indexNames, ['by_author', 'by_title'], |
| 227 'Committing a transaction with a failed store rename attempt ' + |
| 228 "should not change the index's IDBObjectStore.indexNames"); |
| 229 const index = store.index('by_author'); |
| 230 return checkAuthorIndexContents( |
| 231 testCase, index, |
| 232 'Committing a transaction with a failed rename attempt should not' + |
| 233 "change the index's contents").then(() => database.close()); |
| 234 }); |
| 235 }, 'IndexedDB index rename to the name of another index throws'); |
| 236 |
| 237 promise_test(testCase => { |
| 238 return createDatabase(testCase, (database, transaction) => { |
| 239 createBooksStore(testCase, database); |
| 240 }).then(database => { |
| 241 database.close(); |
| 242 }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { |
| 243 const store = transaction.objectStore('books'); |
| 244 const index = store.index('by_author'); |
| 245 store.deleteIndex('by_title'); |
| 246 index.name = 'by_title'; |
| 247 assert_array_equals( |
| 248 store.indexNames, ['by_title'], |
| 249 'IDBObjectStore.indexNames should immediately reflect the rename'); |
| 250 })).then(database => { |
| 251 const transaction = database.transaction('books', 'readonly'); |
| 252 const store = transaction.objectStore('books'); |
| 253 assert_array_equals( |
| 254 store.indexNames, ['by_title'], |
| 255 'IDBObjectStore.indexNames should still reflect the rename after ' + |
| 256 'the versionchange transaction commits'); |
| 257 const index = store.index('by_title'); |
| 258 return checkAuthorIndexContents( |
| 259 testCase, index, |
| 260 'Renaming an index should not change its contents').then( |
| 261 () => database.close()); |
| 262 }); |
| 263 }, 'IndexedDB index rename to the name of a deleted index succeeds'); |
| 264 |
| 265 promise_test(testCase => { |
| 266 return createDatabase(testCase, (database, transaction) => { |
| 267 createBooksStore(testCase, database); |
| 268 }).then(database => { |
| 269 database.close(); |
| 270 }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { |
| 271 const store = transaction.objectStore('books'); |
| 272 store.index('by_author').name = 'tmp'; |
| 273 store.index('by_title').name = 'by_author'; |
| 274 store.index('tmp').name = 'by_title'; |
| 275 assert_array_equals( |
| 276 store.indexNames, ['by_author', 'by_title'], |
| 277 'IDBObjectStore.indexNames should reflect the swap immediately ' + |
| 278 'after the renames'); |
| 279 return checkTitleIndexContents( |
| 280 testCase, store.index('by_author'), |
| 281 'Renaming an index should not change its contents'); |
| 282 })).then(database => { |
| 283 const transaction = database.transaction('books', 'readonly'); |
| 284 const store = transaction.objectStore('books'); |
| 285 assert_array_equals( |
| 286 store.indexNames, ['by_author', 'by_title'], |
| 287 'IDBObjectStore.indexNames should still reflect the swap after ' + |
| 288 'the versionchange transaction commits'); |
| 289 const index = store.index('by_title'); |
| 290 return checkAuthorIndexContents( |
| 291 testCase, index, |
| 292 'Renaming an index should not change its contents').then( |
| 293 () => database.close()); |
| 294 }); |
| 295 }, 'IndexedDB index swapping via renames succeeds'); |
| 296 |
| 297 promise_test(testCase => { |
| 298 return createDatabase(testCase, (database, transaction) => { |
| 299 createBooksStore(testCase, database); |
| 300 }).then(database => { |
| 301 database.close(); |
| 302 }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { |
| 303 const store = transaction.objectStore('books'); |
| 304 const index = store.index('by_author'); |
| 305 |
| 306 index.name = 42; |
| 307 assert_equals(index.name, '42', |
| 308 'IDBIndex name should change immediately after a rename to a ' + |
| 309 'number'); |
| 310 assert_array_equals( |
| 311 store.indexNames, ['42', 'by_title'], |
| 312 'IDBObjectStore.indexNames should immediately reflect the ' + |
| 313 'stringifying rename'); |
| 314 |
| 315 index.name = true; |
| 316 assert_equals(index.name, 'true', |
| 317 'IDBIndex name should change immediately after a rename to a ' + |
| 318 'boolean'); |
| 319 |
| 320 index.name = {}; |
| 321 assert_equals(index.name, '[object Object]', |
| 322 'IDBIndex name should change immediately after a rename to an ' + |
| 323 'object'); |
| 324 |
| 325 index.name = () => null; |
| 326 assert_equals(index.name, '() => null', |
| 327 'IDBIndex name should change immediately after a rename to a ' + |
| 328 'function'); |
| 329 |
| 330 index.name = undefined; |
| 331 assert_equals(index.name, 'undefined', |
| 332 'IDBIndex name should change immediately after a rename to ' + |
| 333 'undefined'); |
| 334 })).then(database => { |
| 335 const transaction = database.transaction('books', 'readonly'); |
| 336 const store = transaction.objectStore('books'); |
| 337 assert_array_equals( |
| 338 store.indexNames, ['by_title', 'undefined'], |
| 339 'IDBObjectStore.indexNames should reflect the last rename ' + |
| 340 'after the versionchange transaction commits'); |
| 341 const index = store.index('undefined'); |
| 342 return checkAuthorIndexContents( |
| 343 testCase, index, |
| 344 'Renaming an index should not change its contents').then( |
| 345 () => database.close()); |
| 346 }); |
| 347 }, 'IndexedDB object store rename stringifies non-string names'); |
| 348 |
| 349 promise_test(testCase => { |
| 350 return createDatabase(testCase, (database, transaction) => { |
| 351 createBooksStore(testCase, database); |
| 352 }).then(database => { |
| 353 database.close(); |
| 354 }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { |
| 355 const store = transaction.objectStore('books'); |
| 356 const index = store.index('by_author'); |
| 357 |
| 358 assert_throws( |
| 359 { name: 'Custom stringifying error'}, |
| 360 () => { |
| 361 index.name = { |
| 362 toString: () => { throw { name: 'Custom stringifying error'}; } |
| 363 }; |
| 364 }, 'IDBObjectStore rename should re-raise toString() exception'); |
| 365 assert_array_equals( |
| 366 store.indexNames, ['by_author', 'by_title'], |
| 367 'An index rename that throws an exception should not change the ' + |
| 368 "index's IDBObjectStore.indexNames"); |
| 369 })).then(database => { |
| 370 const transaction = database.transaction('books', 'readonly'); |
| 371 const store = transaction.objectStore('books'); |
| 372 assert_array_equals( |
| 373 store.indexNames, ['by_author', 'by_title'], |
| 374 'Committing a transaction with a failed store rename attempt ' + |
| 375 "should not change the index's IDBObjectStore.indexNames"); |
| 376 const index = store.index('by_author'); |
| 377 return checkAuthorIndexContents( |
| 378 testCase, index, |
| 379 'Committing a transaction with a failed rename attempt should not' + |
| 380 "change the index's contents").then(() => database.close()); |
| 381 }); |
| 382 }, 'IndexedDB object store rename handles exceptions when stringifying names'); |
| 383 |
| 384 for (let escapedName of ['', '\\u0000', '\\uDC00\\uD800']) ((escapedName) => { |
| 385 const name = JSON.parse('"' + escapedName + '"'); |
| 386 promise_test(testCase => { |
| 387 return createDatabase(testCase, (database, transaction) => { |
| 388 createBooksStore(testCase, database); |
| 389 }).then(database => { |
| 390 database.close(); |
| 391 }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { |
| 392 const store = transaction.objectStore('books'); |
| 393 const index = store.index('by_author'); |
| 394 |
| 395 index.name = name; |
| 396 assert_equals(index.name, name, |
| 397 'IDBIndex name should change immediately after the rename'); |
| 398 assert_array_equals( |
| 399 store.indexNames, [name, 'by_title'].sort(), |
| 400 'IDBObjectStore.indexNames should immediately reflect the rename'); |
| 401 })).then(database => { |
| 402 const transaction = database.transaction('books', 'readonly'); |
| 403 const store = transaction.objectStore('books'); |
| 404 assert_array_equals( |
| 405 store.indexNames, [name, 'by_title'].sort(), |
| 406 'IDBObjectStore.indexNames should reflect the rename ' + |
| 407 'after the versionchange transaction commits'); |
| 408 const index = store.index(name); |
| 409 return checkAuthorIndexContents( |
| 410 testCase, index, |
| 411 'Renaming an index should not change its contents').then( |
| 412 () => database.close()); |
| 413 }); |
| 414 }, 'IndexedDB object store can be renamed to "' + escapedName + '"'); |
| 415 })(escapedName); |
| 416 |
| 417 promise_test(testCase => { |
| 418 const dbName = databaseName(testCase); |
| 419 let authorIndex = null, authorIndex2 = null; |
| 420 return createDatabase(testCase, (database, transaction) => { |
| 421 const store = createBooksStore(testCase, database); |
| 422 }).then(database => { |
| 423 database.close(); |
| 424 }).then(() => new Promise((resolve, reject) => { |
| 425 const request = indexedDB.open(dbName, 2); |
| 426 request.onupgradeneeded = event => { |
| 427 const database = event.target.result; |
| 428 const transaction = event.target.transaction; |
| 429 const store = transaction.objectStore('books'); |
| 430 authorIndex = store.index('by_author'); |
| 431 authorIndex.name = 'renamed_by_author'; |
| 432 request.onerror = event => { |
| 433 event.preventDefault(); |
| 434 resolve(event); |
| 435 } |
| 436 transaction.abort(); |
| 437 |
| 438 assert_equals( |
| 439 authorIndex.name, 'by_author', |
| 440 'IDBIndex.name should not reflect the rename anymore ' + |
| 441 'immediately after transaction.abort() returns'); |
| 442 assert_array_equals( |
| 443 store.indexNames, ['by_author', 'by_title'], |
| 444 'IDBObjectStore.indexNames should not reflect the rename ' + |
| 445 'anymore immediately after transaction.abort() returns'); |
| 446 }; |
| 447 request.onerror = event => reject(event.target.error); |
| 448 request.onsuccess = () => reject(new Error( |
| 449 'indexedDB.open was not supposed to succeed')); |
| 450 })).then(event => { |
| 451 assert_equals(authorIndex.name, 'by_author', |
| 452 'IDBIndex.name should not reflect the rename anymore ' + |
| 453 'after the versionchange transaction is aborted'); |
| 454 |
| 455 const request = indexedDB.open(dbName, 1); |
| 456 return requestWatcher(testCase, request).wait_for('success'); |
| 457 }).then(event => { |
| 458 const database = event.target.result; |
| 459 const transaction = database.transaction('books', 'readonly'); |
| 460 const store = transaction.objectStore('books'); |
| 461 assert_array_equals( |
| 462 store.indexNames, ['by_author', 'by_title'], |
| 463 'IDBDatabase.objectStoreNames should not reflect the rename ' + |
| 464 'after the versionchange transaction is aborted'); |
| 465 |
| 466 authorIndex2 = store.index('by_author'); |
| 467 return checkAuthorIndexContents( |
| 468 testCase, authorIndex2, |
| 469 'Aborting an index rename transaction should not change the ' + |
| 470 "index's records").then(() => database.close()); |
| 471 }).then(() => { |
| 472 assert_equals( |
| 473 authorIndex.name, 'by_author', |
| 474 'IDBIndex used in aborted rename transaction should not reflect ' + |
| 475 'the rename after the transaction is aborted'); |
| 476 assert_equals(authorIndex2.name, 'by_author', |
| 477 'IDBIndex obtained after an aborted rename transaction should ' + |
| 478 'not reflect the rename'); |
| 479 }); |
| 480 }, 'IndexedDB object store rename in aborted transaction'); |
| 481 </script> |
OLD | NEW |