Bug 1837494 [wpt PR 40457] - Ignore urllib3's warnings when run on LibreSSL, a=testonly
[gecko.git] / toolkit / modules / Sqlite.sys.mjs
blob991105dd76f87466e4f0b21fd19098c776486aaa
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 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
7 import { setTimeout } from "resource://gre/modules/Timer.sys.mjs";
9 const lazy = {};
11 ChromeUtils.defineESModuleGetters(lazy, {
12   AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
13   FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
14   Log: "resource://gre/modules/Log.sys.mjs",
15   PromiseUtils: "resource://gre/modules/PromiseUtils.sys.mjs",
16 });
18 XPCOMUtils.defineLazyServiceGetter(
19   lazy,
20   "FinalizationWitnessService",
21   "@mozilla.org/toolkit/finalizationwitness;1",
22   "nsIFinalizationWitnessService"
25 // Regular expression used by isInvalidBoundLikeQuery
26 var likeSqlRegex = /\bLIKE\b\s(?![@:?])/i;
28 // Counts the number of created connections per database basename(). This is
29 // used for logging to distinguish connection instances.
30 var connectionCounters = new Map();
32 // Tracks identifiers of wrapped connections, that are Storage connections
33 // opened through mozStorage and then wrapped by Sqlite.sys.mjs to use its syntactic
34 // sugar API.  Since these connections have an unknown origin, we use this set
35 // to differentiate their behavior.
36 var wrappedConnections = new Set();
38 /**
39  * Once `true`, reject any attempt to open or close a database.
40  */
41 function isClosed() {
42   // If Barriers have not been initialized yet, just trust AppStartup.
43   if (
44     typeof Object.getOwnPropertyDescriptor(lazy, "Barriers").get == "function"
45   ) {
46     // It's still possible to open new connections at profile-before-change, so
47     // use the next phase here, as a fallback.
48     return Services.startup.isInOrBeyondShutdownPhase(
49       Ci.nsIAppStartup.SHUTDOWN_PHASE_XPCOMWILLSHUTDOWN
50     );
51   }
52   return lazy.Barriers.shutdown.client.isClosed;
55 var Debugging = {
56   // Tests should fail if a connection auto closes.  The exception is
57   // when finalization itself is tested, in which case this flag
58   // should be set to false.
59   failTestsOnAutoClose: true,
62 /**
63  * Helper function to check whether LIKE is implemented using proper bindings.
64  *
65  * @param sql
66  *        (string) The SQL query to be verified.
67  * @return boolean value telling us whether query was correct or not
68  */
69 function isInvalidBoundLikeQuery(sql) {
70   return likeSqlRegex.test(sql);
73 // Displays a script error message
74 function logScriptError(message) {
75   let consoleMessage = Cc["@mozilla.org/scripterror;1"].createInstance(
76     Ci.nsIScriptError
77   );
78   let stack = new Error();
79   consoleMessage.init(
80     message,
81     stack.fileName,
82     null,
83     stack.lineNumber,
84     0,
85     Ci.nsIScriptError.errorFlag,
86     "component javascript"
87   );
88   Services.console.logMessage(consoleMessage);
90   // This `Promise.reject` will cause tests to fail.  The debugging
91   // flag can be used to suppress this for tests that explicitly
92   // test auto closes.
93   if (Debugging.failTestsOnAutoClose) {
94     Promise.reject(new Error(message));
95   }
98 /**
99  * Gets connection identifier from its database file name.
101  * @param fileName
102  *        A database file string name.
103  * @return the connection identifier.
104  */
105 function getIdentifierByFileName(fileName) {
106   let number = connectionCounters.get(fileName) || 0;
107   connectionCounters.set(fileName, number + 1);
108   return fileName + "#" + number;
112  * Convert mozIStorageError to common NS_ERROR_*
113  * The conversion is mostly based on the one in
114  * mozStoragePrivateHelpers::ConvertResultCode, plus a few additions.
116  * @param {integer} result a mozIStorageError result code.
117  * @returns {integer} an NS_ERROR_* result code.
118  */
119 function convertStorageErrorResult(result) {
120   switch (result) {
121     case Ci.mozIStorageError.PERM:
122     case Ci.mozIStorageError.AUTH:
123     case Ci.mozIStorageError.CANTOPEN:
124       return Cr.NS_ERROR_FILE_ACCESS_DENIED;
125     case Ci.mozIStorageError.LOCKED:
126       return Cr.NS_ERROR_FILE_IS_LOCKED;
127     case Ci.mozIStorageError.READONLY:
128       return Cr.NS_ERROR_FILE_READ_ONLY;
129     case Ci.mozIStorageError.ABORT:
130     case Ci.mozIStorageError.INTERRUPT:
131       return Cr.NS_ERROR_ABORT;
132     case Ci.mozIStorageError.TOOBIG:
133     case Ci.mozIStorageError.FULL:
134       return Cr.NS_ERROR_FILE_NO_DEVICE_SPACE;
135     case Ci.mozIStorageError.NOMEM:
136       return Cr.NS_ERROR_OUT_OF_MEMORY;
137     case Ci.mozIStorageError.BUSY:
138       return Cr.NS_ERROR_STORAGE_BUSY;
139     case Ci.mozIStorageError.CONSTRAINT:
140       return Cr.NS_ERROR_STORAGE_CONSTRAINT;
141     case Ci.mozIStorageError.NOLFS:
142     case Ci.mozIStorageError.IOERR:
143       return Cr.NS_ERROR_STORAGE_IOERR;
144     case Ci.mozIStorageError.SCHEMA:
145     case Ci.mozIStorageError.MISMATCH:
146     case Ci.mozIStorageError.MISUSE:
147     case Ci.mozIStorageError.RANGE:
148       return Ci.NS_ERROR_UNEXPECTED;
149     case Ci.mozIStorageError.CORRUPT:
150     case Ci.mozIStorageError.EMPTY:
151     case Ci.mozIStorageError.FORMAT:
152     case Ci.mozIStorageError.NOTADB:
153       return Cr.NS_ERROR_FILE_CORRUPTED;
154     default:
155       return Cr.NS_ERROR_FAILURE;
156   }
159  * Barriers used to ensure that Sqlite.sys.mjs is shutdown after all
160  * its clients.
161  */
162 XPCOMUtils.defineLazyGetter(lazy, "Barriers", () => {
163   let Barriers = {
164     /**
165      * Public barrier that clients may use to add blockers to the
166      * shutdown of Sqlite.sys.mjs. Triggered by profile-before-change.
167      * Once all blockers of this barrier are lifted, we close the
168      * ability to open new connections.
169      */
170     shutdown: new lazy.AsyncShutdown.Barrier(
171       "Sqlite.sys.mjs: wait until all clients have completed their task"
172     ),
174     /**
175      * Private barrier blocked by connections that are still open.
176      * Triggered after Barriers.shutdown is lifted and `isClosed()` returns
177      * `true`.
178      */
179     connections: new lazy.AsyncShutdown.Barrier(
180       "Sqlite.sys.mjs: wait until all connections are closed"
181     ),
182   };
184   /**
185    * Observer for the event which is broadcasted when the finalization
186    * witness `_witness` of `OpenedConnection` is garbage collected.
187    *
188    * The observer is passed the connection identifier of the database
189    * connection that is being finalized.
190    */
191   let finalizationObserver = function (subject, topic, identifier) {
192     let connectionData = ConnectionData.byId.get(identifier);
194     if (connectionData === undefined) {
195       logScriptError(
196         "Error: Attempt to finalize unknown Sqlite connection: " +
197           identifier +
198           "\n"
199       );
200       return;
201     }
203     ConnectionData.byId.delete(identifier);
204     logScriptError(
205       "Warning: Sqlite connection '" +
206         identifier +
207         "' was not properly closed. Auto-close triggered by garbage collection.\n"
208     );
209     connectionData.close();
210   };
211   Services.obs.addObserver(finalizationObserver, "sqlite-finalization-witness");
213   /**
214    * Ensure that Sqlite.sys.mjs:
215    * - informs its clients before shutting down;
216    * - lets clients open connections during shutdown, if necessary;
217    * - waits for all connections to be closed before shutdown.
218    */
219   lazy.AsyncShutdown.profileBeforeChange.addBlocker(
220     "Sqlite.sys.mjs shutdown blocker",
221     async function () {
222       await Barriers.shutdown.wait();
223       // At this stage, all clients have had a chance to open (and close)
224       // their databases. Some previous close operations may still be pending,
225       // so we need to wait until they are complete before proceeding.
226       await Barriers.connections.wait();
228       // Everything closed, no finalization events to catch
229       Services.obs.removeObserver(
230         finalizationObserver,
231         "sqlite-finalization-witness"
232       );
233     },
235     function status() {
236       if (isClosed()) {
237         // We are waiting for the connections to close. The interesting
238         // status is therefore the list of connections still pending.
239         return {
240           description: "Waiting for connections to close",
241           state: Barriers.connections.state,
242         };
243       }
245       // We are still in the first stage: waiting for the barrier
246       // to be lifted. The interesting status is therefore that of
247       // the barrier.
248       return {
249         description: "Waiting for the barrier to be lifted",
250         state: Barriers.shutdown.state,
251       };
252     }
253   );
255   return Barriers;
258 const VACUUM_CATEGORY = "vacuum-participant";
259 const VACUUM_CONTRACTID = "@sqlite.module.js/vacuum-participant;";
260 var registeredVacuumParticipants = new Map();
262 function registerVacuumParticipant(connectionData) {
263   let contractId = VACUUM_CONTRACTID + connectionData._identifier;
264   let factory = {
265     createInstance(iid) {
266       return connectionData.QueryInterface(iid);
267     },
268     QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
269   };
270   let cid = Services.uuid.generateUUID();
271   Components.manager
272     .QueryInterface(Ci.nsIComponentRegistrar)
273     .registerFactory(cid, contractId, contractId, factory);
274   Services.catMan.addCategoryEntry(
275     VACUUM_CATEGORY,
276     contractId,
277     contractId,
278     false,
279     false
280   );
281   registeredVacuumParticipants.set(contractId, { cid, factory });
284 function unregisterVacuumParticipant(connectionData) {
285   let contractId = VACUUM_CONTRACTID + connectionData._identifier;
286   let component = registeredVacuumParticipants.get(contractId);
287   if (component) {
288     Components.manager
289       .QueryInterface(Ci.nsIComponentRegistrar)
290       .unregisterFactory(component.cid, component.factory);
291     Services.catMan.deleteCategoryEntry(VACUUM_CATEGORY, contractId, false);
292   }
296  * Connection data with methods necessary for closing the connection.
298  * To support auto-closing in the event of garbage collection, this
299  * data structure contains all the connection data of an opened
300  * connection and all of the methods needed for sucessfully closing
301  * it.
303  * By putting this information in its own separate object, it is
304  * possible to store an additional reference to it without preventing
305  * a garbage collection of a finalization witness in
306  * OpenedConnection. When the witness detects a garbage collection,
307  * this object can be used to close the connection.
309  * This object contains more methods than just `close`.  When
310  * OpenedConnection needs to use the methods in this object, it will
311  * dispatch its method calls here.
312  */
313 function ConnectionData(connection, identifier, options = {}) {
314   this._log = lazy.Log.repository.getLoggerWithMessagePrefix(
315     "Sqlite.sys.mjs",
316     `Connection ${identifier}: `
317   );
318   this._log.manageLevelFromPref("toolkit.sqlitejsm.loglevel");
319   this._log.debug("Opened");
321   this._dbConn = connection;
323   // This is a unique identifier for the connection, generated through
324   // getIdentifierByFileName.  It may be used for logging or as a key in Maps.
325   this._identifier = identifier;
327   this._open = true;
329   this._cachedStatements = new Map();
330   this._anonymousStatements = new Map();
331   this._anonymousCounter = 0;
333   // A map from statement index to mozIStoragePendingStatement, to allow for
334   // canceling prior to finalizing the mozIStorageStatements.
335   this._pendingStatements = new Map();
337   // Increments for each executed statement for the life of the connection.
338   this._statementCounter = 0;
340   // Increments whenever we request a unique operation id.
341   this._operationsCounter = 0;
343   if ("defaultTransactionType" in options) {
344     this.defaultTransactionType = options.defaultTransactionType;
345   } else {
346     this.defaultTransactionType = convertStorageTransactionType(
347       this._dbConn.defaultTransactionType
348     );
349   }
350   // Tracks whether this instance initiated a transaction.
351   this._initiatedTransaction = false;
352   // Manages a chain of transactions promises, so that new transactions
353   // always happen in queue to the previous ones.  It never rejects.
354   this._transactionQueue = Promise.resolve();
356   this._idleShrinkMS = options.shrinkMemoryOnConnectionIdleMS;
357   if (this._idleShrinkMS) {
358     this._idleShrinkTimer = Cc["@mozilla.org/timer;1"].createInstance(
359       Ci.nsITimer
360     );
361     // We wait for the first statement execute to start the timer because
362     // shrinking now would not do anything.
363   }
365   // Deferred whose promise is resolved when the connection closing procedure
366   // is complete.
367   this._deferredClose = lazy.PromiseUtils.defer();
368   this._closeRequested = false;
370   // An AsyncShutdown barrier used to make sure that we wait until clients
371   // are done before shutting down the connection.
372   this._barrier = new lazy.AsyncShutdown.Barrier(
373     `${this._identifier}: waiting for clients`
374   );
376   lazy.Barriers.connections.client.addBlocker(
377     this._identifier + ": waiting for shutdown",
378     this._deferredClose.promise,
379     () => ({
380       identifier: this._identifier,
381       isCloseRequested: this._closeRequested,
382       hasDbConn: !!this._dbConn,
383       initiatedTransaction: this._initiatedTransaction,
384       pendingStatements: this._pendingStatements.size,
385       statementCounter: this._statementCounter,
386     })
387   );
389   // We avoid creating a timer for every transaction, because in most cases they
390   // are not canceled and they are only used as a timeout.
391   // Instead the timer is reused when it's sufficiently close to the previous
392   // creation time (see `_getTimeoutPromise` for more info).
393   this._timeoutPromise = null;
394   // The last timestamp when we should consider using `this._timeoutPromise`.
395   this._timeoutPromiseExpires = 0;
397   this._useIncrementalVacuum = !!options.incrementalVacuum;
398   if (this._useIncrementalVacuum) {
399     this._log.debug("Set auto_vacuum INCREMENTAL");
400     this.execute("PRAGMA auto_vacuum = 2").catch(ex => {
401       this._log.error("Setting auto_vacuum to INCREMENTAL failed.");
402       console.error(ex);
403     });
404   }
406   this._expectedPageSize = options.pageSize ?? 0;
407   if (this._expectedPageSize) {
408     this._log.debug("Set page_size to " + this._expectedPageSize);
409     this.execute("PRAGMA page_size = " + this._expectedPageSize).catch(ex => {
410       this._log.error(`Setting page_size to ${this._expectedPageSize} failed.`);
411       console.error(ex);
412     });
413   }
415   this._vacuumOnIdle = options.vacuumOnIdle;
416   if (this._vacuumOnIdle) {
417     this._log.debug("Register as vacuum participant");
418     this.QueryInterface = ChromeUtils.generateQI([
419       Ci.mozIStorageVacuumParticipant,
420     ]);
421     registerVacuumParticipant(this);
422   }
426  * Map of connection identifiers to ConnectionData objects
428  * The connection identifier is a human-readable name of the
429  * database. Used by finalization witnesses to be able to close opened
430  * connections on garbage collection.
432  * Key: _identifier of ConnectionData
433  * Value: ConnectionData object
434  */
435 ConnectionData.byId = new Map();
437 ConnectionData.prototype = Object.freeze({
438   get expectedDatabasePageSize() {
439     return this._expectedPageSize;
440   },
442   get useIncrementalVacuum() {
443     return this._useIncrementalVacuum;
444   },
446   /**
447    * This should only be used by the VacuumManager component.
448    * @see unsafeRawConnection for an official (but still unsafe) API.
449    */
450   get databaseConnection() {
451     if (this._vacuumOnIdle) {
452       return this._dbConn;
453     }
454     return null;
455   },
457   onBeginVacuum() {
458     let granted = !this.transactionInProgress;
459     this._log.debug("Begin Vacuum - " + granted ? "granted" : "denied");
460     return granted;
461   },
463   onEndVacuum(succeeded) {
464     this._log.debug("End Vacuum - " + succeeded ? "success" : "failure");
465   },
467   /**
468    * Run a task, ensuring that its execution will not be interrupted by shutdown.
469    *
470    * As the operations of this module are asynchronous, a sequence of operations,
471    * or even an individual operation, can still be pending when the process shuts
472    * down. If any of this operations is a write, this can cause data loss, simply
473    * because the write has not been completed (or even started) by shutdown.
474    *
475    * To avoid this risk, clients are encouraged to use `executeBeforeShutdown` for
476    * any write operation, as follows:
477    *
478    * myConnection.executeBeforeShutdown("Bookmarks: Removing a bookmark",
479    *   async function(db) {
480    *     // The connection will not be closed and shutdown will not proceed
481    *     // until this task has completed.
482    *
483    *     // `db` exposes the same API as `myConnection` but provides additional
484    *     // logging support to help debug hard-to-catch shutdown timeouts.
485    *
486    *     await db.execute(...);
487    * }));
488    *
489    * @param {string} name A human-readable name for the ongoing operation, used
490    *  for logging and debugging purposes.
491    * @param {function(db)} task A function that takes as argument a Sqlite.sys.mjs
492    *  db and returns a Promise.
493    */
494   executeBeforeShutdown(parent, name, task) {
495     if (!name) {
496       throw new TypeError("Expected a human-readable name as first argument");
497     }
498     if (typeof task != "function") {
499       throw new TypeError("Expected a function as second argument");
500     }
501     if (this._closeRequested) {
502       throw new Error(
503         `${this._identifier}: cannot execute operation ${name}, the connection is already closing`
504       );
505     }
507     // Status, used for AsyncShutdown crash reports.
508     let status = {
509       // The latest command started by `task`, either as a
510       // sql string, or as one of "<not started>" or "<closing>".
511       command: "<not started>",
513       // `true` if `command` was started but not completed yet.
514       isPending: false,
515     };
517     // An object with the same API as `this` but with
518     // additional logging. To keep logging simple, we
519     // assume that `task` is not running several queries
520     // concurrently.
521     let loggedDb = Object.create(parent, {
522       execute: {
523         value: async (sql, ...rest) => {
524           status.isPending = true;
525           status.command = sql;
526           try {
527             return await this.execute(sql, ...rest);
528           } finally {
529             status.isPending = false;
530           }
531         },
532       },
533       close: {
534         value: async () => {
535           status.isPending = true;
536           status.command = "<close>";
537           try {
538             return await this.close();
539           } finally {
540             status.isPending = false;
541           }
542         },
543       },
544       executeCached: {
545         value: async (sql, ...rest) => {
546           status.isPending = true;
547           status.command = "cached: " + sql;
548           try {
549             return await this.executeCached(sql, ...rest);
550           } finally {
551             status.isPending = false;
552           }
553         },
554       },
555     });
557     let promiseResult = task(loggedDb);
558     if (
559       !promiseResult ||
560       typeof promiseResult != "object" ||
561       !("then" in promiseResult)
562     ) {
563       throw new TypeError("Expected a Promise");
564     }
565     let key = `${this._identifier}: ${name} (${this._getOperationId()})`;
566     let promiseComplete = promiseResult.catch(() => {});
567     this._barrier.client.addBlocker(key, promiseComplete, {
568       fetchState: () => status,
569     });
571     return (async () => {
572       try {
573         return await promiseResult;
574       } finally {
575         this._barrier.client.removeBlocker(key, promiseComplete);
576       }
577     })();
578   },
579   close() {
580     this._closeRequested = true;
582     if (!this._dbConn) {
583       return this._deferredClose.promise;
584     }
586     this._log.debug("Request to close connection.");
587     this._clearIdleShrinkTimer();
589     if (this._vacuumOnIdle) {
590       this._log.debug("Unregister as vacuum participant");
591       unregisterVacuumParticipant(this);
592     }
594     return this._barrier.wait().then(() => {
595       if (!this._dbConn) {
596         return undefined;
597       }
598       return this._finalize();
599     });
600   },
602   clone(readOnly = false) {
603     this.ensureOpen();
605     this._log.debug("Request to clone connection.");
607     let options = {
608       connection: this._dbConn,
609       readOnly,
610     };
611     if (this._idleShrinkMS) {
612       options.shrinkMemoryOnConnectionIdleMS = this._idleShrinkMS;
613     }
615     return cloneStorageConnection(options);
616   },
617   _getOperationId() {
618     return this._operationsCounter++;
619   },
620   _finalize() {
621     this._log.debug("Finalizing connection.");
622     // Cancel any pending statements.
623     for (let [, /* k */ statement] of this._pendingStatements) {
624       statement.cancel();
625     }
626     this._pendingStatements.clear();
628     // We no longer need to track these.
629     this._statementCounter = 0;
631     // Next we finalize all active statements.
632     for (let [, /* k */ statement] of this._anonymousStatements) {
633       statement.finalize();
634     }
635     this._anonymousStatements.clear();
637     for (let [, /* k */ statement] of this._cachedStatements) {
638       statement.finalize();
639     }
640     this._cachedStatements.clear();
642     // This guards against operations performed between the call to this
643     // function and asyncClose() finishing. See also bug 726990.
644     this._open = false;
646     // We must always close the connection at the Sqlite.sys.mjs-level, not
647     // necessarily at the mozStorage-level.
648     let markAsClosed = () => {
649       this._log.debug("Closed");
650       // Now that the connection is closed, no need to keep
651       // a blocker for Barriers.connections.
652       lazy.Barriers.connections.client.removeBlocker(
653         this._deferredClose.promise
654       );
655       this._deferredClose.resolve();
656     };
657     if (wrappedConnections.has(this._identifier)) {
658       wrappedConnections.delete(this._identifier);
659       this._dbConn = null;
660       markAsClosed();
661     } else {
662       this._log.debug("Calling asyncClose().");
663       try {
664         this._dbConn.asyncClose(markAsClosed);
665       } catch (ex) {
666         // If for any reason asyncClose fails, we must still remove the
667         // shutdown blockers and resolve _deferredClose.
668         markAsClosed();
669       } finally {
670         this._dbConn = null;
671       }
672     }
673     return this._deferredClose.promise;
674   },
676   executeCached(sql, params = null, onRow = null) {
677     this.ensureOpen();
679     if (!sql) {
680       throw new Error("sql argument is empty.");
681     }
683     let statement = this._cachedStatements.get(sql);
684     if (!statement) {
685       statement = this._dbConn.createAsyncStatement(sql);
686       this._cachedStatements.set(sql, statement);
687     }
689     this._clearIdleShrinkTimer();
691     return new Promise((resolve, reject) => {
692       try {
693         this._executeStatement(sql, statement, params, onRow).then(
694           result => {
695             this._startIdleShrinkTimer();
696             resolve(result);
697           },
698           error => {
699             this._startIdleShrinkTimer();
700             reject(error);
701           }
702         );
703       } catch (ex) {
704         this._startIdleShrinkTimer();
705         throw ex;
706       }
707     });
708   },
710   execute(sql, params = null, onRow = null) {
711     if (typeof sql != "string") {
712       throw new Error("Must define SQL to execute as a string: " + sql);
713     }
715     this.ensureOpen();
717     let statement = this._dbConn.createAsyncStatement(sql);
718     let index = this._anonymousCounter++;
720     this._anonymousStatements.set(index, statement);
721     this._clearIdleShrinkTimer();
723     let onFinished = () => {
724       this._anonymousStatements.delete(index);
725       statement.finalize();
726       this._startIdleShrinkTimer();
727     };
729     return new Promise((resolve, reject) => {
730       try {
731         this._executeStatement(sql, statement, params, onRow).then(
732           rows => {
733             onFinished();
734             resolve(rows);
735           },
736           error => {
737             onFinished();
738             reject(error);
739           }
740         );
741       } catch (ex) {
742         onFinished();
743         throw ex;
744       }
745     });
746   },
748   get transactionInProgress() {
749     return this._open && this._dbConn.transactionInProgress;
750   },
752   executeTransaction(func, type) {
753     // Identify the caller for debugging purposes.
754     let caller = new Error().stack
755       .split("\n", 3)
756       .pop()
757       .match(/^([^@]*@).*\/([^\/:]+)[:0-9]*$/);
758     caller = caller[1] + caller[2];
759     this._log.debug(`Transaction (type ${type}) requested by: ${caller}`);
761     if (type == OpenedConnection.prototype.TRANSACTION_DEFAULT) {
762       type = this.defaultTransactionType;
763     } else if (!OpenedConnection.TRANSACTION_TYPES.includes(type)) {
764       throw new Error("Unknown transaction type: " + type);
765     }
766     this.ensureOpen();
768     // If a transaction yields on a never resolved promise, or is mistakenly
769     // nested, it could hang the transactions queue forever.  Thus we timeout
770     // the execution after a meaningful amount of time, to ensure in any case
771     // we'll proceed after a while.
772     let timeoutPromise = this._getTimeoutPromise();
774     let promise = this._transactionQueue.then(() => {
775       if (this._closeRequested) {
776         throw new Error("Transaction canceled due to a closed connection.");
777       }
779       let transactionPromise = (async () => {
780         // At this point we should never have an in progress transaction, since
781         // they are enqueued.
782         if (this._initiatedTransaction) {
783           this._log.error(
784             "Unexpected transaction in progress when trying to start a new one."
785           );
786         }
787         try {
788           // We catch errors in statement execution to detect nested transactions.
789           try {
790             await this.execute("BEGIN " + type + " TRANSACTION");
791             this._log.debug(`Begin transaction`);
792             this._initiatedTransaction = true;
793           } catch (ex) {
794             // Unfortunately, if we are wrapping an existing connection, a
795             // transaction could have been started by a client of the same
796             // connection that doesn't use Sqlite.sys.mjs (e.g. C++ consumer).
797             // The best we can do is proceed without a transaction and hope
798             // things won't break.
799             if (wrappedConnections.has(this._identifier)) {
800               this._log.warn(
801                 "A new transaction could not be started cause the wrapped connection had one in progress",
802                 ex
803               );
804             } else {
805               this._log.warn(
806                 "A transaction was already in progress, likely a nested transaction",
807                 ex
808               );
809               throw ex;
810             }
811           }
813           let result;
814           try {
815             result = await Promise.race([func(), timeoutPromise]);
816           } catch (ex) {
817             // It's possible that the exception has been caused by trying to
818             // close the connection in the middle of a transaction.
819             if (this._closeRequested) {
820               this._log.warn(
821                 "Connection closed while performing a transaction",
822                 ex
823               );
824             } else {
825               // Otherwise the function didn't resolve before the timeout, or
826               // generated an unexpected error. Then we rollback.
827               if (ex.becauseTimedOut) {
828                 let caller_module = caller.split(":", 1)[0];
829                 Services.telemetry.keyedScalarAdd(
830                   "mozstorage.sqlitejsm_transaction_timeout",
831                   caller_module,
832                   1
833                 );
834                 this._log.error(
835                   `The transaction requested by ${caller} timed out. Rolling back`,
836                   ex
837                 );
838               } else {
839                 this._log.error(
840                   `Error during transaction requested by ${caller}. Rolling back`,
841                   ex
842                 );
843               }
844               // If we began a transaction, we must rollback it.
845               if (this._initiatedTransaction) {
846                 try {
847                   await this.execute("ROLLBACK TRANSACTION");
848                   this._initiatedTransaction = false;
849                   this._log.debug(`Roll back transaction`);
850                 } catch (inner) {
851                   this._log.error("Could not roll back transaction", inner);
852                 }
853               }
854             }
855             // Rethrow the exception.
856             throw ex;
857           }
859           // See comment above about connection being closed during transaction.
860           if (this._closeRequested) {
861             this._log.warn(
862               "Connection closed before committing the transaction."
863             );
864             throw new Error(
865               "Connection closed before committing the transaction."
866             );
867           }
869           // If we began a transaction, we must commit it.
870           if (this._initiatedTransaction) {
871             try {
872               await this.execute("COMMIT TRANSACTION");
873               this._log.debug(`Commit transaction`);
874             } catch (ex) {
875               this._log.warn("Error committing transaction", ex);
876               throw ex;
877             }
878           }
880           return result;
881         } finally {
882           this._initiatedTransaction = false;
883         }
884       })();
886       return Promise.race([transactionPromise, timeoutPromise]);
887     });
888     // Atomically update the queue before anyone else has a chance to enqueue
889     // further transactions.
890     this._transactionQueue = promise.catch(ex => {
891       this._log.error(ex);
892     });
894     // Make sure that we do not shutdown the connection during a transaction.
895     this._barrier.client.addBlocker(
896       `Transaction (${this._getOperationId()})`,
897       this._transactionQueue
898     );
899     return promise;
900   },
902   shrinkMemory() {
903     this._log.debug("Shrinking memory usage.");
904     return this.execute("PRAGMA shrink_memory").finally(() => {
905       this._clearIdleShrinkTimer();
906     });
907   },
909   discardCachedStatements() {
910     let count = 0;
911     for (let [, /* k */ statement] of this._cachedStatements) {
912       ++count;
913       statement.finalize();
914     }
915     this._cachedStatements.clear();
916     this._log.debug("Discarded " + count + " cached statements.");
917     return count;
918   },
920   interrupt() {
921     this._log.debug("Trying to interrupt.");
922     this.ensureOpen();
923     this._dbConn.interrupt();
924   },
926   /**
927    * Helper method to bind parameters of various kinds through
928    * reflection.
929    */
930   _bindParameters(statement, params) {
931     if (!params) {
932       return;
933     }
935     function bindParam(obj, key, val) {
936       let isBlob =
937         val && typeof val == "object" && val.constructor.name == "Uint8Array";
938       let args = [key, val];
939       if (isBlob) {
940         args.push(val.length);
941       }
942       let methodName = `bind${isBlob ? "Blob" : ""}By${
943         typeof key == "number" ? "Index" : "Name"
944       }`;
945       obj[methodName](...args);
946     }
948     if (Array.isArray(params)) {
949       // It's an array of separate params.
950       if (params.length && typeof params[0] == "object" && params[0] !== null) {
951         let paramsArray = statement.newBindingParamsArray();
952         for (let p of params) {
953           let bindings = paramsArray.newBindingParams();
954           for (let [key, value] of Object.entries(p)) {
955             bindParam(bindings, key, value);
956           }
957           paramsArray.addParams(bindings);
958         }
960         statement.bindParameters(paramsArray);
961         return;
962       }
964       // Indexed params.
965       for (let i = 0; i < params.length; i++) {
966         bindParam(statement, i, params[i]);
967       }
968       return;
969     }
971     // Named params.
972     if (params && typeof params == "object") {
973       for (let k in params) {
974         bindParam(statement, k, params[k]);
975       }
976       return;
977     }
979     throw new Error(
980       "Invalid type for bound parameters. Expected Array or " +
981         "object. Got: " +
982         params
983     );
984   },
986   _executeStatement(sql, statement, params, onRow) {
987     if (statement.state != statement.MOZ_STORAGE_STATEMENT_READY) {
988       throw new Error("Statement is not ready for execution.");
989     }
991     if (onRow && typeof onRow != "function") {
992       throw new Error("onRow must be a function. Got: " + onRow);
993     }
995     this._bindParameters(statement, params);
997     let index = this._statementCounter++;
999     let deferred = lazy.PromiseUtils.defer();
1000     let userCancelled = false;
1001     let errors = [];
1002     let rows = [];
1003     let handledRow = false;
1005     // Don't incur overhead for serializing params unless the messages go
1006     // somewhere.
1007     if (this._log.level <= lazy.Log.Level.Trace) {
1008       let msg = "Stmt #" + index + " " + sql;
1010       if (params) {
1011         msg += " - " + JSON.stringify(params);
1012       }
1013       this._log.trace(msg);
1014     } else {
1015       this._log.debug("Stmt #" + index + " starting");
1016     }
1018     let self = this;
1019     let pending = statement.executeAsync({
1020       handleResult(resultSet) {
1021         // .cancel() may not be immediate and handleResult() could be called
1022         // after a .cancel().
1023         for (
1024           let row = resultSet.getNextRow();
1025           row && !userCancelled;
1026           row = resultSet.getNextRow()
1027         ) {
1028           if (!onRow) {
1029             rows.push(row);
1030             continue;
1031           }
1033           handledRow = true;
1035           try {
1036             onRow(row, () => {
1037               userCancelled = true;
1038               pending.cancel();
1039             });
1040           } catch (e) {
1041             self._log.warn("Exception when calling onRow callback", e);
1042           }
1043         }
1044       },
1046       handleError(error) {
1047         self._log.warn(
1048           "Error when executing SQL (" + error.result + "): " + error.message
1049         );
1050         errors.push(error);
1051       },
1053       handleCompletion(reason) {
1054         self._log.debug("Stmt #" + index + " finished.");
1055         self._pendingStatements.delete(index);
1057         switch (reason) {
1058           case Ci.mozIStorageStatementCallback.REASON_FINISHED:
1059           case Ci.mozIStorageStatementCallback.REASON_CANCELED:
1060             // If there is an onRow handler, we always instead resolve to a
1061             // boolean indicating whether the onRow handler was called or not.
1062             let result = onRow ? handledRow : rows;
1063             deferred.resolve(result);
1064             break;
1066           case Ci.mozIStorageStatementCallback.REASON_ERROR:
1067             let error = new Error(
1068               "Error(s) encountered during statement execution: " +
1069                 errors.map(e => e.message).join(", ")
1070             );
1071             error.errors = errors;
1073             // Forward the error result.
1074             // Corruption is the most critical one so it's handled apart.
1075             if (errors.some(e => e.result == Ci.mozIStorageError.CORRUPT)) {
1076               error.result = Cr.NS_ERROR_FILE_CORRUPTED;
1077             } else {
1078               // Just use the first error result in the other cases.
1079               error.result = convertStorageErrorResult(errors[0]?.result);
1080             }
1082             deferred.reject(error);
1083             break;
1085           default:
1086             deferred.reject(
1087               new Error("Unknown completion reason code: " + reason)
1088             );
1089             break;
1090         }
1091       },
1092     });
1094     this._pendingStatements.set(index, pending);
1095     return deferred.promise;
1096   },
1098   ensureOpen() {
1099     if (!this._open) {
1100       throw new Error("Connection is not open.");
1101     }
1102   },
1104   _clearIdleShrinkTimer() {
1105     if (!this._idleShrinkTimer) {
1106       return;
1107     }
1109     this._idleShrinkTimer.cancel();
1110   },
1112   _startIdleShrinkTimer() {
1113     if (!this._idleShrinkTimer) {
1114       return;
1115     }
1117     this._idleShrinkTimer.initWithCallback(
1118       this.shrinkMemory.bind(this),
1119       this._idleShrinkMS,
1120       this._idleShrinkTimer.TYPE_ONE_SHOT
1121     );
1122   },
1124   /**
1125    * Returns a promise that will resolve after a time comprised between 80% of
1126    * `TRANSACTIONS_TIMEOUT_MS` and `TRANSACTIONS_TIMEOUT_MS`. Use
1127    * this method instead of creating several individual timers that may survive
1128    * longer than necessary.
1129    */
1130   _getTimeoutPromise() {
1131     if (this._timeoutPromise && Cu.now() <= this._timeoutPromiseExpires) {
1132       return this._timeoutPromise;
1133     }
1134     let timeoutPromise = new Promise((resolve, reject) => {
1135       setTimeout(() => {
1136         // Clear out this._timeoutPromise if it hasn't changed since we set it.
1137         if (this._timeoutPromise == timeoutPromise) {
1138           this._timeoutPromise = null;
1139         }
1140         let e = new Error(
1141           "Transaction timeout, most likely caused by unresolved pending work."
1142         );
1143         e.becauseTimedOut = true;
1144         reject(e);
1145       }, Sqlite.TRANSACTIONS_TIMEOUT_MS);
1146     });
1147     this._timeoutPromise = timeoutPromise;
1148     this._timeoutPromiseExpires =
1149       Cu.now() + Sqlite.TRANSACTIONS_TIMEOUT_MS * 0.2;
1150     return this._timeoutPromise;
1151   },
1155  * Opens a connection to a SQLite database.
1157  * The following parameters can control the connection:
1159  *   path -- (string) The filesystem path of the database file to open. If the
1160  *       file does not exist, a new database will be created.
1162  *   sharedMemoryCache -- (bool) Whether multiple connections to the database
1163  *       share the same memory cache. Sharing the memory cache likely results
1164  *       in less memory utilization. However, sharing also requires connections
1165  *       to obtain a lock, possibly making database access slower. Defaults to
1166  *       true.
1168  *   shrinkMemoryOnConnectionIdleMS -- (integer) If defined, the connection
1169  *       will attempt to minimize its memory usage after this many
1170  *       milliseconds of connection idle. The connection is idle when no
1171  *       statements are executing. There is no default value which means no
1172  *       automatic memory minimization will occur. Please note that this is
1173  *       *not* a timer on the idle service and this could fire while the
1174  *       application is active.
1176  *   readOnly -- (bool) Whether to open the database with SQLITE_OPEN_READONLY
1177  *       set. If used, writing to the database will fail. Defaults to false.
1179  *   ignoreLockingMode -- (bool) Whether to ignore locks on the database held
1180  *       by other connections. If used, implies readOnly. Defaults to false.
1181  *       USE WITH EXTREME CAUTION. This mode WILL produce incorrect results or
1182  *       return "false positive" corruption errors if other connections write
1183  *       to the DB at the same time.
1185  *   vacuumOnIdle -- (bool) Whether to register this connection to be vacuumed
1186  *       on idle by the VacuumManager component.
1187  *       If you're vacuum-ing an incremental vacuum database, ensure to also
1188  *       set incrementalVacuum to true, otherwise this will try to change it
1189  *       to full vacuum mode.
1191  *   incrementalVacuum -- (bool) if set to true auto_vacuum = INCREMENTAL will
1192  *       be enabled for the database.
1193  *       Changing auto vacuum of an already populated database requires a full
1194  *       VACUUM. You can evaluate to enable vacuumOnIdle for that.
1196  *   pageSize -- (integer) This allows to set a custom page size for the
1197  *       database. It is usually not necessary to set it, since the default
1198  *       value should be good for most consumers.
1199  *       Changing the page size of an already populated database requires a full
1200  *       VACUUM. You can evaluate to enable vacuumOnIdle for that.
1202  *   testDelayedOpenPromise -- (promise) Used by tests to delay the open
1203  *       callback handling and execute code between asyncOpen and its callback.
1205  * FUTURE options to control:
1207  *   special named databases
1208  *   pragma TEMP STORE = MEMORY
1209  *   TRUNCATE JOURNAL
1210  *   SYNCHRONOUS = full
1212  * @param options
1213  *        (Object) Parameters to control connection and open options.
1215  * @return Promise<OpenedConnection>
1216  */
1217 function openConnection(options) {
1218   let log = lazy.Log.repository.getLoggerWithMessagePrefix(
1219     "Sqlite.sys.mjs",
1220     `ConnectionOpener: `
1221   );
1222   log.manageLevelFromPref("toolkit.sqlitejsm.loglevel");
1224   if (!options.path) {
1225     throw new Error("path not specified in connection options.");
1226   }
1228   if (isClosed()) {
1229     throw new Error(
1230       "Sqlite.sys.mjs has been shutdown. Cannot open connection to: " +
1231         options.path
1232     );
1233   }
1235   // Retains absolute paths and normalizes relative as relative to profile.
1236   let path = options.path;
1237   let file;
1238   try {
1239     file = lazy.FileUtils.File(path);
1240   } catch (ex) {
1241     // For relative paths, we will get an exception from trying to initialize
1242     // the file. We must then join this path to the profile directory.
1243     if (ex.result == Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH) {
1244       path = PathUtils.joinRelative(
1245         Services.dirsvc.get("ProfD", Ci.nsIFile).path,
1246         options.path
1247       );
1248       file = lazy.FileUtils.File(path);
1249     } else {
1250       throw ex;
1251     }
1252   }
1254   let sharedMemoryCache =
1255     "sharedMemoryCache" in options ? options.sharedMemoryCache : true;
1257   let openedOptions = {};
1259   if ("shrinkMemoryOnConnectionIdleMS" in options) {
1260     if (!Number.isInteger(options.shrinkMemoryOnConnectionIdleMS)) {
1261       throw new Error(
1262         "shrinkMemoryOnConnectionIdleMS must be an integer. " +
1263           "Got: " +
1264           options.shrinkMemoryOnConnectionIdleMS
1265       );
1266     }
1268     openedOptions.shrinkMemoryOnConnectionIdleMS =
1269       options.shrinkMemoryOnConnectionIdleMS;
1270   }
1272   if ("defaultTransactionType" in options) {
1273     let defaultTransactionType = options.defaultTransactionType;
1274     if (!OpenedConnection.TRANSACTION_TYPES.includes(defaultTransactionType)) {
1275       throw new Error(
1276         "Unknown default transaction type: " + defaultTransactionType
1277       );
1278     }
1280     openedOptions.defaultTransactionType = defaultTransactionType;
1281   }
1283   if ("vacuumOnIdle" in options) {
1284     if (typeof options.vacuumOnIdle != "boolean") {
1285       throw new Error("Invalid vacuumOnIdle: " + options.vacuumOnIdle);
1286     }
1287     openedOptions.vacuumOnIdle = options.vacuumOnIdle;
1288   }
1290   if ("incrementalVacuum" in options) {
1291     if (typeof options.incrementalVacuum != "boolean") {
1292       throw new Error(
1293         "Invalid incrementalVacuum: " + options.incrementalVacuum
1294       );
1295     }
1296     openedOptions.incrementalVacuum = options.incrementalVacuum;
1297   }
1299   if ("pageSize" in options) {
1300     if (
1301       ![512, 1024, 2048, 4096, 8192, 16384, 32768, 65536].includes(
1302         options.pageSize
1303       )
1304     ) {
1305       throw new Error("Invalid pageSize: " + options.pageSize);
1306     }
1307     openedOptions.pageSize = options.pageSize;
1308   }
1310   let identifier = getIdentifierByFileName(PathUtils.filename(path));
1312   log.debug("Opening database: " + path + " (" + identifier + ")");
1314   return new Promise((resolve, reject) => {
1315     let dbOpenOptions = Ci.mozIStorageService.OPEN_DEFAULT;
1316     if (sharedMemoryCache) {
1317       dbOpenOptions |= Ci.mozIStorageService.OPEN_SHARED;
1318     }
1319     if (options.readOnly) {
1320       dbOpenOptions |= Ci.mozIStorageService.OPEN_READONLY;
1321     }
1322     if (options.ignoreLockingMode) {
1323       dbOpenOptions |= Ci.mozIStorageService.OPEN_IGNORE_LOCKING_MODE;
1324       dbOpenOptions |= Ci.mozIStorageService.OPEN_READONLY;
1325     }
1327     let dbConnectionOptions = Ci.mozIStorageService.CONNECTION_DEFAULT;
1329     Services.storage.openAsyncDatabase(
1330       file,
1331       dbOpenOptions,
1332       dbConnectionOptions,
1333       async (status, connection) => {
1334         if (!connection) {
1335           log.error(`Could not open connection to ${path}: ${status}`);
1336           let error = new Components.Exception(
1337             `Could not open connection to ${path}: ${status}`,
1338             status
1339           );
1340           reject(error);
1341           return;
1342         }
1343         log.debug("Connection opened");
1345         if (options.testDelayedOpenPromise) {
1346           await options.testDelayedOpenPromise;
1347         }
1349         if (isClosed()) {
1350           connection.QueryInterface(Ci.mozIStorageAsyncConnection).asyncClose();
1351           reject(
1352             new Error(
1353               "Sqlite.sys.mjs has been shutdown. Cannot open connection to: " +
1354                 options.path
1355             )
1356           );
1357           return;
1358         }
1360         try {
1361           resolve(
1362             new OpenedConnection(
1363               connection.QueryInterface(Ci.mozIStorageAsyncConnection),
1364               identifier,
1365               openedOptions
1366             )
1367           );
1368         } catch (ex) {
1369           log.error("Could not open database", ex);
1370           connection.asyncClose();
1371           reject(ex);
1372         }
1373       }
1374     );
1375   });
1379  * Creates a clone of an existing and open Storage connection.  The clone has
1380  * the same underlying characteristics of the original connection and is
1381  * returned in form of an OpenedConnection handle.
1383  * The following parameters can control the cloned connection:
1385  *   connection -- (mozIStorageAsyncConnection) The original Storage connection
1386  *       to clone.  It's not possible to clone connections to memory databases.
1388  *   readOnly -- (boolean) - If true the clone will be read-only.  If the
1389  *       original connection is already read-only, the clone will be, regardless
1390  *       of this option.  If the original connection is using the shared cache,
1391  *       this parameter will be ignored and the clone will be as privileged as
1392  *       the original connection.
1393  *   shrinkMemoryOnConnectionIdleMS -- (integer) If defined, the connection
1394  *       will attempt to minimize its memory usage after this many
1395  *       milliseconds of connection idle. The connection is idle when no
1396  *       statements are executing. There is no default value which means no
1397  *       automatic memory minimization will occur. Please note that this is
1398  *       *not* a timer on the idle service and this could fire while the
1399  *       application is active.
1402  * @param options
1403  *        (Object) Parameters to control connection and clone options.
1405  * @return Promise<OpenedConnection>
1406  */
1407 function cloneStorageConnection(options) {
1408   let log = lazy.Log.repository.getLoggerWithMessagePrefix(
1409     "Sqlite.sys.mjs",
1410     `ConnectionCloner: `
1411   );
1412   log.manageLevelFromPref("toolkit.sqlitejsm.loglevel");
1414   let source = options && options.connection;
1415   if (!source) {
1416     throw new TypeError("connection not specified in clone options.");
1417   }
1418   if (!(source instanceof Ci.mozIStorageAsyncConnection)) {
1419     throw new TypeError("Connection must be a valid Storage connection.");
1420   }
1422   if (isClosed()) {
1423     throw new Error(
1424       "Sqlite.sys.mjs has been shutdown. Cannot clone connection to: " +
1425         source.databaseFile.path
1426     );
1427   }
1429   let openedOptions = {};
1431   if ("shrinkMemoryOnConnectionIdleMS" in options) {
1432     if (!Number.isInteger(options.shrinkMemoryOnConnectionIdleMS)) {
1433       throw new TypeError(
1434         "shrinkMemoryOnConnectionIdleMS must be an integer. " +
1435           "Got: " +
1436           options.shrinkMemoryOnConnectionIdleMS
1437       );
1438     }
1439     openedOptions.shrinkMemoryOnConnectionIdleMS =
1440       options.shrinkMemoryOnConnectionIdleMS;
1441   }
1443   let path = source.databaseFile.path;
1444   let identifier = getIdentifierByFileName(PathUtils.filename(path));
1446   log.debug("Cloning database: " + path + " (" + identifier + ")");
1448   return new Promise((resolve, reject) => {
1449     source.asyncClone(!!options.readOnly, (status, connection) => {
1450       if (!connection) {
1451         log.error("Could not clone connection: " + status);
1452         reject(new Error("Could not clone connection: " + status));
1453         return;
1454       }
1455       log.debug("Connection cloned");
1457       if (isClosed()) {
1458         connection.QueryInterface(Ci.mozIStorageAsyncConnection).asyncClose();
1459         reject(
1460           new Error(
1461             "Sqlite.sys.mjs has been shutdown. Cannot open connection to: " +
1462               options.path
1463           )
1464         );
1465         return;
1466       }
1468       try {
1469         let conn = connection.QueryInterface(Ci.mozIStorageAsyncConnection);
1470         resolve(new OpenedConnection(conn, identifier, openedOptions));
1471       } catch (ex) {
1472         log.error("Could not clone database", ex);
1473         connection.asyncClose();
1474         reject(ex);
1475       }
1476     });
1477   });
1481  * Wraps an existing and open Storage connection with Sqlite.sys.mjs API.  The
1482  * wrapped connection clone has the same underlying characteristics of the
1483  * original connection and is returned in form of an OpenedConnection handle.
1485  * Clients are responsible for closing both the Sqlite.sys.mjs wrapper and the
1486  * underlying mozStorage connection.
1488  * The following parameters can control the wrapped connection:
1490  *   connection -- (mozIStorageAsyncConnection) The original Storage connection
1491  *       to wrap.
1493  * @param options
1494  *        (Object) Parameters to control connection and wrap options.
1496  * @return Promise<OpenedConnection>
1497  */
1498 function wrapStorageConnection(options) {
1499   let log = lazy.Log.repository.getLoggerWithMessagePrefix(
1500     "Sqlite.sys.mjs",
1501     `ConnectionCloner: `
1502   );
1503   log.manageLevelFromPref("toolkit.sqlitejsm.loglevel");
1505   let connection = options && options.connection;
1506   if (!connection || !(connection instanceof Ci.mozIStorageAsyncConnection)) {
1507     throw new TypeError("connection not specified or invalid.");
1508   }
1510   if (isClosed()) {
1511     throw new Error(
1512       "Sqlite.sys.mjs has been shutdown. Cannot wrap connection to: " +
1513         connection.databaseFile.path
1514     );
1515   }
1517   let identifier = getIdentifierByFileName(connection.databaseFile.leafName);
1519   log.debug("Wrapping database: " + identifier);
1520   return new Promise(resolve => {
1521     try {
1522       let conn = connection.QueryInterface(Ci.mozIStorageAsyncConnection);
1523       let wrapper = new OpenedConnection(conn, identifier);
1524       // We must not handle shutdown of a wrapped connection, since that is
1525       // already handled by the opener.
1526       wrappedConnections.add(identifier);
1527       resolve(wrapper);
1528     } catch (ex) {
1529       log.error("Could not wrap database", ex);
1530       throw ex;
1531     }
1532   });
1536  * Handle on an opened SQLite database.
1538  * This is essentially a glorified wrapper around mozIStorageConnection.
1539  * However, it offers some compelling advantages.
1541  * The main functions on this type are `execute` and `executeCached`. These are
1542  * ultimately how all SQL statements are executed. It's worth explaining their
1543  * differences.
1545  * `execute` is used to execute one-shot SQL statements. These are SQL
1546  * statements that are executed one time and then thrown away. They are useful
1547  * for dynamically generated SQL statements and clients who don't care about
1548  * performance (either their own or wasting resources in the overall
1549  * application). Because of the performance considerations, it is recommended
1550  * to avoid `execute` unless the statement you are executing will only be
1551  * executed once or seldomly.
1553  * `executeCached` is used to execute a statement that will presumably be
1554  * executed multiple times. The statement is parsed once and stuffed away
1555  * inside the connection instance. Subsequent calls to `executeCached` will not
1556  * incur the overhead of creating a new statement object. This should be used
1557  * in preference to `execute` when a specific SQL statement will be executed
1558  * multiple times.
1560  * Instances of this type are not meant to be created outside of this file.
1561  * Instead, first open an instance of `UnopenedSqliteConnection` and obtain
1562  * an instance of this type by calling `open`.
1564  * FUTURE IMPROVEMENTS
1566  *   Ability to enqueue operations. Currently there can be race conditions,
1567  *   especially as far as transactions are concerned. It would be nice to have
1568  *   an enqueueOperation(func) API that serially executes passed functions.
1570  *   Support for SAVEPOINT (named/nested transactions) might be useful.
1572  * @param connection
1573  *        (mozIStorageConnection) Underlying SQLite connection.
1574  * @param identifier
1575  *        (string) The unique identifier of this database. It may be used for
1576  *        logging or as a key in Maps.
1577  * @param options [optional]
1578  *        (object) Options to control behavior of connection. See
1579  *        `openConnection`.
1580  */
1581 function OpenedConnection(connection, identifier, options = {}) {
1582   // Store all connection data in a field distinct from the
1583   // witness. This enables us to store an additional reference to this
1584   // field without preventing garbage collection of
1585   // OpenedConnection. On garbage collection, we will still be able to
1586   // close the database using this extra reference.
1587   this._connectionData = new ConnectionData(connection, identifier, options);
1589   // Store the extra reference in a map with connection identifier as
1590   // key.
1591   ConnectionData.byId.set(
1592     this._connectionData._identifier,
1593     this._connectionData
1594   );
1596   // Make a finalization witness. If this object is garbage collected
1597   // before its `forget` method has been called, an event with topic
1598   // "sqlite-finalization-witness" is broadcasted along with the
1599   // connection identifier string of the database.
1600   this._witness = lazy.FinalizationWitnessService.make(
1601     "sqlite-finalization-witness",
1602     this._connectionData._identifier
1603   );
1606 OpenedConnection.TRANSACTION_TYPES = ["DEFERRED", "IMMEDIATE", "EXCLUSIVE"];
1608 // Converts a `mozIStorageAsyncConnection::TRANSACTION_*` constant into the
1609 // corresponding `OpenedConnection.TRANSACTION_TYPES` constant.
1610 function convertStorageTransactionType(type) {
1611   if (!(type in OpenedConnection.TRANSACTION_TYPES)) {
1612     throw new Error("Unknown storage transaction type: " + type);
1613   }
1614   return OpenedConnection.TRANSACTION_TYPES[type];
1617 OpenedConnection.prototype = Object.freeze({
1618   TRANSACTION_DEFAULT: "DEFAULT",
1619   TRANSACTION_DEFERRED: "DEFERRED",
1620   TRANSACTION_IMMEDIATE: "IMMEDIATE",
1621   TRANSACTION_EXCLUSIVE: "EXCLUSIVE",
1623   /**
1624    * Returns a handle to the underlying `mozIStorageAsyncConnection`. This is
1625    * ⚠️ **extremely unsafe** ⚠️ because `Sqlite.sys.mjs` continues to manage the
1626    * connection's lifecycle, including transactions and shutdown blockers.
1627    * Misusing the raw connection can easily lead to data loss, memory leaks,
1628    * and errors.
1629    *
1630    * Consumers of the raw connection **must not** close or re-wrap it,
1631    * and should not run statements concurrently with `Sqlite.sys.mjs`.
1632    *
1633    * It's _much_ safer to open a `mozIStorage{Async}Connection` yourself,
1634    * and access it from JavaScript via `Sqlite.wrapStorageConnection`.
1635    * `unsafeRawConnection` is an escape hatch for cases where you can't
1636    * do that.
1637    *
1638    * Please do _not_ add new uses of `unsafeRawConnection` without review
1639    * from a storage peer.
1640    */
1641   get unsafeRawConnection() {
1642     return this._connectionData._dbConn;
1643   },
1645   /**
1646    * Returns the maximum number of bound parameters for statements executed
1647    * on this connection.
1648    *
1649    * @type {number}
1650    */
1651   get variableLimit() {
1652     return this.unsafeRawConnection.variableLimit;
1653   },
1655   /**
1656    * The integer schema version of the database.
1657    *
1658    * This is 0 if not schema version has been set.
1659    *
1660    * @return Promise<int>
1661    */
1662   getSchemaVersion(schemaName = "main") {
1663     return this.execute(`PRAGMA ${schemaName}.user_version`).then(result =>
1664       result[0].getInt32(0)
1665     );
1666   },
1668   setSchemaVersion(value, schemaName = "main") {
1669     if (!Number.isInteger(value)) {
1670       // Guarding against accidental SQLi
1671       throw new TypeError("Schema version must be an integer. Got " + value);
1672     }
1673     this._connectionData.ensureOpen();
1674     return this.execute(`PRAGMA ${schemaName}.user_version = ${value}`);
1675   },
1677   /**
1678    * Close the database connection.
1679    *
1680    * This must be performed when you are finished with the database.
1681    *
1682    * Closing the database connection has the side effect of forcefully
1683    * cancelling all active statements. Therefore, callers should ensure that
1684    * all active statements have completed before closing the connection, if
1685    * possible.
1686    *
1687    * The returned promise will be resolved once the connection is closed.
1688    * Successive calls to close() return the same promise.
1689    *
1690    * IMPROVEMENT: Resolve the promise to a closed connection which can be
1691    * reopened.
1692    *
1693    * @return Promise<>
1694    */
1695   close() {
1696     // Unless cleanup has already been done by a previous call to
1697     // `close`, delete the database entry from map and tell the
1698     // finalization witness to forget.
1699     if (ConnectionData.byId.has(this._connectionData._identifier)) {
1700       ConnectionData.byId.delete(this._connectionData._identifier);
1701       this._witness.forget();
1702     }
1703     return this._connectionData.close();
1704   },
1706   /**
1707    * Clones this connection to a new Sqlite one.
1708    *
1709    * The following parameters can control the cloned connection:
1710    *
1711    * @param readOnly
1712    *        (boolean) - If true the clone will be read-only.  If the original
1713    *        connection is already read-only, the clone will be, regardless of
1714    *        this option.  If the original connection is using the shared cache,
1715    *        this parameter will be ignored and the clone will be as privileged as
1716    *        the original connection.
1717    *
1718    * @return Promise<OpenedConnection>
1719    */
1720   clone(readOnly = false) {
1721     return this._connectionData.clone(readOnly);
1722   },
1724   executeBeforeShutdown(name, task) {
1725     return this._connectionData.executeBeforeShutdown(this, name, task);
1726   },
1728   /**
1729    * Execute a SQL statement and cache the underlying statement object.
1730    *
1731    * This function executes a SQL statement and also caches the underlying
1732    * derived statement object so subsequent executions are faster and use
1733    * less resources.
1734    *
1735    * This function optionally binds parameters to the statement as well as
1736    * optionally invokes a callback for every row retrieved.
1737    *
1738    * By default, no parameters are bound and no callback will be invoked for
1739    * every row.
1740    *
1741    * Bound parameters can be defined as an Array of positional arguments or
1742    * an object mapping named parameters to their values. If there are no bound
1743    * parameters, the caller can pass nothing or null for this argument.
1744    *
1745    * Callers are encouraged to pass objects rather than Arrays for bound
1746    * parameters because they prevent foot guns. With positional arguments, it
1747    * is simple to modify the parameter count or positions without fixing all
1748    * users of the statement. Objects/named parameters are a little safer
1749    * because changes in order alone won't result in bad things happening.
1750    *
1751    * When `onRow` is not specified, all returned rows are buffered before the
1752    * returned promise is resolved. For INSERT or UPDATE statements, this has
1753    * no effect because no rows are returned from these. However, it has
1754    * implications for SELECT statements.
1755    *
1756    * If your SELECT statement could return many rows or rows with large amounts
1757    * of data, for performance reasons it is recommended to pass an `onRow`
1758    * handler. Otherwise, the buffering may consume unacceptable amounts of
1759    * resources.
1760    *
1761    * If the second parameter of an `onRow` handler is called during execution
1762    * of the `onRow` handler, the execution of the statement is immediately
1763    * cancelled. Subsequent rows will not be processed and no more `onRow`
1764    * invocations will be made. The promise is resolved immediately.
1765    *
1766    * If an exception is thrown by the `onRow` handler, the exception is logged
1767    * and processing of subsequent rows occurs as if nothing happened. The
1768    * promise is still resolved (not rejected).
1769    *
1770    * The return value is a promise that will be resolved when the statement
1771    * has completed fully.
1772    *
1773    * The promise will be rejected with an `Error` instance if the statement
1774    * did not finish execution fully. The `Error` may have an `errors` property.
1775    * If defined, it will be an Array of objects describing individual errors.
1776    * Each object has the properties `result` and `message`. `result` is a
1777    * numeric error code and `message` is a string description of the problem.
1778    *
1779    * @param name
1780    *        (string) The name of the registered statement to execute.
1781    * @param params optional
1782    *        (Array or object) Parameters to bind.
1783    * @param onRow optional
1784    *        (function) Callback to receive each row from result.
1785    */
1786   executeCached(sql, params = null, onRow = null) {
1787     if (isInvalidBoundLikeQuery(sql)) {
1788       throw new Error("Please enter a LIKE clause with bindings");
1789     }
1790     return this._connectionData.executeCached(sql, params, onRow);
1791   },
1793   /**
1794    * Execute a one-shot SQL statement.
1795    *
1796    * If you find yourself feeding the same SQL string in this function, you
1797    * should *not* use this function and instead use `executeCached`.
1798    *
1799    * See `executeCached` for the meaning of the arguments and extended usage info.
1800    *
1801    * @param sql
1802    *        (string) SQL to execute.
1803    * @param params optional
1804    *        (Array or Object) Parameters to bind to the statement.
1805    * @param onRow optional
1806    *        (function) Callback to receive result of a single row.
1807    */
1808   execute(sql, params = null, onRow = null) {
1809     if (isInvalidBoundLikeQuery(sql)) {
1810       throw new Error("Please enter a LIKE clause with bindings");
1811     }
1812     return this._connectionData.execute(sql, params, onRow);
1813   },
1815   /**
1816    * The default behavior for transactions run on this connection.
1817    */
1818   get defaultTransactionType() {
1819     return this._connectionData.defaultTransactionType;
1820   },
1822   /**
1823    * Whether a transaction is currently in progress.
1824    *
1825    * Note that this is true if a transaction is active on the connection,
1826    * regardless of whether it was started by `Sqlite.sys.mjs` or another consumer.
1827    * See the explanation above `mozIStorageConnection.transactionInProgress` for
1828    * why this distinction matters.
1829    */
1830   get transactionInProgress() {
1831     return this._connectionData.transactionInProgress;
1832   },
1834   /**
1835    * Perform a transaction.
1836    *
1837    * *****************************************************************************
1838    * YOU SHOULD _NEVER_ NEST executeTransaction CALLS FOR ANY REASON, NOR
1839    * DIRECTLY, NOR THROUGH OTHER PROMISES.
1840    * FOR EXAMPLE, NEVER DO SOMETHING LIKE:
1841    *   await executeTransaction(async function () {
1842    *     ...some_code...
1843    *     await executeTransaction(async function () { // WRONG!
1844    *       ...some_code...
1845    *     })
1846    *     await someCodeThatExecuteTransaction(); // WRONG!
1847    *     await neverResolvedPromise; // WRONG!
1848    *   });
1849    * NESTING CALLS WILL BLOCK ANY FUTURE TRANSACTION UNTIL A TIMEOUT KICKS IN.
1850    * *****************************************************************************
1851    *
1852    * A transaction is specified by a user-supplied function that is an
1853    * async function. The function receives this connection instance as its argument.
1854    *
1855    * The supplied function is expected to return promises. These are often
1856    * promises created by calling `execute` and `executeCached`. If the
1857    * generator is exhausted without any errors being thrown, the
1858    * transaction is committed. If an error occurs, the transaction is
1859    * rolled back.
1860    *
1861    * The returned value from this function is a promise that will be resolved
1862    * once the transaction has been committed or rolled back. The promise will
1863    * be resolved to whatever value the supplied function resolves to. If
1864    * the transaction is rolled back, the promise is rejected.
1865    *
1866    * @param func
1867    *        (function) What to perform as part of the transaction.
1868    * @param type optional
1869    *        One of the TRANSACTION_* constants attached to this type.
1870    */
1871   executeTransaction(func, type = this.TRANSACTION_DEFAULT) {
1872     return this._connectionData.executeTransaction(() => func(this), type);
1873   },
1875   /**
1876    * Whether a table exists in the database (both persistent and temporary tables).
1877    *
1878    * @param name
1879    *        (string) Name of the table.
1880    *
1881    * @return Promise<bool>
1882    */
1883   tableExists(name) {
1884     return this.execute(
1885       "SELECT name FROM (SELECT * FROM sqlite_master UNION ALL " +
1886         "SELECT * FROM sqlite_temp_master) " +
1887         "WHERE type = 'table' AND name=?",
1888       [name]
1889     ).then(function onResult(rows) {
1890       return Promise.resolve(!!rows.length);
1891     });
1892   },
1894   /**
1895    * Whether a named index exists (both persistent and temporary tables).
1896    *
1897    * @param name
1898    *        (string) Name of the index.
1899    *
1900    * @return Promise<bool>
1901    */
1902   indexExists(name) {
1903     return this.execute(
1904       "SELECT name FROM (SELECT * FROM sqlite_master UNION ALL " +
1905         "SELECT * FROM sqlite_temp_master) " +
1906         "WHERE type = 'index' AND name=?",
1907       [name]
1908     ).then(function onResult(rows) {
1909       return Promise.resolve(!!rows.length);
1910     });
1911   },
1913   /**
1914    * Free up as much memory from the underlying database connection as possible.
1915    *
1916    * @return Promise<>
1917    */
1918   shrinkMemory() {
1919     return this._connectionData.shrinkMemory();
1920   },
1922   /**
1923    * Discard all cached statements.
1924    *
1925    * Note that this relies on us being non-interruptible between
1926    * the insertion or retrieval of a statement in the cache and its
1927    * execution: we finalize all statements, which is only safe if
1928    * they will not be executed again.
1929    *
1930    * @return (integer) the number of statements discarded.
1931    */
1932   discardCachedStatements() {
1933     return this._connectionData.discardCachedStatements();
1934   },
1936   /**
1937    * Interrupts pending database operations returning at the first opportunity.
1938    * Statement execution will throw an NS_ERROR_ABORT failure.
1939    * Can only be used on read-only connections.
1940    */
1941   interrupt() {
1942     this._connectionData.interrupt();
1943   },
1946 export var Sqlite = {
1947   // The maximum time to wait before considering a transaction stuck and
1948   // issuing a ROLLBACK, see `executeTransaction`. Could be modified by tests.
1949   TRANSACTIONS_TIMEOUT_MS: 300000, // 5 minutes
1951   openConnection,
1952   cloneStorageConnection,
1953   wrapStorageConnection,
1954   /**
1955    * Shutdown barrier client. May be used by clients to perform last-minute
1956    * cleanup prior to the shutdown of this module.
1957    *
1958    * See the documentation of AsyncShutdown.Barrier.prototype.client.
1959    */
1960   get shutdown() {
1961     return lazy.Barriers.shutdown.client;
1962   },
1963   failTestsOnAutoClose(enabled) {
1964     Debugging.failTestsOnAutoClose = enabled;
1965   },