1399 lines
39 KiB
JavaScript
1399 lines
39 KiB
JavaScript
"use strict";
|
|
|
|
const PROFILE_DIR = do_get_profile().path;
|
|
|
|
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", null, function 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", true, true);
|
|
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", null, function 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, null, function 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();
|
|
});
|