Bumping manifests a=b2g-bump
[gecko.git] / toolkit / modules / Sqlite.jsm
blob8177522b56e2390187bfbba64e914359ad5612bd
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/. */
5 "use strict";
7 this.EXPORTED_SYMBOLS = [
8   "Sqlite",
9 ];
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();
40 /**
41  * Once `true`, reject any attempt to open or close a database.
42  */
43 let isClosed = false;
45 let Debugging = {
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
63   // test auto closes.
64   if (Debugging.failTestsOnAutoClose) {
65     Promise.reject(new Error(message));
66   }
69 /**
70  * Barriers used to ensure that Sqlite.jsm is shutdown after all
71  * its clients.
72  */
73 XPCOMUtils.defineLazyGetter(this, "Barriers", () => {
74   let Barriers = {
75     /**
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.
80      */
81     shutdown: new AsyncShutdown.Barrier("Sqlite.jsm: wait until all clients have completed their task"),
83     /**
84      * Private barrier blocked by connections that are still open.
85      * Triggered after Barriers.shutdown is lifted and `isClosed` is
86      * set to `true`.
87      */
88     connections: new AsyncShutdown.Barrier("Sqlite.jsm: wait until all connections are closed"),
89   };
91   /**
92    * Observer for the event which is broadcasted when the finalization
93    * witness `_witness` of `OpenedConnection` is garbage collected.
94    *
95    * The observer is passed the connection identifier of the database
96    * connection that is being finalized.
97    */
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");
104       return;
105     }
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();
111   };
112   Services.obs.addObserver(finalizationObserver, "sqlite-finalization-witness", false);
114   /**
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.
119    */
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.
128       isClosed = true;
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");
135     }),
137     function status() {
138       if (isClosed) {
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 };
143       }
145       // We are still in the first stage: waiting for the barrier
146       // to be lifted. The interesting status is therefore that of
147       // the barrier.
148       return { description: "Waiting for the barrier to be lifted",
149                state: Barriers.shutdown.state };
150   });
152   return Barriers;
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
161  * it.
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.
172  */
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;
180   this._open = true;
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.
201   }
203   this._deferredClose = Promise.defer();
204   this._closeRequested = false;
206   Barriers.connections.client.addBlocker(
207     this._connectionIdentifier + ": waiting for shutdown",
208     this._deferredClose.promise,
209     () =>  ({
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,
216     })
217   );
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
229  */
230 ConnectionData.byId = new Map();
232 ConnectionData.prototype = Object.freeze({
233   close: function () {
234     this._closeRequested = true;
236     if (!this._dbConn) {
237       return this._deferredClose.promise;
238     }
240     this._log.debug("Request to close connection.");
241     this._clearIdleShrinkTimer();
243     // We need to take extra care with transactions during shutdown.
244     //
245     // If we don't have a transaction in progress, we can proceed with shutdown
246     // immediately.
247     if (!this._inProgressTransaction) {
248       this._finalize(this._deferredClose);
249       return this._deferredClose.promise;
250     }
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;
264   },
266   clone: function (readOnly=false) {
267     this.ensureOpen();
269     this._log.debug("Request to clone connection.");
271     let options = {
272       connection: this._dbConn,
273       readOnly: readOnly,
274     };
275     if (this._idleShrinkMS)
276       options.shrinkMemoryOnConnectionIdleMS = this._idleShrinkMS;
278     return cloneStorageConnection(options);
279   },
281   _finalize: function (deferred) {
282     this._log.debug("Finalizing connection.");
283     // Cancel any pending statements.
284     for (let [k, statement] of this._pendingStatements) {
285       statement.cancel();
286     }
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();
295     }
296     this._anonymousStatements.clear();
298     for (let [k, statement] of this._cachedStatements) {
299       statement.finalize();
300     }
301     this._cachedStatements.clear();
303     // This guards against operations performed between the call to this
304     // function and asyncClose() finishing. See also bug 726990.
305     this._open = false;
307     this._log.debug("Calling asyncClose().");
308     this._dbConn.asyncClose(() => {
309       this._log.info("Closed");
310       this._dbConn = null;
311       // Now that the connection is closed, no need to keep
312       // a blocker for Barriers.connections.
313       Barriers.connections.client.removeBlocker(deferred.promise);
314       deferred.resolve();
315     });
316   },
318   executeCached: function (sql, params=null, onRow=null) {
319     this.ensureOpen();
321     if (!sql) {
322       throw new Error("sql argument is empty.");
323     }
325     let statement = this._cachedStatements.get(sql);
326     if (!statement) {
327       statement = this._dbConn.createAsyncStatement(sql);
328       this._cachedStatements.set(sql, statement);
329     }
331     this._clearIdleShrinkTimer();
333     let deferred = Promise.defer();
335     try {
336       this._executeStatement(sql, statement, params, onRow).then(
337         result => {
338           this._startIdleShrinkTimer();
339           deferred.resolve(result);
340         },
341         error => {
342           this._startIdleShrinkTimer();
343           deferred.reject(error);
344         }
345       );
346     } catch (ex) {
347       this._startIdleShrinkTimer();
348       throw ex;
349     }
351     return deferred.promise;
352   },
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);
357     }
359     this.ensureOpen();
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();
371     };
373     let deferred = Promise.defer();
375     try {
376       this._executeStatement(sql, statement, params, onRow).then(
377         rows => {
378           onFinished();
379           deferred.resolve(rows);
380         },
381         error => {
382           onFinished();
383           deferred.reject(error);
384         }
385       );
386     } catch (ex) {
387       onFinished();
388       throw ex;
389     }
391     return deferred.promise;
392   },
394   get transactionInProgress() {
395     return this._open && !!this._inProgressTransaction;
396   },
398   executeTransaction: function (func, type) {
399     this.ensureOpen();
401     if (this._inProgressTransaction) {
402       throw new Error("A transaction is already active. Only one transaction " +
403                       "can be active at a time.");
404     }
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");
415       let result;
416       try {
417         result = yield Task.spawn(func);
418       } catch (ex) {
419         // It's possible that a request to close the connection caused the
420         // error.
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));
427           throw ex;
428         }
430         this._log.warn("Error during transaction. Rolling back: " +
431                        CommonUtils.exceptionStr(ex));
432         try {
433           yield this.execute("ROLLBACK TRANSACTION");
434         } catch (inner) {
435           this._log.warn("Could not roll back transaction. This is weird: " +
436                          CommonUtils.exceptionStr(inner));
437         }
439         throw ex;
440       }
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.");
447       }
449       try {
450         yield this.execute("COMMIT TRANSACTION");
451       } catch (ex) {
452         this._log.warn("Error committing transaction: " +
453                        CommonUtils.exceptionStr(ex));
454         throw ex;
455       }
457       throw new Task.Result(result);
458     }.bind(this)).then(
459       function onSuccess(result) {
460         this._inProgressTransaction = null;
461         deferred.resolve(result);
462       }.bind(this),
463       function onError(error) {
464         this._inProgressTransaction = null;
465         deferred.reject(error);
466       }.bind(this)
467     );
469     return deferred.promise;
470   },
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);
476   },
478   discardCachedStatements: function () {
479     let count = 0;
480     for (let [k, statement] of this._cachedStatements) {
481       ++count;
482       statement.finalize();
483     }
484     this._cachedStatements.clear();
485     this._log.debug("Discarded " + count + " cached statements.");
486     return count;
487   },
489   /**
490    * Helper method to bind parameters of various kinds through
491    * reflection.
492    */
493   _bindParameters: function (statement, params) {
494     if (!params) {
495       return;
496     }
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);
506           }
507           paramsArray.addParams(bindings);
508         }
510         statement.bindParameters(paramsArray);
511         return;
512       }
514       // Indexed params.
515       for (let i = 0; i < params.length; i++) {
516         statement.bindByIndex(i, params[i]);
517       }
518       return;
519     }
521     // Named params.
522     if (params && typeof(params) == "object") {
523       for (let k in params) {
524         statement.bindByName(k, params[k]);
525       }
526       return;
527     }
529     throw new Error("Invalid type for bound parameters. Expected Array or " +
530                     "object. Got: " + params);
531   },
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.");
536     }
538     if (onRow && typeof(onRow) != "function") {
539       throw new Error("onRow must be a function. Got: " + onRow);
540     }
542     this._bindParameters(statement, params);
544     let index = this._statementCounter++;
546     let deferred = Promise.defer();
547     let userCancelled = false;
548     let errors = [];
549     let rows = [];
551     // Don't incur overhead for serializing params unless the messages go
552     // somewhere.
553     if (this._log.level <= Log.Level.Trace) {
554       let msg = "Stmt #" + index + " " + sql;
556       if (params) {
557         msg += " - " + JSON.stringify(params);
558       }
559       this._log.trace(msg);
560     } else {
561       this._log.debug("Stmt #" + index + " starting");
562     }
564     let self = this;
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()) {
570           if (!onRow) {
571             rows.push(row);
572             continue;
573           }
575           try {
576             onRow(row);
577           } catch (e if e instanceof StopIteration) {
578             userCancelled = true;
579             pending.cancel();
580             break;
581           } catch (ex) {
582             self._log.warn("Exception when calling onRow callback: " +
583                            CommonUtils.exceptionStr(ex));
584           }
585         }
586       },
588       handleError: function (error) {
589         self._log.info("Error when executing SQL (" +
590                        error.result + "): " + error.message);
591         errors.push(error);
592       },
594       handleCompletion: function (reason) {
595         self._log.debug("Stmt #" + index + " finished.");
596         self._pendingStatements.delete(index);
598         switch (reason) {
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);
603             break;
605           case Ci.mozIStorageStatementCallback.REASON_CANCELLED:
606             // It is not an error if the user explicitly requested cancel via
607             // the onRow handler.
608             if (userCancelled) {
609               let result = onRow ? null : rows;
610               deferred.resolve(result);
611             } else {
612               deferred.reject(new Error("Statement was cancelled."));
613             }
615             break;
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);
621             break;
623           default:
624             deferred.reject(new Error("Unknown completion reason code: " +
625                                       reason));
626             break;
627         }
628       },
629     });
631     this._pendingStatements.set(index, pending);
632     return deferred.promise;
633   },
635   ensureOpen: function () {
636     if (!this._open) {
637       throw new Error("Connection is not open.");
638     }
639   },
641   _clearIdleShrinkTimer: function () {
642     if (!this._idleShrinkTimer) {
643       return;
644     }
646     this._idleShrinkTimer.cancel();
647   },
649   _startIdleShrinkTimer: function () {
650     if (!this._idleShrinkTimer) {
651       return;
652     }
654     this._idleShrinkTimer.initWithCallback(this.shrinkMemory.bind(this),
655                                            this._idleShrinkMS,
656                                            this._idleShrinkTimer.TYPE_ONE_SHOT);
657   }
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
672  *       true.
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
686  *   TRUNCATE JOURNAL
687  *   SYNCHRONOUS = full
689  * @param options
690  *        (Object) Parameters to control connection and open options.
692  * @return Promise<OpenedConnection>
693  */
694 function openConnection(options) {
695   let log = Log.repository.getLogger("Sqlite.ConnectionOpener");
697   if (!options.path) {
698     throw new Error("path not specified in connection options.");
699   }
701   if (isClosed) {
702     throw new Error("Sqlite.jsm has been shutdown. Cannot open connection to: " + options.path);
703   }
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);
718     }
720     openedOptions.shrinkMemoryOnConnectionIdleMS =
721       options.shrinkMemoryOnConnectionIdleMS;
722   }
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);
739   }
740   Services.storage.openAsyncDatabase(file, dbOptions, function(status, connection) {
741     if (!connection) {
742       log.warn("Could not open connection: " + status);
743       deferred.reject(new Error("Could not open connection: " + status));
744       return;
745     }
746     log.info("Connection opened");
747     try {
748       deferred.resolve(
749         new OpenedConnection(connection.QueryInterface(Ci.mozIStorageAsyncConnection), basename, number,
750         openedOptions));
751     } catch (ex) {
752       log.warn("Could not open database: " + CommonUtils.exceptionStr(ex));
753       deferred.reject(ex);
754     }
755   });
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.
783  * @param options
784  *        (Object) Parameters to control connection and clone options.
786  * @return Promise<OpenedConnection>
787  */
788 function cloneStorageConnection(options) {
789   let log = Log.repository.getLogger("Sqlite.ConnectionCloner");
791   let source = options && options.connection;
792   if (!source) {
793     throw new TypeError("connection not specified in clone options.");
794   }
795   if (!source instanceof Ci.mozIStorageAsyncConnection) {
796     throw new TypeError("Connection must be a valid Storage connection.");
797   }
799   if (isClosed) {
800     throw new Error("Sqlite.jsm has been shutdown. Cannot clone connection to: " + source.database.path);
801   }
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);
809     }
810     openedOptions.shrinkMemoryOnConnectionIdleMS =
811       options.shrinkMemoryOnConnectionIdleMS;
812   }
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) => {
824     if (!connection) {
825       log.warn("Could not clone connection: " + status);
826       deferred.reject(new Error("Could not clone connection: " + status));
827     }
828     log.info("Connection cloned");
829     try {
830       let conn = connection.QueryInterface(Ci.mozIStorageAsyncConnection);
831       deferred.resolve(new OpenedConnection(conn, basename, number,
832                                             openedOptions));
833     } catch (ex) {
834       log.warn("Could not clone database: " + CommonUtils.exceptionStr(ex));
835       deferred.reject(ex);
836     }
837   });
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
849  * differences.
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
864  * multiple times.
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.
878  * @param connection
879  *        (mozIStorageConnection) Underlying SQLite connection.
880  * @param basename
881  *        (string) The basename of this database name. Used for logging.
882  * @param number
883  *        (Number) The connection number to this database.
884  * @param options
885  *        (object) Options to control behavior of connection. See
886  *        `openConnection`.
887  */
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
897   // key.
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"],
917   /**
918    * The integer schema version of the database.
919    *
920    * This is 0 if not schema version has been set.
921    *
922    * @return Promise<int>
923    */
924   getSchemaVersion: function() {
925     let self = this;
926     return this.execute("PRAGMA user_version").then(
927       function onSuccess(result) {
928         if (result == null) {
929           return 0;
930         }
931         return JSON.stringify(result[0].getInt32(0));
932       }
933     );
934   },
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);
940     }
941     this._connectionData.ensureOpen();
942     return this.execute("PRAGMA user_version = " + value);
943   },
945   /**
946    * Close the database connection.
947    *
948    * This must be performed when you are finished with the database.
949    *
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
953    * possible.
954    *
955    * The returned promise will be resolved once the connection is closed.
956    * Successive calls to close() return the same promise.
957    *
958    * IMPROVEMENT: Resolve the promise to a closed connection which can be
959    * reopened.
960    *
961    * @return Promise<>
962    */
963   close: function () {
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();
970     }
971     return this._connectionData.close();
972   },
974   /**
975    * Clones this connection to a new Sqlite one.
976    *
977    * The following parameters can control the cloned connection:
978    *
979    * @param readOnly
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.
985    *
986    * @return Promise<OpenedConnection>
987    */
988   clone: function (readOnly=false) {
989     return this._connectionData.clone(readOnly);
990   },
992   /**
993    * Execute a SQL statement and cache the underlying statement object.
994    *
995    * This function executes a SQL statement and also caches the underlying
996    * derived statement object so subsequent executions are faster and use
997    * less resources.
998    *
999    * This function optionally binds parameters to the statement as well as
1000    * optionally invokes a callback for every row retrieved.
1001    *
1002    * By default, no parameters are bound and no callback will be invoked for
1003    * every row.
1004    *
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.
1008    *
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.
1014    *
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.
1019    *
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
1023    * resources.
1024    *
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.
1029    *
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).
1033    *
1034    * The return value is a promise that will be resolved when the statement
1035    * has completed fully.
1036    *
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.
1042    *
1043    * @param name
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.
1049    */
1050   executeCached: function (sql, params=null, onRow=null) {
1051     return this._connectionData.executeCached(sql, params, onRow);
1052   },
1054   /**
1055    * Execute a one-shot SQL statement.
1056    *
1057    * If you find yourself feeding the same SQL string in this function, you
1058    * should *not* use this function and instead use `executeCached`.
1059    *
1060    * See `executeCached` for the meaning of the arguments and extended usage info.
1061    *
1062    * @param sql
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.
1068    */
1069   execute: function (sql, params=null, onRow=null) {
1070     return this._connectionData.execute(sql, params, onRow);
1071   },
1073   /**
1074    * Whether a transaction is currently in progress.
1075    */
1076   get transactionInProgress() {
1077     return this._connectionData.transactionInProgress;
1078   },
1080   /**
1081    * Perform a transaction.
1082    *
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.
1086    *
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
1091    * rolled back.
1092    *
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.
1097    *
1098    * @param func
1099    *        (function) What to perform as part of the transaction.
1100    * @param type optional
1101    *        One of the TRANSACTION_* constants attached to this type.
1102    */
1103   executeTransaction: function (func, type=this.TRANSACTION_DEFERRED) {
1104     if (this.TRANSACTION_TYPES.indexOf(type) == -1) {
1105       throw new Error("Unknown transaction type: " + type);
1106     }
1108     return this._connectionData.executeTransaction(() => func(this), type);
1109   },
1111   /**
1112    * Whether a table exists in the database (both persistent and temporary tables).
1113    *
1114    * @param name
1115    *        (string) Name of the table.
1116    *
1117    * @return Promise<bool>
1118    */
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=?",
1124       [name])
1125       .then(function onResult(rows) {
1126         return Promise.resolve(rows.length > 0);
1127       }
1128     );
1129   },
1131   /**
1132    * Whether a named index exists (both persistent and temporary tables).
1133    *
1134    * @param name
1135    *        (string) Name of the index.
1136    *
1137    * @return Promise<bool>
1138    */
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=?",
1144       [name])
1145       .then(function onResult(rows) {
1146         return Promise.resolve(rows.length > 0);
1147       }
1148     );
1149   },
1151   /**
1152    * Free up as much memory from the underlying database connection as possible.
1153    *
1154    * @return Promise<>
1155    */
1156   shrinkMemory: function () {
1157     return this._connectionData.shrinkMemory();
1158   },
1160   /**
1161    * Discard all cached statements.
1162    *
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.
1167    *
1168    * @return (integer) the number of statements discarded.
1169    */
1170   discardCachedStatements: function () {
1171     return this._connectionData.discardCachedStatements();
1172   },
1175 this.Sqlite = {
1176   openConnection: openConnection,
1177   cloneStorageConnection: cloneStorageConnection,
1178   /**
1179    * Shutdown barrier client. May be used by clients to perform last-minute
1180    * cleanup prior to the shutdown of this module.
1181    *
1182    * See the documentation of AsyncShutdown.Barrier.prototype.client.
1183    */
1184   get shutdown() {
1185     return Barriers.shutdown.client;
1186   }