1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 this.EXPORTED_SYMBOLS = [
11 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
13 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
15 XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
16 "resource://gre/modules/AsyncShutdown.jsm");
17 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
18 "resource://gre/modules/Promise.jsm");
19 XPCOMUtils.defineLazyModuleGetter(this, "Services",
20 "resource://gre/modules/Services.jsm");
21 XPCOMUtils.defineLazyModuleGetter(this, "OS",
22 "resource://gre/modules/osfile.jsm");
23 XPCOMUtils.defineLazyModuleGetter(this, "Log",
24 "resource://gre/modules/Log.jsm");
25 XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
26 "resource://services-common/utils.js");
27 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
28 "resource://gre/modules/FileUtils.jsm");
29 XPCOMUtils.defineLazyModuleGetter(this, "Task",
30 "resource://gre/modules/Task.jsm");
31 XPCOMUtils.defineLazyServiceGetter(this, "FinalizationWitnessService",
32 "@mozilla.org/toolkit/finalizationwitness;1",
33 "nsIFinalizationWitnessService");
36 // Counts the number of created connections per database basename(). This is
37 // used for logging to distinguish connection instances.
38 let connectionCounters = new Map();
41 * Once `true`, reject any attempt to open or close a database.
46 // Tests should fail if a connection auto closes. The exception is
47 // when finalization itself is tested, in which case this flag
48 // should be set to false.
49 failTestsOnAutoClose: true
52 // Displays a script error message
53 function logScriptError(message) {
54 let consoleMessage = Cc["@mozilla.org/scripterror;1"].
55 createInstance(Ci.nsIScriptError);
56 let stack = new Error();
57 consoleMessage.init(message, stack.fileName, null, stack.lineNumber, 0,
58 Ci.nsIScriptError.errorFlag, "component javascript");
59 Services.console.logMessage(consoleMessage);
61 // This `Promise.reject` will cause tests to fail. The debugging
62 // flag can be used to suppress this for tests that explicitly
64 if (Debugging.failTestsOnAutoClose) {
65 Promise.reject(new Error(message));
70 * Barriers used to ensure that Sqlite.jsm is shutdown after all
73 XPCOMUtils.defineLazyGetter(this, "Barriers", () => {
76 * Public barrier that clients may use to add blockers to the
77 * shutdown of Sqlite.jsm. Triggered by profile-before-change.
78 * Once all blockers of this barrier are lifted, we close the
79 * ability to open new connections.
81 shutdown: new AsyncShutdown.Barrier("Sqlite.jsm: wait until all clients have completed their task"),
84 * Private barrier blocked by connections that are still open.
85 * Triggered after Barriers.shutdown is lifted and `isClosed` is
88 connections: new AsyncShutdown.Barrier("Sqlite.jsm: wait until all connections are closed"),
92 * Observer for the event which is broadcasted when the finalization
93 * witness `_witness` of `OpenedConnection` is garbage collected.
95 * The observer is passed the connection identifier of the database
96 * connection that is being finalized.
98 let finalizationObserver = function (subject, topic, connectionIdentifier) {
99 let connectionData = ConnectionData.byId.get(connectionIdentifier);
101 if (connectionData === undefined) {
102 logScriptError("Error: Attempt to finalize unknown Sqlite connection: " +
103 connectionIdentifier + "\n");
107 ConnectionData.byId.delete(connectionIdentifier);
108 logScriptError("Warning: Sqlite connection '" + connectionIdentifier +
109 "' was not properly closed. Auto-close triggered by garbage collection.\n");
110 connectionData.close();
112 Services.obs.addObserver(finalizationObserver, "sqlite-finalization-witness", false);
115 * Ensure that Sqlite.jsm:
116 * - informs its clients before shutting down;
117 * - lets clients open connections during shutdown, if necessary;
118 * - waits for all connections to be closed before shutdown.
120 AsyncShutdown.profileBeforeChange.addBlocker("Sqlite.jsm shutdown blocker",
121 Task.async(function* () {
122 yield Barriers.shutdown.wait();
123 // At this stage, all clients have had a chance to open (and close)
124 // their databases. Some previous close operations may still be pending,
125 // so we need to wait until they are complete before proceeding.
127 // Prevent any new opening.
130 // Now, wait until all databases are closed
131 yield Barriers.connections.wait();
133 // Everything closed, no finalization events to catch
134 Services.obs.removeObserver(finalizationObserver, "sqlite-finalization-witness");
139 // We are waiting for the connections to close. The interesting
140 // status is therefore the list of connections still pending.
141 return { description: "Waiting for connections to close",
142 state: Barriers.connections.state };
145 // We are still in the first stage: waiting for the barrier
146 // to be lifted. The interesting status is therefore that of
148 return { description: "Waiting for the barrier to be lifted",
149 state: Barriers.shutdown.state };
156 * Connection data with methods necessary for closing the connection.
158 * To support auto-closing in the event of garbage collection, this
159 * data structure contains all the connection data of an opened
160 * connection and all of the methods needed for sucessfully closing
163 * By putting this information in its own separate object, it is
164 * possible to store an additional reference to it without preventing
165 * a garbage collection of a finalization witness in
166 * OpenedConnection. When the witness detects a garbage collection,
167 * this object can be used to close the connection.
169 * This object contains more methods than just `close`. When
170 * OpenedConnection needs to use the methods in this object, it will
171 * dispatch its method calls here.
173 function ConnectionData(connection, basename, number, options) {
174 this._log = Log.repository.getLoggerWithMessagePrefix("Sqlite.Connection." + basename,
175 "Conn #" + number + ": ");
176 this._log.info("Opened");
178 this._dbConn = connection;
179 this._connectionIdentifier = basename + " Conn #" + number;
182 this._cachedStatements = new Map();
183 this._anonymousStatements = new Map();
184 this._anonymousCounter = 0;
186 // A map from statement index to mozIStoragePendingStatement, to allow for
187 // canceling prior to finalizing the mozIStorageStatements.
188 this._pendingStatements = new Map();
190 // Increments for each executed statement for the life of the connection.
191 this._statementCounter = 0;
193 this._inProgressTransaction = null;
195 this._idleShrinkMS = options.shrinkMemoryOnConnectionIdleMS;
196 if (this._idleShrinkMS) {
197 this._idleShrinkTimer = Cc["@mozilla.org/timer;1"]
198 .createInstance(Ci.nsITimer);
199 // We wait for the first statement execute to start the timer because
200 // shrinking now would not do anything.
203 this._deferredClose = Promise.defer();
204 this._closeRequested = false;
206 Barriers.connections.client.addBlocker(
207 this._connectionIdentifier + ": waiting for shutdown",
208 this._deferredClose.promise,
210 identifier: this._connectionIdentifier,
211 isCloseRequested: this._closeRequested,
212 hasDbConn: !!this._dbConn,
213 hasInProgressTransaction: !!this._inProgressTransaction,
214 pendingStatements: this._pendingStatements.size,
215 statementCounter: this._statementCounter,
221 * Map of connection identifiers to ConnectionData objects
223 * The connection identifier is a human-readable name of the
224 * database. Used by finalization witnesses to be able to close opened
225 * connections on garbage collection.
227 * Key: _connectionIdentifier of ConnectionData
228 * Value: ConnectionData object
230 ConnectionData.byId = new Map();
232 ConnectionData.prototype = Object.freeze({
234 this._closeRequested = true;
237 return this._deferredClose.promise;
240 this._log.debug("Request to close connection.");
241 this._clearIdleShrinkTimer();
243 // We need to take extra care with transactions during shutdown.
245 // If we don't have a transaction in progress, we can proceed with shutdown
247 if (!this._inProgressTransaction) {
248 this._finalize(this._deferredClose);
249 return this._deferredClose.promise;
252 // Else if we do have a transaction in progress, we forcefully roll it
253 // back. This is an async task, so we wait on it to finish before
254 // performing finalization.
255 this._log.warn("Transaction in progress at time of close. Rolling back.");
257 let onRollback = this._finalize.bind(this, this._deferredClose);
259 this.execute("ROLLBACK TRANSACTION").then(onRollback, onRollback);
260 this._inProgressTransaction.reject(new Error("Connection being closed."));
261 this._inProgressTransaction = null;
263 return this._deferredClose.promise;
266 clone: function (readOnly=false) {
269 this._log.debug("Request to clone connection.");
272 connection: this._dbConn,
275 if (this._idleShrinkMS)
276 options.shrinkMemoryOnConnectionIdleMS = this._idleShrinkMS;
278 return cloneStorageConnection(options);
281 _finalize: function (deferred) {
282 this._log.debug("Finalizing connection.");
283 // Cancel any pending statements.
284 for (let [k, statement] of this._pendingStatements) {
287 this._pendingStatements.clear();
289 // We no longer need to track these.
290 this._statementCounter = 0;
292 // Next we finalize all active statements.
293 for (let [k, statement] of this._anonymousStatements) {
294 statement.finalize();
296 this._anonymousStatements.clear();
298 for (let [k, statement] of this._cachedStatements) {
299 statement.finalize();
301 this._cachedStatements.clear();
303 // This guards against operations performed between the call to this
304 // function and asyncClose() finishing. See also bug 726990.
307 this._log.debug("Calling asyncClose().");
308 this._dbConn.asyncClose(() => {
309 this._log.info("Closed");
311 // Now that the connection is closed, no need to keep
312 // a blocker for Barriers.connections.
313 Barriers.connections.client.removeBlocker(deferred.promise);
318 executeCached: function (sql, params=null, onRow=null) {
322 throw new Error("sql argument is empty.");
325 let statement = this._cachedStatements.get(sql);
327 statement = this._dbConn.createAsyncStatement(sql);
328 this._cachedStatements.set(sql, statement);
331 this._clearIdleShrinkTimer();
333 let deferred = Promise.defer();
336 this._executeStatement(sql, statement, params, onRow).then(
338 this._startIdleShrinkTimer();
339 deferred.resolve(result);
342 this._startIdleShrinkTimer();
343 deferred.reject(error);
347 this._startIdleShrinkTimer();
351 return deferred.promise;
354 execute: function (sql, params=null, onRow=null) {
355 if (typeof(sql) != "string") {
356 throw new Error("Must define SQL to execute as a string: " + sql);
361 let statement = this._dbConn.createAsyncStatement(sql);
362 let index = this._anonymousCounter++;
364 this._anonymousStatements.set(index, statement);
365 this._clearIdleShrinkTimer();
367 let onFinished = () => {
368 this._anonymousStatements.delete(index);
369 statement.finalize();
370 this._startIdleShrinkTimer();
373 let deferred = Promise.defer();
376 this._executeStatement(sql, statement, params, onRow).then(
379 deferred.resolve(rows);
383 deferred.reject(error);
391 return deferred.promise;
394 get transactionInProgress() {
395 return this._open && !!this._inProgressTransaction;
398 executeTransaction: function (func, type) {
401 if (this._inProgressTransaction) {
402 throw new Error("A transaction is already active. Only one transaction " +
403 "can be active at a time.");
406 this._log.debug("Beginning transaction");
407 let deferred = Promise.defer();
408 this._inProgressTransaction = deferred;
409 Task.spawn(function doTransaction() {
410 // It's tempting to not yield here and rely on the implicit serial
411 // execution of issued statements. However, the yield serves an important
412 // purpose: catching errors in statement execution.
413 yield this.execute("BEGIN " + type + " TRANSACTION");
417 result = yield Task.spawn(func);
419 // It's possible that a request to close the connection caused the
421 // Assertion: close() will unset
422 // this._inProgressTransaction when called.
423 if (!this._inProgressTransaction) {
424 this._log.warn("Connection was closed while performing transaction. " +
425 "Received error should be due to closed connection: " +
426 CommonUtils.exceptionStr(ex));
430 this._log.warn("Error during transaction. Rolling back: " +
431 CommonUtils.exceptionStr(ex));
433 yield this.execute("ROLLBACK TRANSACTION");
435 this._log.warn("Could not roll back transaction. This is weird: " +
436 CommonUtils.exceptionStr(inner));
442 // See comment above about connection being closed during transaction.
443 if (!this._inProgressTransaction) {
444 this._log.warn("Connection was closed while performing transaction. " +
445 "Unable to commit.");
446 throw new Error("Connection closed before transaction committed.");
450 yield this.execute("COMMIT TRANSACTION");
452 this._log.warn("Error committing transaction: " +
453 CommonUtils.exceptionStr(ex));
457 throw new Task.Result(result);
459 function onSuccess(result) {
460 this._inProgressTransaction = null;
461 deferred.resolve(result);
463 function onError(error) {
464 this._inProgressTransaction = null;
465 deferred.reject(error);
469 return deferred.promise;
472 shrinkMemory: function () {
473 this._log.info("Shrinking memory usage.");
474 let onShrunk = this._clearIdleShrinkTimer.bind(this);
475 return this.execute("PRAGMA shrink_memory").then(onShrunk, onShrunk);
478 discardCachedStatements: function () {
480 for (let [k, statement] of this._cachedStatements) {
482 statement.finalize();
484 this._cachedStatements.clear();
485 this._log.debug("Discarded " + count + " cached statements.");
490 * Helper method to bind parameters of various kinds through
493 _bindParameters: function (statement, params) {
498 if (Array.isArray(params)) {
499 // It's an array of separate params.
500 if (params.length && (typeof(params[0]) == "object")) {
501 let paramsArray = statement.newBindingParamsArray();
502 for (let p of params) {
503 let bindings = paramsArray.newBindingParams();
504 for (let [key, value] of Iterator(p)) {
505 bindings.bindByName(key, value);
507 paramsArray.addParams(bindings);
510 statement.bindParameters(paramsArray);
515 for (let i = 0; i < params.length; i++) {
516 statement.bindByIndex(i, params[i]);
522 if (params && typeof(params) == "object") {
523 for (let k in params) {
524 statement.bindByName(k, params[k]);
529 throw new Error("Invalid type for bound parameters. Expected Array or " +
530 "object. Got: " + params);
533 _executeStatement: function (sql, statement, params, onRow) {
534 if (statement.state != statement.MOZ_STORAGE_STATEMENT_READY) {
535 throw new Error("Statement is not ready for execution.");
538 if (onRow && typeof(onRow) != "function") {
539 throw new Error("onRow must be a function. Got: " + onRow);
542 this._bindParameters(statement, params);
544 let index = this._statementCounter++;
546 let deferred = Promise.defer();
547 let userCancelled = false;
551 // Don't incur overhead for serializing params unless the messages go
553 if (this._log.level <= Log.Level.Trace) {
554 let msg = "Stmt #" + index + " " + sql;
557 msg += " - " + JSON.stringify(params);
559 this._log.trace(msg);
561 this._log.debug("Stmt #" + index + " starting");
565 let pending = statement.executeAsync({
566 handleResult: function (resultSet) {
567 // .cancel() may not be immediate and handleResult() could be called
568 // after a .cancel().
569 for (let row = resultSet.getNextRow(); row && !userCancelled; row = resultSet.getNextRow()) {
577 } catch (e if e instanceof StopIteration) {
578 userCancelled = true;
582 self._log.warn("Exception when calling onRow callback: " +
583 CommonUtils.exceptionStr(ex));
588 handleError: function (error) {
589 self._log.info("Error when executing SQL (" +
590 error.result + "): " + error.message);
594 handleCompletion: function (reason) {
595 self._log.debug("Stmt #" + index + " finished.");
596 self._pendingStatements.delete(index);
599 case Ci.mozIStorageStatementCallback.REASON_FINISHED:
600 // If there is an onRow handler, we always resolve to null.
601 let result = onRow ? null : rows;
602 deferred.resolve(result);
605 case Ci.mozIStorageStatementCallback.REASON_CANCELLED:
606 // It is not an error if the user explicitly requested cancel via
607 // the onRow handler.
609 let result = onRow ? null : rows;
610 deferred.resolve(result);
612 deferred.reject(new Error("Statement was cancelled."));
617 case Ci.mozIStorageStatementCallback.REASON_ERROR:
618 let error = new Error("Error(s) encountered during statement execution.");
619 error.errors = errors;
620 deferred.reject(error);
624 deferred.reject(new Error("Unknown completion reason code: " +
631 this._pendingStatements.set(index, pending);
632 return deferred.promise;
635 ensureOpen: function () {
637 throw new Error("Connection is not open.");
641 _clearIdleShrinkTimer: function () {
642 if (!this._idleShrinkTimer) {
646 this._idleShrinkTimer.cancel();
649 _startIdleShrinkTimer: function () {
650 if (!this._idleShrinkTimer) {
654 this._idleShrinkTimer.initWithCallback(this.shrinkMemory.bind(this),
656 this._idleShrinkTimer.TYPE_ONE_SHOT);
661 * Opens a connection to a SQLite database.
663 * The following parameters can control the connection:
665 * path -- (string) The filesystem path of the database file to open. If the
666 * file does not exist, a new database will be created.
668 * sharedMemoryCache -- (bool) Whether multiple connections to the database
669 * share the same memory cache. Sharing the memory cache likely results
670 * in less memory utilization. However, sharing also requires connections
671 * to obtain a lock, possibly making database access slower. Defaults to
674 * shrinkMemoryOnConnectionIdleMS -- (integer) If defined, the connection
675 * will attempt to minimize its memory usage after this many
676 * milliseconds of connection idle. The connection is idle when no
677 * statements are executing. There is no default value which means no
678 * automatic memory minimization will occur. Please note that this is
679 * *not* a timer on the idle service and this could fire while the
680 * application is active.
682 * FUTURE options to control:
684 * special named databases
685 * pragma TEMP STORE = MEMORY
690 * (Object) Parameters to control connection and open options.
692 * @return Promise<OpenedConnection>
694 function openConnection(options) {
695 let log = Log.repository.getLogger("Sqlite.ConnectionOpener");
698 throw new Error("path not specified in connection options.");
702 throw new Error("Sqlite.jsm has been shutdown. Cannot open connection to: " + options.path);
706 // Retains absolute paths and normalizes relative as relative to profile.
707 let path = OS.Path.join(OS.Constants.Path.profileDir, options.path);
709 let sharedMemoryCache = "sharedMemoryCache" in options ?
710 options.sharedMemoryCache : true;
712 let openedOptions = {};
714 if ("shrinkMemoryOnConnectionIdleMS" in options) {
715 if (!Number.isInteger(options.shrinkMemoryOnConnectionIdleMS)) {
716 throw new Error("shrinkMemoryOnConnectionIdleMS must be an integer. " +
717 "Got: " + options.shrinkMemoryOnConnectionIdleMS);
720 openedOptions.shrinkMemoryOnConnectionIdleMS =
721 options.shrinkMemoryOnConnectionIdleMS;
724 let file = FileUtils.File(path);
726 let basename = OS.Path.basename(path);
727 let number = connectionCounters.get(basename) || 0;
728 connectionCounters.set(basename, number + 1);
730 let identifier = basename + "#" + number;
732 log.info("Opening database: " + path + " (" + identifier + ")");
733 let deferred = Promise.defer();
734 let dbOptions = null;
735 if (!sharedMemoryCache) {
736 dbOptions = Cc["@mozilla.org/hash-property-bag;1"].
737 createInstance(Ci.nsIWritablePropertyBag);
738 dbOptions.setProperty("shared", false);
740 Services.storage.openAsyncDatabase(file, dbOptions, function(status, connection) {
742 log.warn("Could not open connection: " + status);
743 deferred.reject(new Error("Could not open connection: " + status));
746 log.info("Connection opened");
749 new OpenedConnection(connection.QueryInterface(Ci.mozIStorageAsyncConnection), basename, number,
752 log.warn("Could not open database: " + CommonUtils.exceptionStr(ex));
756 return deferred.promise;
760 * Creates a clone of an existing and open Storage connection. The clone has
761 * the same underlying characteristics of the original connection and is
762 * returned in form of on OpenedConnection handle.
764 * The following parameters can control the cloned connection:
766 * connection -- (mozIStorageAsyncConnection) The original Storage connection
767 * to clone. It's not possible to clone connections to memory databases.
769 * readOnly -- (boolean) - If true the clone will be read-only. If the
770 * original connection is already read-only, the clone will be, regardless
771 * of this option. If the original connection is using the shared cache,
772 * this parameter will be ignored and the clone will be as privileged as
773 * the original connection.
774 * shrinkMemoryOnConnectionIdleMS -- (integer) If defined, the connection
775 * will attempt to minimize its memory usage after this many
776 * milliseconds of connection idle. The connection is idle when no
777 * statements are executing. There is no default value which means no
778 * automatic memory minimization will occur. Please note that this is
779 * *not* a timer on the idle service and this could fire while the
780 * application is active.
784 * (Object) Parameters to control connection and clone options.
786 * @return Promise<OpenedConnection>
788 function cloneStorageConnection(options) {
789 let log = Log.repository.getLogger("Sqlite.ConnectionCloner");
791 let source = options && options.connection;
793 throw new TypeError("connection not specified in clone options.");
795 if (!source instanceof Ci.mozIStorageAsyncConnection) {
796 throw new TypeError("Connection must be a valid Storage connection.");
800 throw new Error("Sqlite.jsm has been shutdown. Cannot clone connection to: " + source.database.path);
803 let openedOptions = {};
805 if ("shrinkMemoryOnConnectionIdleMS" in options) {
806 if (!Number.isInteger(options.shrinkMemoryOnConnectionIdleMS)) {
807 throw new TypeError("shrinkMemoryOnConnectionIdleMS must be an integer. " +
808 "Got: " + options.shrinkMemoryOnConnectionIdleMS);
810 openedOptions.shrinkMemoryOnConnectionIdleMS =
811 options.shrinkMemoryOnConnectionIdleMS;
814 let path = source.databaseFile.path;
815 let basename = OS.Path.basename(path);
816 let number = connectionCounters.get(basename) || 0;
817 connectionCounters.set(basename, number + 1);
818 let identifier = basename + "#" + number;
820 log.info("Cloning database: " + path + " (" + identifier + ")");
821 let deferred = Promise.defer();
823 source.asyncClone(!!options.readOnly, (status, connection) => {
825 log.warn("Could not clone connection: " + status);
826 deferred.reject(new Error("Could not clone connection: " + status));
828 log.info("Connection cloned");
830 let conn = connection.QueryInterface(Ci.mozIStorageAsyncConnection);
831 deferred.resolve(new OpenedConnection(conn, basename, number,
834 log.warn("Could not clone database: " + CommonUtils.exceptionStr(ex));
838 return deferred.promise;
842 * Handle on an opened SQLite database.
844 * This is essentially a glorified wrapper around mozIStorageConnection.
845 * However, it offers some compelling advantages.
847 * The main functions on this type are `execute` and `executeCached`. These are
848 * ultimately how all SQL statements are executed. It's worth explaining their
851 * `execute` is used to execute one-shot SQL statements. These are SQL
852 * statements that are executed one time and then thrown away. They are useful
853 * for dynamically generated SQL statements and clients who don't care about
854 * performance (either their own or wasting resources in the overall
855 * application). Because of the performance considerations, it is recommended
856 * to avoid `execute` unless the statement you are executing will only be
857 * executed once or seldomly.
859 * `executeCached` is used to execute a statement that will presumably be
860 * executed multiple times. The statement is parsed once and stuffed away
861 * inside the connection instance. Subsequent calls to `executeCached` will not
862 * incur the overhead of creating a new statement object. This should be used
863 * in preference to `execute` when a specific SQL statement will be executed
866 * Instances of this type are not meant to be created outside of this file.
867 * Instead, first open an instance of `UnopenedSqliteConnection` and obtain
868 * an instance of this type by calling `open`.
870 * FUTURE IMPROVEMENTS
872 * Ability to enqueue operations. Currently there can be race conditions,
873 * especially as far as transactions are concerned. It would be nice to have
874 * an enqueueOperation(func) API that serially executes passed functions.
876 * Support for SAVEPOINT (named/nested transactions) might be useful.
879 * (mozIStorageConnection) Underlying SQLite connection.
881 * (string) The basename of this database name. Used for logging.
883 * (Number) The connection number to this database.
885 * (object) Options to control behavior of connection. See
888 function OpenedConnection(connection, basename, number, options) {
889 // Store all connection data in a field distinct from the
890 // witness. This enables us to store an additional reference to this
891 // field without preventing garbage collection of
892 // OpenedConnection. On garbage collection, we will still be able to
893 // close the database using this extra reference.
894 this._connectionData = new ConnectionData(connection, basename, number, options);
896 // Store the extra reference in a map with connection identifier as
898 ConnectionData.byId.set(this._connectionData._connectionIdentifier,
899 this._connectionData);
901 // Make a finalization witness. If this object is garbage collected
902 // before its `forget` method has been called, an event with topic
903 // "sqlite-finalization-witness" is broadcasted along with the
904 // connection identifier string of the database.
905 this._witness = FinalizationWitnessService.make(
906 "sqlite-finalization-witness",
907 this._connectionData._connectionIdentifier);
910 OpenedConnection.prototype = Object.freeze({
911 TRANSACTION_DEFERRED: "DEFERRED",
912 TRANSACTION_IMMEDIATE: "IMMEDIATE",
913 TRANSACTION_EXCLUSIVE: "EXCLUSIVE",
915 TRANSACTION_TYPES: ["DEFERRED", "IMMEDIATE", "EXCLUSIVE"],
918 * The integer schema version of the database.
920 * This is 0 if not schema version has been set.
922 * @return Promise<int>
924 getSchemaVersion: function() {
926 return this.execute("PRAGMA user_version").then(
927 function onSuccess(result) {
928 if (result == null) {
931 return JSON.stringify(result[0].getInt32(0));
936 setSchemaVersion: function(value) {
937 if (!Number.isInteger(value)) {
938 // Guarding against accidental SQLi
939 throw new TypeError("Schema version must be an integer. Got " + value);
941 this._connectionData.ensureOpen();
942 return this.execute("PRAGMA user_version = " + value);
946 * Close the database connection.
948 * This must be performed when you are finished with the database.
950 * Closing the database connection has the side effect of forcefully
951 * cancelling all active statements. Therefore, callers should ensure that
952 * all active statements have completed before closing the connection, if
955 * The returned promise will be resolved once the connection is closed.
956 * Successive calls to close() return the same promise.
958 * IMPROVEMENT: Resolve the promise to a closed connection which can be
964 // Unless cleanup has already been done by a previous call to
965 // `close`, delete the database entry from map and tell the
966 // finalization witness to forget.
967 if (ConnectionData.byId.has(this._connectionData._connectionIdentifier)) {
968 ConnectionData.byId.delete(this._connectionData._connectionIdentifier);
969 this._witness.forget();
971 return this._connectionData.close();
975 * Clones this connection to a new Sqlite one.
977 * The following parameters can control the cloned connection:
980 * (boolean) - If true the clone will be read-only. If the original
981 * connection is already read-only, the clone will be, regardless of
982 * this option. If the original connection is using the shared cache,
983 * this parameter will be ignored and the clone will be as privileged as
984 * the original connection.
986 * @return Promise<OpenedConnection>
988 clone: function (readOnly=false) {
989 return this._connectionData.clone(readOnly);
993 * Execute a SQL statement and cache the underlying statement object.
995 * This function executes a SQL statement and also caches the underlying
996 * derived statement object so subsequent executions are faster and use
999 * This function optionally binds parameters to the statement as well as
1000 * optionally invokes a callback for every row retrieved.
1002 * By default, no parameters are bound and no callback will be invoked for
1005 * Bound parameters can be defined as an Array of positional arguments or
1006 * an object mapping named parameters to their values. If there are no bound
1007 * parameters, the caller can pass nothing or null for this argument.
1009 * Callers are encouraged to pass objects rather than Arrays for bound
1010 * parameters because they prevent foot guns. With positional arguments, it
1011 * is simple to modify the parameter count or positions without fixing all
1012 * users of the statement. Objects/named parameters are a little safer
1013 * because changes in order alone won't result in bad things happening.
1015 * When `onRow` is not specified, all returned rows are buffered before the
1016 * returned promise is resolved. For INSERT or UPDATE statements, this has
1017 * no effect because no rows are returned from these. However, it has
1018 * implications for SELECT statements.
1020 * If your SELECT statement could return many rows or rows with large amounts
1021 * of data, for performance reasons it is recommended to pass an `onRow`
1022 * handler. Otherwise, the buffering may consume unacceptable amounts of
1025 * If a `StopIteration` is thrown during execution of an `onRow` handler,
1026 * the execution of the statement is immediately cancelled. Subsequent
1027 * rows will not be processed and no more `onRow` invocations will be made.
1028 * The promise is resolved immediately.
1030 * If a non-`StopIteration` exception is thrown by the `onRow` handler, the
1031 * exception is logged and processing of subsequent rows occurs as if nothing
1032 * happened. The promise is still resolved (not rejected).
1034 * The return value is a promise that will be resolved when the statement
1035 * has completed fully.
1037 * The promise will be rejected with an `Error` instance if the statement
1038 * did not finish execution fully. The `Error` may have an `errors` property.
1039 * If defined, it will be an Array of objects describing individual errors.
1040 * Each object has the properties `result` and `message`. `result` is a
1041 * numeric error code and `message` is a string description of the problem.
1044 * (string) The name of the registered statement to execute.
1045 * @param params optional
1046 * (Array or object) Parameters to bind.
1047 * @param onRow optional
1048 * (function) Callback to receive each row from result.
1050 executeCached: function (sql, params=null, onRow=null) {
1051 return this._connectionData.executeCached(sql, params, onRow);
1055 * Execute a one-shot SQL statement.
1057 * If you find yourself feeding the same SQL string in this function, you
1058 * should *not* use this function and instead use `executeCached`.
1060 * See `executeCached` for the meaning of the arguments and extended usage info.
1063 * (string) SQL to execute.
1064 * @param params optional
1065 * (Array or Object) Parameters to bind to the statement.
1066 * @param onRow optional
1067 * (function) Callback to receive result of a single row.
1069 execute: function (sql, params=null, onRow=null) {
1070 return this._connectionData.execute(sql, params, onRow);
1074 * Whether a transaction is currently in progress.
1076 get transactionInProgress() {
1077 return this._connectionData.transactionInProgress;
1081 * Perform a transaction.
1083 * A transaction is specified by a user-supplied function that is a
1084 * generator function which can be used by Task.jsm's Task.spawn(). The
1085 * function receives this connection instance as its argument.
1087 * The supplied function is expected to yield promises. These are often
1088 * promises created by calling `execute` and `executeCached`. If the
1089 * generator is exhausted without any errors being thrown, the
1090 * transaction is committed. If an error occurs, the transaction is
1093 * The returned value from this function is a promise that will be resolved
1094 * once the transaction has been committed or rolled back. The promise will
1095 * be resolved to whatever value the supplied function resolves to. If
1096 * the transaction is rolled back, the promise is rejected.
1099 * (function) What to perform as part of the transaction.
1100 * @param type optional
1101 * One of the TRANSACTION_* constants attached to this type.
1103 executeTransaction: function (func, type=this.TRANSACTION_DEFERRED) {
1104 if (this.TRANSACTION_TYPES.indexOf(type) == -1) {
1105 throw new Error("Unknown transaction type: " + type);
1108 return this._connectionData.executeTransaction(() => func(this), type);
1112 * Whether a table exists in the database (both persistent and temporary tables).
1115 * (string) Name of the table.
1117 * @return Promise<bool>
1119 tableExists: function (name) {
1120 return this.execute(
1121 "SELECT name FROM (SELECT * FROM sqlite_master UNION ALL " +
1122 "SELECT * FROM sqlite_temp_master) " +
1123 "WHERE type = 'table' AND name=?",
1125 .then(function onResult(rows) {
1126 return Promise.resolve(rows.length > 0);
1132 * Whether a named index exists (both persistent and temporary tables).
1135 * (string) Name of the index.
1137 * @return Promise<bool>
1139 indexExists: function (name) {
1140 return this.execute(
1141 "SELECT name FROM (SELECT * FROM sqlite_master UNION ALL " +
1142 "SELECT * FROM sqlite_temp_master) " +
1143 "WHERE type = 'index' AND name=?",
1145 .then(function onResult(rows) {
1146 return Promise.resolve(rows.length > 0);
1152 * Free up as much memory from the underlying database connection as possible.
1156 shrinkMemory: function () {
1157 return this._connectionData.shrinkMemory();
1161 * Discard all cached statements.
1163 * Note that this relies on us being non-interruptible between
1164 * the insertion or retrieval of a statement in the cache and its
1165 * execution: we finalize all statements, which is only safe if
1166 * they will not be executed again.
1168 * @return (integer) the number of statements discarded.
1170 discardCachedStatements: function () {
1171 return this._connectionData.discardCachedStatements();
1176 openConnection: openConnection,
1177 cloneStorageConnection: cloneStorageConnection,
1179 * Shutdown barrier client. May be used by clients to perform last-minute
1180 * cleanup prior to the shutdown of this module.
1182 * See the documentation of AsyncShutdown.Barrier.prototype.client.
1185 return Barriers.shutdown.client;