diff --git a/toolkit/modules/Sqlite.jsm b/toolkit/modules/Sqlite.jsm index 6d392ff863f..2a7dff36bb6 100644 --- a/toolkit/modules/Sqlite.jsm +++ b/toolkit/modules/Sqlite.jsm @@ -26,7 +26,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "Task", // Counts the number of created connections per database basename(). This is // used for logging to distinguish connection instances. -let connectionCounters = {}; +let connectionCounters = new Map(); /** @@ -89,34 +89,37 @@ function openConnection(options) { } let file = FileUtils.File(path); - let openDatabaseFn = sharedMemoryCache ? - Services.storage.openDatabase : - Services.storage.openUnsharedDatabase; let basename = OS.Path.basename(path); + let number = connectionCounters.get(basename) || 0; + connectionCounters.set(basename, number + 1); - if (!connectionCounters[basename]) { - connectionCounters[basename] = 1; - } - - let number = connectionCounters[basename]++; let identifier = basename + "#" + number; log.info("Opening database: " + path + " (" + identifier + ")"); - try { - let connection = openDatabaseFn(file); - - if (!connection.connectionReady) { - log.warn("Connection is not ready."); - return Promise.reject(new Error("Connection is not ready.")); - } - - return Promise.resolve(new OpenedConnection(connection, basename, number, - openedOptions)); - } catch (ex) { - log.warn("Could not open database: " + CommonUtils.exceptionStr(ex)); - return Promise.reject(ex); + let deferred = Promise.defer(); + let options = null; + if (!sharedMemoryCache) { + options = Cc["@mozilla.org/hash-property-bag;1"]. + createInstance(Ci.nsIWritablePropertyBag); + options.setProperty("shared", false); } + Services.storage.openAsyncDatabase(file, options, function(status, connection) { + if (!connection) { + log.warn("Could not open connection: " + status); + deferred.reject(new Error("Could not open connection: " + status)); + } + log.warn("Connection opened"); + try { + deferred.resolve( + new OpenedConnection(connection.QueryInterface(Ci.mozIStorageAsyncConnection), basename, number, + openedOptions)); + } catch (ex) { + log.warn("Could not open database: " + CommonUtils.exceptionStr(ex)); + deferred.reject(ex); + } + }); + return deferred.promise; } @@ -225,51 +228,32 @@ OpenedConnection.prototype = Object.freeze({ TRANSACTION_TYPES: ["DEFERRED", "IMMEDIATE", "EXCLUSIVE"], - get connectionReady() { - return this._open && this._connection.connectionReady; - }, - - /** - * The row ID from the last INSERT operation. - * - * Because all statements are executed asynchronously, this could - * return unexpected results if multiple statements are performed in - * parallel. It is the caller's responsibility to schedule - * appropriately. - * - * It is recommended to only use this within transactions (which are - * handled as sequential statements via Tasks). - */ - get lastInsertRowID() { - this._ensureOpen(); - return this._connection.lastInsertRowID; - }, - - /** - * The number of rows that were changed, inserted, or deleted by the - * last operation. - * - * The same caveats regarding asynchronous execution for - * `lastInsertRowID` also apply here. - */ - get affectedRows() { - this._ensureOpen(); - return this._connection.affectedRows; - }, - /** * The integer schema version of the database. * * This is 0 if not schema version has been set. + * + * @return Promise */ - get schemaVersion() { - this._ensureOpen(); - return this._connection.schemaVersion; + getSchemaVersion: function() { + let self = this; + return this.execute("PRAGMA user_version").then( + function onSuccess(result) { + if (result == null) { + return 0; + } + return JSON.stringify(result[0].getInt32(0)); + } + ); }, - set schemaVersion(value) { + setSchemaVersion: function(value) { + if (!Number.isInteger(value)) { + // Guarding against accidental SQLi + throw new TypeError("Schema version must be an integer. Got " + value); + } this._ensureOpen(); - this._connection.schemaVersion = value; + return this.execute("PRAGMA user_version = " + value); }, /** diff --git a/toolkit/modules/tests/xpcshell/test_sqlite.js b/toolkit/modules/tests/xpcshell/test_sqlite.js index 3d41800cffb..7fcf947161e 100644 --- a/toolkit/modules/tests/xpcshell/test_sqlite.js +++ b/toolkit/modules/tests/xpcshell/test_sqlite.js @@ -87,6 +87,34 @@ add_task(function test_get_dummy_database() { yield db.close(); }); +add_task(function test_schema_version() { + let db = yield getDummyDatabase("schema_version"); + + let version = yield db.getSchemaVersion(); + do_check_eq(version, 0); + + db.setSchemaVersion(14); + version = yield db.getSchemaVersion(); + do_check_eq(version, 14); + + for (let v of [0.5, "foobar", NaN]) { + let success; + try { + yield db.setSchemaVersion(v); + do_print("Schema version " + v + " should have been rejected"); + success = false; + } catch (ex if ex.message.startsWith("Schema version must be an integer.")) { + success = true; + } + do_check_true(success); + + version = yield db.getSchemaVersion(); + do_check_eq(version, 14); + } + + yield db.close(); +}); + add_task(function test_simple_insert() { let c = yield getDummyDatabase("simple_insert"); @@ -109,8 +137,10 @@ add_task(function test_simple_bound_object() { let result = yield c.execute("INSERT INTO dirs VALUES (:id, :path)", {id: 1, path: "foo"}); do_check_eq(result.length, 0); - do_check_eq(c.lastInsertRowID, 1); - do_check_eq(c.affectedRows, 1); + result = yield c.execute("SELECT id, path FROM dirs"); + do_check_eq(result.length, 1); + do_check_eq(result[0].getResultByName("id"), 1); + do_check_eq(result[0].getResultByName("path"), "foo"); yield c.close(); });