1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/modules/tests/xpcshell/test_sqlite.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,908 @@ 1.4 +/* Any copyright is dedicated to the Public Domain. 1.5 + * http://creativecommons.org/publicdomain/zero/1.0/ */ 1.6 + 1.7 +"use strict"; 1.8 + 1.9 +const {classes: Cc, interfaces: Ci, utils: Cu} = Components; 1.10 + 1.11 +do_get_profile(); 1.12 + 1.13 +Cu.import("resource://gre/modules/Promise.jsm"); 1.14 +Cu.import("resource://gre/modules/osfile.jsm"); 1.15 +Cu.import("resource://gre/modules/FileUtils.jsm"); 1.16 +Cu.import("resource://gre/modules/Services.jsm"); 1.17 +Cu.import("resource://gre/modules/Sqlite.jsm"); 1.18 +Cu.import("resource://gre/modules/Task.jsm"); 1.19 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.20 + 1.21 +// To spin the event loop in test. 1.22 +Cu.import("resource://services-common/async.js"); 1.23 + 1.24 +function sleep(ms) { 1.25 + let deferred = Promise.defer(); 1.26 + 1.27 + let timer = Cc["@mozilla.org/timer;1"] 1.28 + .createInstance(Ci.nsITimer); 1.29 + 1.30 + timer.initWithCallback({ 1.31 + notify: function () { 1.32 + deferred.resolve(); 1.33 + }, 1.34 + }, ms, timer.TYPE_ONE_SHOT); 1.35 + 1.36 + return deferred.promise; 1.37 +} 1.38 + 1.39 +function getConnection(dbName, extraOptions={}) { 1.40 + let path = dbName + ".sqlite"; 1.41 + let options = {path: path}; 1.42 + for (let [k, v] in Iterator(extraOptions)) { 1.43 + options[k] = v; 1.44 + } 1.45 + 1.46 + return Sqlite.openConnection(options); 1.47 +} 1.48 + 1.49 +function getDummyDatabase(name, extraOptions={}) { 1.50 + const TABLES = { 1.51 + dirs: "id INTEGER PRIMARY KEY AUTOINCREMENT, path TEXT", 1.52 + files: "id INTEGER PRIMARY KEY AUTOINCREMENT, dir_id INTEGER, path TEXT", 1.53 + }; 1.54 + 1.55 + let c = yield getConnection(name, extraOptions); 1.56 + c._initialStatementCount = 0; 1.57 + 1.58 + for (let [k, v] in Iterator(TABLES)) { 1.59 + yield c.execute("CREATE TABLE " + k + "(" + v + ")"); 1.60 + c._initialStatementCount++; 1.61 + } 1.62 + 1.63 + throw new Task.Result(c); 1.64 +} 1.65 + 1.66 +function getDummyTempDatabase(name, extraOptions={}) { 1.67 + const TABLES = { 1.68 + dirs: "id INTEGER PRIMARY KEY AUTOINCREMENT, path TEXT", 1.69 + files: "id INTEGER PRIMARY KEY AUTOINCREMENT, dir_id INTEGER, path TEXT", 1.70 + }; 1.71 + 1.72 + let c = yield getConnection(name, extraOptions); 1.73 + c._initialStatementCount = 0; 1.74 + 1.75 + for (let [k, v] in Iterator(TABLES)) { 1.76 + yield c.execute("CREATE TEMP TABLE " + k + "(" + v + ")"); 1.77 + c._initialStatementCount++; 1.78 + } 1.79 + 1.80 + throw new Task.Result(c); 1.81 +} 1.82 + 1.83 +function run_test() { 1.84 + Cu.import("resource://testing-common/services-common/logging.js"); 1.85 + initTestLogging("Trace"); 1.86 + 1.87 + run_next_test(); 1.88 +} 1.89 + 1.90 +add_task(function test_open_normal() { 1.91 + let c = yield Sqlite.openConnection({path: "test_open_normal.sqlite"}); 1.92 + yield c.close(); 1.93 +}); 1.94 + 1.95 +add_task(function test_open_unshared() { 1.96 + let path = OS.Path.join(OS.Constants.Path.profileDir, "test_open_unshared.sqlite"); 1.97 + 1.98 + let c = yield Sqlite.openConnection({path: path, sharedMemoryCache: false}); 1.99 + yield c.close(); 1.100 +}); 1.101 + 1.102 +add_task(function test_get_dummy_database() { 1.103 + let db = yield getDummyDatabase("get_dummy_database"); 1.104 + 1.105 + do_check_eq(typeof(db), "object"); 1.106 + yield db.close(); 1.107 +}); 1.108 + 1.109 +add_task(function test_schema_version() { 1.110 + let db = yield getDummyDatabase("schema_version"); 1.111 + 1.112 + let version = yield db.getSchemaVersion(); 1.113 + do_check_eq(version, 0); 1.114 + 1.115 + db.setSchemaVersion(14); 1.116 + version = yield db.getSchemaVersion(); 1.117 + do_check_eq(version, 14); 1.118 + 1.119 + for (let v of [0.5, "foobar", NaN]) { 1.120 + let success; 1.121 + try { 1.122 + yield db.setSchemaVersion(v); 1.123 + do_print("Schema version " + v + " should have been rejected"); 1.124 + success = false; 1.125 + } catch (ex if ex.message.startsWith("Schema version must be an integer.")) { 1.126 + success = true; 1.127 + } 1.128 + do_check_true(success); 1.129 + 1.130 + version = yield db.getSchemaVersion(); 1.131 + do_check_eq(version, 14); 1.132 + } 1.133 + 1.134 + yield db.close(); 1.135 +}); 1.136 + 1.137 +add_task(function test_simple_insert() { 1.138 + let c = yield getDummyDatabase("simple_insert"); 1.139 + 1.140 + let result = yield c.execute("INSERT INTO dirs VALUES (NULL, 'foo')"); 1.141 + do_check_true(Array.isArray(result)); 1.142 + do_check_eq(result.length, 0); 1.143 + yield c.close(); 1.144 +}); 1.145 + 1.146 +add_task(function test_simple_bound_array() { 1.147 + let c = yield getDummyDatabase("simple_bound_array"); 1.148 + 1.149 + let result = yield c.execute("INSERT INTO dirs VALUES (?, ?)", [1, "foo"]); 1.150 + do_check_eq(result.length, 0); 1.151 + yield c.close(); 1.152 +}); 1.153 + 1.154 +add_task(function test_simple_bound_object() { 1.155 + let c = yield getDummyDatabase("simple_bound_object"); 1.156 + let result = yield c.execute("INSERT INTO dirs VALUES (:id, :path)", 1.157 + {id: 1, path: "foo"}); 1.158 + do_check_eq(result.length, 0); 1.159 + result = yield c.execute("SELECT id, path FROM dirs"); 1.160 + do_check_eq(result.length, 1); 1.161 + do_check_eq(result[0].getResultByName("id"), 1); 1.162 + do_check_eq(result[0].getResultByName("path"), "foo"); 1.163 + yield c.close(); 1.164 +}); 1.165 + 1.166 +// This is mostly a sanity test to ensure simple executions work. 1.167 +add_task(function test_simple_insert_then_select() { 1.168 + let c = yield getDummyDatabase("simple_insert_then_select"); 1.169 + 1.170 + yield c.execute("INSERT INTO dirs VALUES (NULL, 'foo')"); 1.171 + yield c.execute("INSERT INTO dirs (path) VALUES (?)", ["bar"]); 1.172 + 1.173 + let result = yield c.execute("SELECT * FROM dirs"); 1.174 + do_check_eq(result.length, 2); 1.175 + 1.176 + let i = 0; 1.177 + for (let row of result) { 1.178 + i++; 1.179 + 1.180 + do_check_eq(row.numEntries, 2); 1.181 + do_check_eq(row.getResultByIndex(0), i); 1.182 + 1.183 + let expected = {1: "foo", 2: "bar"}[i]; 1.184 + do_check_eq(row.getResultByName("path"), expected); 1.185 + } 1.186 + 1.187 + yield c.close(); 1.188 +}); 1.189 + 1.190 +add_task(function test_repeat_execution() { 1.191 + let c = yield getDummyDatabase("repeat_execution"); 1.192 + 1.193 + let sql = "INSERT INTO dirs (path) VALUES (:path)"; 1.194 + yield c.executeCached(sql, {path: "foo"}); 1.195 + yield c.executeCached(sql); 1.196 + 1.197 + let result = yield c.execute("SELECT * FROM dirs"); 1.198 + 1.199 + do_check_eq(result.length, 2); 1.200 + 1.201 + yield c.close(); 1.202 +}); 1.203 + 1.204 +add_task(function test_table_exists() { 1.205 + let c = yield getDummyDatabase("table_exists"); 1.206 + 1.207 + do_check_false(yield c.tableExists("does_not_exist")); 1.208 + do_check_true(yield c.tableExists("dirs")); 1.209 + do_check_true(yield c.tableExists("files")); 1.210 + 1.211 + yield c.close(); 1.212 +}); 1.213 + 1.214 +add_task(function test_index_exists() { 1.215 + let c = yield getDummyDatabase("index_exists"); 1.216 + 1.217 + do_check_false(yield c.indexExists("does_not_exist")); 1.218 + 1.219 + yield c.execute("CREATE INDEX my_index ON dirs (path)"); 1.220 + do_check_true(yield c.indexExists("my_index")); 1.221 + 1.222 + yield c.close(); 1.223 +}); 1.224 + 1.225 +add_task(function test_temp_table_exists() { 1.226 + let c = yield getDummyTempDatabase("temp_table_exists"); 1.227 + 1.228 + do_check_false(yield c.tableExists("temp_does_not_exist")); 1.229 + do_check_true(yield c.tableExists("dirs")); 1.230 + do_check_true(yield c.tableExists("files")); 1.231 + 1.232 + yield c.close(); 1.233 +}); 1.234 + 1.235 +add_task(function test_temp_index_exists() { 1.236 + let c = yield getDummyTempDatabase("temp_index_exists"); 1.237 + 1.238 + do_check_false(yield c.indexExists("temp_does_not_exist")); 1.239 + 1.240 + yield c.execute("CREATE INDEX my_index ON dirs (path)"); 1.241 + do_check_true(yield c.indexExists("my_index")); 1.242 + 1.243 + yield c.close(); 1.244 +}); 1.245 + 1.246 +add_task(function test_close_cached() { 1.247 + let c = yield getDummyDatabase("close_cached"); 1.248 + 1.249 + yield c.executeCached("SELECT * FROM dirs"); 1.250 + yield c.executeCached("SELECT * FROM files"); 1.251 + 1.252 + yield c.close(); 1.253 +}); 1.254 + 1.255 +add_task(function test_execute_invalid_statement() { 1.256 + let c = yield getDummyDatabase("invalid_statement"); 1.257 + 1.258 + let deferred = Promise.defer(); 1.259 + 1.260 + do_check_eq(c._anonymousStatements.size, 0); 1.261 + 1.262 + c.execute("SELECT invalid FROM unknown").then(do_throw, function onError(error) { 1.263 + deferred.resolve(); 1.264 + }); 1.265 + 1.266 + yield deferred.promise; 1.267 + 1.268 + // Ensure we don't leak the statement instance. 1.269 + do_check_eq(c._anonymousStatements.size, 0); 1.270 + 1.271 + yield c.close(); 1.272 +}); 1.273 + 1.274 +add_task(function test_on_row_exception_ignored() { 1.275 + let c = yield getDummyDatabase("on_row_exception_ignored"); 1.276 + 1.277 + let sql = "INSERT INTO dirs (path) VALUES (?)"; 1.278 + for (let i = 0; i < 10; i++) { 1.279 + yield c.executeCached(sql, ["dir" + i]); 1.280 + } 1.281 + 1.282 + let i = 0; 1.283 + yield c.execute("SELECT * FROM DIRS", null, function onRow(row) { 1.284 + i++; 1.285 + 1.286 + throw new Error("Some silly error."); 1.287 + }); 1.288 + 1.289 + do_check_eq(i, 10); 1.290 + 1.291 + yield c.close(); 1.292 +}); 1.293 + 1.294 +// Ensure StopIteration during onRow causes processing to stop. 1.295 +add_task(function test_on_row_stop_iteration() { 1.296 + let c = yield getDummyDatabase("on_row_stop_iteration"); 1.297 + 1.298 + let sql = "INSERT INTO dirs (path) VALUES (?)"; 1.299 + for (let i = 0; i < 10; i++) { 1.300 + yield c.executeCached(sql, ["dir" + i]); 1.301 + } 1.302 + 1.303 + let i = 0; 1.304 + let result = yield c.execute("SELECT * FROM dirs", null, function onRow(row) { 1.305 + i++; 1.306 + 1.307 + if (i == 5) { 1.308 + throw StopIteration; 1.309 + } 1.310 + }); 1.311 + 1.312 + do_check_null(result); 1.313 + do_check_eq(i, 5); 1.314 + 1.315 + yield c.close(); 1.316 +}); 1.317 + 1.318 +add_task(function test_invalid_transaction_type() { 1.319 + let c = yield getDummyDatabase("invalid_transaction_type"); 1.320 + 1.321 + let errored = false; 1.322 + try { 1.323 + c.executeTransaction(function () {}, "foobar"); 1.324 + } catch (ex) { 1.325 + errored = true; 1.326 + do_check_true(ex.message.startsWith("Unknown transaction type")); 1.327 + } finally { 1.328 + do_check_true(errored); 1.329 + } 1.330 + 1.331 + yield c.close(); 1.332 +}); 1.333 + 1.334 +add_task(function test_execute_transaction_success() { 1.335 + let c = yield getDummyDatabase("execute_transaction_success"); 1.336 + 1.337 + do_check_false(c.transactionInProgress); 1.338 + 1.339 + yield c.executeTransaction(function transaction(conn) { 1.340 + do_check_eq(c, conn); 1.341 + do_check_true(conn.transactionInProgress); 1.342 + 1.343 + yield conn.execute("INSERT INTO dirs (path) VALUES ('foo')"); 1.344 + }); 1.345 + 1.346 + do_check_false(c.transactionInProgress); 1.347 + let rows = yield c.execute("SELECT * FROM dirs"); 1.348 + do_check_true(Array.isArray(rows)); 1.349 + do_check_eq(rows.length, 1); 1.350 + 1.351 + yield c.close(); 1.352 +}); 1.353 + 1.354 +add_task(function test_execute_transaction_rollback() { 1.355 + let c = yield getDummyDatabase("execute_transaction_rollback"); 1.356 + 1.357 + let deferred = Promise.defer(); 1.358 + 1.359 + c.executeTransaction(function transaction(conn) { 1.360 + yield conn.execute("INSERT INTO dirs (path) VALUES ('foo')"); 1.361 + print("Expecting error with next statement."); 1.362 + yield conn.execute("INSERT INTO invalid VALUES ('foo')"); 1.363 + 1.364 + // We should never get here. 1.365 + do_throw(); 1.366 + }).then(do_throw, function onError(error) { 1.367 + deferred.resolve(); 1.368 + }); 1.369 + 1.370 + yield deferred.promise; 1.371 + 1.372 + let rows = yield c.execute("SELECT * FROM dirs"); 1.373 + do_check_eq(rows.length, 0); 1.374 + 1.375 + yield c.close(); 1.376 +}); 1.377 + 1.378 +add_task(function test_close_during_transaction() { 1.379 + let c = yield getDummyDatabase("close_during_transaction"); 1.380 + 1.381 + yield c.execute("INSERT INTO dirs (path) VALUES ('foo')"); 1.382 + 1.383 + let errored = false; 1.384 + try { 1.385 + yield c.executeTransaction(function transaction(conn) { 1.386 + yield c.execute("INSERT INTO dirs (path) VALUES ('bar')"); 1.387 + yield c.close(); 1.388 + }); 1.389 + } catch (ex) { 1.390 + errored = true; 1.391 + do_check_eq(ex.message, "Connection being closed."); 1.392 + } finally { 1.393 + do_check_true(errored); 1.394 + } 1.395 + 1.396 + let c2 = yield getConnection("close_during_transaction"); 1.397 + let rows = yield c2.execute("SELECT * FROM dirs"); 1.398 + do_check_eq(rows.length, 1); 1.399 + 1.400 + yield c2.close(); 1.401 +}); 1.402 + 1.403 +add_task(function test_detect_multiple_transactions() { 1.404 + let c = yield getDummyDatabase("detect_multiple_transactions"); 1.405 + 1.406 + yield c.executeTransaction(function main() { 1.407 + yield c.execute("INSERT INTO dirs (path) VALUES ('foo')"); 1.408 + 1.409 + let errored = false; 1.410 + try { 1.411 + yield c.executeTransaction(function child() { 1.412 + yield c.execute("INSERT INTO dirs (path) VALUES ('bar')"); 1.413 + }); 1.414 + } catch (ex) { 1.415 + errored = true; 1.416 + do_check_true(ex.message.startsWith("A transaction is already active.")); 1.417 + } finally { 1.418 + do_check_true(errored); 1.419 + } 1.420 + }); 1.421 + 1.422 + let rows = yield c.execute("SELECT * FROM dirs"); 1.423 + do_check_eq(rows.length, 1); 1.424 + 1.425 + yield c.close(); 1.426 +}); 1.427 + 1.428 +add_task(function test_shrink_memory() { 1.429 + let c = yield getDummyDatabase("shrink_memory"); 1.430 + 1.431 + // It's just a simple sanity test. We have no way of measuring whether this 1.432 + // actually does anything. 1.433 + 1.434 + yield c.shrinkMemory(); 1.435 + yield c.close(); 1.436 +}); 1.437 + 1.438 +add_task(function test_no_shrink_on_init() { 1.439 + let c = yield getConnection("no_shrink_on_init", 1.440 + {shrinkMemoryOnConnectionIdleMS: 200}); 1.441 + 1.442 + let oldShrink = c.shrinkMemory; 1.443 + let count = 0; 1.444 + Object.defineProperty(c, "shrinkMemory", { 1.445 + value: function () { 1.446 + count++; 1.447 + }, 1.448 + }); 1.449 + 1.450 + // We should not shrink until a statement has been executed. 1.451 + yield sleep(220); 1.452 + do_check_eq(count, 0); 1.453 + 1.454 + yield c.execute("SELECT 1"); 1.455 + yield sleep(220); 1.456 + do_check_eq(count, 1); 1.457 + 1.458 + yield c.close(); 1.459 +}); 1.460 + 1.461 +add_task(function test_idle_shrink_fires() { 1.462 + let c = yield getDummyDatabase("idle_shrink_fires", 1.463 + {shrinkMemoryOnConnectionIdleMS: 200}); 1.464 + c._clearIdleShrinkTimer(); 1.465 + 1.466 + let oldShrink = c.shrinkMemory; 1.467 + let shrinkPromises = []; 1.468 + 1.469 + let count = 0; 1.470 + Object.defineProperty(c, "shrinkMemory", { 1.471 + value: function () { 1.472 + count++; 1.473 + let promise = oldShrink.call(c); 1.474 + shrinkPromises.push(promise); 1.475 + return promise; 1.476 + }, 1.477 + }); 1.478 + 1.479 + // We reset the idle shrink timer after monkeypatching because otherwise the 1.480 + // installed timer callback will reference the non-monkeypatched function. 1.481 + c._startIdleShrinkTimer(); 1.482 + 1.483 + yield sleep(220); 1.484 + do_check_eq(count, 1); 1.485 + do_check_eq(shrinkPromises.length, 1); 1.486 + yield shrinkPromises[0]; 1.487 + shrinkPromises.shift(); 1.488 + 1.489 + // We shouldn't shrink again unless a statement was executed. 1.490 + yield sleep(300); 1.491 + do_check_eq(count, 1); 1.492 + 1.493 + yield c.execute("SELECT 1"); 1.494 + yield sleep(300); 1.495 + 1.496 + do_check_eq(count, 2); 1.497 + do_check_eq(shrinkPromises.length, 1); 1.498 + yield shrinkPromises[0]; 1.499 + 1.500 + yield c.close(); 1.501 +}); 1.502 + 1.503 +add_task(function test_idle_shrink_reset_on_operation() { 1.504 + const INTERVAL = 500; 1.505 + let c = yield getDummyDatabase("idle_shrink_reset_on_operation", 1.506 + {shrinkMemoryOnConnectionIdleMS: INTERVAL}); 1.507 + 1.508 + c._clearIdleShrinkTimer(); 1.509 + 1.510 + let oldShrink = c.shrinkMemory; 1.511 + let shrinkPromises = []; 1.512 + let count = 0; 1.513 + 1.514 + Object.defineProperty(c, "shrinkMemory", { 1.515 + value: function () { 1.516 + count++; 1.517 + let promise = oldShrink.call(c); 1.518 + shrinkPromises.push(promise); 1.519 + return promise; 1.520 + }, 1.521 + }); 1.522 + 1.523 + let now = new Date(); 1.524 + c._startIdleShrinkTimer(); 1.525 + 1.526 + let initialIdle = new Date(now.getTime() + INTERVAL); 1.527 + 1.528 + // Perform database operations until initial scheduled time has been passed. 1.529 + let i = 0; 1.530 + while (new Date() < initialIdle) { 1.531 + yield c.execute("INSERT INTO dirs (path) VALUES (?)", ["" + i]); 1.532 + i++; 1.533 + } 1.534 + 1.535 + do_check_true(i > 0); 1.536 + 1.537 + // We should not have performed an idle while doing operations. 1.538 + do_check_eq(count, 0); 1.539 + 1.540 + // Wait for idle timer. 1.541 + yield sleep(INTERVAL); 1.542 + 1.543 + // Ensure we fired. 1.544 + do_check_eq(count, 1); 1.545 + do_check_eq(shrinkPromises.length, 1); 1.546 + yield shrinkPromises[0]; 1.547 + 1.548 + yield c.close(); 1.549 +}); 1.550 + 1.551 +add_task(function test_in_progress_counts() { 1.552 + let c = yield getDummyDatabase("in_progress_counts"); 1.553 + do_check_eq(c._statementCounter, c._initialStatementCount); 1.554 + do_check_eq(c._pendingStatements.size, 0); 1.555 + yield c.executeCached("INSERT INTO dirs (path) VALUES ('foo')"); 1.556 + do_check_eq(c._statementCounter, c._initialStatementCount + 1); 1.557 + do_check_eq(c._pendingStatements.size, 0); 1.558 + 1.559 + let expectOne; 1.560 + let expectTwo; 1.561 + 1.562 + // Please forgive me. 1.563 + let inner = Async.makeSpinningCallback(); 1.564 + let outer = Async.makeSpinningCallback(); 1.565 + 1.566 + // We want to make sure that two queries executing simultaneously 1.567 + // result in `_pendingStatements.size` reaching 2, then dropping back to 0. 1.568 + // 1.569 + // To do so, we kick off a second statement within the row handler 1.570 + // of the first, then wait for both to finish. 1.571 + 1.572 + yield c.executeCached("SELECT * from dirs", null, function onRow() { 1.573 + // In the onRow handler, we're still an outstanding query. 1.574 + // Expect a single in-progress entry. 1.575 + expectOne = c._pendingStatements.size; 1.576 + 1.577 + // Start another query, checking that after its statement has been created 1.578 + // there are two statements in progress. 1.579 + let p = c.executeCached("SELECT 10, path from dirs"); 1.580 + expectTwo = c._pendingStatements.size; 1.581 + 1.582 + // Now wait for it to be done before we return from the row handler … 1.583 + p.then(function onInner() { 1.584 + inner(); 1.585 + }); 1.586 + }).then(function onOuter() { 1.587 + // … and wait for the inner to be done before we finish … 1.588 + inner.wait(); 1.589 + outer(); 1.590 + }); 1.591 + 1.592 + // … and wait for both queries to have finished before we go on and 1.593 + // test postconditions. 1.594 + outer.wait(); 1.595 + 1.596 + do_check_eq(expectOne, 1); 1.597 + do_check_eq(expectTwo, 2); 1.598 + do_check_eq(c._statementCounter, c._initialStatementCount + 3); 1.599 + do_check_eq(c._pendingStatements.size, 0); 1.600 + 1.601 + yield c.close(); 1.602 +}); 1.603 + 1.604 +add_task(function test_discard_while_active() { 1.605 + let c = yield getDummyDatabase("discard_while_active"); 1.606 + 1.607 + yield c.executeCached("INSERT INTO dirs (path) VALUES ('foo')"); 1.608 + yield c.executeCached("INSERT INTO dirs (path) VALUES ('bar')"); 1.609 + 1.610 + let discarded = -1; 1.611 + let first = true; 1.612 + let sql = "SELECT * FROM dirs"; 1.613 + yield c.executeCached(sql, null, function onRow(row) { 1.614 + if (!first) { 1.615 + return; 1.616 + } 1.617 + first = false; 1.618 + discarded = c.discardCachedStatements(); 1.619 + }); 1.620 + 1.621 + // We discarded everything, because the SELECT had already started to run. 1.622 + do_check_eq(3, discarded); 1.623 + 1.624 + // And again is safe. 1.625 + do_check_eq(0, c.discardCachedStatements()); 1.626 + 1.627 + yield c.close(); 1.628 +}); 1.629 + 1.630 +add_task(function test_discard_cached() { 1.631 + let c = yield getDummyDatabase("discard_cached"); 1.632 + 1.633 + yield c.executeCached("SELECT * from dirs"); 1.634 + do_check_eq(1, c._cachedStatements.size); 1.635 + 1.636 + yield c.executeCached("SELECT * from files"); 1.637 + do_check_eq(2, c._cachedStatements.size); 1.638 + 1.639 + yield c.executeCached("SELECT * from dirs"); 1.640 + do_check_eq(2, c._cachedStatements.size); 1.641 + 1.642 + c.discardCachedStatements(); 1.643 + do_check_eq(0, c._cachedStatements.size); 1.644 + 1.645 + yield c.close(); 1.646 +}); 1.647 + 1.648 +add_task(function test_programmatic_binding() { 1.649 + let c = yield getDummyDatabase("programmatic_binding"); 1.650 + 1.651 + let bindings = [ 1.652 + {id: 1, path: "foobar"}, 1.653 + {id: null, path: "baznoo"}, 1.654 + {id: 5, path: "toofoo"}, 1.655 + ]; 1.656 + 1.657 + let sql = "INSERT INTO dirs VALUES (:id, :path)"; 1.658 + let result = yield c.execute(sql, bindings); 1.659 + do_check_eq(result.length, 0); 1.660 + 1.661 + let rows = yield c.executeCached("SELECT * from dirs"); 1.662 + do_check_eq(rows.length, 3); 1.663 + yield c.close(); 1.664 +}); 1.665 + 1.666 +add_task(function test_programmatic_binding_transaction() { 1.667 + let c = yield getDummyDatabase("programmatic_binding_transaction"); 1.668 + 1.669 + let bindings = [ 1.670 + {id: 1, path: "foobar"}, 1.671 + {id: null, path: "baznoo"}, 1.672 + {id: 5, path: "toofoo"}, 1.673 + ]; 1.674 + 1.675 + let sql = "INSERT INTO dirs VALUES (:id, :path)"; 1.676 + yield c.executeTransaction(function transaction() { 1.677 + let result = yield c.execute(sql, bindings); 1.678 + do_check_eq(result.length, 0); 1.679 + 1.680 + let rows = yield c.executeCached("SELECT * from dirs"); 1.681 + do_check_eq(rows.length, 3); 1.682 + }); 1.683 + 1.684 + // Transaction committed. 1.685 + let rows = yield c.executeCached("SELECT * from dirs"); 1.686 + do_check_eq(rows.length, 3); 1.687 + yield c.close(); 1.688 +}); 1.689 + 1.690 +add_task(function test_programmatic_binding_transaction_partial_rollback() { 1.691 + let c = yield getDummyDatabase("programmatic_binding_transaction_partial_rollback"); 1.692 + 1.693 + let bindings = [ 1.694 + {id: 2, path: "foobar"}, 1.695 + {id: 3, path: "toofoo"}, 1.696 + ]; 1.697 + 1.698 + let sql = "INSERT INTO dirs VALUES (:id, :path)"; 1.699 + 1.700 + // Add some data in an implicit transaction before beginning the batch insert. 1.701 + yield c.execute(sql, {id: 1, path: "works"}); 1.702 + 1.703 + let secondSucceeded = false; 1.704 + try { 1.705 + yield c.executeTransaction(function transaction() { 1.706 + // Insert one row. This won't implicitly start a transaction. 1.707 + let result = yield c.execute(sql, bindings[0]); 1.708 + 1.709 + // Insert multiple rows. mozStorage will want to start a transaction. 1.710 + // One of the inserts will fail, so the transaction should be rolled back. 1.711 + let result = yield c.execute(sql, bindings); 1.712 + secondSucceeded = true; 1.713 + }); 1.714 + } catch (ex) { 1.715 + print("Caught expected exception: " + ex); 1.716 + } 1.717 + 1.718 + // We did not get to the end of our in-transaction block. 1.719 + do_check_false(secondSucceeded); 1.720 + 1.721 + // Everything that happened in *our* transaction, not mozStorage's, got 1.722 + // rolled back, but the first row still exists. 1.723 + let rows = yield c.executeCached("SELECT * from dirs"); 1.724 + do_check_eq(rows.length, 1); 1.725 + do_check_eq(rows[0].getResultByName("path"), "works"); 1.726 + yield c.close(); 1.727 +}); 1.728 + 1.729 +/** 1.730 + * Just like the previous test, but relying on the implicit 1.731 + * transaction established by mozStorage. 1.732 + */ 1.733 +add_task(function test_programmatic_binding_implicit_transaction() { 1.734 + let c = yield getDummyDatabase("programmatic_binding_implicit_transaction"); 1.735 + 1.736 + let bindings = [ 1.737 + {id: 2, path: "foobar"}, 1.738 + {id: 1, path: "toofoo"}, 1.739 + ]; 1.740 + 1.741 + let sql = "INSERT INTO dirs VALUES (:id, :path)"; 1.742 + let secondSucceeded = false; 1.743 + yield c.execute(sql, {id: 1, path: "works"}); 1.744 + try { 1.745 + let result = yield c.execute(sql, bindings); 1.746 + secondSucceeded = true; 1.747 + } catch (ex) { 1.748 + print("Caught expected exception: " + ex); 1.749 + } 1.750 + 1.751 + do_check_false(secondSucceeded); 1.752 + 1.753 + // The entire batch failed. 1.754 + let rows = yield c.executeCached("SELECT * from dirs"); 1.755 + do_check_eq(rows.length, 1); 1.756 + do_check_eq(rows[0].getResultByName("path"), "works"); 1.757 + yield c.close(); 1.758 +}); 1.759 + 1.760 +/** 1.761 + * Test that direct binding of params and execution through mozStorage doesn't 1.762 + * error when we manually create a transaction. See Bug 856925. 1.763 + */ 1.764 +add_task(function test_direct() { 1.765 + let file = FileUtils.getFile("TmpD", ["test_direct.sqlite"]); 1.766 + file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); 1.767 + print("Opening " + file.path); 1.768 + 1.769 + let db = Services.storage.openDatabase(file); 1.770 + print("Opened " + db); 1.771 + 1.772 + db.executeSimpleSQL("CREATE TABLE types (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, UNIQUE (name))"); 1.773 + print("Executed setup."); 1.774 + 1.775 + let statement = db.createAsyncStatement("INSERT INTO types (name) VALUES (:name)"); 1.776 + let params = statement.newBindingParamsArray(); 1.777 + let one = params.newBindingParams(); 1.778 + one.bindByName("name", null); 1.779 + params.addParams(one); 1.780 + let two = params.newBindingParams(); 1.781 + two.bindByName("name", "bar"); 1.782 + params.addParams(two); 1.783 + 1.784 + print("Beginning transaction."); 1.785 + let begin = db.createAsyncStatement("BEGIN DEFERRED TRANSACTION"); 1.786 + let end = db.createAsyncStatement("COMMIT TRANSACTION"); 1.787 + 1.788 + let deferred = Promise.defer(); 1.789 + begin.executeAsync({ 1.790 + handleCompletion: function (reason) { 1.791 + deferred.resolve(); 1.792 + } 1.793 + }); 1.794 + yield deferred.promise; 1.795 + 1.796 + statement.bindParameters(params); 1.797 + 1.798 + deferred = Promise.defer(); 1.799 + print("Executing async."); 1.800 + statement.executeAsync({ 1.801 + handleResult: function (resultSet) { 1.802 + }, 1.803 + 1.804 + handleError: function (error) { 1.805 + print("Error when executing SQL (" + error.result + "): " + 1.806 + error.message); 1.807 + print("Original error: " + error.error); 1.808 + errors.push(error); 1.809 + deferred.reject(); 1.810 + }, 1.811 + 1.812 + handleCompletion: function (reason) { 1.813 + print("Completed."); 1.814 + deferred.resolve(); 1.815 + } 1.816 + }); 1.817 + 1.818 + yield deferred.promise; 1.819 + 1.820 + deferred = Promise.defer(); 1.821 + end.executeAsync({ 1.822 + handleCompletion: function (reason) { 1.823 + deferred.resolve(); 1.824 + } 1.825 + }); 1.826 + yield deferred.promise; 1.827 + 1.828 + statement.finalize(); 1.829 + begin.finalize(); 1.830 + end.finalize(); 1.831 + 1.832 + deferred = Promise.defer(); 1.833 + db.asyncClose(function () { 1.834 + deferred.resolve() 1.835 + }); 1.836 + yield deferred.promise; 1.837 +}); 1.838 + 1.839 +/** 1.840 + * Test Sqlite.cloneStorageConnection. 1.841 + */ 1.842 +add_task(function* test_cloneStorageConnection() { 1.843 + let file = new FileUtils.File(OS.Path.join(OS.Constants.Path.profileDir, 1.844 + "test_cloneStorageConnection.sqlite")); 1.845 + let c = yield new Promise((success, failure) => { 1.846 + Services.storage.openAsyncDatabase(file, null, (status, db) => { 1.847 + if (Components.isSuccessCode(status)) { 1.848 + success(db.QueryInterface(Ci.mozIStorageAsyncConnection)); 1.849 + } else { 1.850 + failure(new Error(status)); 1.851 + } 1.852 + }); 1.853 + }); 1.854 + 1.855 + let clone = yield Sqlite.cloneStorageConnection({ connection: c, readOnly: true }); 1.856 + // Just check that it works. 1.857 + yield clone.execute("SELECT 1"); 1.858 + 1.859 + let clone2 = yield Sqlite.cloneStorageConnection({ connection: c, readOnly: false }); 1.860 + // Just check that it works. 1.861 + yield clone2.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)"); 1.862 + 1.863 + // Closing order should not matter. 1.864 + yield c.asyncClose(); 1.865 + yield clone2.close(); 1.866 + yield clone.close(); 1.867 +}); 1.868 + 1.869 +/** 1.870 + * Test Sqlite.cloneStorageConnection invalid argument. 1.871 + */ 1.872 +add_task(function* test_cloneStorageConnection() { 1.873 + try { 1.874 + let clone = yield Sqlite.cloneStorageConnection({ connection: null }); 1.875 + do_throw(new Error("Should throw on invalid connection")); 1.876 + } catch (ex if ex.name == "TypeError") {} 1.877 +}); 1.878 + 1.879 +/** 1.880 + * Test clone() method. 1.881 + */ 1.882 +add_task(function* test_clone() { 1.883 + let c = yield getDummyDatabase("clone"); 1.884 + 1.885 + let clone = yield c.clone(); 1.886 + // Just check that it works. 1.887 + yield clone.execute("SELECT 1"); 1.888 + // Closing order should not matter. 1.889 + yield c.close(); 1.890 + yield clone.close(); 1.891 +}); 1.892 + 1.893 +/** 1.894 + * Test clone(readOnly) method. 1.895 + */ 1.896 +add_task(function* test_readOnly_clone() { 1.897 + let path = OS.Path.join(OS.Constants.Path.profileDir, "test_readOnly_clone.sqlite"); 1.898 + let c = yield Sqlite.openConnection({path: path, sharedMemoryCache: false}); 1.899 + 1.900 + let clone = yield c.clone(true); 1.901 + // Just check that it works. 1.902 + yield clone.execute("SELECT 1"); 1.903 + // But should not be able to write. 1.904 + try { 1.905 + yield clone.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)"); 1.906 + do_throw(new Error("Should not be able to write to a read-only clone.")); 1.907 + } catch (ex) {} 1.908 + // Closing order should not matter. 1.909 + yield c.close(); 1.910 + yield clone.close(); 1.911 +});