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