test_storage_connection.js (31001B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 // This file tests the functions of mozIStorageConnection 6 7 function fetchAllNames(conn) { 8 let names = []; 9 let stmt = conn.createStatement(`SELECT name FROM test ORDER BY name`); 10 while (stmt.executeStep()) { 11 names.push(stmt.getUTF8String(0)); 12 } 13 stmt.finalize(); 14 return names; 15 } 16 17 // Test Functions 18 19 add_task(async function test_connectionReady_open() { 20 // there doesn't seem to be a way for the connection to not be ready (unless 21 // we close it with mozIStorageConnection::Close(), but we don't for this). 22 // It can only fail if GetPath fails on the database file, or if we run out 23 // of memory trying to use an in-memory database 24 25 var msc = getOpenedDatabase(); 26 Assert.ok(msc.connectionReady); 27 }); 28 29 add_task(async function test_connectionReady_closed() { 30 // This also tests mozIStorageConnection::Close() 31 32 var msc = getOpenedDatabase(); 33 msc.close(); 34 Assert.ok(!msc.connectionReady); 35 gDBConn = null; // this is so later tests don't start to fail. 36 }); 37 38 add_task(async function test_databaseFile() { 39 var msc = getOpenedDatabase(); 40 Assert.ok(getTestDB().equals(msc.databaseFile)); 41 }); 42 43 add_task(async function test_tableExists_not_created() { 44 var msc = getOpenedDatabase(); 45 Assert.ok(!msc.tableExists("foo")); 46 }); 47 48 add_task(async function test_indexExists_not_created() { 49 var msc = getOpenedDatabase(); 50 Assert.ok(!msc.indexExists("foo")); 51 }); 52 53 add_task(async function test_temp_tableExists_and_indexExists() { 54 var msc = getOpenedDatabase(); 55 msc.executeSimpleSQL( 56 "CREATE TEMP TABLE test_temp(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)" 57 ); 58 Assert.ok(msc.tableExists("test_temp")); 59 60 msc.executeSimpleSQL("CREATE INDEX test_temp_ind ON test_temp (name)"); 61 Assert.ok(msc.indexExists("test_temp_ind")); 62 63 msc.executeSimpleSQL("DROP INDEX test_temp_ind"); 64 msc.executeSimpleSQL("DROP TABLE test_temp"); 65 }); 66 67 add_task(async function test_createTable_not_created() { 68 var msc = getOpenedDatabase(); 69 msc.createTable("test", "id INTEGER PRIMARY KEY, name TEXT"); 70 Assert.ok(msc.tableExists("test")); 71 }); 72 73 add_task(async function test_indexExists_created() { 74 var msc = getOpenedDatabase(); 75 msc.executeSimpleSQL("CREATE INDEX name_ind ON test (name)"); 76 Assert.ok(msc.indexExists("name_ind")); 77 }); 78 79 add_task(async function test_createTable_already_created() { 80 var msc = getOpenedDatabase(); 81 Assert.ok(msc.tableExists("test")); 82 Assert.throws( 83 () => msc.createTable("test", "id INTEGER PRIMARY KEY, name TEXT"), 84 /NS_ERROR_FAILURE/ 85 ); 86 }); 87 88 add_task(async function test_attach_createTable_tableExists_indexExists() { 89 var msc = getOpenedDatabase(); 90 var file = do_get_file("storage_attach.sqlite", true); 91 var msc2 = getDatabase(file); 92 msc.executeSimpleSQL("ATTACH DATABASE '" + file.path + "' AS sample"); 93 94 Assert.ok(!msc.tableExists("sample.test")); 95 msc.createTable("sample.test", "id INTEGER PRIMARY KEY, name TEXT"); 96 Assert.ok(msc.tableExists("sample.test")); 97 Assert.throws( 98 () => msc.createTable("sample.test", "id INTEGER PRIMARY KEY, name TEXT"), 99 /NS_ERROR_FAILURE/ 100 ); 101 102 Assert.ok(!msc.indexExists("sample.test_ind")); 103 msc.executeSimpleSQL("CREATE INDEX sample.test_ind ON test (name)"); 104 Assert.ok(msc.indexExists("sample.test_ind")); 105 106 msc.executeSimpleSQL("DETACH DATABASE sample"); 107 msc2.close(); 108 try { 109 file.remove(false); 110 } catch (e) { 111 // Do nothing. 112 } 113 }); 114 115 add_task(async function test_lastInsertRowID() { 116 var msc = getOpenedDatabase(); 117 msc.executeSimpleSQL("INSERT INTO test (name) VALUES ('foo')"); 118 Assert.equal(1, msc.lastInsertRowID); 119 }); 120 121 add_task(async function test_transactionInProgress_no() { 122 var msc = getOpenedDatabase(); 123 Assert.ok(!msc.transactionInProgress); 124 }); 125 126 add_task(async function test_transactionInProgress_yes() { 127 var msc = getOpenedDatabase(); 128 msc.beginTransaction(); 129 Assert.ok(msc.transactionInProgress); 130 msc.commitTransaction(); 131 Assert.ok(!msc.transactionInProgress); 132 133 msc.beginTransaction(); 134 Assert.ok(msc.transactionInProgress); 135 msc.rollbackTransaction(); 136 Assert.ok(!msc.transactionInProgress); 137 }); 138 139 add_task(async function test_commitTransaction_no_transaction() { 140 var msc = getOpenedDatabase(); 141 Assert.ok(!msc.transactionInProgress); 142 Assert.throws(() => msc.commitTransaction(), /NS_ERROR_UNEXPECTED/); 143 }); 144 145 add_task(async function test_rollbackTransaction_no_transaction() { 146 var msc = getOpenedDatabase(); 147 Assert.ok(!msc.transactionInProgress); 148 Assert.throws(() => msc.rollbackTransaction(), /NS_ERROR_UNEXPECTED/); 149 }); 150 151 add_task(async function test_get_schemaVersion_not_set() { 152 Assert.equal(0, getOpenedDatabase().schemaVersion); 153 }); 154 155 add_task(async function test_set_schemaVersion() { 156 var msc = getOpenedDatabase(); 157 const version = 1; 158 msc.schemaVersion = version; 159 Assert.equal(version, msc.schemaVersion); 160 }); 161 162 add_task(async function test_set_schemaVersion_same() { 163 var msc = getOpenedDatabase(); 164 const version = 1; 165 msc.schemaVersion = version; // should still work ok 166 Assert.equal(version, msc.schemaVersion); 167 }); 168 169 add_task(async function test_set_schemaVersion_negative() { 170 var msc = getOpenedDatabase(); 171 const version = -1; 172 msc.schemaVersion = version; 173 Assert.equal(version, msc.schemaVersion); 174 }); 175 176 add_task(async function test_createTable() { 177 var temp = getTestDB().parent; 178 temp.append("test_db_table"); 179 try { 180 var con = Services.storage.openDatabase(temp); 181 con.createTable("a", ""); 182 } catch (e) { 183 if (temp.exists()) { 184 try { 185 temp.remove(false); 186 } catch (e2) { 187 // Do nothing. 188 } 189 } 190 Assert.ok( 191 e.result == Cr.NS_ERROR_NOT_INITIALIZED || e.result == Cr.NS_ERROR_FAILURE 192 ); 193 } finally { 194 if (con) { 195 con.close(); 196 } 197 } 198 }); 199 200 add_task(async function test_defaultSynchronousAtNormal() { 201 getOpenedDatabase(); 202 var stmt = createStatement("PRAGMA synchronous;"); 203 try { 204 stmt.executeStep(); 205 Assert.equal(1, stmt.getInt32(0)); 206 } finally { 207 stmt.reset(); 208 stmt.finalize(); 209 } 210 }); 211 212 // must be ran before executeAsync tests 213 add_task(async function test_close_does_not_spin_event_loop() { 214 // We want to make sure that the event loop on the calling thread does not 215 // spin when close is called. 216 let event = { 217 ran: false, 218 run() { 219 this.ran = true; 220 }, 221 }; 222 223 // Post the event before we call close, so it would run if the event loop was 224 // spun during close. 225 Services.tm.dispatchToMainThread(event); 226 227 // Sanity check, then close the database. Afterwards, we should not have ran! 228 Assert.ok(!event.ran); 229 getOpenedDatabase().close(); 230 Assert.ok(!event.ran); 231 232 // Reset gDBConn so that later tests will get a new connection object. 233 gDBConn = null; 234 }); 235 236 add_task( 237 async function test_asyncClose_succeeds_with_finalized_async_statement() { 238 // XXX this test isn't perfect since we can't totally control when events will 239 // run. If this paticular function fails randomly, it means we have a 240 // real bug. 241 242 // We want to make sure we create a cached async statement to make sure that 243 // when we finalize our statement, we end up finalizing the async one too so 244 // close will succeed. 245 let stmt = createStatement("SELECT * FROM test"); 246 stmt.executeAsync(); 247 stmt.finalize(); 248 249 await asyncClose(getOpenedDatabase()); 250 // Reset gDBConn so that later tests will get a new connection object. 251 gDBConn = null; 252 } 253 ); 254 255 // Would assert on debug builds. 256 if (!AppConstants.DEBUG) { 257 add_task(async function test_close_then_release_statement() { 258 // Testing the behavior in presence of a bad client that finalizes 259 // statements after the database has been closed (typically by 260 // letting the gc finalize the statement). 261 let db = getOpenedDatabase(); 262 let stmt = createStatement( 263 "SELECT * FROM test -- test_close_then_release_statement" 264 ); 265 db.close(); 266 stmt.finalize(); // Finalize too late - this should not crash 267 268 // Reset gDBConn so that later tests will get a new connection object. 269 gDBConn = null; 270 }); 271 272 add_task(async function test_asyncClose_then_release_statement() { 273 // Testing the behavior in presence of a bad client that finalizes 274 // statements after the database has been async closed (typically by 275 // letting the gc finalize the statement). 276 let db = getOpenedDatabase(); 277 let stmt = createStatement( 278 "SELECT * FROM test -- test_asyncClose_then_release_statement" 279 ); 280 await asyncClose(db); 281 stmt.finalize(); // Finalize too late - this should not crash 282 283 // Reset gDBConn so that later tests will get a new connection object. 284 gDBConn = null; 285 }); 286 } 287 288 // In debug builds this would cause a fatal assertion. 289 if (!AppConstants.DEBUG) { 290 add_task(async function test_close_fails_with_async_statement_ran() { 291 let stmt = createStatement("SELECT * FROM test"); 292 stmt.executeAsync(); 293 stmt.finalize(); 294 295 let db = getOpenedDatabase(); 296 Assert.throws(() => db.close(), /NS_ERROR_UNEXPECTED/); 297 // Reset gDBConn so that later tests will get a new connection object. 298 gDBConn = null; 299 }); 300 } 301 302 add_task(async function test_clone_optional_param() { 303 let db1 = Services.storage.openUnsharedDatabase(getTestDB()); 304 let db2 = db1.clone(); 305 Assert.ok(db2.connectionReady); 306 307 // A write statement should not fail here. 308 let stmt = db2.createStatement("INSERT INTO test (name) VALUES (:name)"); 309 stmt.params.name = "dwitte"; 310 stmt.execute(); 311 stmt.finalize(); 312 313 // And a read statement should succeed. 314 stmt = db2.createStatement("SELECT * FROM test"); 315 Assert.ok(stmt.executeStep()); 316 stmt.finalize(); 317 318 // Additionally check that it is a connection on the same database. 319 Assert.ok(db1.databaseFile.equals(db2.databaseFile)); 320 321 db1.close(); 322 db2.close(); 323 }); 324 325 async function standardAsyncTest(promisedDB, name, shouldInit = false) { 326 info("Performing standard async test " + name); 327 328 let adb = await promisedDB; 329 Assert.ok(adb instanceof Ci.mozIStorageAsyncConnection); 330 Assert.ok(adb instanceof Ci.mozIStorageConnection); 331 332 if (shouldInit) { 333 let stmt = adb.createAsyncStatement("CREATE TABLE test(name TEXT)"); 334 await executeAsync(stmt); 335 stmt.finalize(); 336 } 337 338 // Generate a name to insert and fetch back 339 name = "worker bee " + Math.random() + " (" + name + ")"; 340 341 let stmt = adb.createAsyncStatement("INSERT INTO test (name) VALUES (:name)"); 342 stmt.params.name = name; 343 let result = await executeAsync(stmt); 344 info("Request complete"); 345 stmt.finalize(); 346 Assert.ok(Components.isSuccessCode(result)); 347 info("Extracting data"); 348 stmt = adb.createAsyncStatement("SELECT * FROM test"); 349 let found = false; 350 await executeAsync(stmt, function (results) { 351 info("Data has been extracted"); 352 for ( 353 let row = results.getNextRow(); 354 row != null; 355 row = results.getNextRow() 356 ) { 357 if (row.getResultByName("name") == name) { 358 found = true; 359 break; 360 } 361 } 362 }); 363 Assert.ok(found); 364 stmt.finalize(); 365 await asyncClose(adb); 366 367 info("Standard async test " + name + " complete"); 368 } 369 370 add_task(async function test_open_async() { 371 await standardAsyncTest(openAsyncDatabase(getTestDB(), null), "default"); 372 await standardAsyncTest(openAsyncDatabase(getTestDB()), "no optional arg"); 373 await standardAsyncTest( 374 openAsyncDatabase(getTestDB(), { shared: false, interruptible: true }), 375 "non-default options" 376 ); 377 await standardAsyncTest( 378 openAsyncDatabase("memory"), 379 "in-memory database", 380 true 381 ); 382 await standardAsyncTest( 383 openAsyncDatabase("memory", { shared: false }), 384 "in-memory database and options", 385 true 386 ); 387 388 info("Testing async opening with readonly option"); 389 const impliedReadOnlyOption = { ignoreLockingMode: true }; 390 391 let raised = false; 392 let adb = await openAsyncDatabase(getTestDB(), impliedReadOnlyOption); 393 let stmt = adb.createAsyncStatement("CREATE TABLE test(name TEXT)"); 394 try { 395 await executeAsync(stmt); // This should throw 396 } catch (e) { 397 raised = true; 398 } finally { 399 if (stmt) { 400 stmt.finalize(); 401 } 402 if (adb) { 403 await asyncClose(adb); 404 } 405 } 406 407 Assert.ok(raised); 408 }); 409 410 add_task(async function test_async_open_with_shared_cache() { 411 info("Testing that opening with a shared cache doesn't break stuff"); 412 let adb = await openAsyncDatabase(getTestDB(), { shared: true }); 413 414 let stmt = adb.createAsyncStatement("INSERT INTO test (name) VALUES (:name)"); 415 stmt.params.name = "clockworker"; 416 let result = await executeAsync(stmt); 417 info("Request complete"); 418 stmt.finalize(); 419 Assert.ok(Components.isSuccessCode(result)); 420 info("Extracting data"); 421 stmt = adb.createAsyncStatement("SELECT * FROM test"); 422 let found = false; 423 await executeAsync(stmt, function (results) { 424 info("Data has been extracted"); 425 for ( 426 let row = results.getNextRow(); 427 row != null; 428 row = results.getNextRow() 429 ) { 430 if (row.getResultByName("name") == "clockworker") { 431 found = true; 432 break; 433 } 434 } 435 }); 436 Assert.ok(found); 437 stmt.finalize(); 438 await asyncClose(adb); 439 }); 440 441 add_task(async function test_clone_trivial_async() { 442 info("Open connection"); 443 let db = Services.storage.openDatabase(getTestDB()); 444 Assert.ok(db instanceof Ci.mozIStorageAsyncConnection); 445 info("AsyncClone connection"); 446 let clone = await asyncClone(db, true); 447 Assert.ok(clone instanceof Ci.mozIStorageAsyncConnection); 448 Assert.ok(clone instanceof Ci.mozIStorageConnection); 449 info("Close connection"); 450 await asyncClose(db); 451 info("Close clone"); 452 await asyncClose(clone); 453 }); 454 455 add_task(async function test_clone_no_optional_param_async() { 456 "use strict"; 457 info("Testing async cloning"); 458 let adb1 = await openAsyncDatabase(getTestDB(), null); 459 Assert.ok(adb1 instanceof Ci.mozIStorageAsyncConnection); 460 Assert.ok(adb1 instanceof Ci.mozIStorageConnection); 461 462 info("Cloning database"); 463 464 let adb2 = await asyncClone(adb1); 465 info( 466 "Testing that the cloned db is a mozIStorageAsyncConnection " + 467 "and not a mozIStorageConnection" 468 ); 469 Assert.ok(adb2 instanceof Ci.mozIStorageAsyncConnection); 470 Assert.ok(adb2 instanceof Ci.mozIStorageConnection); 471 472 info("Inserting data into source db"); 473 let stmt = adb1.createAsyncStatement( 474 "INSERT INTO test (name) VALUES (:name)" 475 ); 476 477 stmt.params.name = "yoric"; 478 let result = await executeAsync(stmt); 479 info("Request complete"); 480 stmt.finalize(); 481 Assert.ok(Components.isSuccessCode(result)); 482 info("Extracting data from clone db"); 483 stmt = adb2.createAsyncStatement("SELECT * FROM test"); 484 let found = false; 485 await executeAsync(stmt, function (results) { 486 info("Data has been extracted"); 487 for ( 488 let row = results.getNextRow(); 489 row != null; 490 row = results.getNextRow() 491 ) { 492 if (row.getResultByName("name") == "yoric") { 493 found = true; 494 break; 495 } 496 } 497 }); 498 Assert.ok(found); 499 stmt.finalize(); 500 info("Closing databases"); 501 await asyncClose(adb2); 502 info("First db closed"); 503 504 await asyncClose(adb1); 505 info("Second db closed"); 506 }); 507 508 add_task(async function test_clone_readonly() { 509 let db1 = Services.storage.openUnsharedDatabase(getTestDB()); 510 let db2 = db1.clone(true); 511 Assert.ok(db2.connectionReady); 512 513 // A write statement should fail here. 514 let stmt = db2.createStatement("INSERT INTO test (name) VALUES (:name)"); 515 stmt.params.name = "reed"; 516 expectError(Cr.NS_ERROR_FILE_READ_ONLY, () => stmt.execute()); 517 stmt.finalize(); 518 519 // And a read statement should succeed. 520 stmt = db2.createStatement("SELECT * FROM test"); 521 Assert.ok(stmt.executeStep()); 522 stmt.finalize(); 523 524 db1.close(); 525 db2.close(); 526 }); 527 528 add_task(async function test_clone_shared_readonly() { 529 let db1 = Services.storage.openDatabase(getTestDB()); 530 let db2 = db1.clone(true); 531 Assert.ok(db2.connectionReady); 532 533 let stmt = db2.createStatement("INSERT INTO test (name) VALUES (:name)"); 534 stmt.params.name = "parker"; 535 // TODO currently SQLite does not actually work correctly here. The behavior 536 // we want is commented out, and the current behavior is being tested 537 // for. Our IDL comments will have to be updated when this starts to 538 // work again. 539 stmt.execute(); 540 // expectError(Components.results.NS_ERROR_FILE_READ_ONLY, () => stmt.execute()); 541 stmt.finalize(); 542 543 // And a read statement should succeed. 544 stmt = db2.createStatement("SELECT * FROM test"); 545 Assert.ok(stmt.executeStep()); 546 stmt.finalize(); 547 548 db1.close(); 549 db2.close(); 550 }); 551 552 add_task(async function test_close_clone_fails() { 553 let calls = ["openDatabase", "openUnsharedDatabase"]; 554 calls.forEach(function (methodName) { 555 let db = Services.storage[methodName](getTestDB()); 556 db.close(); 557 expectError(Cr.NS_ERROR_NOT_INITIALIZED, () => db.clone()); 558 }); 559 }); 560 561 add_task(async function test_clone_copies_functions() { 562 const FUNC_NAME = "test_func"; 563 let calls = ["openDatabase", "openUnsharedDatabase"]; 564 let functionMethods = ["createFunction"]; 565 calls.forEach(function (methodName) { 566 [true, false].forEach(function (readOnly) { 567 functionMethods.forEach(function (functionMethod) { 568 let db1 = Services.storage[methodName](getTestDB()); 569 // Create a function for db1. 570 db1[functionMethod](FUNC_NAME, 1, { 571 onFunctionCall: () => 0, 572 onStep: () => 0, 573 onFinal: () => 0, 574 }); 575 576 // Clone it, and make sure the function exists still. 577 let db2 = db1.clone(readOnly); 578 // Note: this would fail if the function did not exist. 579 let stmt = db2.createStatement( 580 "SELECT " + FUNC_NAME + "(id) FROM test" 581 ); 582 stmt.finalize(); 583 db1.close(); 584 db2.close(); 585 }); 586 }); 587 }); 588 }); 589 590 add_task(async function test_clone_copies_overridden_functions() { 591 const FUNC_NAME = "lower"; 592 function test_func() { 593 this.called = false; 594 } 595 test_func.prototype = { 596 onFunctionCall() { 597 this.called = true; 598 }, 599 onStep() { 600 this.called = true; 601 }, 602 onFinal: () => 0, 603 }; 604 605 let calls = ["openDatabase", "openUnsharedDatabase"]; 606 let functionMethods = ["createFunction"]; 607 calls.forEach(function (methodName) { 608 [true, false].forEach(function (readOnly) { 609 functionMethods.forEach(function (functionMethod) { 610 let db1 = Services.storage[methodName](getTestDB()); 611 // Create a function for db1. 612 let func = new test_func(); 613 db1[functionMethod](FUNC_NAME, 1, func); 614 Assert.ok(!func.called); 615 616 // Clone it, and make sure the function gets called. 617 let db2 = db1.clone(readOnly); 618 let stmt = db2.createStatement( 619 "SELECT " + FUNC_NAME + "(id) FROM test" 620 ); 621 stmt.executeStep(); 622 Assert.ok(func.called); 623 stmt.finalize(); 624 db1.close(); 625 db2.close(); 626 }); 627 }); 628 }); 629 }); 630 631 add_task(async function test_clone_copies_pragmas() { 632 const PRAGMAS = [ 633 { name: "cache_size", value: 500, copied: true }, 634 { name: "temp_store", value: 2, copied: true }, 635 { name: "foreign_keys", value: 1, copied: true }, 636 { name: "journal_size_limit", value: 524288, copied: true }, 637 { name: "synchronous", value: 2, copied: true }, 638 { name: "wal_autocheckpoint", value: 16, copied: true }, 639 { name: "busy_timeout", value: 50, copied: true }, 640 { name: "ignore_check_constraints", value: 1, copied: false }, 641 ]; 642 643 let db1 = Services.storage.openUnsharedDatabase(getTestDB()); 644 645 // Sanity check initial values are different from enforced ones. 646 PRAGMAS.forEach(function (pragma) { 647 let stmt = db1.createStatement("PRAGMA " + pragma.name); 648 Assert.ok(stmt.executeStep()); 649 Assert.notEqual(pragma.value, stmt.getInt32(0)); 650 stmt.finalize(); 651 }); 652 // Execute pragmas. 653 PRAGMAS.forEach(function (pragma) { 654 db1.executeSimpleSQL("PRAGMA " + pragma.name + " = " + pragma.value); 655 }); 656 657 let db2 = db1.clone(); 658 Assert.ok(db2.connectionReady); 659 660 // Check cloned connection inherited pragma values. 661 PRAGMAS.forEach(function (pragma) { 662 let stmt = db2.createStatement("PRAGMA " + pragma.name); 663 Assert.ok(stmt.executeStep()); 664 let validate = pragma.copied ? "equal" : "notEqual"; 665 Assert[validate](pragma.value, stmt.getInt32(0)); 666 stmt.finalize(); 667 }); 668 669 db1.close(); 670 db2.close(); 671 }); 672 673 add_task(async function test_readonly_clone_copies_pragmas() { 674 const PRAGMAS = [ 675 { name: "cache_size", value: 500, copied: true }, 676 { name: "temp_store", value: 2, copied: true }, 677 { name: "foreign_keys", value: 1, copied: false }, 678 { name: "journal_size_limit", value: 524288, copied: false }, 679 { name: "synchronous", value: 2, copied: false }, 680 { name: "wal_autocheckpoint", value: 16, copied: false }, 681 { name: "busy_timeout", value: 50, copied: false }, 682 { name: "ignore_check_constraints", value: 1, copied: false }, 683 ]; 684 685 let db1 = Services.storage.openUnsharedDatabase(getTestDB()); 686 687 // Sanity check initial values are different from enforced ones. 688 PRAGMAS.forEach(function (pragma) { 689 let stmt = db1.createStatement("PRAGMA " + pragma.name); 690 Assert.ok(stmt.executeStep()); 691 Assert.notEqual(pragma.value, stmt.getInt32(0)); 692 stmt.finalize(); 693 }); 694 // Execute pragmas. 695 PRAGMAS.forEach(function (pragma) { 696 db1.executeSimpleSQL("PRAGMA " + pragma.name + " = " + pragma.value); 697 }); 698 699 let db2 = db1.clone(true); 700 Assert.ok(db2.connectionReady); 701 702 // Check cloned connection inherited pragma values. 703 PRAGMAS.forEach(function (pragma) { 704 let stmt = db2.createStatement("PRAGMA " + pragma.name); 705 Assert.ok(stmt.executeStep()); 706 let validate = pragma.copied ? "equal" : "notEqual"; 707 Assert[validate](pragma.value, stmt.getInt32(0)); 708 stmt.finalize(); 709 }); 710 711 db1.close(); 712 db2.close(); 713 }); 714 715 add_task(async function test_clone_attach_database() { 716 let db1 = Services.storage.openUnsharedDatabase(getTestDB()); 717 718 let c = 0; 719 function attachDB(conn, name) { 720 let file = Services.dirsvc.get("ProfD", Ci.nsIFile); 721 file.append("test_storage_" + ++c + ".sqlite"); 722 let db = Services.storage.openUnsharedDatabase(file); 723 conn.executeSimpleSQL( 724 `ATTACH DATABASE '${db.databaseFile.path}' AS ${name}` 725 ); 726 db.executeSimpleSQL(`CREATE TABLE test_${name}(name TEXT);`); 727 db.close(); 728 } 729 attachDB(db1, "attached_1"); 730 attachDB(db1, "attached_2"); 731 db1.executeSimpleSQL(` 732 CREATE TEMP TRIGGER test_temp_afterinsert_trigger 733 AFTER DELETE ON test_attached_1 FOR EACH ROW 734 BEGIN 735 INSERT INTO test(name) VALUES(OLD.name); 736 END`); 737 738 // These should not throw. 739 let stmt = db1.createStatement("SELECT * FROM attached_1.sqlite_master"); 740 stmt.finalize(); 741 stmt = db1.createStatement("SELECT * FROM attached_2.sqlite_master"); 742 stmt.finalize(); 743 db1.executeSimpleSQL("INSERT INTO test_attached_1(name) VALUES('asuth')"); 744 db1.executeSimpleSQL("DELETE FROM test_attached_1"); 745 Assert.ok(fetchAllNames(db1).includes("asuth")); 746 747 // R/W clone. 748 let db2 = db1.clone(); 749 Assert.ok(db2.connectionReady); 750 751 // These should not throw. 752 stmt = db2.createStatement("SELECT * FROM attached_1.sqlite_master"); 753 stmt.finalize(); 754 stmt = db2.createStatement("SELECT * FROM attached_2.sqlite_master"); 755 stmt.finalize(); 756 db2.executeSimpleSQL("INSERT INTO test_attached_1(name) VALUES('past')"); 757 db2.executeSimpleSQL("DELETE FROM test_attached_1"); 758 let newNames = fetchAllNames(db2); 759 Assert.ok(newNames.includes("past")); 760 Assert.deepEqual(fetchAllNames(db1), newNames); 761 762 // R/O clone. 763 let db3 = db1.clone(true); 764 Assert.ok(db3.connectionReady); 765 766 // These should not throw. 767 stmt = db3.createStatement("SELECT * FROM attached_1.sqlite_master"); 768 stmt.finalize(); 769 stmt = db3.createStatement("SELECT * FROM attached_2.sqlite_master"); 770 stmt.finalize(); 771 772 db1.close(); 773 db2.close(); 774 db3.close(); 775 }); 776 777 add_task(async function test_async_clone_with_temp_trigger_and_table() { 778 info("Open connection"); 779 let db = Services.storage.openDatabase(getTestDB()); 780 Assert.ok(db instanceof Ci.mozIStorageAsyncConnection); 781 782 info("Set up tables on original connection"); 783 let createQueries = [ 784 `CREATE TEMP TABLE test_temp(name TEXT)`, 785 `CREATE INDEX test_temp_idx ON test_temp(name)`, 786 `CREATE TEMP TRIGGER test_temp_afterdelete_trigger 787 AFTER DELETE ON test_temp FOR EACH ROW 788 BEGIN 789 INSERT INTO test(name) VALUES(OLD.name); 790 END`, 791 ]; 792 for (let query of createQueries) { 793 let stmt = db.createAsyncStatement(query); 794 await executeAsync(stmt); 795 stmt.finalize(); 796 } 797 798 info("Create read-write clone with temp tables"); 799 let readWriteClone = await asyncClone(db, false); 800 Assert.ok(readWriteClone instanceof Ci.mozIStorageAsyncConnection); 801 802 info("Insert into temp table on read-write clone"); 803 let insertStmt = readWriteClone.createAsyncStatement(` 804 INSERT INTO test_temp(name) VALUES('mak'), ('standard8'), ('markh')`); 805 await executeAsync(insertStmt); 806 insertStmt.finalize(); 807 808 info("Fire temp trigger on read-write clone"); 809 let deleteStmt = readWriteClone.createAsyncStatement(` 810 DELETE FROM test_temp`); 811 await executeAsync(deleteStmt); 812 deleteStmt.finalize(); 813 814 info("Read from original connection"); 815 let names = fetchAllNames(db); 816 Assert.ok(names.includes("mak")); 817 Assert.ok(names.includes("standard8")); 818 Assert.ok(names.includes("markh")); 819 820 info("Create read-only clone"); 821 let readOnlyClone = await asyncClone(db, true); 822 Assert.ok(readOnlyClone instanceof Ci.mozIStorageAsyncConnection); 823 824 info("Read-only clone shouldn't have temp entities"); 825 let badStmt = readOnlyClone.createAsyncStatement(`SELECT 1 FROM test_temp`); 826 await Assert.rejects(executeAsync(badStmt), Ci.mozIStorageError); 827 badStmt.finalize(); 828 829 info("Clean up"); 830 for (let conn of [db, readWriteClone, readOnlyClone]) { 831 await asyncClose(conn); 832 } 833 }); 834 835 add_task(async function test_sync_clone_in_transaction() { 836 info("Open connection"); 837 let db = Services.storage.openDatabase(getTestDB()); 838 Assert.ok(db instanceof Ci.mozIStorageAsyncConnection); 839 840 info("Begin transaction on main connection"); 841 db.beginTransaction(); 842 843 info("Create temp table and trigger in transaction"); 844 let createQueries = [ 845 `CREATE TEMP TABLE test_temp(name TEXT)`, 846 `CREATE TEMP TRIGGER test_temp_afterdelete_trigger 847 AFTER DELETE ON test_temp FOR EACH ROW 848 BEGIN 849 INSERT INTO test(name) VALUES(OLD.name); 850 END`, 851 ]; 852 for (let query of createQueries) { 853 db.executeSimpleSQL(query); 854 } 855 856 info("Clone main connection while transaction is in progress"); 857 let clone = db.clone(/* aReadOnly */ false); 858 859 // Dropping the table also drops `test_temp_afterdelete_trigger`. 860 info("Drop temp table on main connection"); 861 db.executeSimpleSQL(`DROP TABLE test_temp`); 862 863 info("Commit transaction"); 864 db.commitTransaction(); 865 866 info("Clone connection should still have temp entities"); 867 let readTempStmt = clone.createStatement(`SELECT 1 FROM test_temp`); 868 readTempStmt.execute(); 869 readTempStmt.finalize(); 870 871 info("Clean up"); 872 873 db.close(); 874 clone.close(); 875 }); 876 877 add_task(async function test_sync_clone_with_function() { 878 info("Open connection"); 879 let db = Services.storage.openDatabase(getTestDB()); 880 Assert.ok(db instanceof Ci.mozIStorageAsyncConnection); 881 882 info("Create SQL function"); 883 function storeLastInsertedNameFunc() { 884 this.name = null; 885 } 886 storeLastInsertedNameFunc.prototype = { 887 onFunctionCall(args) { 888 this.name = args.getUTF8String(0); 889 }, 890 }; 891 let func = new storeLastInsertedNameFunc(); 892 db.createFunction("store_last_inserted_name", 1, func); 893 894 info("Create temp trigger on main connection"); 895 db.executeSimpleSQL(` 896 CREATE TEMP TRIGGER test_afterinsert_trigger 897 AFTER INSERT ON test FOR EACH ROW 898 BEGIN 899 SELECT store_last_inserted_name(NEW.name); 900 END`); 901 902 info("Clone main connection"); 903 let clone = db.clone(/* aReadOnly */ false); 904 905 info("Write to clone"); 906 clone.executeSimpleSQL(`INSERT INTO test(name) VALUES('kit')`); 907 908 Assert.equal(func.name, "kit"); 909 910 info("Clean up"); 911 db.close(); 912 clone.close(); 913 }); 914 915 add_task(async function test_defaultTransactionType() { 916 info("Open connection"); 917 let db = Services.storage.openDatabase(getTestDB()); 918 Assert.ok(db instanceof Ci.mozIStorageAsyncConnection); 919 920 info("Verify default transaction type"); 921 Assert.equal( 922 db.defaultTransactionType, 923 Ci.mozIStorageConnection.TRANSACTION_DEFERRED 924 ); 925 926 info("Test other transaction types"); 927 for (let type of [ 928 Ci.mozIStorageConnection.TRANSACTION_IMMEDIATE, 929 Ci.mozIStorageConnection.TRANSACTION_EXCLUSIVE, 930 ]) { 931 db.defaultTransactionType = type; 932 Assert.equal(db.defaultTransactionType, type); 933 } 934 935 info("Should reject unknown transaction types"); 936 Assert.throws( 937 () => 938 (db.defaultTransactionType = 939 Ci.mozIStorageConnection.TRANSACTION_DEFAULT), 940 /NS_ERROR_ILLEGAL_VALUE/ 941 ); 942 943 db.defaultTransactionType = Ci.mozIStorageConnection.TRANSACTION_IMMEDIATE; 944 945 info("Clone should inherit default transaction type"); 946 let clone = await asyncClone(db, true); 947 Assert.ok(clone instanceof Ci.mozIStorageAsyncConnection); 948 Assert.equal( 949 clone.defaultTransactionType, 950 Ci.mozIStorageConnection.TRANSACTION_IMMEDIATE 951 ); 952 953 info("Begin immediate transaction on main connection"); 954 db.beginTransaction(); 955 956 info("Queue immediate transaction on clone"); 957 let stmts = [ 958 clone.createAsyncStatement(`BEGIN IMMEDIATE TRANSACTION`), 959 clone.createAsyncStatement(`DELETE FROM test WHERE name = 'new'`), 960 clone.createAsyncStatement(`COMMIT`), 961 ]; 962 let promiseStmtsRan = stmts.map(stmt => executeAsync(stmt)); 963 964 info("Commit immediate transaction on main connection"); 965 db.executeSimpleSQL(`INSERT INTO test(name) VALUES('new')`); 966 db.commitTransaction(); 967 968 info("Wait for transaction to succeed on clone"); 969 await Promise.all(promiseStmtsRan); 970 971 info("Clean up"); 972 for (let stmt of stmts) { 973 stmt.finalize(); 974 } 975 await asyncClose(clone); 976 await asyncClose(db); 977 }); 978 979 add_task(async function test_variableLimit() { 980 info("Open connection"); 981 let db = Services.storage.openDatabase(getTestDB()); 982 Assert.equal(db.variableLimit, 32766, "Should return default limit"); 983 db.variableLimit = 999; 984 Assert.equal(db.variableLimit, 999, "Should return the set limit"); 985 db.variableLimit = 33000; 986 Assert.equal(db.variableLimit, 32766, "Should silently truncate"); 987 await asyncClose(db); 988 }); 989 990 add_task(async function test_getInterface() { 991 let db = getOpenedDatabase(); 992 let target = db 993 .QueryInterface(Ci.nsIInterfaceRequestor) 994 .getInterface(Ci.nsIEventTarget); 995 // Just check that target is non-null. Other tests will ensure that it has 996 // the correct value. 997 Assert.notEqual(target, null); 998 999 await asyncClose(db); 1000 gDBConn = null; 1001 });