dbTest.js (11475B)
1 describe("Zotero.DB", function() { 2 var tmpTable = "tmpDBTest"; 3 4 before(function* () { 5 this.timeout(5000); 6 Zotero.debug("Waiting for DB activity to settle"); 7 yield Zotero.DB.waitForTransaction(); 8 yield Zotero.Promise.delay(1000); 9 }); 10 beforeEach(function* () { 11 yield Zotero.DB.queryAsync("DROP TABLE IF EXISTS " + tmpTable); 12 yield Zotero.DB.queryAsync("CREATE TABLE " + tmpTable + " (foo INT)"); 13 }); 14 after(function* () { 15 yield Zotero.DB.queryAsync("DROP TABLE IF EXISTS " + tmpTable); 16 }); 17 18 19 describe("#queryAsync()", function () { 20 var tmpTable; 21 22 before(function* () { 23 tmpTable = "tmp_queryAsync"; 24 yield Zotero.DB.queryAsync("CREATE TEMPORARY TABLE " + tmpTable + " (a, b)"); 25 yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (1, 2)"); 26 yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (3, 4)"); 27 yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (5, NULL)"); 28 }) 29 after(function* () { 30 if (tmpTable) { 31 yield Zotero.DB.queryAsync("DROP TABLE IF EXISTS " + tmpTable); 32 } 33 }) 34 35 it("should throw an error if no parameters are passed for a query with placeholders", function* () { 36 var e = yield getPromiseError(Zotero.DB.queryAsync("SELECT itemID FROM items WHERE itemID=?")); 37 assert.ok(e); 38 assert.include(e.message, "for query containing placeholders"); 39 }) 40 41 it("should throw an error if too few parameters are passed", function* () { 42 var e = yield getPromiseError(Zotero.DB.queryAsync("SELECT itemID FROM items WHERE itemID=? OR itemID=?", [1])); 43 assert.ok(e); 44 assert.include(e.message, "Incorrect number of parameters provided for query"); 45 }) 46 47 it("should throw an error if too many parameters are passed", function* () { 48 var e = yield getPromiseError(Zotero.DB.queryAsync("SELECT itemID FROM items WHERE itemID=?", [1, 2])); 49 assert.ok(e); 50 assert.include(e.message, "Incorrect number of parameters provided for query"); 51 }) 52 53 it("should throw an error if too many parameters are passed for numbered placeholders", function* () { 54 var e = yield getPromiseError(Zotero.DB.queryAsync("SELECT itemID FROM items WHERE itemID=?1 OR itemID=?1", [1, 2])); 55 assert.ok(e); 56 assert.include(e.message, "Incorrect number of parameters provided for query"); 57 }) 58 59 it("should accept a single placeholder given as a value", function* () { 60 var rows = yield Zotero.DB.queryAsync("SELECT a FROM " + tmpTable + " WHERE b=?", 2); 61 assert.lengthOf(rows, 1); 62 assert.equal(rows[0].a, 1); 63 }) 64 65 it("should accept a single placeholder given as an array", function* () { 66 var rows = yield Zotero.DB.queryAsync("SELECT a FROM " + tmpTable + " WHERE b=?", [2]); 67 assert.lengthOf(rows, 1); 68 assert.equal(rows[0].a, 1); 69 }) 70 71 it("should accept multiple placeholders", function* () { 72 var rows = yield Zotero.DB.queryAsync("SELECT a FROM " + tmpTable + " WHERE b=? OR b=?", [2, 4]); 73 assert.lengthOf(rows, 2); 74 assert.equal(rows[0].a, 1); 75 assert.equal(rows[1].a, 3); 76 }) 77 78 it("should accept a single placeholder within parentheses", function* () { 79 var rows = yield Zotero.DB.queryAsync("SELECT a FROM " + tmpTable + " WHERE b IN (?)", 2); 80 assert.lengthOf(rows, 1); 81 assert.equal(rows[0].a, 1); 82 }) 83 84 it("should accept multiple placeholders within parentheses", function* () { 85 var rows = yield Zotero.DB.queryAsync("SELECT a FROM " + tmpTable + " WHERE b IN (?, ?)", [2, 4]); 86 assert.lengthOf(rows, 2); 87 assert.equal(rows[0].a, 1); 88 assert.equal(rows[1].a, 3); 89 }) 90 91 it("should replace =? with IS NULL if NULL is passed as a value", function* () { 92 var rows = yield Zotero.DB.queryAsync("SELECT a FROM " + tmpTable + " WHERE b=?", null); 93 assert.lengthOf(rows, 1); 94 assert.equal(rows[0].a, 5); 95 }) 96 97 it("should replace =? with IS NULL if NULL is passed in an array", function* () { 98 var rows = yield Zotero.DB.queryAsync("SELECT a FROM " + tmpTable + " WHERE b=?", [null]); 99 assert.lengthOf(rows, 1); 100 assert.equal(rows[0].a, 5); 101 }) 102 103 it("should replace ? with NULL for placeholders within parentheses in INSERT statements", function* () { 104 yield Zotero.DB.queryAsync("CREATE TEMPORARY TABLE tmp_srqwnfpwpinss (a, b)"); 105 // Replace ", ?" 106 yield Zotero.DB.queryAsync("INSERT INTO tmp_srqwnfpwpinss (a, b) VALUES (?, ?)", [1, null]); 107 assert.equal( 108 (yield Zotero.DB.valueQueryAsync("SELECT a FROM tmp_srqwnfpwpinss WHERE b IS NULL")), 109 1 110 ); 111 // Replace "(?" 112 yield Zotero.DB.queryAsync("DELETE FROM tmp_srqwnfpwpinss"); 113 yield Zotero.DB.queryAsync("INSERT INTO tmp_srqwnfpwpinss (a, b) VALUES (?, ?)", [null, 2]); 114 assert.equal( 115 (yield Zotero.DB.valueQueryAsync("SELECT b FROM tmp_srqwnfpwpinss WHERE a IS NULL")), 116 2 117 ); 118 yield Zotero.DB.queryAsync("DROP TABLE tmp_srqwnfpwpinss"); 119 }) 120 121 it("should throw an error if NULL is passed for placeholder within parentheses in a SELECT statement", function* () { 122 var e = yield getPromiseError(Zotero.DB.queryAsync("SELECT a FROM " + tmpTable + " WHERE b IN (?)", null)); 123 assert.ok(e); 124 assert.include(e.message, "NULL cannot be used for parenthesized placeholders in SELECT queries"); 125 }) 126 127 it("should handle numbered parameters", function* () { 128 var rows = yield Zotero.DB.queryAsync("SELECT a FROM " + tmpTable + " WHERE b=?1 " 129 + "UNION SELECT b FROM " + tmpTable + " WHERE b=?1", 2); 130 assert.lengthOf(rows, 2); 131 assert.equal(rows[0].a, 1); 132 assert.equal(rows[1].a, 2); 133 }) 134 135 it("should throw an error if onRow throws an error", function* () { 136 var i = 0; 137 var e = Zotero.DB.queryAsync( 138 "SELECT * FROM " + tmpTable, 139 false, 140 { 141 onRow: function (row) { 142 if (i > 0) { 143 throw new Error("Failed"); 144 } 145 i++; 146 } 147 } 148 ); 149 e = yield getPromiseError(e) 150 assert.ok(e); 151 assert.equal(e.message, "Failed"); 152 }); 153 154 it("should stop gracefully if onRow throws a StopIteration", function* () { 155 var i = 0; 156 var rows = []; 157 yield Zotero.DB.queryAsync( 158 "SELECT * FROM " + tmpTable, 159 false, 160 { 161 onRow: function (row) { 162 if (i > 0) { 163 throw StopIteration; 164 } 165 rows.push(row.getResultByIndex(0)); 166 i++; 167 } 168 } 169 ); 170 assert.lengthOf(rows, 1); 171 }); 172 }) 173 174 175 describe("#executeTransaction()", function () { 176 it("should serialize concurrent transactions", Zotero.Promise.coroutine(function* () { 177 var resolve1, resolve2, reject1, reject2; 178 var promise1 = new Promise(function (resolve, reject) { 179 resolve1 = resolve; 180 reject1 = reject; 181 }); 182 var promise2 = new Promise(function (resolve, reject) { 183 resolve2 = resolve; 184 reject2 = reject; 185 }); 186 187 Zotero.DB.executeTransaction(function* () { 188 yield Zotero.Promise.delay(250); 189 var num = yield Zotero.DB.valueQueryAsync("SELECT COUNT(*) FROM " + tmpTable); 190 assert.equal(num, 0); 191 yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (1)"); 192 assert.ok(Zotero.DB.inTransaction()); 193 }) 194 .then(resolve1) 195 .catch(reject1); 196 197 Zotero.DB.executeTransaction(function* () { 198 var num = yield Zotero.DB.valueQueryAsync("SELECT COUNT(*) FROM " + tmpTable); 199 assert.equal(num, 1); 200 yield Zotero.Promise.delay(500); 201 yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (2)"); 202 assert.ok(Zotero.DB.inTransaction()); 203 }) 204 .then(resolve2) 205 .catch(reject2); 206 207 yield Zotero.Promise.all([promise1, promise2]); 208 })); 209 210 it("should serialize queued transactions", function* () { 211 var resolve1, resolve2, reject1, reject2, resolve3, reject3; 212 var promise1 = new Promise(function (resolve, reject) { 213 resolve1 = resolve; 214 reject1 = reject; 215 }); 216 var promise2 = new Promise(function (resolve, reject) { 217 resolve2 = resolve; 218 reject2 = reject; 219 }); 220 var promise3 = new Promise(function (resolve, reject) { 221 resolve3 = resolve; 222 reject3 = reject; 223 }); 224 225 // Start a transaction and have it delay 226 Zotero.DB.executeTransaction(function* () { 227 yield Zotero.Promise.delay(100); 228 var num = yield Zotero.DB.valueQueryAsync("SELECT COUNT(*) FROM " + tmpTable); 229 assert.equal(num, 0); 230 yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (1)"); 231 assert.ok(Zotero.DB.inTransaction()); 232 }) 233 .then(resolve1) 234 .catch(reject1); 235 236 // Start two more transactions, which should wait on the first 237 Zotero.DB.executeTransaction(function* () { 238 var num = yield Zotero.DB.valueQueryAsync("SELECT COUNT(*) FROM " + tmpTable); 239 assert.equal(num, 1); 240 yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (2)"); 241 assert.ok(Zotero.DB.inTransaction()); 242 }) 243 .then(resolve2) 244 .catch(reject2); 245 246 Zotero.DB.executeTransaction(function* () { 247 var num = yield Zotero.DB.valueQueryAsync("SELECT COUNT(*) FROM " + tmpTable); 248 assert.equal(num, 2); 249 yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (3)"); 250 // But make sure the second queued transaction doesn't start at the same time, 251 // such that the first queued transaction gets closed while the second is still 252 // running 253 assert.ok(Zotero.DB.inTransaction()); 254 }) 255 .then(resolve3) 256 .catch(reject3); 257 258 yield Zotero.Promise.all([promise1, promise2, promise3]); 259 }) 260 261 it("should roll back on error", function* () { 262 yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (1)"); 263 try { 264 yield Zotero.DB.executeTransaction(function* () { 265 yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (2)"); 266 throw 'Aborting transaction -- ignore'; 267 }); 268 } 269 catch (e) { 270 if (typeof e != 'string' || !e.startsWith('Aborting transaction')) throw e; 271 } 272 var count = yield Zotero.DB.valueQueryAsync("SELECT COUNT(*) FROM " + tmpTable + ""); 273 assert.equal(count, 1); 274 275 var conn = yield Zotero.DB._getConnectionAsync(); 276 assert.isFalse(conn.transactionInProgress); 277 278 yield Zotero.DB.queryAsync("DROP TABLE " + tmpTable); 279 }); 280 281 it("should run onRollback callbacks", function* () { 282 var callbackRan = false; 283 try { 284 yield Zotero.DB.executeTransaction( 285 function* () { 286 yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (1)"); 287 throw 'Aborting transaction -- ignore'; 288 }, 289 { 290 onRollback: function () { 291 callbackRan = true; 292 } 293 } 294 ); 295 } 296 catch (e) { 297 if (typeof e != 'string' || !e.startsWith('Aborting transaction')) throw e; 298 } 299 assert.ok(callbackRan); 300 301 yield Zotero.DB.queryAsync("DROP TABLE " + tmpTable); 302 }); 303 304 it("should time out on nested transactions", function* () { 305 var e; 306 yield Zotero.DB.executeTransaction(function* () { 307 e = yield getPromiseError( 308 Zotero.DB.executeTransaction(function* () {}).timeout(250) 309 ); 310 }); 311 assert.ok(e); 312 assert.equal(e.name, "TimeoutError"); 313 }); 314 315 it("should run onRollback callbacks for timed-out nested transactions", function* () { 316 var callback1Ran = false; 317 var callback2Ran = false; 318 try { 319 yield Zotero.DB.executeTransaction(function* () { 320 yield Zotero.DB.executeTransaction( 321 function* () {}, 322 { 323 waitTimeout: 100, 324 onRollback: function () { 325 callback1Ran = true; 326 } 327 } 328 ) 329 }, 330 { 331 onRollback: function () { 332 callback2Ran = true; 333 } 334 }); 335 } 336 catch (e) { 337 if (e.name != "TimeoutError") throw e; 338 } 339 assert.ok(callback1Ran); 340 assert.ok(callback2Ran); 341 }); 342 }) 343 });