Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


SSL test_sqlite.js   Sprache: JAVA

 
"use strict";

const PROFILE_DIR = do_get_profile().path;

const { FileUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/FileUtils.sys.mjs"
);
const { Sqlite } = ChromeUtils.importESModule(
  "resource://gre/modules/Sqlite.sys.mjs"
);
const { TelemetryTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/TelemetryTestUtils.sys.mjs"
);

// Enable the collection (during test) for all products so even products
// that don't collect the data will be able to run the test without failure.
Services.prefs.setBoolPref(
  "toolkit.telemetry.testing.overrideProductsCheck",
  true
);

function sleep(ms) {
  return new Promise(resolve => {
    let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);

    timer.initWithCallback(
      {
        notify() {
          resolve();
        },
      },
      ms,
      timer.TYPE_ONE_SHOT
    );
  });
}

// When testing finalization, use this to tell Sqlite.sys.mjs to not throw
// an uncatchable `Promise.reject`
function failTestsOnAutoClose(enabled) {
  Sqlite.failTestsOnAutoClose(enabled);
}

function getConnection(dbName, extraOptions = {}) {
  let path = dbName + ".sqlite";
  let options = { path };
  for (let [k, v] of Object.entries(extraOptions)) {
    options[k] = v;
  }

  return Sqlite.openConnection(options);
}

async function getDummyDatabase(name, extraOptions = {}) {
  let c = await getConnection(name, extraOptions);
  c._initialStatementCount = 0;

  if (!extraOptions.readOnly) {
    const TABLES = new Map([
      ["dirs""id INTEGER PRIMARY KEY AUTOINCREMENT, path TEXT"],
      [
        "files",
        "id INTEGER PRIMARY KEY AUTOINCREMENT, dir_id INTEGER, path TEXT",
      ],
    ]);
    for (let [k, v] of TABLES) {
      await c.execute("CREATE TABLE " + k + "(" + v + ")");
      c._initialStatementCount++;
    }
  }

  return c;
}

async function getDummyTempDatabase(name, extraOptions = {}) {
  const TABLES = {
    dirs: "id INTEGER PRIMARY KEY AUTOINCREMENT, path TEXT",
    files: "id INTEGER PRIMARY KEY AUTOINCREMENT, dir_id INTEGER, path TEXT",
  };

  let c = await getConnection(name, extraOptions);
  c._initialStatementCount = 0;

  for (let [k, v] of Object.entries(TABLES)) {
    await c.execute("CREATE TEMP TABLE " + k + "(" + v + ")");
    c._initialStatementCount++;
  }

  return c;
}

add_task(async function test_setup() {
  const { initTestLogging } = ChromeUtils.importESModule(
    "resource://testing-common/services/common/logging.sys.mjs"
  );
  initTestLogging("Trace");
});

add_task(async function test_open_normal() {
  let c = await Sqlite.openConnection({ path: "test_open_normal.sqlite" });
  Assert.equal(c.defaultTransactionType, "DEFERRED");
  await c.close();
});

add_task(async function test_open_with_defaultTransactionType() {
  let c = await getConnection("execute_transaction_types", {
    defaultTransactionType: "IMMEDIATE",
  });
  Assert.equal(c.defaultTransactionType, "IMMEDIATE");
  await c.close();
});

add_task(async function test_open_normal_error() {
  let currentDir = do_get_cwd().path;

  let src = PathUtils.join(currentDir, "corrupt.sqlite");
  Assert.ok(await IOUtils.exists(src), "Database file found");

  // Ensure that our database doesn't already exist.
  let path = PathUtils.join(PROFILE_DIR, "corrupt.sqlite");
  Assert.ok(
    !(await IOUtils.exists(path)),
    "Database file should not exist yet"
  );

  await IOUtils.copy(src, path);

  let openPromise = Sqlite.openConnection({ path });
  await Assert.rejects(
    openPromise,
    reason => {
      return reason.result == Cr.NS_ERROR_FILE_CORRUPTED;
    },
    "Check error status"
  );
});

add_task(async function test_open_unshared() {
  let path = PathUtils.join(PROFILE_DIR, "test_open_unshared.sqlite");

  let c = await Sqlite.openConnection({ path, sharedMemoryCache: false });
  await c.close();
});

add_task(async function test_get_dummy_database() {
  let db = await getDummyDatabase("get_dummy_database");

  Assert.equal(typeof db, "object");
  await db.close();
});

add_task(async function test_schema_version() {
  let db = await getDummyDatabase("schema_version");

  let version = await db.getSchemaVersion();
  Assert.strictEqual(version, 0);

  db.setSchemaVersion(14);
  version = await db.getSchemaVersion();
  Assert.strictEqual(version, 14);

  for (let v of [0.5, "foobar", NaN]) {
    let success;
    try {
      await db.setSchemaVersion(v);
      info("Schema version " + v + " should have been rejected");
      success = false;
    } catch (ex) {
      if (!ex.message.startsWith("Schema version must be an integer.")) {
        throw ex;
      }
      success = true;
    }
    Assert.ok(success);

    version = await db.getSchemaVersion();
    Assert.strictEqual(version, 14);
  }

  await db.execute("ATTACH :memory AS attached");

  let attachedVersion = await db.getSchemaVersion("attached");
  Assert.equal(
    attachedVersion,
    0,
    "Should return 0 for initial attached schema version"
  );

  await db.setSchemaVersion(3, "attached");
  attachedVersion = await db.getSchemaVersion("attached");
  Assert.equal(attachedVersion, 3, "Should set attached schema version");

  version = await db.getSchemaVersion();
  Assert.equal(
    version,
    14,
    "Setting attached schema version should not change main schema version"
  );

  await db.setSchemaVersion(15);
  attachedVersion = await db.getSchemaVersion("attached");
  Assert.equal(
    attachedVersion,
    3,
    "Setting main schema version should not change attached schema version"
  );

  await db.close();
});

add_task(async function test_simple_insert() {
  let c = await getDummyDatabase("simple_insert");

  let result = await c.execute("INSERT INTO dirs VALUES (NULL, 'foo')");
  Assert.ok(Array.isArray(result));
  Assert.equal(result.length, 0);
  await c.close();
});

add_task(async function test_simple_bound_array() {
  let c = await getDummyDatabase("simple_bound_array");

  let result = await c.execute("INSERT INTO dirs VALUES (?, ?)", [1, "foo"]);
  Assert.equal(result.length, 0);
  await c.close();
});

add_task(async function test_simple_bound_object() {
  let c = await getDummyDatabase("simple_bound_object");
  let result = await c.execute("INSERT INTO dirs VALUES (:id, :path)", {
    id: 1,
    path: "foo",
  });
  Assert.equal(result.length, 0);
  result = await c.execute("SELECT id, path FROM dirs");
  Assert.equal(result.length, 1);
  Assert.equal(result[0].getResultByName("id"), 1);
  Assert.equal(result[0].getResultByName("path"), "foo");
  await c.close();
});

// This is mostly a sanity test to ensure simple executions work.
add_task(async function test_simple_insert_then_select() {
  let c = await getDummyDatabase("simple_insert_then_select");

  await c.execute("INSERT INTO dirs VALUES (NULL, 'foo')");
  await c.execute("INSERT INTO dirs (path) VALUES (?)", ["bar"]);

  let result = await c.execute("SELECT * FROM dirs");
  Assert.equal(result.length, 2);

  let i = 0;
  for (let row of result) {
    i++;

    Assert.equal(row.numEntries, 2);
    Assert.equal(row.getResultByIndex(0), i);

    let expected = { 1: "foo", 2: "bar" }[i];
    Assert.equal(row.getResultByName("path"), expected);
  }

  await c.close();
});

add_task(async function test_repeat_execution() {
  let c = await getDummyDatabase("repeat_execution");

  let sql = "INSERT INTO dirs (path) VALUES (:path)";
  await c.executeCached(sql, { path: "foo" });
  await c.executeCached(sql);

  let result = await c.execute("SELECT * FROM dirs");

  Assert.equal(result.length, 2);

  await c.close();
});

add_task(async function test_table_exists() {
  let c = await getDummyDatabase("table_exists");

  Assert.equal(false, await c.tableExists("does_not_exist"));
  Assert.ok(await c.tableExists("dirs"));
  Assert.ok(await c.tableExists("files"));

  await c.close();
});

add_task(async function test_index_exists() {
  let c = await getDummyDatabase("index_exists");

  Assert.equal(false, await c.indexExists("does_not_exist"));

  await c.execute("CREATE INDEX my_index ON dirs (path)");
  Assert.ok(await c.indexExists("my_index"));

  await c.close();
});

add_task(async function test_temp_table_exists() {
  let c = await getDummyTempDatabase("temp_table_exists");

  Assert.equal(false, await c.tableExists("temp_does_not_exist"));
  Assert.ok(await c.tableExists("dirs"));
  Assert.ok(await c.tableExists("files"));

  await c.close();
});

add_task(async function test_temp_index_exists() {
  let c = await getDummyTempDatabase("temp_index_exists");

  Assert.equal(false, await c.indexExists("temp_does_not_exist"));

  await c.execute("CREATE INDEX my_index ON dirs (path)");
  Assert.ok(await c.indexExists("my_index"));

  await c.close();
});

add_task(async function test_close_cached() {
  let c = await getDummyDatabase("close_cached");

  await c.executeCached("SELECT * FROM dirs");
  await c.executeCached("SELECT * FROM files");

  await c.close();
});

add_task(async function test_execute_invalid_statement() {
  let c = await getDummyDatabase("invalid_statement");

  await new Promise(resolve => {
    Assert.equal(c._connectionData._anonymousStatements.size, 0);

    c.execute("SELECT invalid FROM unknown").then(do_throw, function onError() {
      resolve();
    });
  });

  // Ensure we don't leak the statement instance.
  Assert.equal(c._connectionData._anonymousStatements.size, 0);

  await c.close();
});

add_task(async function test_incorrect_like_bindings() {
  let c = await getDummyDatabase("incorrect_like_bindings");

  let sql = "select * from dirs where path LIKE 'non%'";
  Assert.throws(() => c.execute(sql), /Please enter a LIKE clause/);
  Assert.throws(() => c.executeCached(sql), /Please enter a LIKE clause/);

  await c.close();
});
add_task(async function test_on_row_exception_ignored() {
  let c = await getDummyDatabase("on_row_exception_ignored");

  let sql = "INSERT INTO dirs (path) VALUES (?)";
  for (let i = 0; i < 10; i++) {
    await c.executeCached(sql, ["dir" + i]);
  }

  let i = 0;
  let hasResult = await c.execute("SELECT * FROM DIRS"nullfunction onRow() {
    i++;

    throw new Error("Some silly error.");
  });

  Assert.equal(hasResult, true);
  Assert.equal(i, 10);

  await c.close();
});

// Ensure StopIteration during onRow causes processing to stop.
add_task(async function test_on_row_stop_iteration() {
  let c = await getDummyDatabase("on_row_stop_iteration");

  let sql = "INSERT INTO dirs (path) VALUES (?)";
  for (let i = 0; i < 10; i++) {
    await c.executeCached(sql, ["dir" + i]);
  }

  let i = 0;
  let hasResult = await c.execute(
    "SELECT * FROM dirs",
    null,
    function onRow(row, cancel) {
      i++;

      if (i == 5) {
        cancel();
      }
    }
  );

  Assert.equal(hasResult, true);
  Assert.equal(i, 5);

  await c.close();
});

// Ensure execute resolves to false when no rows are selected.
add_task(async function test_on_row_stop_iteration() {
  let c = await getDummyDatabase("no_on_row");

  let i = 0;
  let hasResult = await c.execute(
    `SELECT * FROM dirs WHERE path="nonexistent"`,
    null,
    function onRow() {
      i++;
    }
  );

  Assert.equal(hasResult, false);
  Assert.equal(i, 0);

  await c.close();
});

add_task(async function test_invalid_transaction_type() {
  let c = await getDummyDatabase("invalid_transaction_type");

  Assert.throws(
    () => c.executeTransaction(function () {}, "foobar"),
    /Unknown transaction type/,
    "Unknown transaction type should throw"
  );

  await c.close();
});

add_task(async function test_execute_transaction_success() {
  let c = await getDummyDatabase("execute_transaction_success");

  Assert.ok(!c.transactionInProgress);

  await c.executeTransaction(async function transaction(conn) {
    Assert.equal(c, conn);
    Assert.ok(conn.transactionInProgress);

    await conn.execute("INSERT INTO dirs (path) VALUES ('foo')");
  });

  Assert.ok(!c.transactionInProgress);
  let rows = await c.execute("SELECT * FROM dirs");
  Assert.ok(Array.isArray(rows));
  Assert.equal(rows.length, 1);

  await c.close();
});

add_task(async function test_execute_transaction_rollback() {
  let c = await getDummyDatabase("execute_transaction_rollback");

  let deferred = Promise.withResolvers();

  c.executeTransaction(async function transaction(conn) {
    await conn.execute("INSERT INTO dirs (path) VALUES ('foo')");
    print("Expecting error with next statement.");
    await conn.execute("INSERT INTO invalid VALUES ('foo')");

    // We should never get here.
    do_throw();
  }).then(do_throw, function onError() {
    deferred.resolve();
  });

  await deferred.promise;

  let rows = await c.execute("SELECT * FROM dirs");
  Assert.equal(rows.length, 0);

  await c.close();
});

add_task(async function test_close_during_transaction() {
  let c = await getDummyDatabase("close_during_transaction");

  await c.execute("INSERT INTO dirs (path) VALUES ('foo')");

  let promise = c.executeTransaction(async function transaction() {
    await c.execute("INSERT INTO dirs (path) VALUES ('bar')");
  });
  await c.close();

  await Assert.rejects(
    promise,
    /Transaction canceled due to a closed connection/,
    "closing a connection in the middle of a transaction should reject it"
  );

  let c2 = await getConnection("close_during_transaction");
  let rows = await c2.execute("SELECT * FROM dirs");
  Assert.equal(rows.length, 1);

  await c2.close();
});

// Verify that we support concurrent transactions.
add_task(async function test_multiple_transactions() {
  let c = await getDummyDatabase("detect_multiple_transactions");

  for (let i = 0; i < 10; ++i) {
    // We don't wait for these transactions.
    c.executeTransaction(async function () {
      await c.execute("INSERT INTO dirs (path) VALUES (:path)", {
        path: `foo${i}`,
      });
      await c.execute("SELECT * FROM dirs");
    });
  }
  for (let i = 0; i < 10; ++i) {
    await c.executeTransaction(async function () {
      await c.execute("INSERT INTO dirs (path) VALUES (:path)", {
        path: `bar${i}`,
      });
      await c.execute("SELECT * FROM dirs");
    });
  }

  let rows = await c.execute("SELECT * FROM dirs");
  Assert.equal(rows.length, 20);

  await c.close();
});

// Verify that wrapped transactions ignore a BEGIN TRANSACTION failure, when
// an externally opened transaction exists.
add_task(async function test_wrapped_connection_transaction() {
  let file = new FileUtils.File(
    PathUtils.join(PROFILE_DIR, "test_wrapStorageConnection.sqlite")
  );
  let c = await new Promise((resolve, reject) => {
    Services.storage.openAsyncDatabase(
      file,
      /* openFlags */ Ci.mozIStorageService.OPEN_DEFAULT,
      /* connectionFlags */ Ci.mozIStorageService.CONNECTION_DEFAULT,
      (status, db) => {
        if (Components.isSuccessCode(status)) {
          resolve(db.QueryInterface(Ci.mozIStorageAsyncConnection));
        } else {
          reject(new Error(status));
        }
      }
    );
  });

  let wrapper = await Sqlite.wrapStorageConnection({ connection: c });
  // Start a transaction on the raw connection.
  await c.executeSimpleSQLAsync("BEGIN");
  // Now use executeTransaction, it will be executed, but not in a transaction.
  await wrapper.executeTransaction(async function () {
    await wrapper.execute(
      "CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT)"
    );
  });
  // This should not fail cause the internal transaction has not been created.
  await c.executeSimpleSQLAsync("COMMIT");

  await wrapper.execute("SELECT * FROM test");

  // Closing the wrapper should just finalize statements but not close the
  // database.
  await wrapper.close();
  await c.asyncClose();
});

add_task(async function test_transaction_timeout() {
  // Lower the transactions timeout for the test.
  let defaultTimeout = Sqlite.TRANSACTIONS_TIMEOUT_MS;
  Sqlite.TRANSACTIONS_TIMEOUT_MS = 500;
  Services.telemetry.clearScalars();
  let myResolve = () => {};
  try {
    let c = await getDummyDatabase("transaction_timeout");
    Assert.ok(!c.transactionInProgress, "Should not be in a transaction");
    let promise = c.executeTransaction(async function transaction(conn) {
      // Make a change, we'll later check it is undone by ROLLBACK.
      await conn.execute(
        "CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT)"
      );
      Assert.ok(c.transactionInProgress, "Should be in a transaction");
      // Return a never fulfilled promise.
      await new Promise(resolve => {
        // Keep this alive regardless GC, and clean it up in finally.
        myResolve = resolve;
      });
    });

    await Assert.rejects(
      promise,
      /Transaction timeout, most likely caused by unresolved pending work./,
      "A transaction timeout should reject it"
    );

    let rows = await c.execute("SELECT * FROM dirs");
    Assert.equal(rows.length, 0, "Changes should have been rolled back");
    await c.close();

    let scalars = TelemetryTestUtils.getProcessScalars("parent"truetrue);
    TelemetryTestUtils.assertKeyedScalar(
      scalars,
      "mozstorage.sqlitejsm_transaction_timeout",
      "test_transaction_timeout@test_sqlite.js",
      1
    );
  } finally {
    Sqlite.TRANSACTIONS_TIMEOUT_MS = defaultTimeout;
    myResolve();
  }
});

add_task(async function test_shrink_memory() {
  let c = await getDummyDatabase("shrink_memory");

  // It's just a simple sanity test. We have no way of measuring whether this
  // actually does anything.

  await c.shrinkMemory();
  await c.close();
});

add_task(async function test_no_shrink_on_init() {
  let c = await getConnection("no_shrink_on_init", {
    shrinkMemoryOnConnectionIdleMS: 200,
  });

  let count = 0;
  Object.defineProperty(c._connectionData, "shrinkMemory", {
    value() {
      count++;
    },
  });

  // We should not shrink until a statement has been executed.
  await sleep(220);
  Assert.equal(count, 0);

  await c.execute("SELECT 1");
  await sleep(220);
  Assert.equal(count, 1);

  await c.close();
});

add_task(async function test_idle_shrink_fires() {
  let c = await getDummyDatabase("idle_shrink_fires", {
    shrinkMemoryOnConnectionIdleMS: 200,
  });
  c._connectionData._clearIdleShrinkTimer();

  let oldShrink = c._connectionData.shrinkMemory;
  let shrinkPromises = [];

  let count = 0;
  Object.defineProperty(c._connectionData, "shrinkMemory", {
    value() {
      count++;
      let promise = oldShrink.call(c._connectionData);
      shrinkPromises.push(promise);
      return promise;
    },
  });

  // We reset the idle shrink timer after monkeypatching because otherwise the
  // installed timer callback will reference the non-monkeypatched function.
  c._connectionData._startIdleShrinkTimer();

  await sleep(220);
  Assert.equal(count, 1);
  Assert.equal(shrinkPromises.length, 1);
  await shrinkPromises[0];
  shrinkPromises.shift();

  // We shouldn't shrink again unless a statement was executed.
  await sleep(300);
  Assert.equal(count, 1);

  await c.execute("SELECT 1");
  await sleep(300);

  Assert.equal(count, 2);
  Assert.equal(shrinkPromises.length, 1);
  await shrinkPromises[0];

  await c.close();
});

add_task(async function test_idle_shrink_reset_on_operation() {
  const INTERVAL = 500;
  let c = await getDummyDatabase("idle_shrink_reset_on_operation", {
    shrinkMemoryOnConnectionIdleMS: INTERVAL,
  });

  c._connectionData._clearIdleShrinkTimer();

  let oldShrink = c._connectionData.shrinkMemory;
  let shrinkPromises = [];
  let count = 0;

  Object.defineProperty(c._connectionData, "shrinkMemory", {
    value() {
      count++;
      let promise = oldShrink.call(c._connectionData);
      shrinkPromises.push(promise);
      return promise;
    },
  });

  let now = new Date();
  c._connectionData._startIdleShrinkTimer();

  let initialIdle = new Date(now.getTime() + INTERVAL);

  // Perform database operations until initial scheduled time has been passed.
  let i = 0;
  while (new Date() < initialIdle) {
    await c.execute("INSERT INTO dirs (path) VALUES (?)", ["" + i]);
    i++;
  }

  Assert.ok(i > 0);

  // We should not have performed an idle while doing operations.
  Assert.equal(count, 0);

  // Wait for idle timer.
  await sleep(INTERVAL);

  // Ensure we fired.
  Assert.equal(count, 1);
  Assert.equal(shrinkPromises.length, 1);
  await shrinkPromises[0];

  await c.close();
});

add_task(async function test_in_progress_counts() {
  let c = await getDummyDatabase("in_progress_counts");
  Assert.equal(c._connectionData._statementCounter, c._initialStatementCount);
  Assert.equal(c._connectionData._pendingStatements.size, 0);
  await c.executeCached("INSERT INTO dirs (path) VALUES ('foo')");
  Assert.equal(
    c._connectionData._statementCounter,
    c._initialStatementCount + 1
  );
  Assert.equal(c._connectionData._pendingStatements.size, 0);

  let expectOne;
  let expectTwo;

  // We want to make sure that two queries executing simultaneously
  // result in `_pendingStatements.size` reaching 2, then dropping back to 0.
  //
  // To do so, we kick off a second statement within the row handler
  // of the first, then wait for both to finish.

  let inner = Promise.withResolvers();
  await c.executeCached("SELECT * from dirs"nullfunction onRow() {
    // In the onRow handler, we're still an outstanding query.
    // Expect a single in-progress entry.
    expectOne = c._connectionData._pendingStatements.size;

    // Start another query, checking that after its statement has been created
    // there are two statements in progress.
    c.executeCached("SELECT 10, path from dirs").then(inner.resolve);
    expectTwo = c._connectionData._pendingStatements.size;
  });

  await inner.promise;

  Assert.equal(expectOne, 1);
  Assert.equal(expectTwo, 2);
  Assert.equal(
    c._connectionData._statementCounter,
    c._initialStatementCount + 3
  );
  Assert.equal(c._connectionData._pendingStatements.size, 0);

  await c.close();
});

add_task(async function test_discard_while_active() {
  let c = await getDummyDatabase("discard_while_active");

  await c.executeCached("INSERT INTO dirs (path) VALUES ('foo')");
  await c.executeCached("INSERT INTO dirs (path) VALUES ('bar')");

  let discarded = -1;
  let first = true;
  let sql = "SELECT * FROM dirs";
  await c.executeCached(sql, nullfunction onRow() {
    if (!first) {
      return;
    }
    first = false;
    discarded = c.discardCachedStatements();
  });

  // We discarded everything, because the SELECT had already started to run.
  Assert.equal(3, discarded);

  // And again is safe.
  Assert.equal(0, c.discardCachedStatements());

  await c.close();
});

add_task(async function test_discard_cached() {
  let c = await getDummyDatabase("discard_cached");

  await c.executeCached("SELECT * from dirs");
  Assert.equal(1, c._connectionData._cachedStatements.size);

  await c.executeCached("SELECT * from files");
  Assert.equal(2, c._connectionData._cachedStatements.size);

  await c.executeCached("SELECT * from dirs");
  Assert.equal(2, c._connectionData._cachedStatements.size);

  c.discardCachedStatements();
  Assert.equal(0, c._connectionData._cachedStatements.size);

  await c.close();
});

add_task(async function test_array_binding_with_null() {
  let c = await getDummyDatabase("array_binding_null");

  let bindings = [null"test"];

  let sql = "INSERT INTO files (dir_id, path) VALUES (?, ?)";
  let result = await c.execute(sql, bindings);
  Assert.equal(result.length, 0);

  let rows = await c.executeCached("SELECT * from files");
  Assert.equal(rows.length, 1);
  Assert.strictEqual(rows[0].getResultByName("dir_id"), null);
  Assert.equal(rows[0].getResultByName("path"), "test");
  await c.close();
});

add_task(async function test_programmatic_binding() {
  let c = await getDummyDatabase("programmatic_binding");

  let bindings = [
    { id: 1, path: "foobar" },
    { id: null, path: "baznoo" },
    { id: 5, path: "toofoo" },
  ];

  let sql = "INSERT INTO dirs VALUES (:id, :path)";
  let result = await c.execute(sql, bindings);
  Assert.equal(result.length, 0);

  let rows = await c.executeCached("SELECT * from dirs");
  Assert.equal(rows.length, 3);
  await c.close();
});

add_task(async function test_programmatic_binding_transaction() {
  let c = await getDummyDatabase("programmatic_binding_transaction");

  let bindings = [
    { id: 1, path: "foobar" },
    { id: null, path: "baznoo" },
    { id: 5, path: "toofoo" },
  ];

  let sql = "INSERT INTO dirs VALUES (:id, :path)";
  await c.executeTransaction(async function transaction() {
    let result = await c.execute(sql, bindings);
    Assert.equal(result.length, 0);

    let rows = await c.executeCached("SELECT * from dirs");
    Assert.equal(rows.length, 3);
  });

  // Transaction committed.
  let rows = await c.executeCached("SELECT * from dirs");
  Assert.equal(rows.length, 3);
  await c.close();
});

add_task(
  async function test_programmatic_binding_transaction_partial_rollback() {
    let c = await getDummyDatabase(
      "programmatic_binding_transaction_partial_rollback"
    );

    let bindings = [
      { id: 2, path: "foobar" },
      { id: 3, path: "toofoo" },
    ];

    let sql = "INSERT INTO dirs VALUES (:id, :path)";

    // Add some data in an implicit transaction before beginning the batch insert.
    await c.execute(sql, { id: 1, path: "works" });

    let secondSucceeded = false;
    try {
      await c.executeTransaction(async function transaction() {
        // Insert one row. This won't implicitly start a transaction.
        await c.execute(sql, bindings[0]);

        // Insert multiple rows. mozStorage will want to start a transaction.
        // One of the inserts will fail, so the transaction should be rolled back.
        await c.execute(sql, bindings);
        secondSucceeded = true;
      });
    } catch (ex) {
      print("Caught expected exception: " + ex);
      Assert.greater(
        ex.result,
        0x80000000,
        "The ex.result value should be forwarded."
      );
    }

    // We did not get to the end of our in-transaction block.
    Assert.ok(!secondSucceeded);

    // Everything that happened in *our* transaction, not mozStorage's, got
    // rolled back, but the first row still exists.
    let rows = await c.executeCached("SELECT * from dirs");
    Assert.equal(rows.length, 1);
    Assert.equal(rows[0].getResultByName("path"), "works");
    await c.close();
  }
);

// Just like the previous test, but relying on the implicit
// transaction established by mozStorage.
add_task(async function test_programmatic_binding_implicit_transaction() {
  let c = await getDummyDatabase("programmatic_binding_implicit_transaction");

  let bindings = [
    { id: 2, path: "foobar" },
    { id: 1, path: "toofoo" },
  ];

  let sql = "INSERT INTO dirs VALUES (:id, :path)";
  let secondSucceeded = false;
  await c.execute(sql, { id: 1, path: "works" });
  try {
    await c.execute(sql, bindings);
    secondSucceeded = true;
  } catch (ex) {
    print("Caught expected exception: " + ex);
    Assert.greater(
      ex.result,
      0x80000000,
      "The ex.result value should be forwarded."
    );
  }

  Assert.ok(!secondSucceeded);

  // The entire batch failed.
  let rows = await c.executeCached("SELECT * from dirs");
  Assert.equal(rows.length, 1);
  Assert.equal(rows[0].getResultByName("path"), "works");
  await c.close();
});

// Test that direct binding of params and execution through mozStorage doesn't
// error when we manually create a transaction. See Bug 856925.
add_task(async function test_direct() {
  let file = new FileUtils.File(
    PathUtils.join(PathUtils.tempDir, "test_direct.sqlite")
  );
  file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
  print("Opening " + file.path);

  let db = Services.storage.openDatabase(file);
  print("Opened " + db);

  db.executeSimpleSQL(
    "CREATE TABLE types (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, UNIQUE (name))"
  );
  print("Executed setup.");

  let statement = db.createAsyncStatement(
    "INSERT INTO types (name) VALUES (:name)"
  );
  let params = statement.newBindingParamsArray();
  let one = params.newBindingParams();
  one.bindByName("name"null);
  params.addParams(one);
  let two = params.newBindingParams();
  two.bindByName("name""bar");
  params.addParams(two);

  print("Beginning transaction.");
  let begin = db.createAsyncStatement("BEGIN DEFERRED TRANSACTION");
  let end = db.createAsyncStatement("COMMIT TRANSACTION");

  let deferred = Promise.withResolvers();
  begin.executeAsync({
    handleCompletion() {
      deferred.resolve();
    },
  });
  await deferred.promise;

  statement.bindParameters(params);

  deferred = Promise.withResolvers();
  print("Executing async.");
  statement.executeAsync({
    handleResult() {},

    handleError(error) {
      print(
        "Error when executing SQL (" + error.result + "): " + error.message
      );
      print("Original error: " + error.error);
      deferred.reject();
    },

    handleCompletion() {
      print("Completed.");
      deferred.resolve();
    },
  });

  await deferred.promise;

  deferred = Promise.withResolvers();
  end.executeAsync({
    handleCompletion() {
      deferred.resolve();
    },
  });
  await deferred.promise;

  statement.finalize();
  begin.finalize();
  end.finalize();

  deferred = Promise.withResolvers();
  db.asyncClose(function () {
    deferred.resolve();
  });
  await deferred.promise;
});

// Test Sqlite.cloneStorageConnection.
add_task(async function test_cloneStorageConnection() {
  let file = new FileUtils.File(
    PathUtils.join(PROFILE_DIR, "test_cloneStorageConnection.sqlite")
  );
  let c = await new Promise((resolve, reject) => {
    Services.storage.openAsyncDatabase(
      file,
      /* openFlags */ Ci.mozIStorageService.OPEN_DEFAULT,
      /* connectionFlags */ Ci.mozIStorageService.CONNECTION_DEFAULT,
      (status, db) => {
        if (Components.isSuccessCode(status)) {
          resolve(db.QueryInterface(Ci.mozIStorageAsyncConnection));
        } else {
          reject(new Error(status));
        }
      }
    );
  });

  let clone = await Sqlite.cloneStorageConnection({
    connection: c,
    readOnly: true,
  });
  Assert.equal(clone.defaultTransactionType, "DEFERRED");
  // Just check that it works.
  await clone.execute("SELECT 1");

  info("Set default transaction type on storage connection");
  c.defaultTransactionType = Ci.mozIStorageConnection.TRANSACTION_IMMEDIATE;

  let clone2 = await Sqlite.cloneStorageConnection({
    connection: c,
    readOnly: false,
  });
  Assert.equal(clone2.defaultTransactionType, "IMMEDIATE");
  // Just check that it works.
  await clone2.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)");

  // Closing order should not matter.
  await c.asyncClose();
  await clone2.close();
  await clone.close();
});

// Test Sqlite.cloneStorageConnection invalid argument.
add_task(async function test_cloneStorageConnection() {
  try {
    await Sqlite.cloneStorageConnection({ connection: null });
    do_throw(new Error("Should throw on invalid connection"));
  } catch (ex) {
    if (ex.name != "TypeError") {
      throw ex;
    }
  }
});

// Test clone() method.
add_task(async function test_clone() {
  let c = await getDummyDatabase("clone");

  let clone = await c.clone();
  // Just check that it works.
  await clone.execute("SELECT 1");
  // Closing order should not matter.
  await c.close();
  await clone.close();
});

// Test clone(readOnly) method.
add_task(async function test_readOnly_clone() {
  let path = PathUtils.join(PROFILE_DIR, "test_readOnly_clone.sqlite");
  let c = await Sqlite.openConnection({ path, sharedMemoryCache: false });

  let clone = await c.clone(true);
  // Just check that it works.
  await clone.execute("SELECT 1");
  // But should not be able to write.

  await Assert.rejects(
    clone.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)"),
    /readonly/
  );
  // Closing order should not matter.
  await c.close();
  await clone.close();
});

// Test Sqlite.wrapStorageConnection.
add_task(async function test_wrapStorageConnection() {
  let file = new FileUtils.File(
    PathUtils.join(PROFILE_DIR, "test_wrapStorageConnection.sqlite")
  );
  let c = await new Promise((resolve, reject) => {
    Services.storage.openAsyncDatabase(
      file,
      /* openFlags */ Ci.mozIStorageService.OPEN_DEFAULT,
      /* connectionFlags */ Ci.mozIStorageService.CONNECTION_DEFAULT,
      (status, db) => {
        if (Components.isSuccessCode(status)) {
          resolve(db.QueryInterface(Ci.mozIStorageAsyncConnection));
        } else {
          reject(new Error(status));
        }
      }
    );
  });

  let wrapper = await Sqlite.wrapStorageConnection({ connection: c });
  Assert.equal(wrapper.defaultTransactionType, "DEFERRED");
  // Just check that it works.
  await wrapper.execute("SELECT 1");
  await wrapper.executeCached("SELECT 1");

  // Closing the wrapper should just finalize statements but not close the
  // database.
  await wrapper.close();

  info("Set default transaction type on storage connection");
  c.defaultTransactionType = Ci.mozIStorageConnection.TRANSACTION_EXCLUSIVE;

  let wrapper2 = await Sqlite.wrapStorageConnection({ connection: c });
  Assert.equal(wrapper2.defaultTransactionType, "EXCLUSIVE");
  // Just check that it works.
  await wrapper2.execute("SELECT 1");

  await wrapper2.close();

  await c.asyncClose();
});

// Test finalization
add_task(async function test_closed_by_witness() {
  failTestsOnAutoClose(false);
  let c = await getDummyDatabase("closed_by_witness");

  Services.obs.notifyObservers(
    null,
    "sqlite-finalization-witness",
    c._connectionData._identifier
  );
  // Since we triggered finalization ourselves, tell the witness to
  // forget the connection so it does not trigger a finalization again
  c._witness.forget();
  await c._connectionData._deferredClose.promise;
  Assert.ok(!c._connectionData._open);
  failTestsOnAutoClose(true);
});

add_task(async function test_warning_message_on_finalization() {
  failTestsOnAutoClose(false);
  let c = await getDummyDatabase("warning_message_on_finalization");
  let identifier = c._connectionData._identifier;
  let deferred = Promise.withResolvers();

  let listener = {
    observe(msg) {
      let messageText = msg.message;
      // Make sure the message starts with a warning containing the
      // connection identifier
      if (
        messageText.includes("Warning: Sqlite connection '" + identifier + "'")
      ) {
        deferred.resolve();
      }
    },
  };
  Services.console.registerListener(listener);

  Services.obs.notifyObservers(null"sqlite-finalization-witness", identifier);
  // Since we triggered finalization ourselves, tell the witness to
  // forget the connection so it does not trigger a finalization again
  c._witness.forget();

  await deferred.promise;
  Services.console.unregisterListener(listener);
  failTestsOnAutoClose(true);
});

add_task(async function test_error_message_on_unknown_finalization() {
  failTestsOnAutoClose(false);
  let deferred = Promise.withResolvers();

  let listener = {
    observe(msg) {
      let messageText = msg.message;
      if (
        messageText.includes(
          "Error: Attempt to finalize unknown Sqlite connection: foo"
        )
      ) {
        deferred.resolve();
      }
    },
  };
  Services.console.registerListener(listener);
  Services.obs.notifyObservers(null"sqlite-finalization-witness""foo");

  await deferred.promise;
  Services.console.unregisterListener(listener);
  failTestsOnAutoClose(true);
});

add_task(async function test_forget_witness_on_close() {
  let c = await getDummyDatabase("forget_witness_on_close");

  let forgetCalled = false;
  let oldWitness = c._witness;
  c._witness = {
    forget() {
      forgetCalled = true;
      oldWitness.forget();
    },
  };

  await c.close();
  // After close, witness should have forgotten the connection
  Assert.ok(forgetCalled);
});

add_task(async function test_close_database_on_gc() {
  failTestsOnAutoClose(false);
  let finalPromise;

  {
    let collectedPromises = [];
    for (let i = 0; i < 100; ++i) {
      let deferred = Promise.withResolvers();
      let c = await getDummyDatabase("gc_" + i);
      c._connectionData._deferredClose.promise.then(deferred.resolve);
      collectedPromises.push(deferred.promise);
    }
    finalPromise = Promise.all(collectedPromises);
  }

  // Call getDummyDatabase once more to clear any remaining
  // references. This is needed at the moment, otherwise
  // garbage-collection takes place after the shutdown barrier and the
  // test will timeout. Once that is fixed, we can remove this line
  // and be fine as long as the connections are garbage-collected.
  let last = await getDummyDatabase("gc_last");
  await last.close();

  Cu.forceGC();
  Cu.forceCC();
  Cu.forceShrinkingGC();

  await finalPromise;
  failTestsOnAutoClose(true);
});

// Test all supported datatypes
add_task(async function test_datatypes() {
  let c = await getConnection("datatypes");
  await c.execute("DROP TABLE IF EXISTS datatypes");
  await c.execute(`CREATE TABLE datatypes (
                     null_col    NULL,
                     integer_col INTEGER NOT NULL,
                     text_col    TEXT    NOT NULL,
                     blob_col    BLOB    NOT NULL,
                     real_col    REAL    NOT NULL,
                     numeric_col NUMERIC NOT NULL
                   )`);
  const bindings = [
    {
      null_col: null,
      integer_col: 12345,
      text_col: "qwerty",
      blob_col: new Uint8Array(256).map((value, index) => index % 256),
      real_col: 3.14159265359,
      numeric_col: true,
    },
    {
      null_col: null,
      integer_col: -12345,
      text_col: "",
      blob_col: new Uint8Array(256 * 2).map((value, index) => index % 256),
      real_col: Number.NEGATIVE_INFINITY,
      numeric_col: false,
    },
  ];

  await c.execute(
    `INSERT INTO datatypes VALUES (
                     :null_col,
                     :integer_col,
                     :text_col,
                     :blob_col,
                     :real_col,
                     :numeric_col
                   )`,
    bindings
  );

  let rows = await c.execute("SELECT * FROM datatypes");
  Assert.ok(Array.isArray(rows));
  Assert.equal(rows.length, bindings.length);
  for (let i = 0; i < bindings.length; ++i) {
    let binding = bindings[i];
    let row = rows[i];
    for (let colName in binding) {
      // In Sqlite bool is stored and then retrieved as numeric.
      let val =
        typeof binding[colName] == "boolean"
          ? +binding[colName]
          : binding[colName];
      Assert.deepEqual(val, row.getResultByName(colName));
    }
  }
  await c.close();
});

add_task(async function test_interrupt() {
  // Testing the interrupt functionality is left to mozStorage unit tests, here
  // we'll just test error conditions.
  let c = await getDummyDatabase("interrupt");
  await c.interrupt();
  ok(true"Sqlite.interrupt() should not throw on a writable connection");
  await c.close();
  Assert.throws(
    () => c.interrupt(),
    /Connection is not open/,
    "Sqlite.interrupt() should throw on a closed connection"
  );
});

add_task(async function test_pageSize() {
  // Testing the possibility to set the page size on database creation.
  await Assert.rejects(
    getDummyDatabase("pagesize", { pageSize: 1234 }),
    /Invalid pageSize/,
    "Check invalid pageSize value"
  );
  let c = await getDummyDatabase("pagesize", { pageSize: 8192 });
  Assert.equal(
    (await c.execute("PRAGMA page_size"))[0].getResultByIndex(0),
    8192,
    "Check page size was set"
  );
  await c.close();
});

add_task(async function test_loadExtension() {
  await Assert.rejects(
    getDummyDatabase("dummy", { extensions: ["dummy"] }),
    /Could not load extension/,
    "Check unsupported extension"
  );
  let c = await getDummyDatabase("fts", { extensions: ["fts5"] });
  await c.execute("CREATE VIRTUAL TABLE test_fts USING fts5(body)");
  await c.close();
});

Messung V0.5
C=93 H=91 G=91

¤ Dauer der Verarbeitung: 0.46 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge