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/. */
9 * Database file names can be exposed through telemetry and in crash reports on
10 * the https://crash-stats.mozilla.org site, to allow recognizing the affected
12 * if your database name may contain privacy sensitive information, e.g. an
13 * URL origin, you should use openDatabaseWithFileURL and pass an explicit
14 * TelemetryFilename to it. That name will be used both for telemetry and for
15 * thread names in crash reports.
16 * If you have different needs (e.g. using the javascript module or an async
17 * connection from the main thread) please coordinate with the mozStorage peers.
20 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
22 import { setTimeout } from "resource://gre/modules/Timer.sys.mjs";
26 ChromeUtils.defineESModuleGetters(
29 AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
30 FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
32 { global: "contextual" }
35 XPCOMUtils.defineLazyServiceGetter(
37 "FinalizationWitnessService",
38 "@mozilla.org/toolkit/finalizationwitness;1",
39 "nsIFinalizationWitnessService"
42 // Regular expression used by isInvalidBoundLikeQuery
43 var likeSqlRegex = /\bLIKE\b\s(?![@:?])/i;
45 // Counts the number of created connections per database basename(). This is
46 // used for logging to distinguish connection instances.
47 var connectionCounters = new Map();
49 // Tracks identifiers of wrapped connections, that are Storage connections
50 // opened through mozStorage and then wrapped by Sqlite.sys.mjs to use its syntactic
51 // sugar API. Since these connections have an unknown origin, we use this set
52 // to differentiate their behavior.
53 var wrappedConnections = new Set();
56 * Once `true`, reject any attempt to open or close a database.
59 // If Barriers have not been initialized yet, just trust AppStartup.
61 typeof Object.getOwnPropertyDescriptor(lazy, "Barriers").get == "function"
63 // It's still possible to open new connections at profile-before-change, so
64 // use the next phase here, as a fallback.
65 return Services.startup.isInOrBeyondShutdownPhase(
66 Ci.nsIAppStartup.SHUTDOWN_PHASE_XPCOMWILLSHUTDOWN
69 return lazy.Barriers.shutdown.client.isClosed;
73 // Tests should fail if a connection auto closes. The exception is
74 // when finalization itself is tested, in which case this flag
75 // should be set to false.
76 failTestsOnAutoClose: true,
80 * Helper function to check whether LIKE is implemented using proper bindings.
83 * (string) The SQL query to be verified.
84 * @return boolean value telling us whether query was correct or not
86 function isInvalidBoundLikeQuery(sql) {
87 return likeSqlRegex.test(sql);
90 // Displays a script error message
91 function logScriptError(message) {
92 let consoleMessage = Cc["@mozilla.org/scripterror;1"].createInstance(
95 let stack = new Error();
102 Ci.nsIScriptError.errorFlag,
103 "component javascript"
105 Services.console.logMessage(consoleMessage);
107 // This `Promise.reject` will cause tests to fail. The debugging
108 // flag can be used to suppress this for tests that explicitly
110 if (Debugging.failTestsOnAutoClose) {
111 Promise.reject(new Error(message));
116 * Gets connection identifier from its database file name.
119 * A database file string name.
120 * @return the connection identifier.
122 function getIdentifierByFileName(fileName) {
123 let number = connectionCounters.get(fileName) || 0;
124 connectionCounters.set(fileName, number + 1);
125 return fileName + "#" + number;
129 * Convert mozIStorageError to common NS_ERROR_*
130 * The conversion is mostly based on the one in
131 * mozStoragePrivateHelpers::ConvertResultCode, plus a few additions.
133 * @param {integer} result a mozIStorageError result code.
134 * @returns {integer} an NS_ERROR_* result code.
136 function convertStorageErrorResult(result) {
138 case Ci.mozIStorageError.PERM:
139 case Ci.mozIStorageError.AUTH:
140 case Ci.mozIStorageError.CANTOPEN:
141 return Cr.NS_ERROR_FILE_ACCESS_DENIED;
142 case Ci.mozIStorageError.LOCKED:
143 return Cr.NS_ERROR_FILE_IS_LOCKED;
144 case Ci.mozIStorageError.READONLY:
145 return Cr.NS_ERROR_FILE_READ_ONLY;
146 case Ci.mozIStorageError.ABORT:
147 case Ci.mozIStorageError.INTERRUPT:
148 return Cr.NS_ERROR_ABORT;
149 case Ci.mozIStorageError.TOOBIG:
150 case Ci.mozIStorageError.FULL:
151 return Cr.NS_ERROR_FILE_NO_DEVICE_SPACE;
152 case Ci.mozIStorageError.NOMEM:
153 return Cr.NS_ERROR_OUT_OF_MEMORY;
154 case Ci.mozIStorageError.BUSY:
155 return Cr.NS_ERROR_STORAGE_BUSY;
156 case Ci.mozIStorageError.CONSTRAINT:
157 return Cr.NS_ERROR_STORAGE_CONSTRAINT;
158 case Ci.mozIStorageError.NOLFS:
159 case Ci.mozIStorageError.IOERR:
160 return Cr.NS_ERROR_STORAGE_IOERR;
161 case Ci.mozIStorageError.SCHEMA:
162 case Ci.mozIStorageError.MISMATCH:
163 case Ci.mozIStorageError.MISUSE:
164 case Ci.mozIStorageError.RANGE:
165 return Ci.NS_ERROR_UNEXPECTED;
166 case Ci.mozIStorageError.CORRUPT:
167 case Ci.mozIStorageError.EMPTY:
168 case Ci.mozIStorageError.FORMAT:
169 case Ci.mozIStorageError.NOTADB:
170 return Cr.NS_ERROR_FILE_CORRUPTED;
172 return Cr.NS_ERROR_FAILURE;
176 * Barriers used to ensure that Sqlite.sys.mjs is shutdown after all
179 ChromeUtils.defineLazyGetter(lazy, "Barriers", () => {
182 * Public barrier that clients may use to add blockers to the
183 * shutdown of Sqlite.sys.mjs. Triggered by profile-before-change.
184 * Once all blockers of this barrier are lifted, we close the
185 * ability to open new connections.
187 shutdown: new lazy.AsyncShutdown.Barrier(
188 "Sqlite.sys.mjs: wait until all clients have completed their task"
192 * Private barrier blocked by connections that are still open.
193 * Triggered after Barriers.shutdown is lifted and `isClosed()` returns
196 connections: new lazy.AsyncShutdown.Barrier(
197 "Sqlite.sys.mjs: wait until all connections are closed"
202 * Observer for the event which is broadcasted when the finalization
203 * witness `_witness` of `OpenedConnection` is garbage collected.
205 * The observer is passed the connection identifier of the database
206 * connection that is being finalized.
208 let finalizationObserver = function (subject, topic, identifier) {
209 let connectionData = ConnectionData.byId.get(identifier);
211 if (connectionData === undefined) {
213 "Error: Attempt to finalize unknown Sqlite connection: " +
220 ConnectionData.byId.delete(identifier);
222 "Warning: Sqlite connection '" +
224 "' was not properly closed. Auto-close triggered by garbage collection.\n"
226 connectionData.close();
228 Services.obs.addObserver(finalizationObserver, "sqlite-finalization-witness");
231 * Ensure that Sqlite.sys.mjs:
232 * - informs its clients before shutting down;
233 * - lets clients open connections during shutdown, if necessary;
234 * - waits for all connections to be closed before shutdown.
236 lazy.AsyncShutdown.profileBeforeChange.addBlocker(
237 "Sqlite.sys.mjs shutdown blocker",
239 await Barriers.shutdown.wait();
240 // At this stage, all clients have had a chance to open (and close)
241 // their databases. Some previous close operations may still be pending,
242 // so we need to wait until they are complete before proceeding.
243 await Barriers.connections.wait();
245 // Everything closed, no finalization events to catch
246 Services.obs.removeObserver(
247 finalizationObserver,
248 "sqlite-finalization-witness"
254 // We are waiting for the connections to close. The interesting
255 // status is therefore the list of connections still pending.
257 description: "Waiting for connections to close",
258 state: Barriers.connections.state,
262 // We are still in the first stage: waiting for the barrier
263 // to be lifted. The interesting status is therefore that of
266 description: "Waiting for the barrier to be lifted",
267 state: Barriers.shutdown.state,
275 const VACUUM_CATEGORY = "vacuum-participant";
276 const VACUUM_CONTRACTID = "@sqlite.module.js/vacuum-participant;";
277 var registeredVacuumParticipants = new Map();
279 function registerVacuumParticipant(connectionData) {
280 let contractId = VACUUM_CONTRACTID + connectionData._identifier;
282 createInstance(iid) {
283 return connectionData.QueryInterface(iid);
285 QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
287 let cid = Services.uuid.generateUUID();
289 .QueryInterface(Ci.nsIComponentRegistrar)
290 .registerFactory(cid, contractId, contractId, factory);
291 Services.catMan.addCategoryEntry(
298 registeredVacuumParticipants.set(contractId, { cid, factory });
301 function unregisterVacuumParticipant(connectionData) {
302 let contractId = VACUUM_CONTRACTID + connectionData._identifier;
303 let component = registeredVacuumParticipants.get(contractId);
306 .QueryInterface(Ci.nsIComponentRegistrar)
307 .unregisterFactory(component.cid, component.factory);
308 Services.catMan.deleteCategoryEntry(VACUUM_CATEGORY, contractId, false);
313 * Create a ConsoleInstance logger with a given prefix.
314 * @param {string} prefix The prefix to use when logging.
315 * @returns {ConsoleInstance} a console logger.
317 function createLoggerWithPrefix(prefix) {
318 return console.createInstance({
319 prefix: `SQLite JSM (${prefix})`,
320 maxLogLevelPref: "toolkit.sqlitejsm.loglevel",
325 * Connection data with methods necessary for closing the connection.
327 * To support auto-closing in the event of garbage collection, this
328 * data structure contains all the connection data of an opened
329 * connection and all of the methods needed for sucessfully closing
332 * By putting this information in its own separate object, it is
333 * possible to store an additional reference to it without preventing
334 * a garbage collection of a finalization witness in
335 * OpenedConnection. When the witness detects a garbage collection,
336 * this object can be used to close the connection.
338 * This object contains more methods than just `close`. When
339 * OpenedConnection needs to use the methods in this object, it will
340 * dispatch its method calls here.
342 function ConnectionData(connection, identifier, options = {}) {
343 this._logger = createLoggerWithPrefix(`Connection ${identifier}`);
344 this._logger.debug("Opened");
346 this._dbConn = connection;
348 // This is a unique identifier for the connection, generated through
349 // getIdentifierByFileName. It may be used for logging or as a key in Maps.
350 this._identifier = identifier;
354 this._cachedStatements = new Map();
355 this._anonymousStatements = new Map();
356 this._anonymousCounter = 0;
358 // A map from statement index to mozIStoragePendingStatement, to allow for
359 // canceling prior to finalizing the mozIStorageStatements.
360 this._pendingStatements = new Map();
362 // Increments for each executed statement for the life of the connection.
363 this._statementCounter = 0;
365 // Increments whenever we request a unique operation id.
366 this._operationsCounter = 0;
368 if ("defaultTransactionType" in options) {
369 this.defaultTransactionType = options.defaultTransactionType;
371 this.defaultTransactionType = convertStorageTransactionType(
372 this._dbConn.defaultTransactionType
375 // Tracks whether this instance initiated a transaction.
376 this._initiatedTransaction = false;
377 // Manages a chain of transactions promises, so that new transactions
378 // always happen in queue to the previous ones. It never rejects.
379 this._transactionQueue = Promise.resolve();
381 this._idleShrinkMS = options.shrinkMemoryOnConnectionIdleMS;
382 if (this._idleShrinkMS) {
383 this._idleShrinkTimer = Cc["@mozilla.org/timer;1"].createInstance(
386 // We wait for the first statement execute to start the timer because
387 // shrinking now would not do anything.
390 // Deferred whose promise is resolved when the connection closing procedure
392 this._deferredClose = Promise.withResolvers();
393 this._closeRequested = false;
395 // An AsyncShutdown barrier used to make sure that we wait until clients
396 // are done before shutting down the connection.
397 this._barrier = new lazy.AsyncShutdown.Barrier(
398 `${this._identifier}: waiting for clients`
401 lazy.Barriers.connections.client.addBlocker(
402 this._identifier + ": waiting for shutdown",
403 this._deferredClose.promise,
405 identifier: this._identifier,
406 isCloseRequested: this._closeRequested,
407 hasDbConn: !!this._dbConn,
408 initiatedTransaction: this._initiatedTransaction,
409 pendingStatements: this._pendingStatements.size,
410 statementCounter: this._statementCounter,
414 // We avoid creating a timer for every transaction, because in most cases they
415 // are not canceled and they are only used as a timeout.
416 // Instead the timer is reused when it's sufficiently close to the previous
417 // creation time (see `_getTimeoutPromise` for more info).
418 this._timeoutPromise = null;
419 // The last timestamp when we should consider using `this._timeoutPromise`.
420 this._timeoutPromiseExpires = 0;
422 this._useIncrementalVacuum = !!options.incrementalVacuum;
423 if (this._useIncrementalVacuum) {
424 this._logger.debug("Set auto_vacuum INCREMENTAL");
425 this.execute("PRAGMA auto_vacuum = 2").catch(ex => {
426 this._logger.error("Setting auto_vacuum to INCREMENTAL failed.");
431 this._expectedPageSize = options.pageSize ?? 0;
432 if (this._expectedPageSize) {
433 this._logger.debug("Set page_size to " + this._expectedPageSize);
434 this.execute("PRAGMA page_size = " + this._expectedPageSize).catch(ex => {
436 `Setting page_size to ${this._expectedPageSize} failed.`
442 this._vacuumOnIdle = options.vacuumOnIdle;
443 if (this._vacuumOnIdle) {
444 this._logger.debug("Register as vacuum participant");
445 this.QueryInterface = ChromeUtils.generateQI([
446 Ci.mozIStorageVacuumParticipant,
448 registerVacuumParticipant(this);
453 * Map of connection identifiers to ConnectionData objects
455 * The connection identifier is a human-readable name of the
456 * database. Used by finalization witnesses to be able to close opened
457 * connections on garbage collection.
459 * Key: _identifier of ConnectionData
460 * Value: ConnectionData object
462 ConnectionData.byId = new Map();
464 ConnectionData.prototype = Object.freeze({
465 get expectedDatabasePageSize() {
466 return this._expectedPageSize;
469 get useIncrementalVacuum() {
470 return this._useIncrementalVacuum;
474 * This should only be used by the VacuumManager component.
475 * @see unsafeRawConnection for an official (but still unsafe) API.
477 get databaseConnection() {
478 if (this._vacuumOnIdle) {
485 let granted = !this.transactionInProgress;
486 this._logger.debug("Begin Vacuum - " + granted ? "granted" : "denied");
490 onEndVacuum(succeeded) {
491 this._logger.debug("End Vacuum - " + succeeded ? "success" : "failure");
495 * Run a task, ensuring that its execution will not be interrupted by shutdown.
497 * As the operations of this module are asynchronous, a sequence of operations,
498 * or even an individual operation, can still be pending when the process shuts
499 * down. If any of this operations is a write, this can cause data loss, simply
500 * because the write has not been completed (or even started) by shutdown.
502 * To avoid this risk, clients are encouraged to use `executeBeforeShutdown` for
503 * any write operation, as follows:
505 * myConnection.executeBeforeShutdown("Bookmarks: Removing a bookmark",
506 * async function(db) {
507 * // The connection will not be closed and shutdown will not proceed
508 * // until this task has completed.
510 * // `db` exposes the same API as `myConnection` but provides additional
511 * // logging support to help debug hard-to-catch shutdown timeouts.
513 * await db.execute(...);
516 * @param {string} name A human-readable name for the ongoing operation, used
517 * for logging and debugging purposes.
518 * @param {function(db)} task A function that takes as argument a Sqlite.sys.mjs
519 * db and returns a Promise.
521 executeBeforeShutdown(parent, name, task) {
523 throw new TypeError("Expected a human-readable name as first argument");
525 if (typeof task != "function") {
526 throw new TypeError("Expected a function as second argument");
528 if (this._closeRequested) {
530 `${this._identifier}: cannot execute operation ${name}, the connection is already closing`
534 // Status, used for AsyncShutdown crash reports.
536 // The latest command started by `task`, either as a
537 // sql string, or as one of "<not started>" or "<closing>".
538 command: "<not started>",
540 // `true` if `command` was started but not completed yet.
544 // An object with the same API as `this` but with
545 // additional logging. To keep logging simple, we
546 // assume that `task` is not running several queries
548 let loggedDb = Object.create(parent, {
550 value: async (sql, ...rest) => {
551 status.isPending = true;
552 status.command = sql;
554 return await this.execute(sql, ...rest);
556 status.isPending = false;
562 status.isPending = true;
563 status.command = "<close>";
565 return await this.close();
567 status.isPending = false;
572 value: async (sql, ...rest) => {
573 status.isPending = true;
574 status.command = "cached: " + sql;
576 return await this.executeCached(sql, ...rest);
578 status.isPending = false;
584 let promiseResult = task(loggedDb);
587 typeof promiseResult != "object" ||
588 !("then" in promiseResult)
590 throw new TypeError("Expected a Promise");
592 let key = `${this._identifier}: ${name} (${this._getOperationId()})`;
593 let promiseComplete = promiseResult.catch(() => {});
594 this._barrier.client.addBlocker(key, promiseComplete, {
595 fetchState: () => status,
598 return (async () => {
600 return await promiseResult;
602 this._barrier.client.removeBlocker(key, promiseComplete);
607 this._closeRequested = true;
610 return this._deferredClose.promise;
613 this._logger.debug("Request to close connection.");
614 this._clearIdleShrinkTimer();
616 if (this._vacuumOnIdle) {
617 this._logger.debug("Unregister as vacuum participant");
618 unregisterVacuumParticipant(this);
621 return this._barrier.wait().then(() => {
625 return this._finalize();
629 clone(readOnly = false) {
632 this._logger.debug("Request to clone connection.");
635 connection: this._dbConn,
638 if (this._idleShrinkMS) {
639 options.shrinkMemoryOnConnectionIdleMS = this._idleShrinkMS;
642 return cloneStorageConnection(options);
645 return this._operationsCounter++;
648 this._logger.debug("Finalizing connection.");
649 // Cancel any pending statements.
650 for (let [, /* k */ statement] of this._pendingStatements) {
653 this._pendingStatements.clear();
655 // We no longer need to track these.
656 this._statementCounter = 0;
658 // Next we finalize all active statements.
659 for (let [, /* k */ statement] of this._anonymousStatements) {
660 statement.finalize();
662 this._anonymousStatements.clear();
664 for (let [, /* k */ statement] of this._cachedStatements) {
665 statement.finalize();
667 this._cachedStatements.clear();
669 // This guards against operations performed between the call to this
670 // function and asyncClose() finishing. See also bug 726990.
673 // We must always close the connection at the Sqlite.sys.mjs-level, not
674 // necessarily at the mozStorage-level.
675 let markAsClosed = () => {
676 this._logger.debug("Closed");
677 // Now that the connection is closed, no need to keep
678 // a blocker for Barriers.connections.
679 lazy.Barriers.connections.client.removeBlocker(
680 this._deferredClose.promise
682 this._deferredClose.resolve();
684 if (wrappedConnections.has(this._identifier)) {
685 wrappedConnections.delete(this._identifier);
689 this._logger.debug("Calling asyncClose().");
691 this._dbConn.asyncClose(markAsClosed);
693 // If for any reason asyncClose fails, we must still remove the
694 // shutdown blockers and resolve _deferredClose.
700 return this._deferredClose.promise;
703 executeCached(sql, params = null, onRow = null) {
707 throw new Error("sql argument is empty.");
710 let statement = this._cachedStatements.get(sql);
712 statement = this._dbConn.createAsyncStatement(sql);
713 this._cachedStatements.set(sql, statement);
716 this._clearIdleShrinkTimer();
718 return new Promise((resolve, reject) => {
720 this._executeStatement(sql, statement, params, onRow).then(
722 this._startIdleShrinkTimer();
726 this._startIdleShrinkTimer();
731 this._startIdleShrinkTimer();
737 execute(sql, params = null, onRow = null) {
738 if (typeof sql != "string") {
739 throw new Error("Must define SQL to execute as a string: " + sql);
744 let statement = this._dbConn.createAsyncStatement(sql);
745 let index = this._anonymousCounter++;
747 this._anonymousStatements.set(index, statement);
748 this._clearIdleShrinkTimer();
750 let onFinished = () => {
751 this._anonymousStatements.delete(index);
752 statement.finalize();
753 this._startIdleShrinkTimer();
756 return new Promise((resolve, reject) => {
758 this._executeStatement(sql, statement, params, onRow).then(
775 get transactionInProgress() {
776 return this._open && this._dbConn.transactionInProgress;
779 executeTransaction(func, type) {
780 // Identify the caller for debugging purposes.
781 let caller = new Error().stack
784 .match(/^([^@]*@).*\/([^\/:]+)[:0-9]*$/);
785 caller = caller[1] + caller[2];
786 this._logger.debug(`Transaction (type ${type}) requested by: ${caller}`);
788 if (type == OpenedConnection.prototype.TRANSACTION_DEFAULT) {
789 type = this.defaultTransactionType;
790 } else if (!OpenedConnection.TRANSACTION_TYPES.includes(type)) {
791 throw new Error("Unknown transaction type: " + type);
795 // If a transaction yields on a never resolved promise, or is mistakenly
796 // nested, it could hang the transactions queue forever. Thus we timeout
797 // the execution after a meaningful amount of time, to ensure in any case
798 // we'll proceed after a while.
799 let timeoutPromise = this._getTimeoutPromise();
801 let promise = this._transactionQueue.then(() => {
802 if (this._closeRequested) {
803 throw new Error("Transaction canceled due to a closed connection.");
806 let transactionPromise = (async () => {
807 // At this point we should never have an in progress transaction, since
808 // they are enqueued.
809 if (this._initiatedTransaction) {
811 "Unexpected transaction in progress when trying to start a new one."
815 // We catch errors in statement execution to detect nested transactions.
817 await this.execute("BEGIN " + type + " TRANSACTION");
818 this._logger.debug(`Begin transaction`);
819 this._initiatedTransaction = true;
821 // Unfortunately, if we are wrapping an existing connection, a
822 // transaction could have been started by a client of the same
823 // connection that doesn't use Sqlite.sys.mjs (e.g. C++ consumer).
824 // The best we can do is proceed without a transaction and hope
825 // things won't break.
826 if (wrappedConnections.has(this._identifier)) {
828 "A new transaction could not be started cause the wrapped connection had one in progress",
833 "A transaction was already in progress, likely a nested transaction",
842 result = await Promise.race([func(), timeoutPromise]);
844 // It's possible that the exception has been caused by trying to
845 // close the connection in the middle of a transaction.
846 if (this._closeRequested) {
848 "Connection closed while performing a transaction",
852 // Otherwise the function didn't resolve before the timeout, or
853 // generated an unexpected error. Then we rollback.
854 if (ex.becauseTimedOut) {
855 let caller_module = caller.split(":", 1)[0];
856 Services.telemetry.keyedScalarAdd(
857 "mozstorage.sqlitejsm_transaction_timeout",
862 `The transaction requested by ${caller} timed out. Rolling back`,
867 `Error during transaction requested by ${caller}. Rolling back`,
871 // If we began a transaction, we must rollback it.
872 if (this._initiatedTransaction) {
874 await this.execute("ROLLBACK TRANSACTION");
875 this._initiatedTransaction = false;
876 this._logger.debug(`Roll back transaction`);
878 this._logger.error("Could not roll back transaction", inner);
882 // Rethrow the exception.
886 // See comment above about connection being closed during transaction.
887 if (this._closeRequested) {
889 "Connection closed before committing the transaction."
892 "Connection closed before committing the transaction."
896 // If we began a transaction, we must commit it.
897 if (this._initiatedTransaction) {
899 await this.execute("COMMIT TRANSACTION");
900 this._logger.debug(`Commit transaction`);
902 this._logger.warn("Error committing transaction", ex);
909 this._initiatedTransaction = false;
913 return Promise.race([transactionPromise, timeoutPromise]);
915 // Atomically update the queue before anyone else has a chance to enqueue
916 // further transactions.
917 this._transactionQueue = promise.catch(ex => {
918 this._logger.error(ex);
921 // Make sure that we do not shutdown the connection during a transaction.
922 this._barrier.client.addBlocker(
923 `Transaction (${this._getOperationId()})`,
924 this._transactionQueue
930 this._logger.debug("Shrinking memory usage.");
931 return this.execute("PRAGMA shrink_memory").finally(() => {
932 this._clearIdleShrinkTimer();
936 discardCachedStatements() {
938 for (let [, /* k */ statement] of this._cachedStatements) {
940 statement.finalize();
942 this._cachedStatements.clear();
943 this._logger.debug("Discarded " + count + " cached statements.");
948 this._logger.debug("Trying to interrupt.");
950 this._dbConn.interrupt();
954 * Helper method to bind parameters of various kinds through
957 _bindParameters(statement, params) {
962 function bindParam(obj, key, val) {
964 val && typeof val == "object" && val.constructor.name == "Uint8Array";
965 let args = [key, val];
967 args.push(val.length);
969 let methodName = `bind${isBlob ? "Blob" : ""}By${
970 typeof key == "number" ? "Index" : "Name"
972 obj[methodName](...args);
975 if (Array.isArray(params)) {
976 // It's an array of separate params.
977 if (params.length && typeof params[0] == "object" && params[0] !== null) {
978 let paramsArray = statement.newBindingParamsArray();
979 for (let p of params) {
980 let bindings = paramsArray.newBindingParams();
981 for (let [key, value] of Object.entries(p)) {
982 bindParam(bindings, key, value);
984 paramsArray.addParams(bindings);
987 statement.bindParameters(paramsArray);
992 for (let i = 0; i < params.length; i++) {
993 bindParam(statement, i, params[i]);
999 if (params && typeof params == "object") {
1000 for (let k in params) {
1001 bindParam(statement, k, params[k]);
1007 "Invalid type for bound parameters. Expected Array or " +
1013 _executeStatement(sql, statement, params, onRow) {
1014 if (statement.state != statement.MOZ_STORAGE_STATEMENT_READY) {
1015 throw new Error("Statement is not ready for execution.");
1018 if (onRow && typeof onRow != "function") {
1019 throw new Error("onRow must be a function. Got: " + onRow);
1022 this._bindParameters(statement, params);
1024 let index = this._statementCounter++;
1026 let deferred = Promise.withResolvers();
1027 let userCancelled = false;
1030 let handledRow = false;
1032 // Don't incur overhead for serializing params unless the messages go
1034 if (this._logger.shouldLog("Trace")) {
1035 let msg = "Stmt #" + index + " " + sql;
1038 msg += " - " + JSON.stringify(params);
1040 this._logger.trace(msg);
1042 this._logger.debug("Stmt #" + index + " starting");
1046 let pending = statement.executeAsync({
1047 handleResult(resultSet) {
1048 // .cancel() may not be immediate and handleResult() could be called
1049 // after a .cancel().
1051 let row = resultSet.getNextRow();
1052 row && !userCancelled;
1053 row = resultSet.getNextRow()
1064 userCancelled = true;
1068 self._logger.warn("Exception when calling onRow callback", e);
1073 handleError(error) {
1075 "Error when executing SQL (" + error.result + "): " + error.message
1080 handleCompletion(reason) {
1081 self._logger.debug("Stmt #" + index + " finished.");
1082 self._pendingStatements.delete(index);
1085 case Ci.mozIStorageStatementCallback.REASON_FINISHED:
1086 case Ci.mozIStorageStatementCallback.REASON_CANCELED:
1087 // If there is an onRow handler, we always instead resolve to a
1088 // boolean indicating whether the onRow handler was called or not.
1089 let result = onRow ? handledRow : rows;
1090 deferred.resolve(result);
1093 case Ci.mozIStorageStatementCallback.REASON_ERROR:
1094 let error = new Error(
1095 "Error(s) encountered during statement execution: " +
1096 errors.map(e => e.message).join(", ")
1098 error.errors = errors;
1100 // Forward the error result.
1101 // Corruption is the most critical one so it's handled apart.
1102 if (errors.some(e => e.result == Ci.mozIStorageError.CORRUPT)) {
1103 error.result = Cr.NS_ERROR_FILE_CORRUPTED;
1105 // Just use the first error result in the other cases.
1106 error.result = convertStorageErrorResult(errors[0]?.result);
1109 deferred.reject(error);
1114 new Error("Unknown completion reason code: " + reason)
1121 this._pendingStatements.set(index, pending);
1122 return deferred.promise;
1127 throw new Error("Connection is not open.");
1131 _clearIdleShrinkTimer() {
1132 if (!this._idleShrinkTimer) {
1136 this._idleShrinkTimer.cancel();
1139 _startIdleShrinkTimer() {
1140 if (!this._idleShrinkTimer) {
1144 this._idleShrinkTimer.initWithCallback(
1145 this.shrinkMemory.bind(this),
1147 this._idleShrinkTimer.TYPE_ONE_SHOT
1152 * Returns a promise that will resolve after a time comprised between 80% of
1153 * `TRANSACTIONS_TIMEOUT_MS` and `TRANSACTIONS_TIMEOUT_MS`. Use
1154 * this method instead of creating several individual timers that may survive
1155 * longer than necessary.
1157 _getTimeoutPromise() {
1158 if (this._timeoutPromise && Cu.now() <= this._timeoutPromiseExpires) {
1159 return this._timeoutPromise;
1161 let timeoutPromise = new Promise((resolve, reject) => {
1163 // Clear out this._timeoutPromise if it hasn't changed since we set it.
1164 if (this._timeoutPromise == timeoutPromise) {
1165 this._timeoutPromise = null;
1168 "Transaction timeout, most likely caused by unresolved pending work."
1170 e.becauseTimedOut = true;
1172 }, Sqlite.TRANSACTIONS_TIMEOUT_MS);
1174 this._timeoutPromise = timeoutPromise;
1175 this._timeoutPromiseExpires =
1176 Cu.now() + Sqlite.TRANSACTIONS_TIMEOUT_MS * 0.2;
1177 return this._timeoutPromise;
1181 * Asynchronously makes a copy of the SQLite database while there may still be
1182 * open connections on it.
1184 * @param {string} destFilePath
1185 * The path on the local filesystem to write the database copy. Any existing
1186 * file at this path will be overwritten.
1187 * @param {number} [pagesPerStep=0]
1188 * The number of pages to copy per step. If not supplied or is 0, falls back
1189 * to the platform default which is currently 5.
1190 * @param {number} [stepDelayMs=0]
1191 * The number of milliseconds to wait between copying step. If not supplied
1192 * or is 0, falls back to the platform default which is currently 250.
1193 * @return Promise<undefined, nsresult>
1195 async backupToFile(destFilePath, pagesPerStep = 0, stepDelayMs = 0) {
1196 if (!this._dbConn) {
1197 return Promise.reject(
1198 new Error("No opened database connection to create a backup from.")
1201 let destFile = await IOUtils.getFile(destFilePath);
1202 return new Promise((resolve, reject) => {
1203 this._dbConn.backupToFileAsync(
1206 if (Components.isSuccessCode(result)) {
1220 * Opens a connection to a SQLite database.
1222 * The following parameters can control the connection:
1224 * path -- (string) The filesystem path of the database file to open. If the
1225 * file does not exist, a new database will be created.
1227 * sharedMemoryCache -- (bool) Whether multiple connections to the database
1228 * share the same memory cache. Sharing the memory cache likely results
1229 * in less memory utilization. However, sharing also requires connections
1230 * to obtain a lock, possibly making database access slower. Defaults to
1233 * shrinkMemoryOnConnectionIdleMS -- (integer) If defined, the connection
1234 * will attempt to minimize its memory usage after this many
1235 * milliseconds of connection idle. The connection is idle when no
1236 * statements are executing. There is no default value which means no
1237 * automatic memory minimization will occur. Please note that this is
1238 * *not* a timer on the idle service and this could fire while the
1239 * application is active.
1241 * readOnly -- (bool) Whether to open the database with SQLITE_OPEN_READONLY
1242 * set. If used, writing to the database will fail. Defaults to false.
1244 * ignoreLockingMode -- (bool) Whether to ignore locks on the database held
1245 * by other connections. If used, implies readOnly. Defaults to false.
1246 * USE WITH EXTREME CAUTION. This mode WILL produce incorrect results or
1247 * return "false positive" corruption errors if other connections write
1248 * to the DB at the same time.
1250 * openNotExclusive -- (bool) Whether to open the database without an exclusive
1251 * lock so the database can be accessed from multiple processes.
1253 * vacuumOnIdle -- (bool) Whether to register this connection to be vacuumed
1254 * on idle by the VacuumManager component.
1255 * If you're vacuum-ing an incremental vacuum database, ensure to also
1256 * set incrementalVacuum to true, otherwise this will try to change it
1257 * to full vacuum mode.
1259 * incrementalVacuum -- (bool) if set to true auto_vacuum = INCREMENTAL will
1260 * be enabled for the database.
1261 * Changing auto vacuum of an already populated database requires a full
1262 * VACUUM. You can evaluate to enable vacuumOnIdle for that.
1264 * pageSize -- (integer) This allows to set a custom page size for the
1265 * database. It is usually not necessary to set it, since the default
1266 * value should be good for most consumers.
1267 * Changing the page size of an already populated database requires a full
1268 * VACUUM. You can evaluate to enable vacuumOnIdle for that.
1270 * testDelayedOpenPromise -- (promise) Used by tests to delay the open
1271 * callback handling and execute code between asyncOpen and its callback.
1273 * FUTURE options to control:
1275 * special named databases
1276 * pragma TEMP STORE = MEMORY
1278 * SYNCHRONOUS = full
1281 * (Object) Parameters to control connection and open options.
1283 * @return Promise<OpenedConnection>
1285 function openConnection(options) {
1286 let logger = createLoggerWithPrefix("ConnectionOpener");
1288 if (!options.path) {
1289 throw new Error("path not specified in connection options.");
1294 "Sqlite.sys.mjs has been shutdown. Cannot open connection to: " +
1299 // Retains absolute paths and normalizes relative as relative to profile.
1300 let path = options.path;
1303 file = lazy.FileUtils.File(path);
1305 // For relative paths, we will get an exception from trying to initialize
1306 // the file. We must then join this path to the profile directory.
1307 if (ex.result == Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH) {
1308 path = PathUtils.joinRelative(
1309 Services.dirsvc.get("ProfD", Ci.nsIFile).path,
1312 file = lazy.FileUtils.File(path);
1318 let sharedMemoryCache =
1319 "sharedMemoryCache" in options ? options.sharedMemoryCache : true;
1321 let openedOptions = {};
1323 if ("shrinkMemoryOnConnectionIdleMS" in options) {
1324 if (!Number.isInteger(options.shrinkMemoryOnConnectionIdleMS)) {
1326 "shrinkMemoryOnConnectionIdleMS must be an integer. " +
1328 options.shrinkMemoryOnConnectionIdleMS
1332 openedOptions.shrinkMemoryOnConnectionIdleMS =
1333 options.shrinkMemoryOnConnectionIdleMS;
1336 if ("defaultTransactionType" in options) {
1337 let defaultTransactionType = options.defaultTransactionType;
1338 if (!OpenedConnection.TRANSACTION_TYPES.includes(defaultTransactionType)) {
1340 "Unknown default transaction type: " + defaultTransactionType
1344 openedOptions.defaultTransactionType = defaultTransactionType;
1347 if ("vacuumOnIdle" in options) {
1348 if (typeof options.vacuumOnIdle != "boolean") {
1349 throw new Error("Invalid vacuumOnIdle: " + options.vacuumOnIdle);
1351 openedOptions.vacuumOnIdle = options.vacuumOnIdle;
1354 if ("incrementalVacuum" in options) {
1355 if (typeof options.incrementalVacuum != "boolean") {
1357 "Invalid incrementalVacuum: " + options.incrementalVacuum
1360 openedOptions.incrementalVacuum = options.incrementalVacuum;
1363 if ("pageSize" in options) {
1365 ![512, 1024, 2048, 4096, 8192, 16384, 32768, 65536].includes(
1369 throw new Error("Invalid pageSize: " + options.pageSize);
1371 openedOptions.pageSize = options.pageSize;
1374 let identifier = getIdentifierByFileName(PathUtils.filename(path));
1376 logger.debug("Opening database: " + path + " (" + identifier + ")");
1378 return new Promise((resolve, reject) => {
1379 let dbOpenOptions = Ci.mozIStorageService.OPEN_DEFAULT;
1380 if (sharedMemoryCache) {
1381 dbOpenOptions |= Ci.mozIStorageService.OPEN_SHARED;
1383 if (options.readOnly) {
1384 dbOpenOptions |= Ci.mozIStorageService.OPEN_READONLY;
1386 if (options.ignoreLockingMode) {
1387 dbOpenOptions |= Ci.mozIStorageService.OPEN_IGNORE_LOCKING_MODE;
1388 dbOpenOptions |= Ci.mozIStorageService.OPEN_READONLY;
1390 if (options.openNotExclusive) {
1391 dbOpenOptions |= Ci.mozIStorageService.OPEN_NOT_EXCLUSIVE;
1394 let dbConnectionOptions = Ci.mozIStorageService.CONNECTION_DEFAULT;
1396 Services.storage.openAsyncDatabase(
1399 dbConnectionOptions,
1400 async (status, connection) => {
1402 logger.error(`Could not open connection to ${path}: ${status}`);
1403 let error = new Components.Exception(
1404 `Could not open connection to ${path}: ${status}`,
1410 logger.debug("Connection opened");
1412 if (options.testDelayedOpenPromise) {
1413 await options.testDelayedOpenPromise;
1417 connection.QueryInterface(Ci.mozIStorageAsyncConnection).asyncClose();
1420 "Sqlite.sys.mjs has been shutdown. Cannot open connection to: " +
1429 new OpenedConnection(
1430 connection.QueryInterface(Ci.mozIStorageAsyncConnection),
1436 logger.error("Could not open database", ex);
1437 connection.asyncClose();
1446 * Creates a clone of an existing and open Storage connection. The clone has
1447 * the same underlying characteristics of the original connection and is
1448 * returned in form of an OpenedConnection handle.
1450 * The following parameters can control the cloned connection:
1452 * connection -- (mozIStorageAsyncConnection) The original Storage connection
1453 * to clone. It's not possible to clone connections to memory databases.
1455 * readOnly -- (boolean) - If true the clone will be read-only. If the
1456 * original connection is already read-only, the clone will be, regardless
1457 * of this option. If the original connection is using the shared cache,
1458 * this parameter will be ignored and the clone will be as privileged as
1459 * the original connection.
1460 * shrinkMemoryOnConnectionIdleMS -- (integer) If defined, the connection
1461 * will attempt to minimize its memory usage after this many
1462 * milliseconds of connection idle. The connection is idle when no
1463 * statements are executing. There is no default value which means no
1464 * automatic memory minimization will occur. Please note that this is
1465 * *not* a timer on the idle service and this could fire while the
1466 * application is active.
1470 * (Object) Parameters to control connection and clone options.
1472 * @return Promise<OpenedConnection>
1474 function cloneStorageConnection(options) {
1475 let logger = createLoggerWithPrefix("ConnectionCloner");
1477 let source = options && options.connection;
1479 throw new TypeError("connection not specified in clone options.");
1481 if (!(source instanceof Ci.mozIStorageAsyncConnection)) {
1482 throw new TypeError("Connection must be a valid Storage connection.");
1487 "Sqlite.sys.mjs has been shutdown. Cannot clone connection to: " +
1488 source.databaseFile.path
1492 let openedOptions = {};
1494 if ("shrinkMemoryOnConnectionIdleMS" in options) {
1495 if (!Number.isInteger(options.shrinkMemoryOnConnectionIdleMS)) {
1496 throw new TypeError(
1497 "shrinkMemoryOnConnectionIdleMS must be an integer. " +
1499 options.shrinkMemoryOnConnectionIdleMS
1502 openedOptions.shrinkMemoryOnConnectionIdleMS =
1503 options.shrinkMemoryOnConnectionIdleMS;
1506 let path = source.databaseFile.path;
1507 let identifier = getIdentifierByFileName(PathUtils.filename(path));
1509 logger.debug("Cloning database: " + path + " (" + identifier + ")");
1511 return new Promise((resolve, reject) => {
1512 source.asyncClone(!!options.readOnly, (status, connection) => {
1514 logger.error("Could not clone connection: " + status);
1515 reject(new Error("Could not clone connection: " + status));
1518 logger.debug("Connection cloned");
1521 connection.QueryInterface(Ci.mozIStorageAsyncConnection).asyncClose();
1524 "Sqlite.sys.mjs has been shutdown. Cannot open connection to: " +
1532 let conn = connection.QueryInterface(Ci.mozIStorageAsyncConnection);
1533 resolve(new OpenedConnection(conn, identifier, openedOptions));
1535 logger.error("Could not clone database", ex);
1536 connection.asyncClose();
1544 * Wraps an existing and open Storage connection with Sqlite.sys.mjs API. The
1545 * wrapped connection clone has the same underlying characteristics of the
1546 * original connection and is returned in form of an OpenedConnection handle.
1548 * Clients are responsible for closing both the Sqlite.sys.mjs wrapper and the
1549 * underlying mozStorage connection.
1551 * The following parameters can control the wrapped connection:
1553 * connection -- (mozIStorageAsyncConnection) The original Storage connection
1557 * (Object) Parameters to control connection and wrap options.
1559 * @return Promise<OpenedConnection>
1561 function wrapStorageConnection(options) {
1562 let logger = createLoggerWithPrefix("ConnectionWrapper");
1564 let connection = options && options.connection;
1565 if (!connection || !(connection instanceof Ci.mozIStorageAsyncConnection)) {
1566 throw new TypeError("connection not specified or invalid.");
1571 "Sqlite.sys.mjs has been shutdown. Cannot wrap connection to: " +
1572 connection.databaseFile.path
1576 let identifier = getIdentifierByFileName(connection.databaseFile.leafName);
1578 logger.debug("Wrapping database: " + identifier);
1579 return new Promise(resolve => {
1581 let conn = connection.QueryInterface(Ci.mozIStorageAsyncConnection);
1582 let wrapper = new OpenedConnection(conn, identifier);
1583 // We must not handle shutdown of a wrapped connection, since that is
1584 // already handled by the opener.
1585 wrappedConnections.add(identifier);
1588 logger.error("Could not wrap database", ex);
1595 * Handle on an opened SQLite database.
1597 * This is essentially a glorified wrapper around mozIStorageConnection.
1598 * However, it offers some compelling advantages.
1600 * The main functions on this type are `execute` and `executeCached`. These are
1601 * ultimately how all SQL statements are executed. It's worth explaining their
1604 * `execute` is used to execute one-shot SQL statements. These are SQL
1605 * statements that are executed one time and then thrown away. They are useful
1606 * for dynamically generated SQL statements and clients who don't care about
1607 * performance (either their own or wasting resources in the overall
1608 * application). Because of the performance considerations, it is recommended
1609 * to avoid `execute` unless the statement you are executing will only be
1610 * executed once or seldomly.
1612 * `executeCached` is used to execute a statement that will presumably be
1613 * executed multiple times. The statement is parsed once and stuffed away
1614 * inside the connection instance. Subsequent calls to `executeCached` will not
1615 * incur the overhead of creating a new statement object. This should be used
1616 * in preference to `execute` when a specific SQL statement will be executed
1619 * Instances of this type are not meant to be created outside of this file.
1620 * Instead, first open an instance of `UnopenedSqliteConnection` and obtain
1621 * an instance of this type by calling `open`.
1623 * FUTURE IMPROVEMENTS
1625 * Ability to enqueue operations. Currently there can be race conditions,
1626 * especially as far as transactions are concerned. It would be nice to have
1627 * an enqueueOperation(func) API that serially executes passed functions.
1629 * Support for SAVEPOINT (named/nested transactions) might be useful.
1632 * (mozIStorageConnection) Underlying SQLite connection.
1634 * (string) The unique identifier of this database. It may be used for
1635 * logging or as a key in Maps.
1636 * @param options [optional]
1637 * (object) Options to control behavior of connection. See
1640 function OpenedConnection(connection, identifier, options = {}) {
1641 // Store all connection data in a field distinct from the
1642 // witness. This enables us to store an additional reference to this
1643 // field without preventing garbage collection of
1644 // OpenedConnection. On garbage collection, we will still be able to
1645 // close the database using this extra reference.
1646 this._connectionData = new ConnectionData(connection, identifier, options);
1648 // Store the extra reference in a map with connection identifier as
1650 ConnectionData.byId.set(
1651 this._connectionData._identifier,
1652 this._connectionData
1655 // Make a finalization witness. If this object is garbage collected
1656 // before its `forget` method has been called, an event with topic
1657 // "sqlite-finalization-witness" is broadcasted along with the
1658 // connection identifier string of the database.
1659 this._witness = lazy.FinalizationWitnessService.make(
1660 "sqlite-finalization-witness",
1661 this._connectionData._identifier
1665 OpenedConnection.TRANSACTION_TYPES = ["DEFERRED", "IMMEDIATE", "EXCLUSIVE"];
1667 // Converts a `mozIStorageAsyncConnection::TRANSACTION_*` constant into the
1668 // corresponding `OpenedConnection.TRANSACTION_TYPES` constant.
1669 function convertStorageTransactionType(type) {
1670 if (!(type in OpenedConnection.TRANSACTION_TYPES)) {
1671 throw new Error("Unknown storage transaction type: " + type);
1673 return OpenedConnection.TRANSACTION_TYPES[type];
1676 OpenedConnection.prototype = Object.freeze({
1677 TRANSACTION_DEFAULT: "DEFAULT",
1678 TRANSACTION_DEFERRED: "DEFERRED",
1679 TRANSACTION_IMMEDIATE: "IMMEDIATE",
1680 TRANSACTION_EXCLUSIVE: "EXCLUSIVE",
1683 * Returns a handle to the underlying `mozIStorageAsyncConnection`. This is
1684 * ⚠️ **extremely unsafe** ⚠️ because `Sqlite.sys.mjs` continues to manage the
1685 * connection's lifecycle, including transactions and shutdown blockers.
1686 * Misusing the raw connection can easily lead to data loss, memory leaks,
1689 * Consumers of the raw connection **must not** close or re-wrap it,
1690 * and should not run statements concurrently with `Sqlite.sys.mjs`.
1692 * It's _much_ safer to open a `mozIStorage{Async}Connection` yourself,
1693 * and access it from JavaScript via `Sqlite.wrapStorageConnection`.
1694 * `unsafeRawConnection` is an escape hatch for cases where you can't
1697 * Please do _not_ add new uses of `unsafeRawConnection` without review
1698 * from a storage peer.
1700 get unsafeRawConnection() {
1701 return this._connectionData._dbConn;
1705 * Returns the maximum number of bound parameters for statements executed
1706 * on this connection.
1708 * @returns {number} The bound parameters limit.
1710 get variableLimit() {
1711 return this.unsafeRawConnection.variableLimit;
1715 * Set the the maximum number of bound parameters for statements executed
1716 * on this connection. If the passed-in value is higher than the maximum
1717 * default value, it will be silently truncated.
1719 * @param {number} newLimit The bound parameters limit.
1721 set variableLimit(newLimit) {
1722 this.unsafeRawConnection.variableLimit = newLimit;
1726 * The integer schema version of the database.
1728 * This is 0 if not schema version has been set.
1730 * @return Promise<int>
1732 getSchemaVersion(schemaName = "main") {
1733 return this.execute(`PRAGMA ${schemaName}.user_version`).then(result =>
1734 result[0].getInt32(0)
1738 setSchemaVersion(value, schemaName = "main") {
1739 if (!Number.isInteger(value)) {
1740 // Guarding against accidental SQLi
1741 throw new TypeError("Schema version must be an integer. Got " + value);
1743 this._connectionData.ensureOpen();
1744 return this.execute(`PRAGMA ${schemaName}.user_version = ${value}`);
1748 * Close the database connection.
1750 * This must be performed when you are finished with the database.
1752 * Closing the database connection has the side effect of forcefully
1753 * cancelling all active statements. Therefore, callers should ensure that
1754 * all active statements have completed before closing the connection, if
1757 * The returned promise will be resolved once the connection is closed.
1758 * Successive calls to close() return the same promise.
1760 * IMPROVEMENT: Resolve the promise to a closed connection which can be
1766 // Unless cleanup has already been done by a previous call to
1767 // `close`, delete the database entry from map and tell the
1768 // finalization witness to forget.
1769 if (ConnectionData.byId.has(this._connectionData._identifier)) {
1770 ConnectionData.byId.delete(this._connectionData._identifier);
1771 this._witness.forget();
1773 return this._connectionData.close();
1777 * Clones this connection to a new Sqlite one.
1779 * The following parameters can control the cloned connection:
1782 * (boolean) - If true the clone will be read-only. If the original
1783 * connection is already read-only, the clone will be, regardless of
1784 * this option. If the original connection is using the shared cache,
1785 * this parameter will be ignored and the clone will be as privileged as
1786 * the original connection.
1788 * @return Promise<OpenedConnection>
1790 clone(readOnly = false) {
1791 return this._connectionData.clone(readOnly);
1794 executeBeforeShutdown(name, task) {
1795 return this._connectionData.executeBeforeShutdown(this, name, task);
1799 * Execute a SQL statement and cache the underlying statement object.
1801 * This function executes a SQL statement and also caches the underlying
1802 * derived statement object so subsequent executions are faster and use
1805 * This function optionally binds parameters to the statement as well as
1806 * optionally invokes a callback for every row retrieved.
1808 * By default, no parameters are bound and no callback will be invoked for
1811 * Bound parameters can be defined as an Array of positional arguments or
1812 * an object mapping named parameters to their values. If there are no bound
1813 * parameters, the caller can pass nothing or null for this argument.
1815 * Callers are encouraged to pass objects rather than Arrays for bound
1816 * parameters because they prevent foot guns. With positional arguments, it
1817 * is simple to modify the parameter count or positions without fixing all
1818 * users of the statement. Objects/named parameters are a little safer
1819 * because changes in order alone won't result in bad things happening.
1821 * When `onRow` is not specified, all returned rows are buffered before the
1822 * returned promise is resolved. For INSERT or UPDATE statements, this has
1823 * no effect because no rows are returned from these. However, it has
1824 * implications for SELECT statements.
1826 * If your SELECT statement could return many rows or rows with large amounts
1827 * of data, for performance reasons it is recommended to pass an `onRow`
1828 * handler. Otherwise, the buffering may consume unacceptable amounts of
1831 * If the second parameter of an `onRow` handler is called during execution
1832 * of the `onRow` handler, the execution of the statement is immediately
1833 * cancelled. Subsequent rows will not be processed and no more `onRow`
1834 * invocations will be made. The promise is resolved immediately.
1836 * If an exception is thrown by the `onRow` handler, the exception is logged
1837 * and processing of subsequent rows occurs as if nothing happened. The
1838 * promise is still resolved (not rejected).
1840 * The return value is a promise that will be resolved when the statement
1841 * has completed fully.
1843 * The promise will be rejected with an `Error` instance if the statement
1844 * did not finish execution fully. The `Error` may have an `errors` property.
1845 * If defined, it will be an Array of objects describing individual errors.
1846 * Each object has the properties `result` and `message`. `result` is a
1847 * numeric error code and `message` is a string description of the problem.
1850 * (string) The name of the registered statement to execute.
1851 * @param params optional
1852 * (Array or object) Parameters to bind.
1853 * @param onRow optional
1854 * (function) Callback to receive each row from result.
1856 executeCached(sql, params = null, onRow = null) {
1857 if (isInvalidBoundLikeQuery(sql)) {
1858 throw new Error("Please enter a LIKE clause with bindings");
1860 return this._connectionData.executeCached(sql, params, onRow);
1864 * Execute a one-shot SQL statement.
1866 * If you find yourself feeding the same SQL string in this function, you
1867 * should *not* use this function and instead use `executeCached`.
1869 * See `executeCached` for the meaning of the arguments and extended usage info.
1872 * (string) SQL to execute.
1873 * @param params optional
1874 * (Array or Object) Parameters to bind to the statement.
1875 * @param onRow optional
1876 * (function) Callback to receive result of a single row.
1878 execute(sql, params = null, onRow = null) {
1879 if (isInvalidBoundLikeQuery(sql)) {
1880 throw new Error("Please enter a LIKE clause with bindings");
1882 return this._connectionData.execute(sql, params, onRow);
1886 * The default behavior for transactions run on this connection.
1888 get defaultTransactionType() {
1889 return this._connectionData.defaultTransactionType;
1893 * Whether a transaction is currently in progress.
1895 * Note that this is true if a transaction is active on the connection,
1896 * regardless of whether it was started by `Sqlite.sys.mjs` or another consumer.
1897 * See the explanation above `mozIStorageConnection.transactionInProgress` for
1898 * why this distinction matters.
1900 get transactionInProgress() {
1901 return this._connectionData.transactionInProgress;
1905 * Perform a transaction.
1907 * *****************************************************************************
1908 * YOU SHOULD _NEVER_ NEST executeTransaction CALLS FOR ANY REASON, NOR
1909 * DIRECTLY, NOR THROUGH OTHER PROMISES.
1910 * FOR EXAMPLE, NEVER DO SOMETHING LIKE:
1911 * await executeTransaction(async function () {
1913 * await executeTransaction(async function () { // WRONG!
1916 * await someCodeThatExecuteTransaction(); // WRONG!
1917 * await neverResolvedPromise; // WRONG!
1919 * NESTING CALLS WILL BLOCK ANY FUTURE TRANSACTION UNTIL A TIMEOUT KICKS IN.
1920 * *****************************************************************************
1922 * A transaction is specified by a user-supplied function that is an
1923 * async function. The function receives this connection instance as its argument.
1925 * The supplied function is expected to return promises. These are often
1926 * promises created by calling `execute` and `executeCached`. If the
1927 * generator is exhausted without any errors being thrown, the
1928 * transaction is committed. If an error occurs, the transaction is
1931 * The returned value from this function is a promise that will be resolved
1932 * once the transaction has been committed or rolled back. The promise will
1933 * be resolved to whatever value the supplied function resolves to. If
1934 * the transaction is rolled back, the promise is rejected.
1937 * (function) What to perform as part of the transaction.
1938 * @param type optional
1939 * One of the TRANSACTION_* constants attached to this type.
1941 executeTransaction(func, type = this.TRANSACTION_DEFAULT) {
1942 return this._connectionData.executeTransaction(() => func(this), type);
1946 * Whether a table exists in the database (both persistent and temporary tables).
1949 * (string) Name of the table.
1951 * @return Promise<bool>
1954 return this.execute(
1955 "SELECT name FROM (SELECT * FROM sqlite_master UNION ALL " +
1956 "SELECT * FROM sqlite_temp_master) " +
1957 "WHERE type = 'table' AND name=?",
1959 ).then(function onResult(rows) {
1960 return Promise.resolve(!!rows.length);
1965 * Whether a named index exists (both persistent and temporary tables).
1968 * (string) Name of the index.
1970 * @return Promise<bool>
1973 return this.execute(
1974 "SELECT name FROM (SELECT * FROM sqlite_master UNION ALL " +
1975 "SELECT * FROM sqlite_temp_master) " +
1976 "WHERE type = 'index' AND name=?",
1978 ).then(function onResult(rows) {
1979 return Promise.resolve(!!rows.length);
1984 * Free up as much memory from the underlying database connection as possible.
1989 return this._connectionData.shrinkMemory();
1993 * Discard all cached statements.
1995 * Note that this relies on us being non-interruptible between
1996 * the insertion or retrieval of a statement in the cache and its
1997 * execution: we finalize all statements, which is only safe if
1998 * they will not be executed again.
2000 * @return (integer) the number of statements discarded.
2002 discardCachedStatements() {
2003 return this._connectionData.discardCachedStatements();
2007 * Interrupts pending database operations returning at the first opportunity.
2008 * Statement execution will throw an NS_ERROR_ABORT failure.
2009 * Can only be used on read-only connections.
2012 this._connectionData.interrupt();
2016 * Asynchronously makes a copy of the SQLite database while there may still be
2017 * open connections on it.
2019 * @param {string} destFilePath
2020 * The path on the local filesystem to write the database copy. Any existing
2021 * file at this path will be overwritten.
2022 * @param {number} [pagesPerStep=0]
2023 * The number of pages to copy per step. If not supplied or is 0, falls back
2024 * to the platform default which is currently 5.
2025 * @param {number} [stepDelayMs=0]
2026 * The number of milliseconds to wait between copying step. If not supplied
2027 * or is 0, falls back to the platform default which is currently 250.
2028 * @return Promise<undefined, nsresult>
2030 backup(destFilePath, pagesPerStep = 0, stepDelayMs = 0) {
2031 return this._connectionData.backupToFile(
2039 export var Sqlite = {
2040 // The maximum time to wait before considering a transaction stuck and
2041 // issuing a ROLLBACK, see `executeTransaction`. Could be modified by tests.
2042 TRANSACTIONS_TIMEOUT_MS: 300000, // 5 minutes
2045 cloneStorageConnection,
2046 wrapStorageConnection,
2048 * Shutdown barrier client. May be used by clients to perform last-minute
2049 * cleanup prior to the shutdown of this module.
2051 * See the documentation of AsyncShutdown.Barrier.prototype.client.
2054 return lazy.Barriers.shutdown.client;
2056 failTestsOnAutoClose(enabled) {
2057 Debugging.failTestsOnAutoClose = enabled;