|
1 /* Any copyright is dedicated to the Public Domain. |
|
2 * http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
3 |
|
4 "use strict"; |
|
5 |
|
6 const {classes: Cc, interfaces: Ci, utils: Cu} = Components; |
|
7 |
|
8 do_get_profile(); |
|
9 |
|
10 Cu.import("resource://gre/modules/Promise.jsm"); |
|
11 Cu.import("resource://gre/modules/osfile.jsm"); |
|
12 Cu.import("resource://gre/modules/FileUtils.jsm"); |
|
13 Cu.import("resource://gre/modules/Services.jsm"); |
|
14 Cu.import("resource://gre/modules/Sqlite.jsm"); |
|
15 Cu.import("resource://gre/modules/Task.jsm"); |
|
16 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
17 |
|
18 // To spin the event loop in test. |
|
19 Cu.import("resource://services-common/async.js"); |
|
20 |
|
21 function sleep(ms) { |
|
22 let deferred = Promise.defer(); |
|
23 |
|
24 let timer = Cc["@mozilla.org/timer;1"] |
|
25 .createInstance(Ci.nsITimer); |
|
26 |
|
27 timer.initWithCallback({ |
|
28 notify: function () { |
|
29 deferred.resolve(); |
|
30 }, |
|
31 }, ms, timer.TYPE_ONE_SHOT); |
|
32 |
|
33 return deferred.promise; |
|
34 } |
|
35 |
|
36 function getConnection(dbName, extraOptions={}) { |
|
37 let path = dbName + ".sqlite"; |
|
38 let options = {path: path}; |
|
39 for (let [k, v] in Iterator(extraOptions)) { |
|
40 options[k] = v; |
|
41 } |
|
42 |
|
43 return Sqlite.openConnection(options); |
|
44 } |
|
45 |
|
46 function getDummyDatabase(name, extraOptions={}) { |
|
47 const TABLES = { |
|
48 dirs: "id INTEGER PRIMARY KEY AUTOINCREMENT, path TEXT", |
|
49 files: "id INTEGER PRIMARY KEY AUTOINCREMENT, dir_id INTEGER, path TEXT", |
|
50 }; |
|
51 |
|
52 let c = yield getConnection(name, extraOptions); |
|
53 c._initialStatementCount = 0; |
|
54 |
|
55 for (let [k, v] in Iterator(TABLES)) { |
|
56 yield c.execute("CREATE TABLE " + k + "(" + v + ")"); |
|
57 c._initialStatementCount++; |
|
58 } |
|
59 |
|
60 throw new Task.Result(c); |
|
61 } |
|
62 |
|
63 function getDummyTempDatabase(name, extraOptions={}) { |
|
64 const TABLES = { |
|
65 dirs: "id INTEGER PRIMARY KEY AUTOINCREMENT, path TEXT", |
|
66 files: "id INTEGER PRIMARY KEY AUTOINCREMENT, dir_id INTEGER, path TEXT", |
|
67 }; |
|
68 |
|
69 let c = yield getConnection(name, extraOptions); |
|
70 c._initialStatementCount = 0; |
|
71 |
|
72 for (let [k, v] in Iterator(TABLES)) { |
|
73 yield c.execute("CREATE TEMP TABLE " + k + "(" + v + ")"); |
|
74 c._initialStatementCount++; |
|
75 } |
|
76 |
|
77 throw new Task.Result(c); |
|
78 } |
|
79 |
|
80 function run_test() { |
|
81 Cu.import("resource://testing-common/services-common/logging.js"); |
|
82 initTestLogging("Trace"); |
|
83 |
|
84 run_next_test(); |
|
85 } |
|
86 |
|
87 add_task(function test_open_normal() { |
|
88 let c = yield Sqlite.openConnection({path: "test_open_normal.sqlite"}); |
|
89 yield c.close(); |
|
90 }); |
|
91 |
|
92 add_task(function test_open_unshared() { |
|
93 let path = OS.Path.join(OS.Constants.Path.profileDir, "test_open_unshared.sqlite"); |
|
94 |
|
95 let c = yield Sqlite.openConnection({path: path, sharedMemoryCache: false}); |
|
96 yield c.close(); |
|
97 }); |
|
98 |
|
99 add_task(function test_get_dummy_database() { |
|
100 let db = yield getDummyDatabase("get_dummy_database"); |
|
101 |
|
102 do_check_eq(typeof(db), "object"); |
|
103 yield db.close(); |
|
104 }); |
|
105 |
|
106 add_task(function test_schema_version() { |
|
107 let db = yield getDummyDatabase("schema_version"); |
|
108 |
|
109 let version = yield db.getSchemaVersion(); |
|
110 do_check_eq(version, 0); |
|
111 |
|
112 db.setSchemaVersion(14); |
|
113 version = yield db.getSchemaVersion(); |
|
114 do_check_eq(version, 14); |
|
115 |
|
116 for (let v of [0.5, "foobar", NaN]) { |
|
117 let success; |
|
118 try { |
|
119 yield db.setSchemaVersion(v); |
|
120 do_print("Schema version " + v + " should have been rejected"); |
|
121 success = false; |
|
122 } catch (ex if ex.message.startsWith("Schema version must be an integer.")) { |
|
123 success = true; |
|
124 } |
|
125 do_check_true(success); |
|
126 |
|
127 version = yield db.getSchemaVersion(); |
|
128 do_check_eq(version, 14); |
|
129 } |
|
130 |
|
131 yield db.close(); |
|
132 }); |
|
133 |
|
134 add_task(function test_simple_insert() { |
|
135 let c = yield getDummyDatabase("simple_insert"); |
|
136 |
|
137 let result = yield c.execute("INSERT INTO dirs VALUES (NULL, 'foo')"); |
|
138 do_check_true(Array.isArray(result)); |
|
139 do_check_eq(result.length, 0); |
|
140 yield c.close(); |
|
141 }); |
|
142 |
|
143 add_task(function test_simple_bound_array() { |
|
144 let c = yield getDummyDatabase("simple_bound_array"); |
|
145 |
|
146 let result = yield c.execute("INSERT INTO dirs VALUES (?, ?)", [1, "foo"]); |
|
147 do_check_eq(result.length, 0); |
|
148 yield c.close(); |
|
149 }); |
|
150 |
|
151 add_task(function test_simple_bound_object() { |
|
152 let c = yield getDummyDatabase("simple_bound_object"); |
|
153 let result = yield c.execute("INSERT INTO dirs VALUES (:id, :path)", |
|
154 {id: 1, path: "foo"}); |
|
155 do_check_eq(result.length, 0); |
|
156 result = yield c.execute("SELECT id, path FROM dirs"); |
|
157 do_check_eq(result.length, 1); |
|
158 do_check_eq(result[0].getResultByName("id"), 1); |
|
159 do_check_eq(result[0].getResultByName("path"), "foo"); |
|
160 yield c.close(); |
|
161 }); |
|
162 |
|
163 // This is mostly a sanity test to ensure simple executions work. |
|
164 add_task(function test_simple_insert_then_select() { |
|
165 let c = yield getDummyDatabase("simple_insert_then_select"); |
|
166 |
|
167 yield c.execute("INSERT INTO dirs VALUES (NULL, 'foo')"); |
|
168 yield c.execute("INSERT INTO dirs (path) VALUES (?)", ["bar"]); |
|
169 |
|
170 let result = yield c.execute("SELECT * FROM dirs"); |
|
171 do_check_eq(result.length, 2); |
|
172 |
|
173 let i = 0; |
|
174 for (let row of result) { |
|
175 i++; |
|
176 |
|
177 do_check_eq(row.numEntries, 2); |
|
178 do_check_eq(row.getResultByIndex(0), i); |
|
179 |
|
180 let expected = {1: "foo", 2: "bar"}[i]; |
|
181 do_check_eq(row.getResultByName("path"), expected); |
|
182 } |
|
183 |
|
184 yield c.close(); |
|
185 }); |
|
186 |
|
187 add_task(function test_repeat_execution() { |
|
188 let c = yield getDummyDatabase("repeat_execution"); |
|
189 |
|
190 let sql = "INSERT INTO dirs (path) VALUES (:path)"; |
|
191 yield c.executeCached(sql, {path: "foo"}); |
|
192 yield c.executeCached(sql); |
|
193 |
|
194 let result = yield c.execute("SELECT * FROM dirs"); |
|
195 |
|
196 do_check_eq(result.length, 2); |
|
197 |
|
198 yield c.close(); |
|
199 }); |
|
200 |
|
201 add_task(function test_table_exists() { |
|
202 let c = yield getDummyDatabase("table_exists"); |
|
203 |
|
204 do_check_false(yield c.tableExists("does_not_exist")); |
|
205 do_check_true(yield c.tableExists("dirs")); |
|
206 do_check_true(yield c.tableExists("files")); |
|
207 |
|
208 yield c.close(); |
|
209 }); |
|
210 |
|
211 add_task(function test_index_exists() { |
|
212 let c = yield getDummyDatabase("index_exists"); |
|
213 |
|
214 do_check_false(yield c.indexExists("does_not_exist")); |
|
215 |
|
216 yield c.execute("CREATE INDEX my_index ON dirs (path)"); |
|
217 do_check_true(yield c.indexExists("my_index")); |
|
218 |
|
219 yield c.close(); |
|
220 }); |
|
221 |
|
222 add_task(function test_temp_table_exists() { |
|
223 let c = yield getDummyTempDatabase("temp_table_exists"); |
|
224 |
|
225 do_check_false(yield c.tableExists("temp_does_not_exist")); |
|
226 do_check_true(yield c.tableExists("dirs")); |
|
227 do_check_true(yield c.tableExists("files")); |
|
228 |
|
229 yield c.close(); |
|
230 }); |
|
231 |
|
232 add_task(function test_temp_index_exists() { |
|
233 let c = yield getDummyTempDatabase("temp_index_exists"); |
|
234 |
|
235 do_check_false(yield c.indexExists("temp_does_not_exist")); |
|
236 |
|
237 yield c.execute("CREATE INDEX my_index ON dirs (path)"); |
|
238 do_check_true(yield c.indexExists("my_index")); |
|
239 |
|
240 yield c.close(); |
|
241 }); |
|
242 |
|
243 add_task(function test_close_cached() { |
|
244 let c = yield getDummyDatabase("close_cached"); |
|
245 |
|
246 yield c.executeCached("SELECT * FROM dirs"); |
|
247 yield c.executeCached("SELECT * FROM files"); |
|
248 |
|
249 yield c.close(); |
|
250 }); |
|
251 |
|
252 add_task(function test_execute_invalid_statement() { |
|
253 let c = yield getDummyDatabase("invalid_statement"); |
|
254 |
|
255 let deferred = Promise.defer(); |
|
256 |
|
257 do_check_eq(c._anonymousStatements.size, 0); |
|
258 |
|
259 c.execute("SELECT invalid FROM unknown").then(do_throw, function onError(error) { |
|
260 deferred.resolve(); |
|
261 }); |
|
262 |
|
263 yield deferred.promise; |
|
264 |
|
265 // Ensure we don't leak the statement instance. |
|
266 do_check_eq(c._anonymousStatements.size, 0); |
|
267 |
|
268 yield c.close(); |
|
269 }); |
|
270 |
|
271 add_task(function test_on_row_exception_ignored() { |
|
272 let c = yield getDummyDatabase("on_row_exception_ignored"); |
|
273 |
|
274 let sql = "INSERT INTO dirs (path) VALUES (?)"; |
|
275 for (let i = 0; i < 10; i++) { |
|
276 yield c.executeCached(sql, ["dir" + i]); |
|
277 } |
|
278 |
|
279 let i = 0; |
|
280 yield c.execute("SELECT * FROM DIRS", null, function onRow(row) { |
|
281 i++; |
|
282 |
|
283 throw new Error("Some silly error."); |
|
284 }); |
|
285 |
|
286 do_check_eq(i, 10); |
|
287 |
|
288 yield c.close(); |
|
289 }); |
|
290 |
|
291 // Ensure StopIteration during onRow causes processing to stop. |
|
292 add_task(function test_on_row_stop_iteration() { |
|
293 let c = yield getDummyDatabase("on_row_stop_iteration"); |
|
294 |
|
295 let sql = "INSERT INTO dirs (path) VALUES (?)"; |
|
296 for (let i = 0; i < 10; i++) { |
|
297 yield c.executeCached(sql, ["dir" + i]); |
|
298 } |
|
299 |
|
300 let i = 0; |
|
301 let result = yield c.execute("SELECT * FROM dirs", null, function onRow(row) { |
|
302 i++; |
|
303 |
|
304 if (i == 5) { |
|
305 throw StopIteration; |
|
306 } |
|
307 }); |
|
308 |
|
309 do_check_null(result); |
|
310 do_check_eq(i, 5); |
|
311 |
|
312 yield c.close(); |
|
313 }); |
|
314 |
|
315 add_task(function test_invalid_transaction_type() { |
|
316 let c = yield getDummyDatabase("invalid_transaction_type"); |
|
317 |
|
318 let errored = false; |
|
319 try { |
|
320 c.executeTransaction(function () {}, "foobar"); |
|
321 } catch (ex) { |
|
322 errored = true; |
|
323 do_check_true(ex.message.startsWith("Unknown transaction type")); |
|
324 } finally { |
|
325 do_check_true(errored); |
|
326 } |
|
327 |
|
328 yield c.close(); |
|
329 }); |
|
330 |
|
331 add_task(function test_execute_transaction_success() { |
|
332 let c = yield getDummyDatabase("execute_transaction_success"); |
|
333 |
|
334 do_check_false(c.transactionInProgress); |
|
335 |
|
336 yield c.executeTransaction(function transaction(conn) { |
|
337 do_check_eq(c, conn); |
|
338 do_check_true(conn.transactionInProgress); |
|
339 |
|
340 yield conn.execute("INSERT INTO dirs (path) VALUES ('foo')"); |
|
341 }); |
|
342 |
|
343 do_check_false(c.transactionInProgress); |
|
344 let rows = yield c.execute("SELECT * FROM dirs"); |
|
345 do_check_true(Array.isArray(rows)); |
|
346 do_check_eq(rows.length, 1); |
|
347 |
|
348 yield c.close(); |
|
349 }); |
|
350 |
|
351 add_task(function test_execute_transaction_rollback() { |
|
352 let c = yield getDummyDatabase("execute_transaction_rollback"); |
|
353 |
|
354 let deferred = Promise.defer(); |
|
355 |
|
356 c.executeTransaction(function transaction(conn) { |
|
357 yield conn.execute("INSERT INTO dirs (path) VALUES ('foo')"); |
|
358 print("Expecting error with next statement."); |
|
359 yield conn.execute("INSERT INTO invalid VALUES ('foo')"); |
|
360 |
|
361 // We should never get here. |
|
362 do_throw(); |
|
363 }).then(do_throw, function onError(error) { |
|
364 deferred.resolve(); |
|
365 }); |
|
366 |
|
367 yield deferred.promise; |
|
368 |
|
369 let rows = yield c.execute("SELECT * FROM dirs"); |
|
370 do_check_eq(rows.length, 0); |
|
371 |
|
372 yield c.close(); |
|
373 }); |
|
374 |
|
375 add_task(function test_close_during_transaction() { |
|
376 let c = yield getDummyDatabase("close_during_transaction"); |
|
377 |
|
378 yield c.execute("INSERT INTO dirs (path) VALUES ('foo')"); |
|
379 |
|
380 let errored = false; |
|
381 try { |
|
382 yield c.executeTransaction(function transaction(conn) { |
|
383 yield c.execute("INSERT INTO dirs (path) VALUES ('bar')"); |
|
384 yield c.close(); |
|
385 }); |
|
386 } catch (ex) { |
|
387 errored = true; |
|
388 do_check_eq(ex.message, "Connection being closed."); |
|
389 } finally { |
|
390 do_check_true(errored); |
|
391 } |
|
392 |
|
393 let c2 = yield getConnection("close_during_transaction"); |
|
394 let rows = yield c2.execute("SELECT * FROM dirs"); |
|
395 do_check_eq(rows.length, 1); |
|
396 |
|
397 yield c2.close(); |
|
398 }); |
|
399 |
|
400 add_task(function test_detect_multiple_transactions() { |
|
401 let c = yield getDummyDatabase("detect_multiple_transactions"); |
|
402 |
|
403 yield c.executeTransaction(function main() { |
|
404 yield c.execute("INSERT INTO dirs (path) VALUES ('foo')"); |
|
405 |
|
406 let errored = false; |
|
407 try { |
|
408 yield c.executeTransaction(function child() { |
|
409 yield c.execute("INSERT INTO dirs (path) VALUES ('bar')"); |
|
410 }); |
|
411 } catch (ex) { |
|
412 errored = true; |
|
413 do_check_true(ex.message.startsWith("A transaction is already active.")); |
|
414 } finally { |
|
415 do_check_true(errored); |
|
416 } |
|
417 }); |
|
418 |
|
419 let rows = yield c.execute("SELECT * FROM dirs"); |
|
420 do_check_eq(rows.length, 1); |
|
421 |
|
422 yield c.close(); |
|
423 }); |
|
424 |
|
425 add_task(function test_shrink_memory() { |
|
426 let c = yield getDummyDatabase("shrink_memory"); |
|
427 |
|
428 // It's just a simple sanity test. We have no way of measuring whether this |
|
429 // actually does anything. |
|
430 |
|
431 yield c.shrinkMemory(); |
|
432 yield c.close(); |
|
433 }); |
|
434 |
|
435 add_task(function test_no_shrink_on_init() { |
|
436 let c = yield getConnection("no_shrink_on_init", |
|
437 {shrinkMemoryOnConnectionIdleMS: 200}); |
|
438 |
|
439 let oldShrink = c.shrinkMemory; |
|
440 let count = 0; |
|
441 Object.defineProperty(c, "shrinkMemory", { |
|
442 value: function () { |
|
443 count++; |
|
444 }, |
|
445 }); |
|
446 |
|
447 // We should not shrink until a statement has been executed. |
|
448 yield sleep(220); |
|
449 do_check_eq(count, 0); |
|
450 |
|
451 yield c.execute("SELECT 1"); |
|
452 yield sleep(220); |
|
453 do_check_eq(count, 1); |
|
454 |
|
455 yield c.close(); |
|
456 }); |
|
457 |
|
458 add_task(function test_idle_shrink_fires() { |
|
459 let c = yield getDummyDatabase("idle_shrink_fires", |
|
460 {shrinkMemoryOnConnectionIdleMS: 200}); |
|
461 c._clearIdleShrinkTimer(); |
|
462 |
|
463 let oldShrink = c.shrinkMemory; |
|
464 let shrinkPromises = []; |
|
465 |
|
466 let count = 0; |
|
467 Object.defineProperty(c, "shrinkMemory", { |
|
468 value: function () { |
|
469 count++; |
|
470 let promise = oldShrink.call(c); |
|
471 shrinkPromises.push(promise); |
|
472 return promise; |
|
473 }, |
|
474 }); |
|
475 |
|
476 // We reset the idle shrink timer after monkeypatching because otherwise the |
|
477 // installed timer callback will reference the non-monkeypatched function. |
|
478 c._startIdleShrinkTimer(); |
|
479 |
|
480 yield sleep(220); |
|
481 do_check_eq(count, 1); |
|
482 do_check_eq(shrinkPromises.length, 1); |
|
483 yield shrinkPromises[0]; |
|
484 shrinkPromises.shift(); |
|
485 |
|
486 // We shouldn't shrink again unless a statement was executed. |
|
487 yield sleep(300); |
|
488 do_check_eq(count, 1); |
|
489 |
|
490 yield c.execute("SELECT 1"); |
|
491 yield sleep(300); |
|
492 |
|
493 do_check_eq(count, 2); |
|
494 do_check_eq(shrinkPromises.length, 1); |
|
495 yield shrinkPromises[0]; |
|
496 |
|
497 yield c.close(); |
|
498 }); |
|
499 |
|
500 add_task(function test_idle_shrink_reset_on_operation() { |
|
501 const INTERVAL = 500; |
|
502 let c = yield getDummyDatabase("idle_shrink_reset_on_operation", |
|
503 {shrinkMemoryOnConnectionIdleMS: INTERVAL}); |
|
504 |
|
505 c._clearIdleShrinkTimer(); |
|
506 |
|
507 let oldShrink = c.shrinkMemory; |
|
508 let shrinkPromises = []; |
|
509 let count = 0; |
|
510 |
|
511 Object.defineProperty(c, "shrinkMemory", { |
|
512 value: function () { |
|
513 count++; |
|
514 let promise = oldShrink.call(c); |
|
515 shrinkPromises.push(promise); |
|
516 return promise; |
|
517 }, |
|
518 }); |
|
519 |
|
520 let now = new Date(); |
|
521 c._startIdleShrinkTimer(); |
|
522 |
|
523 let initialIdle = new Date(now.getTime() + INTERVAL); |
|
524 |
|
525 // Perform database operations until initial scheduled time has been passed. |
|
526 let i = 0; |
|
527 while (new Date() < initialIdle) { |
|
528 yield c.execute("INSERT INTO dirs (path) VALUES (?)", ["" + i]); |
|
529 i++; |
|
530 } |
|
531 |
|
532 do_check_true(i > 0); |
|
533 |
|
534 // We should not have performed an idle while doing operations. |
|
535 do_check_eq(count, 0); |
|
536 |
|
537 // Wait for idle timer. |
|
538 yield sleep(INTERVAL); |
|
539 |
|
540 // Ensure we fired. |
|
541 do_check_eq(count, 1); |
|
542 do_check_eq(shrinkPromises.length, 1); |
|
543 yield shrinkPromises[0]; |
|
544 |
|
545 yield c.close(); |
|
546 }); |
|
547 |
|
548 add_task(function test_in_progress_counts() { |
|
549 let c = yield getDummyDatabase("in_progress_counts"); |
|
550 do_check_eq(c._statementCounter, c._initialStatementCount); |
|
551 do_check_eq(c._pendingStatements.size, 0); |
|
552 yield c.executeCached("INSERT INTO dirs (path) VALUES ('foo')"); |
|
553 do_check_eq(c._statementCounter, c._initialStatementCount + 1); |
|
554 do_check_eq(c._pendingStatements.size, 0); |
|
555 |
|
556 let expectOne; |
|
557 let expectTwo; |
|
558 |
|
559 // Please forgive me. |
|
560 let inner = Async.makeSpinningCallback(); |
|
561 let outer = Async.makeSpinningCallback(); |
|
562 |
|
563 // We want to make sure that two queries executing simultaneously |
|
564 // result in `_pendingStatements.size` reaching 2, then dropping back to 0. |
|
565 // |
|
566 // To do so, we kick off a second statement within the row handler |
|
567 // of the first, then wait for both to finish. |
|
568 |
|
569 yield c.executeCached("SELECT * from dirs", null, function onRow() { |
|
570 // In the onRow handler, we're still an outstanding query. |
|
571 // Expect a single in-progress entry. |
|
572 expectOne = c._pendingStatements.size; |
|
573 |
|
574 // Start another query, checking that after its statement has been created |
|
575 // there are two statements in progress. |
|
576 let p = c.executeCached("SELECT 10, path from dirs"); |
|
577 expectTwo = c._pendingStatements.size; |
|
578 |
|
579 // Now wait for it to be done before we return from the row handler … |
|
580 p.then(function onInner() { |
|
581 inner(); |
|
582 }); |
|
583 }).then(function onOuter() { |
|
584 // … and wait for the inner to be done before we finish … |
|
585 inner.wait(); |
|
586 outer(); |
|
587 }); |
|
588 |
|
589 // … and wait for both queries to have finished before we go on and |
|
590 // test postconditions. |
|
591 outer.wait(); |
|
592 |
|
593 do_check_eq(expectOne, 1); |
|
594 do_check_eq(expectTwo, 2); |
|
595 do_check_eq(c._statementCounter, c._initialStatementCount + 3); |
|
596 do_check_eq(c._pendingStatements.size, 0); |
|
597 |
|
598 yield c.close(); |
|
599 }); |
|
600 |
|
601 add_task(function test_discard_while_active() { |
|
602 let c = yield getDummyDatabase("discard_while_active"); |
|
603 |
|
604 yield c.executeCached("INSERT INTO dirs (path) VALUES ('foo')"); |
|
605 yield c.executeCached("INSERT INTO dirs (path) VALUES ('bar')"); |
|
606 |
|
607 let discarded = -1; |
|
608 let first = true; |
|
609 let sql = "SELECT * FROM dirs"; |
|
610 yield c.executeCached(sql, null, function onRow(row) { |
|
611 if (!first) { |
|
612 return; |
|
613 } |
|
614 first = false; |
|
615 discarded = c.discardCachedStatements(); |
|
616 }); |
|
617 |
|
618 // We discarded everything, because the SELECT had already started to run. |
|
619 do_check_eq(3, discarded); |
|
620 |
|
621 // And again is safe. |
|
622 do_check_eq(0, c.discardCachedStatements()); |
|
623 |
|
624 yield c.close(); |
|
625 }); |
|
626 |
|
627 add_task(function test_discard_cached() { |
|
628 let c = yield getDummyDatabase("discard_cached"); |
|
629 |
|
630 yield c.executeCached("SELECT * from dirs"); |
|
631 do_check_eq(1, c._cachedStatements.size); |
|
632 |
|
633 yield c.executeCached("SELECT * from files"); |
|
634 do_check_eq(2, c._cachedStatements.size); |
|
635 |
|
636 yield c.executeCached("SELECT * from dirs"); |
|
637 do_check_eq(2, c._cachedStatements.size); |
|
638 |
|
639 c.discardCachedStatements(); |
|
640 do_check_eq(0, c._cachedStatements.size); |
|
641 |
|
642 yield c.close(); |
|
643 }); |
|
644 |
|
645 add_task(function test_programmatic_binding() { |
|
646 let c = yield getDummyDatabase("programmatic_binding"); |
|
647 |
|
648 let bindings = [ |
|
649 {id: 1, path: "foobar"}, |
|
650 {id: null, path: "baznoo"}, |
|
651 {id: 5, path: "toofoo"}, |
|
652 ]; |
|
653 |
|
654 let sql = "INSERT INTO dirs VALUES (:id, :path)"; |
|
655 let result = yield c.execute(sql, bindings); |
|
656 do_check_eq(result.length, 0); |
|
657 |
|
658 let rows = yield c.executeCached("SELECT * from dirs"); |
|
659 do_check_eq(rows.length, 3); |
|
660 yield c.close(); |
|
661 }); |
|
662 |
|
663 add_task(function test_programmatic_binding_transaction() { |
|
664 let c = yield getDummyDatabase("programmatic_binding_transaction"); |
|
665 |
|
666 let bindings = [ |
|
667 {id: 1, path: "foobar"}, |
|
668 {id: null, path: "baznoo"}, |
|
669 {id: 5, path: "toofoo"}, |
|
670 ]; |
|
671 |
|
672 let sql = "INSERT INTO dirs VALUES (:id, :path)"; |
|
673 yield c.executeTransaction(function transaction() { |
|
674 let result = yield c.execute(sql, bindings); |
|
675 do_check_eq(result.length, 0); |
|
676 |
|
677 let rows = yield c.executeCached("SELECT * from dirs"); |
|
678 do_check_eq(rows.length, 3); |
|
679 }); |
|
680 |
|
681 // Transaction committed. |
|
682 let rows = yield c.executeCached("SELECT * from dirs"); |
|
683 do_check_eq(rows.length, 3); |
|
684 yield c.close(); |
|
685 }); |
|
686 |
|
687 add_task(function test_programmatic_binding_transaction_partial_rollback() { |
|
688 let c = yield getDummyDatabase("programmatic_binding_transaction_partial_rollback"); |
|
689 |
|
690 let bindings = [ |
|
691 {id: 2, path: "foobar"}, |
|
692 {id: 3, path: "toofoo"}, |
|
693 ]; |
|
694 |
|
695 let sql = "INSERT INTO dirs VALUES (:id, :path)"; |
|
696 |
|
697 // Add some data in an implicit transaction before beginning the batch insert. |
|
698 yield c.execute(sql, {id: 1, path: "works"}); |
|
699 |
|
700 let secondSucceeded = false; |
|
701 try { |
|
702 yield c.executeTransaction(function transaction() { |
|
703 // Insert one row. This won't implicitly start a transaction. |
|
704 let result = yield c.execute(sql, bindings[0]); |
|
705 |
|
706 // Insert multiple rows. mozStorage will want to start a transaction. |
|
707 // One of the inserts will fail, so the transaction should be rolled back. |
|
708 let result = yield c.execute(sql, bindings); |
|
709 secondSucceeded = true; |
|
710 }); |
|
711 } catch (ex) { |
|
712 print("Caught expected exception: " + ex); |
|
713 } |
|
714 |
|
715 // We did not get to the end of our in-transaction block. |
|
716 do_check_false(secondSucceeded); |
|
717 |
|
718 // Everything that happened in *our* transaction, not mozStorage's, got |
|
719 // rolled back, but the first row still exists. |
|
720 let rows = yield c.executeCached("SELECT * from dirs"); |
|
721 do_check_eq(rows.length, 1); |
|
722 do_check_eq(rows[0].getResultByName("path"), "works"); |
|
723 yield c.close(); |
|
724 }); |
|
725 |
|
726 /** |
|
727 * Just like the previous test, but relying on the implicit |
|
728 * transaction established by mozStorage. |
|
729 */ |
|
730 add_task(function test_programmatic_binding_implicit_transaction() { |
|
731 let c = yield getDummyDatabase("programmatic_binding_implicit_transaction"); |
|
732 |
|
733 let bindings = [ |
|
734 {id: 2, path: "foobar"}, |
|
735 {id: 1, path: "toofoo"}, |
|
736 ]; |
|
737 |
|
738 let sql = "INSERT INTO dirs VALUES (:id, :path)"; |
|
739 let secondSucceeded = false; |
|
740 yield c.execute(sql, {id: 1, path: "works"}); |
|
741 try { |
|
742 let result = yield c.execute(sql, bindings); |
|
743 secondSucceeded = true; |
|
744 } catch (ex) { |
|
745 print("Caught expected exception: " + ex); |
|
746 } |
|
747 |
|
748 do_check_false(secondSucceeded); |
|
749 |
|
750 // The entire batch failed. |
|
751 let rows = yield c.executeCached("SELECT * from dirs"); |
|
752 do_check_eq(rows.length, 1); |
|
753 do_check_eq(rows[0].getResultByName("path"), "works"); |
|
754 yield c.close(); |
|
755 }); |
|
756 |
|
757 /** |
|
758 * Test that direct binding of params and execution through mozStorage doesn't |
|
759 * error when we manually create a transaction. See Bug 856925. |
|
760 */ |
|
761 add_task(function test_direct() { |
|
762 let file = FileUtils.getFile("TmpD", ["test_direct.sqlite"]); |
|
763 file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); |
|
764 print("Opening " + file.path); |
|
765 |
|
766 let db = Services.storage.openDatabase(file); |
|
767 print("Opened " + db); |
|
768 |
|
769 db.executeSimpleSQL("CREATE TABLE types (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, UNIQUE (name))"); |
|
770 print("Executed setup."); |
|
771 |
|
772 let statement = db.createAsyncStatement("INSERT INTO types (name) VALUES (:name)"); |
|
773 let params = statement.newBindingParamsArray(); |
|
774 let one = params.newBindingParams(); |
|
775 one.bindByName("name", null); |
|
776 params.addParams(one); |
|
777 let two = params.newBindingParams(); |
|
778 two.bindByName("name", "bar"); |
|
779 params.addParams(two); |
|
780 |
|
781 print("Beginning transaction."); |
|
782 let begin = db.createAsyncStatement("BEGIN DEFERRED TRANSACTION"); |
|
783 let end = db.createAsyncStatement("COMMIT TRANSACTION"); |
|
784 |
|
785 let deferred = Promise.defer(); |
|
786 begin.executeAsync({ |
|
787 handleCompletion: function (reason) { |
|
788 deferred.resolve(); |
|
789 } |
|
790 }); |
|
791 yield deferred.promise; |
|
792 |
|
793 statement.bindParameters(params); |
|
794 |
|
795 deferred = Promise.defer(); |
|
796 print("Executing async."); |
|
797 statement.executeAsync({ |
|
798 handleResult: function (resultSet) { |
|
799 }, |
|
800 |
|
801 handleError: function (error) { |
|
802 print("Error when executing SQL (" + error.result + "): " + |
|
803 error.message); |
|
804 print("Original error: " + error.error); |
|
805 errors.push(error); |
|
806 deferred.reject(); |
|
807 }, |
|
808 |
|
809 handleCompletion: function (reason) { |
|
810 print("Completed."); |
|
811 deferred.resolve(); |
|
812 } |
|
813 }); |
|
814 |
|
815 yield deferred.promise; |
|
816 |
|
817 deferred = Promise.defer(); |
|
818 end.executeAsync({ |
|
819 handleCompletion: function (reason) { |
|
820 deferred.resolve(); |
|
821 } |
|
822 }); |
|
823 yield deferred.promise; |
|
824 |
|
825 statement.finalize(); |
|
826 begin.finalize(); |
|
827 end.finalize(); |
|
828 |
|
829 deferred = Promise.defer(); |
|
830 db.asyncClose(function () { |
|
831 deferred.resolve() |
|
832 }); |
|
833 yield deferred.promise; |
|
834 }); |
|
835 |
|
836 /** |
|
837 * Test Sqlite.cloneStorageConnection. |
|
838 */ |
|
839 add_task(function* test_cloneStorageConnection() { |
|
840 let file = new FileUtils.File(OS.Path.join(OS.Constants.Path.profileDir, |
|
841 "test_cloneStorageConnection.sqlite")); |
|
842 let c = yield new Promise((success, failure) => { |
|
843 Services.storage.openAsyncDatabase(file, null, (status, db) => { |
|
844 if (Components.isSuccessCode(status)) { |
|
845 success(db.QueryInterface(Ci.mozIStorageAsyncConnection)); |
|
846 } else { |
|
847 failure(new Error(status)); |
|
848 } |
|
849 }); |
|
850 }); |
|
851 |
|
852 let clone = yield Sqlite.cloneStorageConnection({ connection: c, readOnly: true }); |
|
853 // Just check that it works. |
|
854 yield clone.execute("SELECT 1"); |
|
855 |
|
856 let clone2 = yield Sqlite.cloneStorageConnection({ connection: c, readOnly: false }); |
|
857 // Just check that it works. |
|
858 yield clone2.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)"); |
|
859 |
|
860 // Closing order should not matter. |
|
861 yield c.asyncClose(); |
|
862 yield clone2.close(); |
|
863 yield clone.close(); |
|
864 }); |
|
865 |
|
866 /** |
|
867 * Test Sqlite.cloneStorageConnection invalid argument. |
|
868 */ |
|
869 add_task(function* test_cloneStorageConnection() { |
|
870 try { |
|
871 let clone = yield Sqlite.cloneStorageConnection({ connection: null }); |
|
872 do_throw(new Error("Should throw on invalid connection")); |
|
873 } catch (ex if ex.name == "TypeError") {} |
|
874 }); |
|
875 |
|
876 /** |
|
877 * Test clone() method. |
|
878 */ |
|
879 add_task(function* test_clone() { |
|
880 let c = yield getDummyDatabase("clone"); |
|
881 |
|
882 let clone = yield c.clone(); |
|
883 // Just check that it works. |
|
884 yield clone.execute("SELECT 1"); |
|
885 // Closing order should not matter. |
|
886 yield c.close(); |
|
887 yield clone.close(); |
|
888 }); |
|
889 |
|
890 /** |
|
891 * Test clone(readOnly) method. |
|
892 */ |
|
893 add_task(function* test_readOnly_clone() { |
|
894 let path = OS.Path.join(OS.Constants.Path.profileDir, "test_readOnly_clone.sqlite"); |
|
895 let c = yield Sqlite.openConnection({path: path, sharedMemoryCache: false}); |
|
896 |
|
897 let clone = yield c.clone(true); |
|
898 // Just check that it works. |
|
899 yield clone.execute("SELECT 1"); |
|
900 // But should not be able to write. |
|
901 try { |
|
902 yield clone.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)"); |
|
903 do_throw(new Error("Should not be able to write to a read-only clone.")); |
|
904 } catch (ex) {} |
|
905 // Closing order should not matter. |
|
906 yield c.close(); |
|
907 yield clone.close(); |
|
908 }); |