tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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