www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

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 });