idb-explicit-commit.any.js (10999B)
1 // META: script=resources/support-promises.js 2 'use strict'; 3 4 promise_test(async testCase => { 5 const db = await createDatabase(testCase, db => { 6 createBooksStore(testCase, db); 7 }); 8 const txn = db.transaction(['books'], 'readwrite'); 9 const objectStore = txn.objectStore('books'); 10 objectStore.put({isbn: 'one', title: 'title1'}); 11 objectStore.put({isbn: 'two', title: 'title2'}); 12 objectStore.put({isbn: 'three', title: 'title3'}); 13 txn.commit(); 14 await promiseForTransaction(testCase, txn); 15 16 const txn2 = db.transaction(['books'], 'readonly'); 17 const objectStore2 = txn2.objectStore('books'); 18 const getRequestitle1 = objectStore2.get('one'); 19 const getRequestitle2 = objectStore2.get('two'); 20 const getRequestitle3 = objectStore2.get('three'); 21 txn2.commit(); 22 await promiseForTransaction(testCase, txn2); 23 assert_array_equals( 24 [getRequestitle1.result.title, 25 getRequestitle2.result.title, 26 getRequestitle3.result.title], 27 ['title1', 'title2', 'title3'], 28 'All three retrieved titles should match those that were put.'); 29 db.close(); 30 }, 'Explicitly committed data can be read back out.'); 31 32 33 promise_test(async testCase => { 34 let db = await createDatabase(testCase, () => {}); 35 assert_equals(1, db.version, 'A database should be created as version 1'); 36 db.close(); 37 38 // Upgrade the versionDB database and explicitly commit its versionchange 39 // transaction. 40 db = await migrateDatabase(testCase, 2, (db, txn) => { 41 txn.commit(); 42 }); 43 assert_equals(2, db.version, 44 'The database version should have been incremented regardless of ' 45 + 'whether the versionchange transaction was explicitly or implicitly ' 46 + 'committed.'); 47 db.close(); 48 }, 'commit() on a version change transaction does not cause errors.'); 49 50 51 promise_test(async testCase => { 52 const db = await createDatabase(testCase, db => { 53 createBooksStore(testCase, db); 54 }); 55 const txn = db.transaction(['books'], 'readwrite'); 56 const objectStore = txn.objectStore('books'); 57 txn.commit(); 58 assert_throws_dom('TransactionInactiveError', 59 () => { objectStore.put({isbn: 'one', title: 'title1'}); }, 60 'After commit is called, the transaction should be inactive.'); 61 db.close(); 62 }, 'A committed transaction becomes inactive immediately.'); 63 64 65 promise_test(async testCase => { 66 const db = await createDatabase(testCase, db => { 67 createBooksStore(testCase, db); 68 }); 69 const txn = db.transaction(['books'], 'readwrite'); 70 const objectStore = txn.objectStore('books'); 71 const putRequest = objectStore.put({isbn: 'one', title: 'title1'}); 72 putRequest.onsuccess = testCase.step_func(() => { 73 assert_throws_dom('TransactionInactiveError', 74 () => { objectStore.put({isbn:'two', title:'title2'}); }, 75 'The transaction should not be active in the callback of a request after ' 76 + 'commit() is called.'); 77 }); 78 txn.commit(); 79 await promiseForTransaction(testCase, txn); 80 db.close(); 81 }, 'A committed transaction is inactive in future request callbacks.'); 82 83 84 promise_test(async testCase => { 85 const db = await createDatabase(testCase, db => { 86 createBooksStore(testCase, db); 87 }); 88 const txn = db.transaction(['books'], 'readwrite'); 89 const objectStore = txn.objectStore('books'); 90 txn.commit(); 91 92 assert_throws_dom('TransactionInactiveError', 93 () => { objectStore.put({isbn:'one', title:'title1'}); }, 94 'After commit is called, the transaction should be inactive.'); 95 96 const txn2 = db.transaction(['books'], 'readonly'); 97 const objectStore2 = txn2.objectStore('books'); 98 const getRequest = objectStore2.get('one'); 99 await promiseForTransaction(testCase, txn2); 100 assert_equals(getRequest.result, undefined); 101 102 db.close(); 103 }, 'Puts issued after commit are not fulfilled.'); 104 105 106 promise_test(async testCase => { 107 const db = await createDatabase(testCase, db => { 108 createBooksStore(testCase, db); 109 }); 110 const txn = db.transaction(['books'], 'readwrite'); 111 const objectStore = txn.objectStore('books'); 112 txn.abort(); 113 assert_throws_dom('InvalidStateError', 114 () => { txn.commit(); }, 115 'The transaction should have been aborted.'); 116 db.close(); 117 }, 'Calling commit on an aborted transaction throws.'); 118 119 120 promise_test(async testCase => { 121 const db = await createDatabase(testCase, db => { 122 createBooksStore(testCase, db); 123 }); 124 const txn = db.transaction(['books'], 'readwrite'); 125 const objectStore = txn.objectStore('books'); 126 txn.commit(); 127 assert_throws_dom('InvalidStateError', 128 () => { txn.commit(); }, 129 'The transaction should have already committed.'); 130 db.close(); 131 }, 'Calling commit on a committed transaction throws.'); 132 133 134 promise_test(async testCase => { 135 const db = await createDatabase(testCase, db => { 136 createBooksStore(testCase, db); 137 }); 138 const txn = db.transaction(['books'], 'readwrite'); 139 const objectStore = txn.objectStore('books'); 140 const putRequest = objectStore.put({isbn:'one', title:'title1'}); 141 txn.commit(); 142 assert_throws_dom('InvalidStateError', 143 () => { txn.abort(); }, 144 'The transaction should already have committed.'); 145 const txn2 = db.transaction(['books'], 'readwrite'); 146 const objectStore2 = txn2.objectStore('books'); 147 const getRequest = objectStore2.get('one'); 148 await promiseForTransaction(testCase, txn2); 149 assert_equals( 150 getRequest.result.title, 151 'title1', 152 'Explicitly committed data should be gettable.'); 153 db.close(); 154 }, 'Calling abort on a committed transaction throws and does not prevent ' 155 + 'persisting the data.'); 156 157 158 promise_test(async testCase => { 159 const db = await createDatabase(testCase, db => { 160 createBooksStore(testCase, db); 161 }); 162 const txn = db.transaction(['books'], 'readwrite'); 163 const objectStore = txn.objectStore('books'); 164 const releaseTxnFunction = keepAlive(testCase, txn, 'books'); 165 166 // Break up the scope of execution to force the transaction into an inactive 167 // state. 168 await timeoutPromise(0); 169 170 assert_throws_dom('InvalidStateError', 171 () => { txn.commit(); }, 172 'The transaction should be inactive so calling commit should throw.'); 173 releaseTxnFunction(); 174 db.close(); 175 }, 'Calling txn.commit() when txn is inactive should throw.'); 176 177 178 promise_test(async testCase => { 179 const db = await createDatabase(testCase, db => { 180 createBooksStore(testCase, db); 181 createNotBooksStore(testCase, db); 182 }); 183 // Txn1 should commit before txn2, even though txn2 uses commit(). 184 const txn1 = db.transaction(['books'], 'readwrite'); 185 txn1.objectStore('books').put({isbn: 'one', title: 'title1'}); 186 const releaseTxnFunction = keepAlive(testCase, txn1, 'books'); 187 188 const txn2 = db.transaction(['books'], 'readwrite'); 189 txn2.objectStore('books').put({isbn:'one', title:'title2'}); 190 txn2.commit(); 191 192 // Exercise the IndexedDB transaction ordering by executing one with a 193 // different scope. A readonly transaction is used here because 194 // implementations are not required to run non-overlapping readwrite 195 // transactions in parallel, and some implementations (ex: Firefox) 196 // will not. 197 const txn3 = db.transaction(['not_books'], 'readonly'); 198 txn3.objectStore('not_books').getAllKeys(); 199 txn3.oncomplete = function() { 200 releaseTxnFunction(); 201 } 202 await Promise.all([promiseForTransaction(testCase, txn1), 203 promiseForTransaction(testCase, txn2)]); 204 205 // Read the data back to verify that txn2 executed last. 206 const txn4 = db.transaction(['books'], 'readonly'); 207 const getRequest4 = txn4.objectStore('books').get('one'); 208 await promiseForTransaction(testCase, txn4); 209 assert_equals(getRequest4.result.title, 'title2'); 210 db.close(); 211 }, 'Transactions with same scope should stay in program order, even if one ' 212 + 'calls commit.'); 213 214 215 promise_test(async testCase => { 216 const db = await createDatabase(testCase, db => { 217 createBooksStore(testCase, db); 218 }); 219 // Txn1 creates the book 'one' so the 'add()' below fails. 220 const txn1 = db.transaction(['books'], 'readwrite'); 221 txn1.objectStore('books').add({isbn:'one', title:'title1'}); 222 txn1.commit(); 223 await promiseForTransaction(testCase, txn1); 224 225 // Txn2 should abort, because the 'add' call is invalid, and commit() was 226 // called. 227 const txn2 = db.transaction(['books'], 'readwrite'); 228 const objectStore2 = txn2.objectStore('books'); 229 objectStore2.put({isbn:'two', title:'title2'}); 230 const addRequest = objectStore2.add({isbn:'one', title:'title2'}); 231 txn2.commit(); 232 txn2.oncomplete = () => { assert_unreached( 233 'Transaction with invalid "add" call should not be completed.'); }; 234 235 // Wait for the transaction to complete. We have to explicitly wait for the 236 // error signal on the transaction because of the nature of the test tooling. 237 await Promise.all([ 238 requestWatcher(testCase, addRequest).wait_for('error'), 239 transactionWatcher(testCase, txn2).wait_for(['error', 'abort']) 240 ]); 241 242 // Read the data back to verify that txn2 was aborted. 243 const txn3 = db.transaction(['books'], 'readonly'); 244 const objectStore3 = txn3.objectStore('books'); 245 const getRequest1 = objectStore3.get('one'); 246 const getRequest2 = objectStore3.count('two'); 247 await promiseForTransaction(testCase, txn3); 248 assert_equals(getRequest1.result.title, 'title1'); 249 assert_equals(getRequest2.result, 0); 250 db.close(); 251 }, 'Transactions that explicitly commit and have errors should abort.'); 252 253 254 promise_test(async testCase => { 255 const db = await createDatabase(testCase, db => { 256 createBooksStore(testCase, db); 257 }); 258 const txn1 = db.transaction(['books'], 'readwrite'); 259 txn1.objectStore('books').add({isbn: 'one', title: 'title1'}); 260 txn1.commit(); 261 await promiseForTransaction(testCase, txn1); 262 263 // The second add request will throw an error, but the onerror handler will 264 // appropriately catch the error allowing the valid put request on the 265 // transaction to commit. 266 const txn2 = db.transaction(['books'], 'readwrite'); 267 const objectStore2 = txn2.objectStore('books'); 268 objectStore2.put({isbn: 'two', title:'title2'}); 269 const addRequest = objectStore2.add({isbn: 'one', title:'unreached_title'}); 270 addRequest.onerror = (event) => { 271 event.preventDefault(); 272 addRequest.transaction.commit(); 273 }; 274 275 // Wait for the transaction to complete. We have to explicitly wait for the 276 // error signal on the transaction because of the nature of the test tooling. 277 await transactionWatcher(testCase,txn2).wait_for(['error', 'complete']) 278 279 // Read the data back to verify that txn2 was committed. 280 const txn3 = db.transaction(['books'], 'readonly'); 281 const objectStore3 = txn3.objectStore('books'); 282 const getRequest1 = objectStore3.get('one'); 283 const getRequest2 = objectStore3.get('two'); 284 await promiseForTransaction(testCase, txn3); 285 assert_equals(getRequest1.result.title, 'title1'); 286 assert_equals(getRequest2.result.title, 'title2'); 287 db.close(); 288 }, 'Transactions that handle all errors properly should behave as ' + 289 'expected when an explicit commit is called in an onerror handler.');