зеркало из https://github.com/mozilla/gecko-dev.git
1043 строки
29 KiB
JavaScript
1043 строки
29 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/*
|
|
* This file tests the functionality of mozIStorageBaseStatement::executeAsync
|
|
* for both mozIStorageStatement and mozIStorageAsyncStatement.
|
|
*/
|
|
|
|
// This file uses the internal _quit from testing/xpcshell/head.js */
|
|
/* global _quit */
|
|
|
|
const INTEGER = 1;
|
|
const TEXT = "this is test text";
|
|
const REAL = 3.23;
|
|
const BLOB = [1, 2];
|
|
|
|
/**
|
|
* Execute the given statement asynchronously, spinning an event loop until the
|
|
* async statement completes.
|
|
*
|
|
* @param aStmt
|
|
* The statement to execute.
|
|
* @param [aOptions={}]
|
|
* @param [aOptions.error=false]
|
|
* If true we should expect an error whose code we do not care about. If
|
|
* a numeric value, that's the error code we expect and require. If we
|
|
* are expecting an error, we expect a completion reason of REASON_ERROR.
|
|
* Otherwise we expect no error notification and a completion reason of
|
|
* REASON_FINISHED.
|
|
* @param [aOptions.cancel]
|
|
* If true we cancel the pending statement and additionally return the
|
|
* pending statement in case you want to further manipulate it.
|
|
* @param [aOptions.returnPending=false]
|
|
* If true we keep the pending statement around and return it to you. We
|
|
* normally avoid doing this to try and minimize the amount of time a
|
|
* reference is held to the returned pending statement.
|
|
* @param [aResults]
|
|
* If omitted, we assume no results rows are expected. If it is a
|
|
* number, we assume it is the number of results rows expected. If it is
|
|
* a function, we assume it is a function that takes the 1) result row
|
|
* number, 2) result tuple, 3) call stack for the original call to
|
|
* execAsync as arguments. If it is a list, we currently assume it is a
|
|
* list of functions where each function is intended to evaluate the
|
|
* result row at that ordinal position and takes the result tuple and
|
|
* the call stack for the original call.
|
|
*/
|
|
function execAsync(aStmt, aOptions, aResults) {
|
|
let caller = Components.stack.caller;
|
|
if (aOptions == null) {
|
|
aOptions = {};
|
|
}
|
|
|
|
let resultsExpected;
|
|
let resultsChecker;
|
|
if (aResults == null) {
|
|
resultsExpected = 0;
|
|
} else if (typeof aResults == "number") {
|
|
resultsExpected = aResults;
|
|
} else if (typeof aResults == "function") {
|
|
resultsChecker = aResults;
|
|
} else {
|
|
// array
|
|
resultsExpected = aResults.length;
|
|
resultsChecker = function(aResultNum, aTup, aCaller) {
|
|
aResults[aResultNum](aTup, aCaller);
|
|
};
|
|
}
|
|
let resultsSeen = 0;
|
|
|
|
let errorCodeExpected = false;
|
|
let reasonExpected = Ci.mozIStorageStatementCallback.REASON_FINISHED;
|
|
let altReasonExpected = null;
|
|
if ("error" in aOptions) {
|
|
errorCodeExpected = aOptions.error;
|
|
if (errorCodeExpected) {
|
|
reasonExpected = Ci.mozIStorageStatementCallback.REASON_ERROR;
|
|
}
|
|
}
|
|
let errorCodeSeen = false;
|
|
|
|
if ("cancel" in aOptions && aOptions.cancel) {
|
|
altReasonExpected = Ci.mozIStorageStatementCallback.REASON_CANCELED;
|
|
}
|
|
|
|
let completed = false;
|
|
|
|
let listener = {
|
|
handleResult(aResultSet) {
|
|
let row,
|
|
resultsSeenThisCall = 0;
|
|
while ((row = aResultSet.getNextRow()) != null) {
|
|
if (resultsChecker) {
|
|
resultsChecker(resultsSeen, row, caller);
|
|
}
|
|
resultsSeen++;
|
|
resultsSeenThisCall++;
|
|
}
|
|
|
|
if (!resultsSeenThisCall) {
|
|
do_throw("handleResult invoked with 0 result rows!");
|
|
}
|
|
},
|
|
handleError(aError) {
|
|
if (errorCodeSeen) {
|
|
do_throw("handleError called when we already had an error!");
|
|
}
|
|
errorCodeSeen = aError.result;
|
|
},
|
|
handleCompletion(aReason) {
|
|
if (completed) {
|
|
// paranoia check
|
|
do_throw("Received a second handleCompletion notification!", caller);
|
|
}
|
|
|
|
if (resultsSeen != resultsExpected) {
|
|
do_throw(
|
|
"Expected " +
|
|
resultsExpected +
|
|
" rows of results but " +
|
|
"got " +
|
|
resultsSeen +
|
|
" rows!",
|
|
caller
|
|
);
|
|
}
|
|
|
|
if (errorCodeExpected && !errorCodeSeen) {
|
|
do_throw("Expected an error, but did not see one.", caller);
|
|
} else if (errorCodeExpected != errorCodeSeen) {
|
|
do_throw(
|
|
"Expected error code " +
|
|
errorCodeExpected +
|
|
" but got " +
|
|
errorCodeSeen,
|
|
caller
|
|
);
|
|
}
|
|
|
|
if (aReason != reasonExpected && aReason != altReasonExpected) {
|
|
do_throw(
|
|
"Expected reason " +
|
|
reasonExpected +
|
|
(altReasonExpected ? " or " + altReasonExpected : "") +
|
|
" but got " +
|
|
aReason,
|
|
caller
|
|
);
|
|
}
|
|
|
|
completed = true;
|
|
},
|
|
};
|
|
|
|
let pending;
|
|
// Only get a pending reference if we're supposed to do.
|
|
// (note: This does not stop XPConnect from holding onto one currently.)
|
|
if (
|
|
("cancel" in aOptions && aOptions.cancel) ||
|
|
("returnPending" in aOptions && aOptions.returnPending)
|
|
) {
|
|
pending = aStmt.executeAsync(listener);
|
|
} else {
|
|
aStmt.executeAsync(listener);
|
|
}
|
|
|
|
if ("cancel" in aOptions && aOptions.cancel) {
|
|
pending.cancel();
|
|
}
|
|
|
|
Services.tm.spinEventLoopUntil(() => completed || _quit);
|
|
|
|
return pending;
|
|
}
|
|
|
|
/**
|
|
* Make sure that illegal SQL generates the expected runtime error and does not
|
|
* result in any crashes. Async-only since the synchronous case generates the
|
|
* error synchronously (and is tested elsewhere).
|
|
*/
|
|
function test_illegal_sql_async_deferred() {
|
|
let histogram = TelemetryTestUtils.getAndClearKeyedHistogram(QUERY_HISTOGRAM);
|
|
|
|
// gibberish
|
|
let stmt = makeTestStatement("I AM A ROBOT. DO AS I SAY.");
|
|
execAsync(stmt, { error: Ci.mozIStorageError.ERROR });
|
|
stmt.finalize();
|
|
|
|
TelemetryTestUtils.assertKeyedHistogramValue(
|
|
histogram,
|
|
TEST_DB_NAME,
|
|
TELEMETRY_VALUES.failure,
|
|
1
|
|
);
|
|
histogram.clear();
|
|
|
|
// legal SQL syntax, but with semantics issues.
|
|
stmt = makeTestStatement("SELECT destination FROM funkytown");
|
|
execAsync(stmt, { error: Ci.mozIStorageError.ERROR });
|
|
stmt.finalize();
|
|
|
|
TelemetryTestUtils.assertKeyedHistogramValue(
|
|
histogram,
|
|
TEST_DB_NAME,
|
|
TELEMETRY_VALUES.failure,
|
|
1
|
|
);
|
|
histogram.clear();
|
|
|
|
run_next_test();
|
|
}
|
|
test_illegal_sql_async_deferred.asyncOnly = true;
|
|
|
|
function test_create_table() {
|
|
// Ensure our table doesn't exist
|
|
Assert.ok(!getOpenedDatabase().tableExists("test"));
|
|
|
|
let histogram = TelemetryTestUtils.getAndClearKeyedHistogram(QUERY_HISTOGRAM);
|
|
|
|
var stmt = makeTestStatement(
|
|
"CREATE TABLE test (" +
|
|
"id INTEGER, " +
|
|
"string TEXT, " +
|
|
"number REAL, " +
|
|
"nuller NULL, " +
|
|
"blober BLOB" +
|
|
")"
|
|
);
|
|
execAsync(stmt);
|
|
stmt.finalize();
|
|
|
|
TelemetryTestUtils.assertKeyedHistogramValue(
|
|
histogram,
|
|
TEST_DB_NAME,
|
|
TELEMETRY_VALUES.success,
|
|
1
|
|
);
|
|
histogram.clear();
|
|
|
|
// Check that the table has been created
|
|
Assert.ok(getOpenedDatabase().tableExists("test"));
|
|
|
|
histogram.clear();
|
|
|
|
// Verify that it's created correctly (this will throw if it wasn't)
|
|
let checkStmt = getOpenedDatabase().createStatement(
|
|
"SELECT id, string, number, nuller, blober FROM test"
|
|
);
|
|
checkStmt.finalize();
|
|
|
|
// Nothing has executed so the histogram should be empty.
|
|
Assert.ok(!histogram.snapshot().values);
|
|
|
|
run_next_test();
|
|
}
|
|
|
|
function test_add_data() {
|
|
let histogram = TelemetryTestUtils.getAndClearKeyedHistogram(QUERY_HISTOGRAM);
|
|
|
|
var stmt = makeTestStatement(
|
|
"INSERT INTO test (id, string, number, nuller, blober) " +
|
|
"VALUES (?, ?, ?, ?, ?)"
|
|
);
|
|
stmt.bindBlobByIndex(4, BLOB, BLOB.length);
|
|
stmt.bindByIndex(3, null);
|
|
stmt.bindByIndex(2, REAL);
|
|
stmt.bindByIndex(1, TEXT);
|
|
stmt.bindByIndex(0, INTEGER);
|
|
|
|
execAsync(stmt);
|
|
stmt.finalize();
|
|
|
|
TelemetryTestUtils.assertKeyedHistogramValue(
|
|
histogram,
|
|
TEST_DB_NAME,
|
|
TELEMETRY_VALUES.success,
|
|
1
|
|
);
|
|
histogram.clear();
|
|
|
|
// Check that the result is in the table
|
|
verifyQuery(
|
|
"SELECT string, number, nuller, blober FROM test WHERE id = ?",
|
|
INTEGER,
|
|
[TEXT, REAL, null, BLOB]
|
|
);
|
|
|
|
TelemetryTestUtils.assertKeyedHistogramValue(
|
|
histogram,
|
|
TEST_DB_NAME,
|
|
TELEMETRY_VALUES.success,
|
|
1
|
|
);
|
|
histogram.clear();
|
|
|
|
run_next_test();
|
|
}
|
|
|
|
function test_get_data() {
|
|
let histogram = TelemetryTestUtils.getAndClearKeyedHistogram(QUERY_HISTOGRAM);
|
|
|
|
var stmt = makeTestStatement(
|
|
"SELECT string, number, nuller, blober, id FROM test WHERE id = ?"
|
|
);
|
|
stmt.bindByIndex(0, INTEGER);
|
|
execAsync(stmt, {}, [
|
|
function(tuple) {
|
|
Assert.notEqual(null, tuple);
|
|
|
|
// Check that it's what we expect
|
|
Assert.ok(!tuple.getIsNull(0));
|
|
Assert.equal(tuple.getResultByName("string"), tuple.getResultByIndex(0));
|
|
Assert.equal(TEXT, tuple.getResultByName("string"));
|
|
Assert.equal(
|
|
Ci.mozIStorageValueArray.VALUE_TYPE_TEXT,
|
|
tuple.getTypeOfIndex(0)
|
|
);
|
|
|
|
Assert.ok(!tuple.getIsNull(1));
|
|
Assert.equal(tuple.getResultByName("number"), tuple.getResultByIndex(1));
|
|
Assert.equal(REAL, tuple.getResultByName("number"));
|
|
Assert.equal(
|
|
Ci.mozIStorageValueArray.VALUE_TYPE_FLOAT,
|
|
tuple.getTypeOfIndex(1)
|
|
);
|
|
|
|
Assert.ok(tuple.getIsNull(2));
|
|
Assert.equal(tuple.getResultByName("nuller"), tuple.getResultByIndex(2));
|
|
Assert.equal(null, tuple.getResultByName("nuller"));
|
|
Assert.equal(
|
|
Ci.mozIStorageValueArray.VALUE_TYPE_NULL,
|
|
tuple.getTypeOfIndex(2)
|
|
);
|
|
|
|
Assert.ok(!tuple.getIsNull(3));
|
|
var blobByName = tuple.getResultByName("blober");
|
|
Assert.equal(BLOB.length, blobByName.length);
|
|
var blobByIndex = tuple.getResultByIndex(3);
|
|
Assert.equal(BLOB.length, blobByIndex.length);
|
|
for (let i = 0; i < BLOB.length; i++) {
|
|
Assert.equal(BLOB[i], blobByName[i]);
|
|
Assert.equal(BLOB[i], blobByIndex[i]);
|
|
}
|
|
var count = { value: 0 };
|
|
var blob = { value: null };
|
|
tuple.getBlob(3, count, blob);
|
|
Assert.equal(BLOB.length, count.value);
|
|
for (let i = 0; i < BLOB.length; i++) {
|
|
Assert.equal(BLOB[i], blob.value[i]);
|
|
}
|
|
Assert.equal(
|
|
Ci.mozIStorageValueArray.VALUE_TYPE_BLOB,
|
|
tuple.getTypeOfIndex(3)
|
|
);
|
|
|
|
Assert.ok(!tuple.getIsNull(4));
|
|
Assert.equal(tuple.getResultByName("id"), tuple.getResultByIndex(4));
|
|
Assert.equal(INTEGER, tuple.getResultByName("id"));
|
|
Assert.equal(
|
|
Ci.mozIStorageValueArray.VALUE_TYPE_INTEGER,
|
|
tuple.getTypeOfIndex(4)
|
|
);
|
|
},
|
|
]);
|
|
stmt.finalize();
|
|
|
|
TelemetryTestUtils.assertKeyedHistogramValue(
|
|
histogram,
|
|
TEST_DB_NAME,
|
|
TELEMETRY_VALUES.success,
|
|
1
|
|
);
|
|
histogram.clear();
|
|
|
|
run_next_test();
|
|
}
|
|
|
|
function test_tuple_out_of_bounds() {
|
|
let histogram = TelemetryTestUtils.getAndClearKeyedHistogram(QUERY_HISTOGRAM);
|
|
|
|
var stmt = makeTestStatement("SELECT string FROM test");
|
|
execAsync(stmt, {}, [
|
|
function(tuple) {
|
|
Assert.notEqual(null, tuple);
|
|
|
|
// Check all out of bounds - should throw
|
|
var methods = [
|
|
"getTypeOfIndex",
|
|
"getInt32",
|
|
"getInt64",
|
|
"getDouble",
|
|
"getUTF8String",
|
|
"getString",
|
|
"getIsNull",
|
|
];
|
|
for (var i in methods) {
|
|
try {
|
|
tuple[methods[i]](tuple.numEntries);
|
|
do_throw("did not throw :(");
|
|
} catch (e) {
|
|
Assert.equal(Cr.NS_ERROR_ILLEGAL_VALUE, e.result);
|
|
}
|
|
}
|
|
|
|
// getBlob requires more args...
|
|
try {
|
|
var blob = { value: null };
|
|
var size = { value: 0 };
|
|
tuple.getBlob(tuple.numEntries, blob, size);
|
|
do_throw("did not throw :(");
|
|
} catch (e) {
|
|
Assert.equal(Cr.NS_ERROR_ILLEGAL_VALUE, e.result);
|
|
}
|
|
},
|
|
]);
|
|
stmt.finalize();
|
|
|
|
TelemetryTestUtils.assertKeyedHistogramValue(
|
|
histogram,
|
|
TEST_DB_NAME,
|
|
TELEMETRY_VALUES.success,
|
|
1
|
|
);
|
|
histogram.clear();
|
|
|
|
run_next_test();
|
|
}
|
|
|
|
function test_no_listener_works_on_success() {
|
|
var stmt = makeTestStatement("DELETE FROM test WHERE id = ?");
|
|
stmt.bindByIndex(0, 0);
|
|
stmt.executeAsync();
|
|
stmt.finalize();
|
|
|
|
// Run the next test.
|
|
run_next_test();
|
|
}
|
|
|
|
function test_no_listener_works_on_results() {
|
|
var stmt = makeTestStatement("SELECT ?");
|
|
stmt.bindByIndex(0, 1);
|
|
stmt.executeAsync();
|
|
stmt.finalize();
|
|
|
|
// Run the next test.
|
|
run_next_test();
|
|
}
|
|
|
|
function test_no_listener_works_on_error() {
|
|
// commit without a transaction will trigger an error
|
|
var stmt = makeTestStatement("COMMIT");
|
|
stmt.executeAsync();
|
|
stmt.finalize();
|
|
|
|
// Run the next test.
|
|
run_next_test();
|
|
}
|
|
|
|
function test_partial_listener_works() {
|
|
var stmt = makeTestStatement("DELETE FROM test WHERE id = ?");
|
|
stmt.bindByIndex(0, 0);
|
|
stmt.executeAsync({
|
|
handleResult(aResultSet) {},
|
|
});
|
|
stmt.executeAsync({
|
|
handleError(aError) {},
|
|
});
|
|
stmt.executeAsync({
|
|
handleCompletion(aReason) {},
|
|
});
|
|
stmt.finalize();
|
|
|
|
// Run the next test.
|
|
run_next_test();
|
|
}
|
|
|
|
/**
|
|
* Dubious cancellation test that depends on system loading may or may not
|
|
* succeed in canceling things. It does at least test if calling cancel blows
|
|
* up. test_AsyncCancellation in test_true_async.cpp is our test that canceling
|
|
* actually works correctly.
|
|
*/
|
|
function test_immediate_cancellation() {
|
|
var stmt = makeTestStatement("DELETE FROM test WHERE id = ?");
|
|
stmt.bindByIndex(0, 0);
|
|
execAsync(stmt, { cancel: true });
|
|
stmt.finalize();
|
|
run_next_test();
|
|
}
|
|
|
|
/**
|
|
* Test that calling cancel twice throws the second time.
|
|
*/
|
|
function test_double_cancellation() {
|
|
var stmt = makeTestStatement("DELETE FROM test WHERE id = ?");
|
|
stmt.bindByIndex(0, 0);
|
|
let pendingStatement = execAsync(stmt, { cancel: true });
|
|
// And cancel again - expect an exception
|
|
expectError(Cr.NS_ERROR_UNEXPECTED, () => pendingStatement.cancel());
|
|
|
|
stmt.finalize();
|
|
run_next_test();
|
|
}
|
|
|
|
/**
|
|
* Verify that nothing untoward happens if we try and cancel something after it
|
|
* has fully run to completion.
|
|
*/
|
|
function test_cancellation_after_execution() {
|
|
var stmt = makeTestStatement("DELETE FROM test WHERE id = ?");
|
|
stmt.bindByIndex(0, 0);
|
|
let pendingStatement = execAsync(stmt, { returnPending: true });
|
|
// (the statement has fully executed at this point)
|
|
// canceling after the statement has run to completion should not throw!
|
|
pendingStatement.cancel();
|
|
|
|
stmt.finalize();
|
|
run_next_test();
|
|
}
|
|
|
|
/**
|
|
* Verifies that a single statement can be executed more than once. Might once
|
|
* have been intended to also ensure that callback notifications were not
|
|
* incorrectly interleaved, but that part was brittle (it's totally fine for
|
|
* handleResult to get called multiple times) and not comprehensive.
|
|
*/
|
|
function test_double_execute() {
|
|
let histogram = TelemetryTestUtils.getAndClearKeyedHistogram(QUERY_HISTOGRAM);
|
|
|
|
var stmt = makeTestStatement("SELECT 1");
|
|
execAsync(stmt, null, 1);
|
|
execAsync(stmt, null, 1);
|
|
stmt.finalize();
|
|
|
|
TelemetryTestUtils.assertKeyedHistogramValue(
|
|
histogram,
|
|
TEST_DB_NAME,
|
|
TELEMETRY_VALUES.success,
|
|
2
|
|
);
|
|
histogram.clear();
|
|
|
|
run_next_test();
|
|
}
|
|
|
|
function test_finalized_statement_does_not_crash() {
|
|
var stmt = makeTestStatement("SELECT * FROM TEST");
|
|
stmt.finalize();
|
|
// we are concerned about a crash here; an error is fine.
|
|
try {
|
|
stmt.executeAsync();
|
|
} catch (ex) {
|
|
// Do nothing.
|
|
}
|
|
|
|
// Run the next test.
|
|
run_next_test();
|
|
}
|
|
|
|
/**
|
|
* Bind by mozIStorageBindingParams on the mozIStorageBaseStatement by index.
|
|
*/
|
|
function test_bind_direct_binding_params_by_index() {
|
|
var stmt = makeTestStatement(
|
|
"INSERT INTO test (id, string, number, nuller, blober) " +
|
|
"VALUES (?, ?, ?, ?, ?)"
|
|
);
|
|
let insertId = nextUniqueId++;
|
|
stmt.bindByIndex(0, insertId);
|
|
stmt.bindByIndex(1, TEXT);
|
|
stmt.bindByIndex(2, REAL);
|
|
stmt.bindByIndex(3, null);
|
|
stmt.bindBlobByIndex(4, BLOB, BLOB.length);
|
|
execAsync(stmt);
|
|
stmt.finalize();
|
|
verifyQuery(
|
|
"SELECT string, number, nuller, blober FROM test WHERE id = ?",
|
|
insertId,
|
|
[TEXT, REAL, null, BLOB]
|
|
);
|
|
run_next_test();
|
|
}
|
|
|
|
/**
|
|
* Bind by mozIStorageBindingParams on the mozIStorageBaseStatement by name.
|
|
*/
|
|
function test_bind_direct_binding_params_by_name() {
|
|
var stmt = makeTestStatement(
|
|
"INSERT INTO test (id, string, number, nuller, blober) " +
|
|
"VALUES (:int, :text, :real, :null, :blob)"
|
|
);
|
|
let insertId = nextUniqueId++;
|
|
stmt.bindByName("int", insertId);
|
|
stmt.bindByName("text", TEXT);
|
|
stmt.bindByName("real", REAL);
|
|
stmt.bindByName("null", null);
|
|
stmt.bindBlobByName("blob", BLOB);
|
|
execAsync(stmt);
|
|
stmt.finalize();
|
|
verifyQuery(
|
|
"SELECT string, number, nuller, blober FROM test WHERE id = ?",
|
|
insertId,
|
|
[TEXT, REAL, null, BLOB]
|
|
);
|
|
run_next_test();
|
|
}
|
|
|
|
function test_bind_js_params_helper_by_index() {
|
|
var stmt = makeTestStatement(
|
|
"INSERT INTO test (id, string, number, nuller, blober) " +
|
|
"VALUES (?, ?, ?, ?, NULL)"
|
|
);
|
|
let insertId = nextUniqueId++;
|
|
// we cannot bind blobs this way; no blober
|
|
stmt.params[3] = null;
|
|
stmt.params[2] = REAL;
|
|
stmt.params[1] = TEXT;
|
|
stmt.params[0] = insertId;
|
|
execAsync(stmt);
|
|
stmt.finalize();
|
|
verifyQuery(
|
|
"SELECT string, number, nuller FROM test WHERE id = ?",
|
|
insertId,
|
|
[TEXT, REAL, null]
|
|
);
|
|
run_next_test();
|
|
}
|
|
|
|
function test_bind_js_params_helper_by_name() {
|
|
var stmt = makeTestStatement(
|
|
"INSERT INTO test (id, string, number, nuller, blober) " +
|
|
"VALUES (:int, :text, :real, :null, NULL)"
|
|
);
|
|
let insertId = nextUniqueId++;
|
|
// we cannot bind blobs this way; no blober
|
|
stmt.params.null = null;
|
|
stmt.params.real = REAL;
|
|
stmt.params.text = TEXT;
|
|
stmt.params.int = insertId;
|
|
execAsync(stmt);
|
|
stmt.finalize();
|
|
verifyQuery(
|
|
"SELECT string, number, nuller FROM test WHERE id = ?",
|
|
insertId,
|
|
[TEXT, REAL, null]
|
|
);
|
|
run_next_test();
|
|
}
|
|
|
|
function test_bind_multiple_rows_by_index() {
|
|
const AMOUNT_TO_ADD = 5;
|
|
var stmt = makeTestStatement(
|
|
"INSERT INTO test (id, string, number, nuller, blober) " +
|
|
"VALUES (?, ?, ?, ?, ?)"
|
|
);
|
|
var array = stmt.newBindingParamsArray();
|
|
for (let i = 0; i < AMOUNT_TO_ADD; i++) {
|
|
let bp = array.newBindingParams();
|
|
bp.bindByIndex(0, INTEGER);
|
|
bp.bindByIndex(1, TEXT);
|
|
bp.bindByIndex(2, REAL);
|
|
bp.bindByIndex(3, null);
|
|
bp.bindBlobByIndex(4, BLOB, BLOB.length);
|
|
array.addParams(bp);
|
|
Assert.equal(array.length, i + 1);
|
|
}
|
|
stmt.bindParameters(array);
|
|
|
|
let rowCount = getTableRowCount("test");
|
|
execAsync(stmt);
|
|
Assert.equal(rowCount + AMOUNT_TO_ADD, getTableRowCount("test"));
|
|
stmt.finalize();
|
|
run_next_test();
|
|
}
|
|
|
|
function test_bind_multiple_rows_by_name() {
|
|
const AMOUNT_TO_ADD = 5;
|
|
var stmt = makeTestStatement(
|
|
"INSERT INTO test (id, string, number, nuller, blober) " +
|
|
"VALUES (:int, :text, :real, :null, :blob)"
|
|
);
|
|
var array = stmt.newBindingParamsArray();
|
|
for (let i = 0; i < AMOUNT_TO_ADD; i++) {
|
|
let bp = array.newBindingParams();
|
|
bp.bindByName("int", INTEGER);
|
|
bp.bindByName("text", TEXT);
|
|
bp.bindByName("real", REAL);
|
|
bp.bindByName("null", null);
|
|
bp.bindBlobByName("blob", BLOB);
|
|
array.addParams(bp);
|
|
Assert.equal(array.length, i + 1);
|
|
}
|
|
stmt.bindParameters(array);
|
|
|
|
let rowCount = getTableRowCount("test");
|
|
execAsync(stmt);
|
|
Assert.equal(rowCount + AMOUNT_TO_ADD, getTableRowCount("test"));
|
|
stmt.finalize();
|
|
run_next_test();
|
|
}
|
|
|
|
/**
|
|
* Verify that a mozIStorageStatement instance throws immediately when we
|
|
* try and bind to an illegal index.
|
|
*/
|
|
function test_bind_out_of_bounds_sync_immediate() {
|
|
let stmt = makeTestStatement("INSERT INTO test (id) VALUES (?)");
|
|
|
|
let array = stmt.newBindingParamsArray();
|
|
let bp = array.newBindingParams();
|
|
|
|
// Check variant binding.
|
|
expectError(Cr.NS_ERROR_INVALID_ARG, () => bp.bindByIndex(1, INTEGER));
|
|
// Check blob binding.
|
|
expectError(Cr.NS_ERROR_INVALID_ARG, () =>
|
|
bp.bindBlobByIndex(1, BLOB, BLOB.length)
|
|
);
|
|
|
|
stmt.finalize();
|
|
run_next_test();
|
|
}
|
|
test_bind_out_of_bounds_sync_immediate.syncOnly = true;
|
|
|
|
/**
|
|
* Verify that a mozIStorageAsyncStatement reports an error asynchronously when
|
|
* we bind to an illegal index.
|
|
*/
|
|
function test_bind_out_of_bounds_async_deferred() {
|
|
let stmt = makeTestStatement("INSERT INTO test (id) VALUES (?)");
|
|
|
|
let array = stmt.newBindingParamsArray();
|
|
let bp = array.newBindingParams();
|
|
|
|
// There is no difference between variant and blob binding for async purposes.
|
|
bp.bindByIndex(1, INTEGER);
|
|
array.addParams(bp);
|
|
stmt.bindParameters(array);
|
|
execAsync(stmt, { error: Ci.mozIStorageError.RANGE });
|
|
|
|
stmt.finalize();
|
|
run_next_test();
|
|
}
|
|
test_bind_out_of_bounds_async_deferred.asyncOnly = true;
|
|
|
|
function test_bind_no_such_name_sync_immediate() {
|
|
let stmt = makeTestStatement("INSERT INTO test (id) VALUES (:foo)");
|
|
|
|
let array = stmt.newBindingParamsArray();
|
|
let bp = array.newBindingParams();
|
|
|
|
// Check variant binding.
|
|
expectError(Cr.NS_ERROR_INVALID_ARG, () =>
|
|
bp.bindByName("doesnotexist", INTEGER)
|
|
);
|
|
// Check blob binding.
|
|
expectError(Cr.NS_ERROR_INVALID_ARG, () =>
|
|
bp.bindBlobByName("doesnotexist", BLOB)
|
|
);
|
|
|
|
stmt.finalize();
|
|
run_next_test();
|
|
}
|
|
test_bind_no_such_name_sync_immediate.syncOnly = true;
|
|
|
|
function test_bind_no_such_name_async_deferred() {
|
|
let stmt = makeTestStatement("INSERT INTO test (id) VALUES (:foo)");
|
|
|
|
let array = stmt.newBindingParamsArray();
|
|
let bp = array.newBindingParams();
|
|
|
|
bp.bindByName("doesnotexist", INTEGER);
|
|
array.addParams(bp);
|
|
stmt.bindParameters(array);
|
|
execAsync(stmt, { error: Ci.mozIStorageError.RANGE });
|
|
|
|
stmt.finalize();
|
|
run_next_test();
|
|
}
|
|
test_bind_no_such_name_async_deferred.asyncOnly = true;
|
|
|
|
function test_bind_bogus_type_by_index() {
|
|
// We try to bind a JS Object here that should fail to bind.
|
|
let stmt = makeTestStatement("INSERT INTO test (blober) VALUES (?)");
|
|
|
|
let array = stmt.newBindingParamsArray();
|
|
let bp = array.newBindingParams();
|
|
Assert.throws(() => bp.bindByIndex(0, run_test), /NS_ERROR_UNEXPECTED/);
|
|
|
|
stmt.finalize();
|
|
run_next_test();
|
|
}
|
|
|
|
function test_bind_bogus_type_by_name() {
|
|
// We try to bind a JS Object here that should fail to bind.
|
|
let stmt = makeTestStatement("INSERT INTO test (blober) VALUES (:blob)");
|
|
|
|
let array = stmt.newBindingParamsArray();
|
|
let bp = array.newBindingParams();
|
|
Assert.throws(() => bp.bindByName("blob", run_test), /NS_ERROR_UNEXPECTED/);
|
|
|
|
stmt.finalize();
|
|
run_next_test();
|
|
}
|
|
|
|
function test_bind_params_already_locked() {
|
|
let stmt = makeTestStatement("INSERT INTO test (id) VALUES (:int)");
|
|
|
|
let array = stmt.newBindingParamsArray();
|
|
let bp = array.newBindingParams();
|
|
bp.bindByName("int", INTEGER);
|
|
array.addParams(bp);
|
|
|
|
// We should get an error after we call addParams and try to bind again.
|
|
expectError(Cr.NS_ERROR_UNEXPECTED, () => bp.bindByName("int", INTEGER));
|
|
|
|
stmt.finalize();
|
|
run_next_test();
|
|
}
|
|
|
|
function test_bind_params_array_already_locked() {
|
|
let stmt = makeTestStatement("INSERT INTO test (id) VALUES (:int)");
|
|
|
|
let array = stmt.newBindingParamsArray();
|
|
let bp1 = array.newBindingParams();
|
|
bp1.bindByName("int", INTEGER);
|
|
array.addParams(bp1);
|
|
let bp2 = array.newBindingParams();
|
|
stmt.bindParameters(array);
|
|
bp2.bindByName("int", INTEGER);
|
|
|
|
// We should get an error after we have bound the array to the statement.
|
|
expectError(Cr.NS_ERROR_UNEXPECTED, () => array.addParams(bp2));
|
|
|
|
stmt.finalize();
|
|
run_next_test();
|
|
}
|
|
|
|
function test_no_binding_params_from_locked_array() {
|
|
let stmt = makeTestStatement("INSERT INTO test (id) VALUES (:int)");
|
|
|
|
let array = stmt.newBindingParamsArray();
|
|
let bp = array.newBindingParams();
|
|
bp.bindByName("int", INTEGER);
|
|
array.addParams(bp);
|
|
stmt.bindParameters(array);
|
|
|
|
// We should not be able to get a new BindingParams object after we have bound
|
|
// to the statement.
|
|
expectError(Cr.NS_ERROR_UNEXPECTED, () => array.newBindingParams());
|
|
|
|
stmt.finalize();
|
|
run_next_test();
|
|
}
|
|
|
|
function test_not_right_owning_array() {
|
|
let stmt = makeTestStatement("INSERT INTO test (id) VALUES (:int)");
|
|
|
|
let array1 = stmt.newBindingParamsArray();
|
|
let array2 = stmt.newBindingParamsArray();
|
|
let bp = array1.newBindingParams();
|
|
bp.bindByName("int", INTEGER);
|
|
|
|
// We should not be able to add bp to array2 since it was created from array1.
|
|
expectError(Cr.NS_ERROR_UNEXPECTED, () => array2.addParams(bp));
|
|
|
|
stmt.finalize();
|
|
run_next_test();
|
|
}
|
|
|
|
function test_not_right_owning_statement() {
|
|
let stmt1 = makeTestStatement("INSERT INTO test (id) VALUES (:int)");
|
|
let stmt2 = makeTestStatement("INSERT INTO test (id) VALUES (:int)");
|
|
|
|
let array1 = stmt1.newBindingParamsArray();
|
|
stmt2.newBindingParamsArray();
|
|
let bp = array1.newBindingParams();
|
|
bp.bindByName("int", INTEGER);
|
|
array1.addParams(bp);
|
|
|
|
// We should not be able to bind array1 since it was created from stmt1.
|
|
expectError(Cr.NS_ERROR_UNEXPECTED, () => stmt2.bindParameters(array1));
|
|
|
|
stmt1.finalize();
|
|
stmt2.finalize();
|
|
run_next_test();
|
|
}
|
|
|
|
function test_bind_empty_array() {
|
|
let stmt = makeTestStatement("INSERT INTO test (id) VALUES (:int)");
|
|
|
|
let paramsArray = stmt.newBindingParamsArray();
|
|
|
|
// We should not be able to bind this array to the statement because it is
|
|
// empty.
|
|
expectError(Cr.NS_ERROR_UNEXPECTED, () => stmt.bindParameters(paramsArray));
|
|
|
|
stmt.finalize();
|
|
run_next_test();
|
|
}
|
|
|
|
function test_multiple_results() {
|
|
let expectedResults = getTableRowCount("test");
|
|
// Sanity check - we should have more than one result, but let's be sure.
|
|
Assert.ok(expectedResults > 1);
|
|
|
|
// Now check that we get back two rows of data from our async query.
|
|
let stmt = makeTestStatement("SELECT * FROM test");
|
|
execAsync(stmt, {}, expectedResults);
|
|
|
|
stmt.finalize();
|
|
run_next_test();
|
|
}
|
|
|
|
// Test Runner
|
|
|
|
const TEST_PASS_SYNC = 0;
|
|
const TEST_PASS_ASYNC = 1;
|
|
/**
|
|
* We run 2 passes against the test. One where makeTestStatement generates
|
|
* synchronous (mozIStorageStatement) statements and one where it generates
|
|
* asynchronous (mozIStorageAsyncStatement) statements.
|
|
*
|
|
* Because of differences in the ability to know the number of parameters before
|
|
* dispatching, some tests are sync/async specific. These functions are marked
|
|
* with 'syncOnly' or 'asyncOnly' attributes and run_next_test knows what to do.
|
|
*/
|
|
var testPass = TEST_PASS_SYNC;
|
|
|
|
/**
|
|
* Create a statement of the type under test per testPass.
|
|
*
|
|
* @param aSQL
|
|
* The SQL string from which to build a statement.
|
|
* @return a statement of the type under test per testPass.
|
|
*/
|
|
function makeTestStatement(aSQL) {
|
|
if (testPass == TEST_PASS_SYNC) {
|
|
return getOpenedDatabase().createStatement(aSQL);
|
|
}
|
|
return getOpenedDatabase().createAsyncStatement(aSQL);
|
|
}
|
|
|
|
var tests = [
|
|
test_illegal_sql_async_deferred,
|
|
test_create_table,
|
|
test_add_data,
|
|
test_get_data,
|
|
test_tuple_out_of_bounds,
|
|
test_no_listener_works_on_success,
|
|
test_no_listener_works_on_results,
|
|
test_no_listener_works_on_error,
|
|
test_partial_listener_works,
|
|
test_immediate_cancellation,
|
|
test_double_cancellation,
|
|
test_cancellation_after_execution,
|
|
test_double_execute,
|
|
test_finalized_statement_does_not_crash,
|
|
test_bind_direct_binding_params_by_index,
|
|
test_bind_direct_binding_params_by_name,
|
|
test_bind_js_params_helper_by_index,
|
|
test_bind_js_params_helper_by_name,
|
|
test_bind_multiple_rows_by_index,
|
|
test_bind_multiple_rows_by_name,
|
|
test_bind_out_of_bounds_sync_immediate,
|
|
test_bind_out_of_bounds_async_deferred,
|
|
test_bind_no_such_name_sync_immediate,
|
|
test_bind_no_such_name_async_deferred,
|
|
test_bind_bogus_type_by_index,
|
|
test_bind_bogus_type_by_name,
|
|
test_bind_params_already_locked,
|
|
test_bind_params_array_already_locked,
|
|
test_bind_empty_array,
|
|
test_no_binding_params_from_locked_array,
|
|
test_not_right_owning_array,
|
|
test_not_right_owning_statement,
|
|
test_multiple_results,
|
|
];
|
|
var index = 0;
|
|
|
|
const STARTING_UNIQUE_ID = 2;
|
|
var nextUniqueId = STARTING_UNIQUE_ID;
|
|
|
|
function run_next_test() {
|
|
function _run_next_test() {
|
|
// use a loop so we can skip tests...
|
|
while (index < tests.length) {
|
|
let test = tests[index++];
|
|
// skip tests not appropriate to the current test pass
|
|
if (
|
|
(testPass == TEST_PASS_SYNC && "asyncOnly" in test) ||
|
|
(testPass == TEST_PASS_ASYNC && "syncOnly" in test)
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
// Asynchronous tests means that exceptions don't kill the test.
|
|
try {
|
|
print("****** Running the next test: " + test.name);
|
|
test();
|
|
return;
|
|
} catch (e) {
|
|
do_throw(e);
|
|
}
|
|
}
|
|
|
|
// if we only completed the first pass, move to the next pass
|
|
if (testPass == TEST_PASS_SYNC) {
|
|
print("********* Beginning mozIStorageAsyncStatement pass.");
|
|
testPass++;
|
|
index = 0;
|
|
// a new pass demands a new database
|
|
asyncCleanup();
|
|
nextUniqueId = STARTING_UNIQUE_ID;
|
|
_run_next_test();
|
|
return;
|
|
}
|
|
|
|
// we did some async stuff; we need to clean up.
|
|
asyncCleanup();
|
|
do_test_finished();
|
|
}
|
|
|
|
// Don't actually schedule another test if we're quitting.
|
|
if (!_quit) {
|
|
// For saner stacks, we execute this code RSN.
|
|
executeSoon(_run_next_test);
|
|
}
|
|
}
|
|
|
|
function run_test() {
|
|
// Thunderbird doesn't have one or more of the probes used in this test.
|
|
// Ensure the data is collected anyway.
|
|
Services.prefs.setBoolPref(
|
|
"toolkit.telemetry.testing.overrideProductsCheck",
|
|
true
|
|
);
|
|
|
|
cleanup();
|
|
|
|
do_test_pending();
|
|
run_next_test();
|
|
}
|