toolkit/modules/tests/xpcshell/test_sqlite.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial