diff options
Diffstat (limited to 'components/storage')
80 files changed, 16021 insertions, 0 deletions
diff --git a/components/storage/.eslintrc.js b/components/storage/.eslintrc.js new file mode 100644 index 000000000..69afc2f3c --- /dev/null +++ b/components/storage/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../toolkit/.eslintrc.js" + ] +}; diff --git a/components/storage/moz.build b/components/storage/moz.build new file mode 100644 index 000000000..457f38622 --- /dev/null +++ b/components/storage/moz.build @@ -0,0 +1,95 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Note: On Windows our sqlite build assumes we use jemalloc. If you disable +# MOZ_STORAGE_MEMORY on Windows, you will also need to change the "ifdef +# MOZ_MEMORY" options in db/sqlite3/src/Makefile.in. +if CONFIG['MOZ_MEMORY']: + DEFINES['MOZ_STORAGE_MEMORY'] = True + +# This is the default value. If we ever change it when compiling sqlite, we +# will need to change it here as well. +DEFINES['SQLITE_MAX_LIKE_PATTERN_LENGTH'] = 50000 + +# See Sqlite moz.build for reasoning about TEMP_STORE. + +XPIDL_SOURCES += [ + 'public/mozIStorageAggregateFunction.idl', + 'public/mozIStorageAsyncConnection.idl', + 'public/mozIStorageAsyncStatement.idl', + 'public/mozIStorageBaseStatement.idl', + 'public/mozIStorageBindingParams.idl', + 'public/mozIStorageBindingParamsArray.idl', + 'public/mozIStorageCompletionCallback.idl', + 'public/mozIStorageConnection.idl', + 'public/mozIStorageError.idl', + 'public/mozIStorageFunction.idl', + 'public/mozIStoragePendingStatement.idl', + 'public/mozIStorageProgressHandler.idl', + 'public/mozIStorageResultSet.idl', + 'public/mozIStorageRow.idl', + 'public/mozIStorageService.idl', + 'public/mozIStorageStatement.idl', + 'public/mozIStorageStatementCallback.idl', + 'public/mozIStorageStatementParams.idl', + 'public/mozIStorageStatementRow.idl', + 'public/mozIStorageVacuumParticipant.idl', + 'public/mozIStorageValueArray.idl', +] + +EXPORTS += [ + 'src/mozStorageCID.h', + 'src/mozStorageHelper.h', +] + +EXPORTS.mozilla += ['src/storage.h'] + +# NOTE When adding something to this list, you probably need to add it to the +# storage.h file too. +EXPORTS.mozilla.storage += [ + 'src/StatementCache.h', + 'src/Variant.h', + 'src/Variant_inl.h', +] +# SEE ABOVE NOTE! + +SOURCES += [ + 'src/FileSystemModule.cpp', + 'src/mozStorageArgValueArray.cpp', + 'src/mozStorageAsyncStatement.cpp', + 'src/mozStorageAsyncStatementExecution.cpp', + 'src/mozStorageAsyncStatementJSHelper.cpp', + 'src/mozStorageAsyncStatementParams.cpp', + 'src/mozStorageBindingParams.cpp', + 'src/mozStorageBindingParamsArray.cpp', + 'src/mozStorageConnection.cpp', + 'src/mozStorageError.cpp', + 'src/mozStorageModule.cpp', + 'src/mozStoragePrivateHelpers.cpp', + 'src/mozStorageResultSet.cpp', + 'src/mozStorageRow.cpp', + 'src/mozStorageService.cpp', + 'src/mozStorageSQLFunctions.cpp', + 'src/mozStorageStatement.cpp', + 'src/mozStorageStatementJSHelper.cpp', + 'src/mozStorageStatementParams.cpp', + 'src/mozStorageStatementRow.cpp', + 'src/SQLCollations.cpp', + 'src/StorageBaseStatementInternal.cpp', + 'src/TelemetryVFS.cpp', + 'src/VacuumManager.cpp', +] + +LOCAL_INCLUDES += [ + '/dom/base', + '/libs/sqlite3/src', +] + +CXXFLAGS += CONFIG['SQLITE_CFLAGS'] + +XPIDL_MODULE = 'storage' +FINAL_LIBRARY = 'xul' + +include('/ipc/chromium/chromium-config.mozbuild')
\ No newline at end of file diff --git a/components/storage/public/mozIStorageAggregateFunction.idl b/components/storage/public/mozIStorageAggregateFunction.idl new file mode 100644 index 000000000..28579318d --- /dev/null +++ b/components/storage/public/mozIStorageAggregateFunction.idl @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface mozIStorageConnection; +interface mozIStorageValueArray; +interface nsIArray; +interface nsIVariant; + +/** + * mozIStorageAggregateFunction represents aggregate SQL function. + * Common examples of aggregate functions are SUM() and COUNT(). + * + * An aggregate function calculates one result for a given set of data, where + * a set of data is a group of tuples. There can be one group + * per request or many of them, if GROUP BY clause is used or not. + */ +[scriptable, uuid(763217b7-3123-11da-918d-000347412e16)] +interface mozIStorageAggregateFunction : nsISupports { + /** + * onStep is called when next value should be passed to + * a custom function. + * + * @param aFunctionArguments The arguments passed in to the function + */ + void onStep(in mozIStorageValueArray aFunctionArguments); + + /** + * Called when all tuples in a group have been processed and the engine + * needs the aggregate function's value. + * + * @returns aggregate result as Variant. + */ + nsIVariant onFinal(); +}; diff --git a/components/storage/public/mozIStorageAsyncConnection.idl b/components/storage/public/mozIStorageAsyncConnection.idl new file mode 100644 index 000000000..aeb2bf1b5 --- /dev/null +++ b/components/storage/public/mozIStorageAsyncConnection.idl @@ -0,0 +1,220 @@ +/* -*- Mode: idl; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface mozIStorageAggregateFunction; +interface mozIStorageCompletionCallback; +interface mozIStorageFunction; +interface mozIStorageProgressHandler; +interface mozIStorageBaseStatement; +interface mozIStorageStatement; +interface mozIStorageAsyncStatement; +interface mozIStorageStatementCallback; +interface mozIStoragePendingStatement; +interface nsIFile; + +/** + * mozIStorageAsyncConnection represents an asynchronous database + * connection attached to a specific file or to an in-memory data + * storage. It is the primary interface for interacting with a + * database from the main thread, including creating prepared + * statements, executing SQL, and examining database errors. + */ +[scriptable, uuid(8bfd34d5-4ddf-4e4b-89dd-9b14f33534c6)] +interface mozIStorageAsyncConnection : nsISupports { + /** + * Close this database connection, allowing all pending statements + * to complete first. + * + * @param aCallback [optional] + * A callback that will be notified when the close is completed, + * with the following arguments: + * - status: the status of the call + * - value: |null| + * + * @throws NS_ERROR_NOT_SAME_THREAD + * If called on a thread other than the one that opened it. The + * callback will not be dispatched. + * @throws NS_ERROR_NOT_INITIALIZED + * If called on a connection that has already been closed or was + * never properly opened. The callback will still be dispatched + * to the main thread despite the returned error. + */ + void asyncClose([optional] in mozIStorageCompletionCallback aCallback); + + /** + * Clone a database and make the clone read only if needed. + * SQL Functions and attached on-disk databases are applied to the new clone. + * + * @param aReadOnly + * If true, the returned database should be put into read-only mode. + * + * @param aCallback + * A callback that will be notified when the operation is complete, + * with the following arguments: + * - status: the status of the operation + * - value: in case of success, an intance of + * mozIStorageAsyncConnection cloned from this one. + * + * @throws NS_ERROR_NOT_SAME_THREAD + * If is called on a thread other than the one that opened it. + * @throws NS_ERROR_UNEXPECTED + * If this connection is a memory database. + * + * @note If your connection is already read-only, you will get a read-only + * clone. + * @note Due to a bug in SQLite, if you use the shared cache + * (see mozIStorageService), you end up with the same privileges as the + * first connection opened regardless of what is specified in aReadOnly. + * @note The following pragmas are copied over to a read-only clone: + * - cache_size + * - temp_store + * The following pragmas are copied over to a writeable clone: + * - cache_size + * - temp_store + * - foreign_keys + * - journal_size_limit + * - synchronous + * - wal_autocheckpoint + */ + void asyncClone(in boolean aReadOnly, + in mozIStorageCompletionCallback aCallback); + + /** + * The current database nsIFile. Null if the database + * connection refers to an in-memory database. + */ + readonly attribute nsIFile databaseFile; + + ////////////////////////////////////////////////////////////////////////////// + //// Statement creation + + /** + * Create an asynchronous statement for the given SQL. An + * asynchronous statement can only be used to dispatch asynchronous + * requests to the asynchronous execution thread and cannot be used + * to take any synchronous actions on the database. + * + * The expression may use ? to indicate sequential numbered arguments, + * ?1, ?2 etc. to indicate specific numbered arguments or :name and + * $var to indicate named arguments. + * + * @param aSQLStatement + * The SQL statement to execute. + * @return a new mozIStorageAsyncStatement + * @note The statement is created lazily on first execution. + */ + mozIStorageAsyncStatement createAsyncStatement(in AUTF8String aSQLStatement); + + /** + * Execute an array of statements created with this connection using + * any currently bound parameters. When the array contains multiple + * statements, the execution is wrapped in a single + * transaction. These statements can be reused immediately, and + * reset does not need to be called. + * + * @param aStatements + * The array of statements to execute asynchronously, in the order they + * are given in the array. + * @param aNumStatements + * The number of statements in aStatements. + * @param aCallback [optional] + * The callback object that will be notified of progress, errors, and + * completion. + * @return an object that can be used to cancel the statements execution. + * + * @note If you have any custom defined functions, they must be + * re-entrant since they can be called on multiple threads. + */ + mozIStoragePendingStatement executeAsync( + [array, size_is(aNumStatements)] in mozIStorageBaseStatement aStatements, + in unsigned long aNumStatements, + [optional] in mozIStorageStatementCallback aCallback + ); + + /** + * Execute asynchronously an SQL expression, expecting no arguments. + * + * @param aSQLStatement + * The SQL statement to execute + * @param aCallback [optional] + * The callback object that will be notified of progress, errors, and + * completion. + * @return an object that can be used to cancel the statement execution. + */ + mozIStoragePendingStatement executeSimpleSQLAsync( + in AUTF8String aSQLStatement, + [optional] in mozIStorageStatementCallback aCallback); + + ////////////////////////////////////////////////////////////////////////////// + //// Functions + + /** + * Create a new SQL function. If you use your connection on multiple threads, + * your function needs to be threadsafe, or it should only be called on one + * thread. + * + * @param aFunctionName + * The name of function to create, as seen in SQL. + * @param aNumArguments + * The number of arguments the function takes. Pass -1 for + * variable-argument functions. + * @param aFunction + * The instance of mozIStorageFunction, which implements the function + * in question. + */ + void createFunction(in AUTF8String aFunctionName, + in long aNumArguments, + in mozIStorageFunction aFunction); + + /** + * Create a new SQL aggregate function. If you use your connection on + * multiple threads, your function needs to be threadsafe, or it should only + * be called on one thread. + * + * @param aFunctionName + * The name of aggregate function to create, as seen in SQL. + * @param aNumArguments + * The number of arguments the function takes. Pass -1 for + * variable-argument functions. + * @param aFunction + * The instance of mozIStorageAggreagteFunction, which implements the + * function in question. + */ + void createAggregateFunction(in AUTF8String aFunctionName, + in long aNumArguments, + in mozIStorageAggregateFunction aFunction); + /** + * Delete custom SQL function (simple or aggregate one). + * + * @param aFunctionName + * The name of function to remove. + */ + void removeFunction(in AUTF8String aFunctionName); + + /** + * Sets a progress handler. Only one handler can be registered at a time. + * If you need more than one, you need to chain them yourself. This progress + * handler should be threadsafe if you use this connection object on more than + * one thread. + * + * @param aGranularity + * The number of SQL virtual machine steps between progress handler + * callbacks. + * @param aHandler + * The instance of mozIStorageProgressHandler. + * @return previous registered handler. + */ + mozIStorageProgressHandler setProgressHandler(in int32_t aGranularity, + in mozIStorageProgressHandler aHandler); + + /** + * Remove a progress handler. + * + * @return previous registered handler. + */ + mozIStorageProgressHandler removeProgressHandler(); +}; diff --git a/components/storage/public/mozIStorageAsyncStatement.idl b/components/storage/public/mozIStorageAsyncStatement.idl new file mode 100644 index 000000000..41d03f122 --- /dev/null +++ b/components/storage/public/mozIStorageAsyncStatement.idl @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 sts=2 expandtab + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozIStorageBaseStatement.idl" + +/** + * An asynchronous SQL statement. This differs from mozIStorageStatement by + * only being usable for asynchronous execution. (mozIStorageStatement can + * be used for both synchronous and asynchronous purposes.) This specialization + * for asynchronous operation allows us to avoid needing to acquire + * synchronization primitives also used by the asynchronous execution thread. + * In contrast, mozIStorageStatement may need to acquire the primitives and + * consequently can cause the main thread to lock for extended intervals while + * the asynchronous thread performs some long-running operation. + */ +[scriptable, uuid(52e49370-3b2e-4a27-a3fc-79e20ad4056b)] +interface mozIStorageAsyncStatement : mozIStorageBaseStatement { + /* + * 'params' provides a magic JS helper that lets you assign parameters by + * name. Unlike the helper on mozIStorageStatement, you cannot enumerate + * in order to find out what parameters are legal. + * + * This does not work for BLOBs. You must use an explicit binding API for + * that. + * + * example: + * stmt.params.foo = 1; + * stmt.params["bar"] = 2; + * let argName = "baz"; + * stmt.params[argName] = 3; + * + * readonly attribute nsIMagic params; + */ +}; diff --git a/components/storage/public/mozIStorageBaseStatement.idl b/components/storage/public/mozIStorageBaseStatement.idl new file mode 100644 index 000000000..52cd30500 --- /dev/null +++ b/components/storage/public/mozIStorageBaseStatement.idl @@ -0,0 +1,158 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 sts=2 et + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "mozIStorageBindingParams.idl" + +interface mozIStorageConnection; +interface mozIStorageStatementCallback; +interface mozIStoragePendingStatement; +interface mozIStorageBindingParams; +interface mozIStorageBindingParamsArray; + +/** + * The base interface for both pure asynchronous storage statements + * (mozIStorageAsyncStatement) and 'classic' storage statements + * (mozIStorageStatement) that can be used for both synchronous and asynchronous + * purposes. + */ +[scriptable, uuid(16ca67aa-1325-43e2-aac7-859afd1590b2)] +interface mozIStorageBaseStatement : mozIStorageBindingParams { + /** + * Finalizes a statement so you can successfully close a database connection. + * Once a statement has been finalized it can no longer be used for any + * purpose. + * + * Statements are implicitly finalized when their reference counts hits zero. + * If you are a native (C++) caller this is accomplished by setting all of + * your nsCOMPtr instances to be NULL. If you are operating from JavaScript + * code then you cannot rely on this behavior because of the involvement of + * garbage collection. + * + * When finalizing an asynchronous statement you do not need to worry about + * whether the statement has actually been executed by the asynchronous + * thread; you just need to call finalize after your last call to executeAsync + * involving the statement. However, you do need to use asyncClose instead of + * close on the connection if any statements have been used asynchronously. + */ + void finalize(); + + /** + * Bind the given value at the given numeric index. + * + * @param aParamIndex + * 0-based index, 0 corresponding to the first numbered argument or + * "?1". + * @param aValue + * Argument value. + * @param aValueSize + * Length of aValue in bytes. + * @{ + */ + [deprecated] void bindUTF8StringParameter(in unsigned long aParamIndex, + in AUTF8String aValue); + [deprecated] void bindStringParameter(in unsigned long aParamIndex, + in AString aValue); + [deprecated] void bindDoubleParameter(in unsigned long aParamIndex, + in double aValue); + [deprecated] void bindInt32Parameter(in unsigned long aParamIndex, + in long aValue); + [deprecated] void bindInt64Parameter(in unsigned long aParamIndex, + in long long aValue); + [deprecated] void bindNullParameter(in unsigned long aParamIndex); + [deprecated] void bindBlobParameter( + in unsigned long aParamIndex, + [array,const,size_is(aValueSize)] in octet aValue, + in unsigned long aValueSize); + [deprecated] void bindStringAsBlobParameter( + in unsigned long aParamIndex, + in AString aValue); + [deprecated] void bindUTF8StringAsBlobParameter( + in unsigned long aParamIndex, + in AUTF8String aValue); + [deprecated] void bindAdoptedBlobParameter( + in unsigned long aParamIndex, + [array,size_is(aValueSize)] in octet aValue, + in unsigned long aValueSize); + /**@}*/ + + /** + * Binds the array of parameters to the statement. When executeAsync is + * called, all the parameters in aParameters are bound and then executed. + * + * @param aParameters + * The array of parameters to bind to the statement upon execution. + * + * @note This is only works on statements being used asynchronously. + */ + void bindParameters(in mozIStorageBindingParamsArray aParameters); + + /** + * Creates a new mozIStorageBindingParamsArray that can be used to bind + * multiple sets of data to a statement with bindParameters. + * + * @return a mozIStorageBindingParamsArray that multiple sets of parameters + * can be bound to. + * + * @note This is only useful for statements being used asynchronously. + */ + mozIStorageBindingParamsArray newBindingParamsArray(); + + /** + * Execute a query asynchronously using any currently bound parameters. This + * statement can be reused immediately, and reset does not need to be called. + * + * @note If you have any custom defined functions, they must be re-entrant + * since they can be called on multiple threads. + * + * @param aCallback [optional] + * The callback object that will be notified of progress, errors, and + * completion. + * @return an object that can be used to cancel the statements execution. + */ + mozIStoragePendingStatement executeAsync( + [optional] in mozIStorageStatementCallback aCallback + ); + + /** + * The statement is not usable, either because it failed to initialize or + * was explicitly finalized. + */ + const long MOZ_STORAGE_STATEMENT_INVALID = 0; + /** + * The statement is usable. + */ + const long MOZ_STORAGE_STATEMENT_READY = 1; + /** + * Indicates that the statement is executing and the row getters may be used. + * + * @note This is only relevant for mozIStorageStatement instances being used + * in a synchronous fashion. + */ + const long MOZ_STORAGE_STATEMENT_EXECUTING = 2; + + /** + * Find out whether the statement is usable (has not been finalized). + */ + readonly attribute long state; + + /** + * Escape a string for SQL LIKE search. + * + * @note Consumers will have to use same escape char when doing statements + * such as: ...LIKE '?1' ESCAPE '/'... + * + * @param aValue + * The string to escape for SQL LIKE. + * @param aEscapeChar + * The escape character. + * @return an AString of an escaped version of aValue + * (%, _ and the escape char are escaped with the escape char) + * For example, we will convert "foo/bar_baz%20cheese" + * into "foo//bar/_baz/%20cheese" (if the escape char is '/'). + */ + AString escapeStringForLIKE(in AString aValue, in wchar aEscapeChar); +}; diff --git a/components/storage/public/mozIStorageBindingParams.idl b/components/storage/public/mozIStorageBindingParams.idl new file mode 100644 index 000000000..2c537aaf0 --- /dev/null +++ b/components/storage/public/mozIStorageBindingParams.idl @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 sts=2 et + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIVariant; + +[scriptable, uuid(2d09f42f-966e-4663-b4b3-b0c8676bf2bf)] +interface mozIStorageBindingParams : nsISupports { + /** + * Binds aValue to the parameter with the name aName. + * + * @param aName + * The name of the parameter to bind aValue to. + * @param aValue + * The value to bind. + */ + void bindByName(in AUTF8String aName, + in nsIVariant aValue); + [noscript] void bindUTF8StringByName(in AUTF8String aName, + in AUTF8String aValue); + [noscript] void bindStringByName(in AUTF8String aName, + in AString aValue); + [noscript] void bindDoubleByName(in AUTF8String aName, + in double aValue); + [noscript] void bindInt32ByName(in AUTF8String aName, + in long aValue); + [noscript] void bindInt64ByName(in AUTF8String aName, + in long long aValue); + [noscript] void bindNullByName(in AUTF8String aName); + void bindBlobByName(in AUTF8String aName, + [array, const, size_is(aValueSize)] in octet aValue, + in unsigned long aValueSize); + + // Convenience routines for storing strings as blobs. + void bindStringAsBlobByName(in AUTF8String aName, in AString aValue); + void bindUTF8StringAsBlobByName(in AUTF8String aName, in AUTF8String aValue); + + // The function adopts the storage for the provided blob. After calling + // this function, mozStorage will ensure that free is called on the + // underlying pointer. + [noscript] + void bindAdoptedBlobByName(in AUTF8String aName, + [array, size_is(aValueSize)] in octet aValue, + in unsigned long aValueSize); + + /** + * Binds aValue to the parameter with the index aIndex. + * + * @param aIndex + * The zero-based index of the parameter to bind aValue to. + * @param aValue + * The value to bind. + */ + void bindByIndex(in unsigned long aIndex, + in nsIVariant aValue); + [noscript] void bindUTF8StringByIndex(in unsigned long aIndex, + in AUTF8String aValue); + [noscript] void bindStringByIndex(in unsigned long aIndex, + in AString aValue); + [noscript] void bindDoubleByIndex(in unsigned long aIndex, + in double aValue); + [noscript] void bindInt32ByIndex(in unsigned long aIndex, + in long aValue); + [noscript] void bindInt64ByIndex(in unsigned long aIndex, + in long long aValue); + [noscript] void bindNullByIndex(in unsigned long aIndex); + void bindBlobByIndex(in unsigned long aIndex, + [array, const, size_is(aValueSize)] in octet aValue, + in unsigned long aValueSize); + + // Convenience routines for storing strings as blobs. + void bindStringAsBlobByIndex(in unsigned long aIndex, in AString aValue); + void bindUTF8StringAsBlobByIndex(in unsigned long aIndex, in AUTF8String aValue); + + // The function adopts the storage for the provided blob. After calling + // this function, mozStorage will ensure that free is called on the + // underlying pointer. + [noscript] + void bindAdoptedBlobByIndex(in unsigned long aIndex, + [array, size_is(aValueSize)] in octet aValue, + in unsigned long aValueSize); +}; diff --git a/components/storage/public/mozIStorageBindingParamsArray.idl b/components/storage/public/mozIStorageBindingParamsArray.idl new file mode 100644 index 000000000..5f504c051 --- /dev/null +++ b/components/storage/public/mozIStorageBindingParamsArray.idl @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 sts=2 et + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface mozIStorageBindingParams; + +[scriptable, uuid(67eea5c3-4881-41ff-b0fe-09f2356aeadb)] +interface mozIStorageBindingParamsArray : nsISupports { + /** + * Creates a new mozIStorageBindingParams object that can be added to this + * array. + * + * @return a mozIStorageBindingParams object that can be used to specify + * parameters that need to be bound. + */ + mozIStorageBindingParams newBindingParams(); + + /** + * Adds the parameters to the end of this array. + * + * @param aParameters + * The parameters to add to this array. + */ + void addParams(in mozIStorageBindingParams aParameters); + + /** + * The number of mozIStorageBindingParams this object contains. + */ + readonly attribute unsigned long length; +}; diff --git a/components/storage/public/mozIStorageCompletionCallback.idl b/components/storage/public/mozIStorageCompletionCallback.idl new file mode 100644 index 000000000..1c31cc2c2 --- /dev/null +++ b/components/storage/public/mozIStorageCompletionCallback.idl @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, function, uuid(8cbf2dc2-91e0-44bc-984f-553638412071)] +interface mozIStorageCompletionCallback : nsISupports { + /** + * Indicates that the event this callback was passed in for has completed. + * + * @param status + * The status of the call. Generally NS_OK if the operation + * completed successfully. + * @param value + * If the operation produces a result, the result. Otherwise, + * |null|. + * + * @see The calling method for expected values. + */ + void complete(in nsresult status, [optional] in nsISupports value); +}; diff --git a/components/storage/public/mozIStorageConnection.idl b/components/storage/public/mozIStorageConnection.idl new file mode 100644 index 000000000..11d8aa5ac --- /dev/null +++ b/components/storage/public/mozIStorageConnection.idl @@ -0,0 +1,268 @@ +/* -*- Mode: idl; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "mozIStorageAsyncConnection.idl" + +%{C++ +namespace mozilla { +namespace dom { +namespace quota { +class QuotaObject; +} +} +} + +%} + +[ptr] native QuotaObject(mozilla::dom::quota::QuotaObject); + +interface mozIStorageAggregateFunction; +interface mozIStorageCompletionCallback; +interface mozIStorageFunction; +interface mozIStorageProgressHandler; +interface mozIStorageBaseStatement; +interface mozIStorageStatement; +interface mozIStorageAsyncStatement; +interface mozIStorageStatementCallback; +interface mozIStoragePendingStatement; +interface nsIFile; + +/** + * mozIStorageConnection represents a database connection attached to + * a specific file or to the in-memory data storage. It is the + * primary interface for interacting with a database, including + * creating prepared statements, executing SQL, and examining database + * errors. + * + * @note From the main thread, you should rather use mozIStorageAsyncConnection. + * + * @threadsafe + */ +[scriptable, uuid(4aa2ac47-8d24-4004-9b31-ec0bd85f0cc3)] +interface mozIStorageConnection : mozIStorageAsyncConnection { + /** + * Closes a database connection. Callers must finalize all statements created + * for this connection prior to calling this method. It is illegal to use + * call this method if any asynchronous statements have been executed on this + * connection. + * + * @throws NS_ERROR_UNEXPECTED + * If any statement has been executed asynchronously on this object. + * @throws NS_ERROR_UNEXPECTED + * If is called on a thread other than the one that opened it. + */ + void close(); + + /** + * Clones a database connection and makes the clone read only if needed. + * SQL Functions and attached on-disk databases are applied to the new clone. + * + * @param aReadOnly + * If true, the returned database should be put into read-only mode. + * Defaults to false. + * @return the cloned database connection. + * + * @throws NS_ERROR_UNEXPECTED + * If this connection is a memory database. + * @note If your connection is already read-only, you will get a read-only + * clone. + * @note Due to a bug in SQLite, if you use the shared cache (openDatabase), + * you end up with the same privileges as the first connection opened + * regardless of what is specified in aReadOnly. + * @note The following pragmas are copied over to a read-only clone: + * - cache_size + * - temp_store + * The following pragmas are copied over to a writeable clone: + * - cache_size + * - temp_store + * - foreign_keys + * - journal_size_limit + * - synchronous + * - wal_autocheckpoint + * + */ + mozIStorageConnection clone([optional] in boolean aReadOnly); + + /** + * The default size for SQLite database pages used by mozStorage for new + * databases. + */ + readonly attribute long defaultPageSize; + + /** + * Indicates if the connection is open and ready to use. This will be false + * if the connection failed to open, or it has been closed. + */ + readonly attribute boolean connectionReady; + + /** + * lastInsertRowID returns the row ID from the last INSERT + * operation. + */ + readonly attribute long long lastInsertRowID; + + /** + * affectedRows returns the number of database rows that were changed or + * inserted or deleted by last operation. + */ + readonly attribute long affectedRows; + + /** + * The last error SQLite error code. + */ + readonly attribute long lastError; + + /** + * The last SQLite error as a string (in english, straight from the + * sqlite library). + */ + readonly attribute AUTF8String lastErrorString; + + /** + * The schema version of the database. This should not be used until the + * database is ready. The schema will be reported as zero if it is not set. + */ + attribute long schemaVersion; + + ////////////////////////////////////////////////////////////////////////////// + //// Statement creation + + /** + * Create a mozIStorageStatement for the given SQL expression. The + * expression may use ? to indicate sequential numbered arguments, + * ?1, ?2 etc. to indicate specific numbered arguments or :name and + * $var to indicate named arguments. + * + * @param aSQLStatement + * The SQL statement to execute. + * @return a new mozIStorageStatement + */ + mozIStorageStatement createStatement(in AUTF8String aSQLStatement); + + /** + * Execute a SQL expression, expecting no arguments. + * + * @param aSQLStatement The SQL statement to execute + */ + void executeSimpleSQL(in AUTF8String aSQLStatement); + + /** + * Check if the given table exists. + * + * @param aTableName + * The table to check + * @return TRUE if table exists, FALSE otherwise. + */ + boolean tableExists(in AUTF8String aTableName); + + /** + * Check if the given index exists. + * + * @param aIndexName The index to check + * @return TRUE if the index exists, FALSE otherwise. + */ + boolean indexExists(in AUTF8String aIndexName); + + ////////////////////////////////////////////////////////////////////////////// + //// Transactions + + /** + * Returns true if a transaction is active on this connection. + */ + readonly attribute boolean transactionInProgress; + + /** + * Begin a new transaction. sqlite default transactions are deferred. + * If a transaction is active, throws an error. + */ + void beginTransaction(); + + /** + * Begins a new transaction with the given type. + */ + const int32_t TRANSACTION_DEFERRED = 0; + const int32_t TRANSACTION_IMMEDIATE = 1; + const int32_t TRANSACTION_EXCLUSIVE = 2; + void beginTransactionAs(in int32_t transactionType); + + /** + * Commits the current transaction. If no transaction is active, + * @throws NS_ERROR_UNEXPECTED. + * @throws NS_ERROR_NOT_INITIALIZED. + */ + void commitTransaction(); + + /** + * Rolls back the current transaction. If no transaction is active, + * @throws NS_ERROR_UNEXPECTED. + * @throws NS_ERROR_NOT_INITIALIZED. + */ + void rollbackTransaction(); + + ////////////////////////////////////////////////////////////////////////////// + //// Tables + + /** + * Create the table with the given name and schema. + * + * If the table already exists, NS_ERROR_FAILURE is thrown. + * (XXX at some point in the future it will check if the schema is + * the same as what is specified, but that doesn't happen currently.) + * + * @param aTableName + * The table name to be created, consisting of [A-Za-z0-9_], and + * beginning with a letter. + * @param aTableSchema + * The schema of the table; what would normally go between the parens + * in a CREATE TABLE statement: e.g., "foo INTEGER, bar STRING". + * + * @throws NS_ERROR_FAILURE + * If the table already exists or could not be created for any other + * reason. + */ + void createTable(in string aTableName, + in string aTableSchema); + + /** + * Controls SQLITE_FCNTL_CHUNK_SIZE setting in sqlite. This helps avoid fragmentation + * by growing/shrinking the database file in SQLITE_FCNTL_CHUNK_SIZE increments. To + * conserve memory on systems short on storage space, this function will have no effect + * on mobile devices or if less than 500MiB of space is left available. + * + * @param aIncrement + * The database file will grow in multiples of chunkSize. + * @param aDatabaseName + * Sqlite database name. "" means pass NULL for zDbName to sqlite3_file_control. + * See http://sqlite.org/c3ref/file_control.html for more details. + * @throws NS_ERROR_FILE_TOO_BIG + * If the system is short on storage space. + */ + void setGrowthIncrement(in int32_t aIncrement, in AUTF8String aDatabaseName); + + /** + * Enable a predefined virtual table implementation. + * + * @param aModuleName + * The module to enable. Only "filesystem" is currently supported. + * + * @throws NS_ERROR_FAILURE + * For unknown module names. + */ + [noscript] void enableModule(in ACString aModuleName); + + /** + * Get quota objects. + * + * @param[out] aDatabaseQuotaObject + * The QuotaObject associated with the database file. + * @param[out] aJournalQuotaObject + * The QuotaObject associated with the journal file. + * + * @throws NS_ERROR_NOT_INITIALIZED. + */ + [noscript] void getQuotaObjects(out QuotaObject aDatabaseQuotaObject, + out QuotaObject aJournalQuotaObject); +}; diff --git a/components/storage/public/mozIStorageError.idl b/components/storage/public/mozIStorageError.idl new file mode 100644 index 000000000..7707a81dc --- /dev/null +++ b/components/storage/public/mozIStorageError.idl @@ -0,0 +1,149 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 sts=2 expandtab + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{C++ +#ifdef ERROR +#undef ERROR +#endif +%} + +[scriptable, uuid(1f350f96-7023-434a-8864-40a1c493aac1)] +interface mozIStorageError : nsISupports { + + /** + * General SQL error or missing database. + */ + const long ERROR = 1; + + /** + * Internal logic error. + */ + const long INTERNAL = 2; + + /** + * Access permission denied. + */ + const long PERM = 3; + + /** + * A callback routine requested an abort. + */ + const long ABORT = 4; + + /** + * The database file is locked. + */ + const long BUSY = 5; + + /** + * A table in the database is locked. + */ + const long LOCKED = 6; + + /** + * An allocation failed. + */ + const long NOMEM = 7; + + /** + * Attempt to write to a readonly database. + */ + const long READONLY = 8; + + /** + * Operation was terminated by an interrupt. + */ + const long INTERRUPT = 9; + + /** + * Some kind of disk I/O error occurred. + */ + const long IOERR = 10; + + /** + * The database disk image is malformed. + */ + const long CORRUPT = 11; + + /** + * An insertion failed because the database is full. + */ + const long FULL = 13; + + /** + * Unable to open the database file. + */ + const long CANTOPEN = 14; + + /** + * The database is empty. + */ + const long EMPTY = 16; + + /** + * The database scheme changed. + */ + const long SCHEMA = 17; + + /** + * A string or blob exceeds the size limit. + */ + const long TOOBIG = 18; + + /** + * Abort due to a constraint violation. + */ + const long CONSTRAINT = 19; + + /** + * Data type mismatch. + */ + const long MISMATCH = 20; + + /** + * Library used incorrectly. + */ + const long MISUSE = 21; + + /** + * Uses OS features not supported on the host system. + */ + const long NOLFS = 22; + + /** + * Authorization denied. + */ + const long AUTH = 23; + + /** + * Auxiliary database format error. + */ + const long FORMAT = 24; + + /** + * Attempt to bind a parameter using an out-of-range index or nonexistent + * named parameter name. + */ + const long RANGE = 25; + + /** + * File opened that is not a database file. + */ + const long NOTADB = 26; + + + /** + * Indicates what type of error occurred. + */ + readonly attribute long result; + + /** + * An error string the gives more details, if available. + */ + readonly attribute AUTF8String message; +}; diff --git a/components/storage/public/mozIStorageFunction.idl b/components/storage/public/mozIStorageFunction.idl new file mode 100644 index 000000000..7f9878377 --- /dev/null +++ b/components/storage/public/mozIStorageFunction.idl @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +#include "mozIStorageValueArray.idl" + +interface mozIStorageConnection; +interface nsIArray; +interface nsIVariant; + +/** + * mozIStorageFunction is to be implemented by storage consumers that + * wish to receive callbacks during the request execution. + * + * SQL can apply functions to values from tables. Examples of + * such functions are MIN(a1,a2) or SQRT(num). Many functions are + * implemented in SQL engine. + * + * This interface allows consumers to implement their own, + * problem-specific functions. + * These functions can be called from triggers, too. + * + */ +[scriptable, function, uuid(9ff02465-21cb-49f3-b975-7d5b38ceec73)] +interface mozIStorageFunction : nsISupports { + /** + * onFunctionCall is called when execution of a custom + * function should occur. + * + * @param aNumArguments The number of arguments + * @param aFunctionArguments The arguments passed in to the function + * + * @returns any value as Variant type. + */ + + nsIVariant onFunctionCall(in mozIStorageValueArray aFunctionArguments); +}; diff --git a/components/storage/public/mozIStoragePendingStatement.idl b/components/storage/public/mozIStoragePendingStatement.idl new file mode 100644 index 000000000..a72ce96b9 --- /dev/null +++ b/components/storage/public/mozIStoragePendingStatement.idl @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 sts=2 expandtab + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(00da7d20-3768-4398-bedc-e310c324b3f0)] +interface mozIStoragePendingStatement : nsISupports { + + /** + * Cancels a pending statement, if possible. This will only fail if you try + * cancel more than once. + * + * @note For read statements (such as SELECT), you will no longer receive any + * notifications about results once cancel is called. + */ + void cancel(); +}; diff --git a/components/storage/public/mozIStorageProgressHandler.idl b/components/storage/public/mozIStorageProgressHandler.idl new file mode 100644 index 000000000..14ebb55ed --- /dev/null +++ b/components/storage/public/mozIStorageProgressHandler.idl @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface mozIStorageConnection; + +/** + * mozIProgressHandler is to be implemented by storage consumers that + * wish to receive callbacks during the request execution. + */ +[scriptable, uuid(a3a6fcd4-bf89-4208-a837-bf2a73afd30c)] +interface mozIStorageProgressHandler : nsISupports { + /** + * onProgress is invoked periodically during long running calls. + * + * @param aConnection connection, for which progress handler is + * invoked. + * + * @return true to abort request, false to continue work. + */ + + boolean onProgress(in mozIStorageConnection aConnection); +}; diff --git a/components/storage/public/mozIStorageResultSet.idl b/components/storage/public/mozIStorageResultSet.idl new file mode 100644 index 000000000..de63b297b --- /dev/null +++ b/components/storage/public/mozIStorageResultSet.idl @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 sts=2 expandtab + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +interface mozIStorageRow; + +[scriptable, uuid(18dd7953-076d-4598-8105-3e32ad26ab24)] +interface mozIStorageResultSet : nsISupports { + + /** + * Obtains the next row from the result set from the statement that was + * executed. + * + * @returns the next row from the result set. This will be null when there + * are no more results. + */ + mozIStorageRow getNextRow(); +}; diff --git a/components/storage/public/mozIStorageRow.idl b/components/storage/public/mozIStorageRow.idl new file mode 100644 index 000000000..ce12d77cc --- /dev/null +++ b/components/storage/public/mozIStorageRow.idl @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 sts=2 expandtab + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozIStorageValueArray.idl" +interface nsIVariant; + +[scriptable, uuid(62d1b6bd-cbfe-4f9b-aee1-0ead4af4e6dc)] +interface mozIStorageRow : mozIStorageValueArray { + + /** + * Obtains the result of a given column specified by aIndex. + * + * @param aIndex + * Zero-based index of the result to get from the tuple. + * @returns the result of the specified column. + */ + nsIVariant getResultByIndex(in unsigned long aIndex); + + /** + * Obtains the result of a given column specified by aName. + * + * @param aName + * Name of the result to get from the tuple. + * @returns the result of the specified column. + * @note The name of a result column is the value of the "AS" clause for that + * column. If there is no AS clause then the name of the column is + * unspecified and may change from one release to the next. + */ + nsIVariant getResultByName(in AUTF8String aName); +}; diff --git a/components/storage/public/mozIStorageService.idl b/components/storage/public/mozIStorageService.idl new file mode 100644 index 000000000..56d2a127e --- /dev/null +++ b/components/storage/public/mozIStorageService.idl @@ -0,0 +1,193 @@ +/* -*- Mode: idl; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface mozIStorageConnection; +interface nsIFile; +interface nsIFileURL; +interface nsIPropertyBag2; +interface nsIVariant; +interface mozIStorageCompletionCallback; + +/** + * The mozIStorageService interface is intended to be implemented by + * a service that can create storage connections (mozIStorageConnection) + * to either a well-known profile database or to a specific database file. + * + * This is the only way to open a database connection. + * + * @note The first reference to mozIStorageService must be made on the main + * thread. + */ +[scriptable, uuid(07b6b2f5-6d97-47b4-9584-e65bc467fe9e)] +interface mozIStorageService : nsISupports { + /** + * Open an asynchronous connection to a database. + * + * This method MUST be called from the main thread. The connection object + * returned by this function is not threadsafe. You MUST use it only from + * the main thread. + * + * If you have more than one connection to a file, you MUST use the EXACT + * SAME NAME for the file each time, including case. The sqlite code uses + * a simple string compare to see if there is already a connection. Opening + * a connection to "Foo.sqlite" and "foo.sqlite" will CORRUPT YOUR DATABASE. + * + * @param aDatabaseStore Either a nsIFile representing the file that contains + * the database or a special string to open a special database. The special + * string may be: + * - "memory" to open an in-memory database. + * + * @param aOptions A set of options (may be null). Options may contain: + * - bool shared (defaults to |false|). + * -- If |true|, opens the database with a shared-cache. The + * shared-cache mode is more memory-efficient when many + * connections to the same database are expected, though, the + * connections will contend the cache resource. In any cases + * where performance matter, working without a shared-cache will + * improve concurrency. @see openUnsharedDatabase + * + * - int growthIncrement (defaults to none). + * -- Set the growth increment for the main database. This hints SQLite to + * grow the database file by a given chunk size and may reduce + * filesystem fragmentation on large databases. + * @see mozIStorageConnection::setGrowthIncrement + * + * @param aCallback A callback that will receive the result of the operation. + * In case of error, it may receive as status: + * - NS_ERROR_OUT_OF_MEMORY if allocating a new storage object fails. + * - NS_ERROR_FILE_CORRUPTED if the database file is corrupted. + * In case of success, it receives as argument the new database + * connection, as an instance of |mozIStorageAsyncConnection|. + * + * @throws NS_ERROR_INVALID_ARG if |aDatabaseStore| is neither a file nor + * one of the special strings understood by this method, or if one of + * the options passed through |aOptions| does not have the right type. + * @throws NS_ERROR_NOT_SAME_THREAD if called from a thread other than the + * main thread. + */ + void openAsyncDatabase(in nsIVariant aDatabaseStore, + [optional] in nsIPropertyBag2 aOptions, + in mozIStorageCompletionCallback aCallback); + /** + * Get a connection to a named special database storage. + * + * @param aStorageKey a string key identifying the type of storage + * requested. Valid values include: "memory". + * + * @see openDatabase for restrictions on how database connections may be + * used. For the profile database, you should only access it from the main + * thread since other callers may also have connections. + * + * @returns a new mozIStorageConnection for the requested + * storage database. + * + * @throws NS_ERROR_INVALID_ARG if aStorageKey is invalid. + */ + mozIStorageConnection openSpecialDatabase(in string aStorageKey); + + /** + * Open a connection to the specified file. + * + * Consumers should check mozIStorageConnection::connectionReady to ensure + * that they can use the database. If this value is false, it is strongly + * recommended that the database be backed up with + * mozIStorageConnection::backupDB so user data is not lost. + * + * ========== + * DANGER + * ========== + * + * If you have more than one connection to a file, you MUST use the EXACT + * SAME NAME for the file each time, including case. The sqlite code uses + * a simple string compare to see if there is already a connection. Opening + * a connection to "Foo.sqlite" and "foo.sqlite" will CORRUPT YOUR DATABASE. + * + * The connection object returned by this function is not threadsafe. You must + * use it only from the thread you created it from. + * + * @param aDatabaseFile + * A nsIFile that represents the database that is to be opened.. + * + * @returns a mozIStorageConnection for the requested database file. + * + * @throws NS_ERROR_OUT_OF_MEMORY + * If allocating a new storage object fails. + * @throws NS_ERROR_FILE_CORRUPTED + * If the database file is corrupted. + */ + mozIStorageConnection openDatabase(in nsIFile aDatabaseFile); + + /** + * Open a connection to the specified file that doesn't share a sqlite cache. + * + * Without a shared-cache, each connection uses its own pages cache, which + * may be memory inefficient with a large number of connections, in such a + * case so you should use openDatabase instead. On the other side, if cache + * contention may be an issue, for instance when concurrency is important to + * ensure responsiveness, using unshared connections may be a performance win. + * + * ========== + * DANGER + * ========== + * + * If you have more than one connection to a file, you MUST use the EXACT + * SAME NAME for the file each time, including case. The sqlite code uses + * a simple string compare to see if there is already a connection. Opening + * a connection to "Foo.sqlite" and "foo.sqlite" will CORRUPT YOUR DATABASE. + * + * The connection object returned by this function is not threadsafe. You must + * use it only from the thread you created it from. + * + * @param aDatabaseFile + * A nsIFile that represents the database that is to be opened. + * + * @returns a mozIStorageConnection for the requested database file. + * + * @throws NS_ERROR_OUT_OF_MEMORY + * If allocating a new storage object fails. + * @throws NS_ERROR_FILE_CORRUPTED + * If the database file is corrupted. + */ + mozIStorageConnection openUnsharedDatabase(in nsIFile aDatabaseFile); + + /** + * See openDatabase(). Exactly the same only initialized with a file URL. + * Custom parameters can be passed to SQLite and VFS implementations through + * the query part of the URL. + * + * @param aURL + * A nsIFileURL that represents the database that is to be opened. + */ + mozIStorageConnection openDatabaseWithFileURL(in nsIFileURL aFileURL); + + /* + * Utilities + */ + + /** + * Copies the specified database file to the specified parent directory with + * the specified file name. If the parent directory is not specified, it + * places the backup in the same directory as the current file. This function + * ensures that the file being created is unique. + * + * @param aDBFile + * The database file that will be backed up. + * @param aBackupFileName + * The name of the new backup file to create. + * @param [optional] aBackupParentDirectory + * The directory you'd like the backup file to be placed. + * @return The nsIFile representing the backup file. + */ + nsIFile backupDatabaseFile(in nsIFile aDBFile, in AString aBackupFileName, + [optional] in nsIFile aBackupParentDirectory); +}; + +%{C++ + +#define MOZ_STORAGE_MEMORY_STORAGE_KEY "memory" + +%} diff --git a/components/storage/public/mozIStorageStatement.idl b/components/storage/public/mozIStorageStatement.idl new file mode 100644 index 000000000..a264cfdfa --- /dev/null +++ b/components/storage/public/mozIStorageStatement.idl @@ -0,0 +1,307 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 sts=2 expandtab + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozIStorageBaseStatement.idl" +%{C++ +#include "mozilla/DebugOnly.h" +%} + +[ptr] native octetPtr(uint8_t); + +/** + * A SQL statement that can be used for both synchronous and asynchronous + * purposes. + */ +[scriptable, uuid(5f567c35-6c32-4140-828c-683ea49cfd3a)] +interface mozIStorageStatement : mozIStorageBaseStatement { + /** + * Create a clone of this statement, by initializing a new statement + * with the same connection and same SQL statement as this one. It + * does not preserve statement state; that is, if a statement is + * being executed when it is cloned, the new statement will not be + * executing. + */ + mozIStorageStatement clone(); + + /* + * Number of parameters + */ + readonly attribute unsigned long parameterCount; + + /** + * Name of nth parameter, if given + */ + AUTF8String getParameterName(in unsigned long aParamIndex); + + /** + * Returns the index of the named parameter. + * + * @param aName + * The name of the parameter you want the index for. This does not + * include the leading ':'. + * @return the index of the named parameter. + */ + unsigned long getParameterIndex(in AUTF8String aName); + + /** + * Number of columns returned + */ + readonly attribute unsigned long columnCount; + + /** + * Name of nth column + */ + AUTF8String getColumnName(in unsigned long aColumnIndex); + + /** + * Obtains the index of the column with the specified name. + * + * @param aName + * The name of the column. + * @return The index of the column with the specified name. + */ + unsigned long getColumnIndex(in AUTF8String aName); + + /** + * Reset parameters/statement execution + */ + void reset(); + + /** + * Execute the query, ignoring any results. This is accomplished by + * calling executeStep() once, and then calling reset(). + * + * Error and last insert info, etc. are available from + * the mozStorageConnection. + */ + void execute(); + + /** + * Execute a query, using any currently-bound parameters. Reset + * must be called on the statement after the last call of + * executeStep. + * + * @return a boolean indicating whether there are more rows or not; + * row data may be accessed using mozIStorageValueArray methods on + * the statement. + */ + boolean executeStep(); + + /** + * Execute a query, using any currently-bound parameters. Reset is called + * when no more data is returned. This method is only available to JavaScript + * consumers. + * + * @deprecated As of Mozilla 1.9.2 in favor of executeStep(). + * + * @return a boolean indicating whether there are more rows or not. + * + * [deprecated] boolean step(); + */ + + /** + * Obtains the current list of named parameters, which are settable. This + * property is only available to JavaScript consumers. + * + * readonly attribute mozIStorageStatementParams params; + */ + + /** + * Obtains the current row, with access to all the data members by name. This + * property is only available to JavaScript consumers. + * + * readonly attribute mozIStorageStatementRow row; + */ + + ////////////////////////////////////////////////////////////////////////////// + //// Copied contents of mozIStorageValueArray + + /** + * These type values are returned by getTypeOfIndex + * to indicate what type of value is present at + * a given column. + */ + const long VALUE_TYPE_NULL = 0; + const long VALUE_TYPE_INTEGER = 1; + const long VALUE_TYPE_FLOAT = 2; + const long VALUE_TYPE_TEXT = 3; + const long VALUE_TYPE_BLOB = 4; + + /** + * The number of entries in the array (each corresponding to a column in the + * database row) + */ + readonly attribute unsigned long numEntries; + + /** + * Indicate the data type of the current result row for the the given column. + * SQLite will perform type conversion if you ask for a value as a different + * type than it is stored as. + * + * @param aIndex + * 0-based column index. + * @return The type of the value at the given column index; one of + * VALUE_TYPE_NULL, VALUE_TYPE_INTEGER, VALUE_TYPE_FLOAT, + * VALUE_TYPE_TEXT, VALUE_TYPE_BLOB. + */ + long getTypeOfIndex(in unsigned long aIndex); + + /** + * Retrieve the contents of a column from the current result row as an + * integer. + * + * @param aIndex + * 0-based colummn index. + * @return Column value interpreted as an integer per type conversion rules. + * @{ + */ + long getInt32(in unsigned long aIndex); + long long getInt64(in unsigned long aIndex); + /** @} */ + /** + * Retrieve the contents of a column from the current result row as a + * floating point double. + * + * @param aIndex + * 0-based colummn index. + * @return Column value interpreted as a double per type conversion rules. + */ + double getDouble(in unsigned long aIndex); + /** + * Retrieve the contents of a column from the current result row as a + * string. + * + * @param aIndex + * 0-based colummn index. + * @return The value for the result column interpreted as a string. If the + * stored value was NULL, you will get an empty string with IsVoid set + * to distinguish it from an explicitly set empty string. + * @{ + */ + AUTF8String getUTF8String(in unsigned long aIndex); + AString getString(in unsigned long aIndex); + /** @} */ + + /** + * Retrieve the contents of a column from the current result row as a + * blob. + * + * @param aIndex + * 0-based colummn index. + * @param[out] aDataSize + * The number of bytes in the blob. + * @param[out] aData + * The contents of the BLOB. This will be NULL if aDataSize == 0. + */ + void getBlob(in unsigned long aIndex, out unsigned long aDataSize, [array,size_is(aDataSize)] out octet aData); + + /** + * Retrieve the contents of a Blob column from the current result row as a + * string. + * + * @param aIndex + * 0-based colummn index. + * @return The value for the result Blob column interpreted as a String. + * No encoding conversion is performed. + */ + AString getBlobAsString(in unsigned long aIndex); + + /** + * Retrieve the contents of a Blob column from the current result row as a + * UTF8 string. + * + * @param aIndex + * 0-based colummn index. + * @return The value for the result Blob column interpreted as a UTF8 String. + * No encoding conversion is performed. + */ + AUTF8String getBlobAsUTF8String(in unsigned long aIndex); + + /** + * Check whether the given column in the current result row is NULL. + * + * @param aIndex + * 0-based colummn index. + * @return true if the value for the result column is null. + */ + boolean getIsNull(in unsigned long aIndex); + + /** + * Returns a shared string pointer + */ + [noscript] void getSharedUTF8String(in unsigned long aIndex, out unsigned long aLength, [shared,retval] out string aResult); + [noscript] void getSharedString(in unsigned long aIndex, out unsigned long aLength, [shared,retval] out wstring aResult); + [noscript] void getSharedBlob(in unsigned long aIndex, out unsigned long aLength, [shared,retval] out octetPtr aResult); + +%{C++ + /** + * Getters for native code that return their values as + * the return type, for convenience and sanity. + * + * Not virtual; no vtable bloat. + */ + + inline int32_t AsInt32(uint32_t idx) { + int32_t v = 0; + mozilla::DebugOnly<nsresult> rv = GetInt32(idx, &v); + MOZ_ASSERT(NS_SUCCEEDED(rv) || IsNull(idx), + "Getting value failed, wrong column index?"); + return v; + } + + inline int64_t AsInt64(uint32_t idx) { + int64_t v = 0; + mozilla::DebugOnly<nsresult> rv = GetInt64(idx, &v); + MOZ_ASSERT(NS_SUCCEEDED(rv) || IsNull(idx), + "Getting value failed, wrong column index?"); + return v; + } + + inline double AsDouble(uint32_t idx) { + double v = 0.0; + mozilla::DebugOnly<nsresult> rv = GetDouble(idx, &v); + MOZ_ASSERT(NS_SUCCEEDED(rv) || IsNull(idx), + "Getting value failed, wrong column index?"); + return v; + } + + inline const char* AsSharedUTF8String(uint32_t idx, uint32_t *len) { + const char *str = nullptr; + *len = 0; + mozilla::DebugOnly<nsresult> rv = GetSharedUTF8String(idx, len, &str); + MOZ_ASSERT(NS_SUCCEEDED(rv) || IsNull(idx), + "Getting value failed, wrong column index?"); + return str; + } + + inline const char16_t* AsSharedWString(uint32_t idx, uint32_t *len) { + const char16_t *str = nullptr; + *len = 0; + mozilla::DebugOnly<nsresult> rv = GetSharedString(idx, len, &str); + MOZ_ASSERT(NS_SUCCEEDED(rv) || IsNull(idx), + "Getting value failed, wrong column index?"); + return str; + } + + inline const uint8_t* AsSharedBlob(uint32_t idx, uint32_t *len) { + const uint8_t *blob = nullptr; + *len = 0; + mozilla::DebugOnly<nsresult> rv = GetSharedBlob(idx, len, &blob); + MOZ_ASSERT(NS_SUCCEEDED(rv) || IsNull(idx), + "Getting value failed, wrong column index?"); + return blob; + } + + inline bool IsNull(uint32_t idx) { + bool b = false; + mozilla::DebugOnly<nsresult> rv = GetIsNull(idx, &b); + MOZ_ASSERT(NS_SUCCEEDED(rv), + "Getting value failed, wrong column index?"); + return b; + } + +%} +}; diff --git a/components/storage/public/mozIStorageStatementCallback.idl b/components/storage/public/mozIStorageStatementCallback.idl new file mode 100644 index 000000000..3c7bd6f6f --- /dev/null +++ b/components/storage/public/mozIStorageStatementCallback.idl @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 sts=2 expandtab + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface mozIStorageResultSet; +interface mozIStorageError; + +[scriptable, uuid(29383d00-d8c4-4ddd-9f8b-c2feb0f2fcfa)] +interface mozIStorageStatementCallback : nsISupports { + + /** + * Called when some result is obtained from the database. This function can + * be called more than once with a different storageIResultSet each time for + * any given asynchronous statement. + * + * @param aResultSet + * The result set containing the data from the database. + */ + void handleResult(in mozIStorageResultSet aResultSet); + + /** + * Called when some error occurs while executing the statement. This function + * may be called more than once with a different storageIError each time for + * any given asynchronous statement. + * + * @param aError + * An object containing information about the error. + */ + void handleError(in mozIStorageError aError); + + /** + * Called when the statement has finished executing. This function will only + * be called once for any given asynchronous statement. + * + * @param aReason + * Indicates if the statement is no longer executing because it either + * finished (REASON_FINISHED), was canceled (REASON_CANCELED), or + * a fatal error occurred (REASON_ERROR). + */ + const unsigned short REASON_FINISHED = 0; + const unsigned short REASON_CANCELED = 1; + const unsigned short REASON_ERROR = 2; + void handleCompletion(in unsigned short aReason); +}; diff --git a/components/storage/public/mozIStorageStatementParams.idl b/components/storage/public/mozIStorageStatementParams.idl new file mode 100644 index 000000000..efeee9772 --- /dev/null +++ b/components/storage/public/mozIStorageStatementParams.idl @@ -0,0 +1,11 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(e65fe6e2-2643-463c-97e2-27665efe2386)] +interface mozIStorageStatementParams : nsISupports { + // Magic interface for parameter setting that implements nsIXPCScriptable. +}; diff --git a/components/storage/public/mozIStorageStatementRow.idl b/components/storage/public/mozIStorageStatementRow.idl new file mode 100644 index 000000000..8be1da7f1 --- /dev/null +++ b/components/storage/public/mozIStorageStatementRow.idl @@ -0,0 +1,12 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(02eeaf95-c3db-4182-9340-222c29f68f02)] +interface mozIStorageStatementRow : nsISupports { + // Magic interface we return that implements nsIXPCScriptable, to allow + // for by-name access to rows. +}; diff --git a/components/storage/public/mozIStorageVacuumParticipant.idl b/components/storage/public/mozIStorageVacuumParticipant.idl new file mode 100644 index 000000000..a4e0f3a71 --- /dev/null +++ b/components/storage/public/mozIStorageVacuumParticipant.idl @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface mozIStorageConnection; + +/** + * This interface contains the information that the Storage service needs to + * vacuum a database. This interface is created as a service through the + * category manager with the category "vacuum-participant". + * Please see https://developer.mozilla.org/en/mozIStorageVacuumParticipant for + * more information. + */ +[scriptable, uuid(8f367508-1d9a-4d3f-be0c-ac11b6dd7dbf)] +interface mozIStorageVacuumParticipant : nsISupports { + /** + * The expected page size in bytes for the database. The vacuum manager will + * try to correct the page size during idle based on this value. + * + * @note If the database is using the WAL journal mode, the page size won't + * be changed to the requested value. See bug 634374. + * @note Valid page size values are powers of 2 between 512 and 65536. + * The suggested value is mozIStorageConnection::defaultPageSize. + */ + readonly attribute long expectedDatabasePageSize; + + /** + * Connection to the database file to be vacuumed. + */ + readonly attribute mozIStorageConnection databaseConnection; + + /** + * Notifies when a vacuum operation begins. Listeners should avoid using the + * database till onEndVacuum is received. + * + * @return true to proceed with the vacuum, false if the participant wants to + * opt-out for now, it will be retried later. Useful when participant + * is running some other heavy operation that can't be interrupted. + * + * @note When a vacuum operation starts or ends it will also dispatch a global + * "heavy-io-task" notification through the observer service with the + * data argument being either "vacuum-begin" or "vacuum-end". + */ + boolean onBeginVacuum(); + + /** + * Notifies when a vacuum operation ends. + * + * @param aSucceeded + * reports if the vacuum succeeded or failed. + */ + void onEndVacuum(in boolean aSucceeded); +}; diff --git a/components/storage/public/mozIStorageValueArray.idl b/components/storage/public/mozIStorageValueArray.idl new file mode 100644 index 000000000..3dbf75285 --- /dev/null +++ b/components/storage/public/mozIStorageValueArray.idl @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +%{C++ +#include "mozilla/DebugOnly.h" +%} + +[ptr] native octetPtr(uint8_t); + +/** + * mozIStorageValueArray wraps an array of SQL values, such as a single database + * row. + */ +[scriptable, uuid(6e6306f4-ffa7-40f5-96ca-36159ce8f431)] +interface mozIStorageValueArray : nsISupports { + /** + * These type values are returned by getTypeOfIndex + * to indicate what type of value is present at + * a given column. + */ + const long VALUE_TYPE_NULL = 0; + const long VALUE_TYPE_INTEGER = 1; + const long VALUE_TYPE_FLOAT = 2; + const long VALUE_TYPE_TEXT = 3; + const long VALUE_TYPE_BLOB = 4; + + /** + * numEntries + * + * number of entries in the array (each corresponding to a column + * in the database row) + */ + readonly attribute unsigned long numEntries; + + /** + * Returns the type of the value at the given column index; + * one of VALUE_TYPE_NULL, VALUE_TYPE_INTEGER, VALUE_TYPE_FLOAT, + * VALUE_TYPE_TEXT, VALUE_TYPE_BLOB. + */ + long getTypeOfIndex(in unsigned long aIndex); + + /** + * Obtain a value for the given entry (column) index. + * Due to SQLite's type conversion rules, any of these are valid + * for any column regardless of the column's data type. However, + * if the specific type matters, getTypeOfIndex should be used + * first to identify the column type, and then the appropriate + * get method should be called. + * + * If you ask for a string value for a NULL column, you will get an empty + * string with IsVoid set to distinguish it from an explicitly set empty + * string. + */ + long getInt32(in unsigned long aIndex); + long long getInt64(in unsigned long aIndex); + double getDouble(in unsigned long aIndex); + AUTF8String getUTF8String(in unsigned long aIndex); + AString getString(in unsigned long aIndex); + + // data will be NULL if dataSize = 0 + void getBlob(in unsigned long aIndex, out unsigned long aDataSize, [array,size_is(aDataSize)] out octet aData); + AString getBlobAsString(in unsigned long aIndex); + AUTF8String getBlobAsUTF8String(in unsigned long aIndex); + boolean getIsNull(in unsigned long aIndex); + + /** + * Returns a shared string pointer + */ + [noscript] void getSharedUTF8String(in unsigned long aIndex, out unsigned long aLength, [shared,retval] out string aResult); + [noscript] void getSharedString(in unsigned long aIndex, out unsigned long aLength, [shared,retval] out wstring aResult); + [noscript] void getSharedBlob(in unsigned long aIndex, out unsigned long aLength, [shared,retval] out octetPtr aResult); + +%{C++ + /** + * Getters for native code that return their values as + * the return type, for convenience and sanity. + * + * Not virtual; no vtable bloat. + */ + + inline int32_t AsInt32(uint32_t idx) { + int32_t v = 0; + mozilla::DebugOnly<nsresult> rv = GetInt32(idx, &v); + MOZ_ASSERT(NS_SUCCEEDED(rv) || IsNull(idx), + "Getting value failed, wrong column index?"); + return v; + } + + inline int64_t AsInt64(uint32_t idx) { + int64_t v = 0; + mozilla::DebugOnly<nsresult> rv = GetInt64(idx, &v); + MOZ_ASSERT(NS_SUCCEEDED(rv) || IsNull(idx), + "Getting value failed, wrong column index?"); + return v; + } + + inline double AsDouble(uint32_t idx) { + double v = 0.0; + mozilla::DebugOnly<nsresult> rv = GetDouble(idx, &v); + MOZ_ASSERT(NS_SUCCEEDED(rv) || IsNull(idx), + "Getting value failed, wrong column index?"); + return v; + } + + inline const char* AsSharedUTF8String(uint32_t idx, uint32_t *len) { + const char *str = nullptr; + *len = 0; + mozilla::DebugOnly<nsresult> rv = GetSharedUTF8String(idx, len, &str); + MOZ_ASSERT(NS_SUCCEEDED(rv) || IsNull(idx), + "Getting value failed, wrong column index?"); + return str; + } + + inline const char16_t* AsSharedWString(uint32_t idx, uint32_t *len) { + const char16_t *str = nullptr; + *len = 0; + mozilla::DebugOnly<nsresult> rv = GetSharedString(idx, len, &str); + MOZ_ASSERT(NS_SUCCEEDED(rv) || IsNull(idx), + "Getting value failed, wrong column index?"); + return str; + } + + inline const uint8_t* AsSharedBlob(uint32_t idx, uint32_t *len) { + const uint8_t *blob = nullptr; + *len = 0; + mozilla::DebugOnly<nsresult> rv = GetSharedBlob(idx, len, &blob); + MOZ_ASSERT(NS_SUCCEEDED(rv) || IsNull(idx), + "Getting value failed, wrong column index?"); + return blob; + } + + inline bool IsNull(uint32_t idx) { + bool b = false; + mozilla::DebugOnly<nsresult> rv = GetIsNull(idx, &b); + MOZ_ASSERT(NS_SUCCEEDED(rv), + "Getting value failed, wrong column index?"); + return b; + } + +%} + +}; diff --git a/components/storage/src/FileSystemModule.cpp b/components/storage/src/FileSystemModule.cpp new file mode 100644 index 000000000..ed2f8cdef --- /dev/null +++ b/components/storage/src/FileSystemModule.cpp @@ -0,0 +1,304 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FileSystemModule.h" + +#include "sqlite3.h" +#include "nsString.h" +#include "nsISimpleEnumerator.h" +#include "nsIFile.h" + +namespace { + +struct VirtualTableCursorBase +{ + VirtualTableCursorBase() + { + memset(&mBase, 0, sizeof(mBase)); + } + + sqlite3_vtab_cursor mBase; +}; + +struct VirtualTableCursor : public VirtualTableCursorBase +{ +public: + VirtualTableCursor() + : mRowId(-1) + { + mCurrentFileName.SetIsVoid(true); + } + + const nsString& DirectoryPath() const + { + return mDirectoryPath; + } + + const nsString& CurrentFileName() const + { + return mCurrentFileName; + } + + int64_t RowId() const + { + return mRowId; + } + + nsresult Init(const nsAString& aPath); + nsresult NextFile(); + +private: + nsCOMPtr<nsISimpleEnumerator> mEntries; + + nsString mDirectoryPath; + nsString mCurrentFileName; + + int64_t mRowId; +}; + +nsresult +VirtualTableCursor::Init(const nsAString& aPath) +{ + nsCOMPtr<nsIFile> directory = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + NS_ENSURE_TRUE(directory, NS_ERROR_FAILURE); + + nsresult rv = directory->InitWithPath(aPath); + NS_ENSURE_SUCCESS(rv, rv); + + rv = directory->GetPath(mDirectoryPath); + NS_ENSURE_SUCCESS(rv, rv); + + rv = directory->GetDirectoryEntries(getter_AddRefs(mEntries)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NextFile(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +VirtualTableCursor::NextFile() +{ + bool hasMore; + nsresult rv = mEntries->HasMoreElements(&hasMore); + NS_ENSURE_SUCCESS(rv, rv); + + if (!hasMore) { + mCurrentFileName.SetIsVoid(true); + return NS_OK; + } + + nsCOMPtr<nsISupports> entry; + rv = mEntries->GetNext(getter_AddRefs(entry)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> file = do_QueryInterface(entry); + NS_ENSURE_TRUE(file, NS_ERROR_FAILURE); + + rv = file->GetLeafName(mCurrentFileName); + NS_ENSURE_SUCCESS(rv, rv); + + mRowId++; + + return NS_OK; +} + +int Connect(sqlite3* aDB, void* aAux, int aArgc, const char* const* aArgv, + sqlite3_vtab** aVtab, char** aErr) +{ + static const char virtualTableSchema[] = + "CREATE TABLE fs (" + "name TEXT, " + "path TEXT" + ")"; + + int rc = sqlite3_declare_vtab(aDB, virtualTableSchema); + if (rc != SQLITE_OK) { + return rc; + } + + sqlite3_vtab* vt = new sqlite3_vtab(); + memset(vt, 0, sizeof(*vt)); + + *aVtab = vt; + + return SQLITE_OK; +} + +int Disconnect(sqlite3_vtab* aVtab ) +{ + delete aVtab; + + return SQLITE_OK; +} + +int BestIndex(sqlite3_vtab* aVtab, sqlite3_index_info* aInfo) +{ + // Here we specify what index constraints we want to handle. That is, there + // might be some columns with particular constraints in which we can help + // SQLite narrow down the result set. + // + // For example, take the "path = x" where x is a directory. In this case, + // we can narrow our search to just this directory instead of the entire file + // system. This can be a significant optimization. So, we want to handle that + // constraint. To do so, we would look for two specific input conditions: + // + // 1. aInfo->aConstraint[i].iColumn == 1 + // 2. aInfo->aConstraint[i].op == SQLITE_INDEX_CONSTRAINT_EQ + // + // The first states that the path column is being used in one of the input + // constraints and the second states that the constraint involves the equal + // operator. + // + // An even more specific search would be for name='xxx', in which case we + // can limit the search to a single file, if it exists. + // + // What we have to do here is look for all of our index searches and select + // the narrowest. We can only pick one, so obviously we want the one that + // is the most specific, which leads to the smallest result set. + + for(int i = 0; i < aInfo->nConstraint; i++) { + if (aInfo->aConstraint[i].iColumn == 1 && aInfo->aConstraint[i].usable) { + if (aInfo->aConstraint[i].op & SQLITE_INDEX_CONSTRAINT_EQ) { + aInfo->aConstraintUsage[i].argvIndex = 1; + } + break; + } + + // TODO: handle single files (constrained also by the name column) + } + + return SQLITE_OK; +} + +int Open(sqlite3_vtab* aVtab, sqlite3_vtab_cursor** aCursor) +{ + VirtualTableCursor* cursor = new VirtualTableCursor(); + + *aCursor = reinterpret_cast<sqlite3_vtab_cursor*>(cursor); + + return SQLITE_OK; +} + +int Close(sqlite3_vtab_cursor* aCursor) +{ + VirtualTableCursor* cursor = reinterpret_cast<VirtualTableCursor*>(aCursor); + + delete cursor; + + return SQLITE_OK; +} + +int Filter(sqlite3_vtab_cursor* aCursor, int aIdxNum, const char* aIdxStr, + int aArgc, sqlite3_value** aArgv) +{ + VirtualTableCursor* cursor = reinterpret_cast<VirtualTableCursor*>(aCursor); + + if(aArgc <= 0) { + return SQLITE_OK; + } + + nsDependentString path( + reinterpret_cast<const char16_t*>(::sqlite3_value_text16(aArgv[0]))); + + nsresult rv = cursor->Init(path); + NS_ENSURE_SUCCESS(rv, SQLITE_ERROR); + + return SQLITE_OK; +} + +int Next(sqlite3_vtab_cursor* aCursor) +{ + VirtualTableCursor* cursor = reinterpret_cast<VirtualTableCursor*>(aCursor); + + nsresult rv = cursor->NextFile(); + NS_ENSURE_SUCCESS(rv, SQLITE_ERROR); + + return SQLITE_OK; +} + +int Eof(sqlite3_vtab_cursor* aCursor) +{ + VirtualTableCursor* cursor = reinterpret_cast<VirtualTableCursor*>(aCursor); + return cursor->CurrentFileName().IsVoid() ? 1 : 0; +} + +int Column(sqlite3_vtab_cursor* aCursor, sqlite3_context* aContext, + int aColumnIndex) +{ + VirtualTableCursor* cursor = reinterpret_cast<VirtualTableCursor*>(aCursor); + + switch (aColumnIndex) { + // name + case 0: { + const nsString& name = cursor->CurrentFileName(); + sqlite3_result_text16(aContext, name.get(), + name.Length() * sizeof(char16_t), + SQLITE_TRANSIENT); + break; + } + + // path + case 1: { + const nsString& path = cursor->DirectoryPath(); + sqlite3_result_text16(aContext, path.get(), + path.Length() * sizeof(char16_t), + SQLITE_TRANSIENT); + break; + } + default: + NS_NOTREACHED("Unsupported column!"); + } + + return SQLITE_OK; +} + +int RowId(sqlite3_vtab_cursor* aCursor, sqlite3_int64* aRowid) +{ + VirtualTableCursor* cursor = reinterpret_cast<VirtualTableCursor*>(aCursor); + + *aRowid = cursor->RowId(); + + return SQLITE_OK; +} + +} // namespace + +namespace mozilla { +namespace storage { + +int RegisterFileSystemModule(sqlite3* aDB, const char* aName) +{ + static sqlite3_module module = { + 1, + Connect, + Connect, + BestIndex, + Disconnect, + Disconnect, + Open, + Close, + Filter, + Next, + Eof, + Column, + RowId, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr + }; + + return sqlite3_create_module(aDB, aName, &module, nullptr); +} + +} // namespace storage +} // namespace mozilla diff --git a/components/storage/src/FileSystemModule.h b/components/storage/src/FileSystemModule.h new file mode 100644 index 000000000..40c3a77db --- /dev/null +++ b/components/storage/src/FileSystemModule.h @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_storage_FileSystemModule_h +#define mozilla_storage_FileSystemModule_h + +#include "nscore.h" + +struct sqlite3; + +namespace mozilla { +namespace storage { + +int RegisterFileSystemModule(sqlite3* aDB, const char* aName); + +} // namespace storage +} // namespace mozilla + +#endif // mozilla_storage_FileSystemModule_h diff --git a/components/storage/src/IStorageBindingParamsInternal.h b/components/storage/src/IStorageBindingParamsInternal.h new file mode 100644 index 000000000..e02778680 --- /dev/null +++ b/components/storage/src/IStorageBindingParamsInternal.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 sts=2 expandtab + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_storage_IStorageBindingParamsInternal_h_ +#define mozilla_storage_IStorageBindingParamsInternal_h_ + +#include "nsISupports.h" + +struct sqlite3_stmt; +class mozIStorageError; + +namespace mozilla { +namespace storage { + +#define ISTORAGEBINDINGPARAMSINTERNAL_IID \ + {0x4c43d33a, 0xc620, 0x41b8, {0xba, 0x1d, 0x50, 0xc5, 0xb1, 0xe9, 0x1a, 0x04}} + +/** + * Implementation-only interface for mozIStorageBindingParams. This defines the + * set of methods required by the asynchronous execution code in order to + * consume the contents stored in mozIStorageBindingParams instances. + */ +class IStorageBindingParamsInternal : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(ISTORAGEBINDINGPARAMSINTERNAL_IID) + + /** + * Binds our stored data to the statement. + * + * @param aStatement + * The statement to bind our data to. + * @return nullptr on success, or a mozIStorageError object if an error + * occurred. + */ + virtual already_AddRefed<mozIStorageError> bind(sqlite3_stmt *aStatement) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(IStorageBindingParamsInternal, + ISTORAGEBINDINGPARAMSINTERNAL_IID) + +#define NS_DECL_ISTORAGEBINDINGPARAMSINTERNAL \ + already_AddRefed<mozIStorageError> bind(sqlite3_stmt *aStatement) override; + +} // namespace storage +} // namespace mozilla + +#endif // mozilla_storage_IStorageBindingParamsInternal_h_ diff --git a/components/storage/src/SQLCollations.cpp b/components/storage/src/SQLCollations.cpp new file mode 100644 index 000000000..392bcd804 --- /dev/null +++ b/components/storage/src/SQLCollations.cpp @@ -0,0 +1,242 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" + +#include "SQLCollations.h" + +namespace mozilla { +namespace storage { + +//////////////////////////////////////////////////////////////////////////////// +//// Local Helper Functions + +namespace { + +/** + * Helper function for the UTF-8 locale collations. + * + * @param aService + * The Service that owns the nsICollation used by this collation. + * @param aLen1 + * The number of bytes in aStr1. + * @param aStr1 + * The string to be compared against aStr2 as provided by SQLite. It + * must be a non-null-terminated char* buffer. + * @param aLen2 + * The number of bytes in aStr2. + * @param aStr2 + * The string to be compared against aStr1 as provided by SQLite. It + * must be a non-null-terminated char* buffer. + * @param aComparisonStrength + * The sorting strength, one of the nsICollation constants. + * @return aStr1 - aStr2. That is, if aStr1 < aStr2, returns a negative number. + * If aStr1 > aStr2, returns a positive number. If aStr1 == aStr2, + * returns 0. + */ +int +localeCollationHelper8(void *aService, + int aLen1, + const void *aStr1, + int aLen2, + const void *aStr2, + int32_t aComparisonStrength) +{ + NS_ConvertUTF8toUTF16 str1(static_cast<const char *>(aStr1), aLen1); + NS_ConvertUTF8toUTF16 str2(static_cast<const char *>(aStr2), aLen2); + Service *serv = static_cast<Service *>(aService); + return serv->localeCompareStrings(str1, str2, aComparisonStrength); +} + +/** + * Helper function for the UTF-16 locale collations. + * + * @param aService + * The Service that owns the nsICollation used by this collation. + * @param aLen1 + * The number of bytes (not characters) in aStr1. + * @param aStr1 + * The string to be compared against aStr2 as provided by SQLite. It + * must be a non-null-terminated char16_t* buffer. + * @param aLen2 + * The number of bytes (not characters) in aStr2. + * @param aStr2 + * The string to be compared against aStr1 as provided by SQLite. It + * must be a non-null-terminated char16_t* buffer. + * @param aComparisonStrength + * The sorting strength, one of the nsICollation constants. + * @return aStr1 - aStr2. That is, if aStr1 < aStr2, returns a negative number. + * If aStr1 > aStr2, returns a positive number. If aStr1 == aStr2, + * returns 0. + */ +int +localeCollationHelper16(void *aService, + int aLen1, + const void *aStr1, + int aLen2, + const void *aStr2, + int32_t aComparisonStrength) +{ + const char16_t *buf1 = static_cast<const char16_t *>(aStr1); + const char16_t *buf2 = static_cast<const char16_t *>(aStr2); + + // The second argument to the nsDependentSubstring constructor is exclusive: + // It points to the char16_t immediately following the last one in the target + // substring. Since aLen1 and aLen2 are in bytes, divide by sizeof(char16_t) + // so that the pointer arithmetic is correct. + nsDependentSubstring str1(buf1, buf1 + (aLen1 / sizeof(char16_t))); + nsDependentSubstring str2(buf2, buf2 + (aLen2 / sizeof(char16_t))); + Service *serv = static_cast<Service *>(aService); + return serv->localeCompareStrings(str1, str2, aComparisonStrength); +} + +// This struct is used only by registerCollations below, but ISO C++98 forbids +// instantiating a template dependent on a locally-defined type. Boo-urns! +struct Collations { + const char *zName; + int enc; + int(*xCompare)(void*, int, const void*, int, const void*); +}; + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +//// Exposed Functions + +int +registerCollations(sqlite3 *aDB, + Service *aService) +{ + Collations collations[] = { + {"locale", + SQLITE_UTF8, + localeCollation8}, + {"locale_case_sensitive", + SQLITE_UTF8, + localeCollationCaseSensitive8}, + {"locale_accent_sensitive", + SQLITE_UTF8, + localeCollationAccentSensitive8}, + {"locale_case_accent_sensitive", + SQLITE_UTF8, + localeCollationCaseAccentSensitive8}, + {"locale", + SQLITE_UTF16, + localeCollation16}, + {"locale_case_sensitive", + SQLITE_UTF16, + localeCollationCaseSensitive16}, + {"locale_accent_sensitive", + SQLITE_UTF16, + localeCollationAccentSensitive16}, + {"locale_case_accent_sensitive", + SQLITE_UTF16, + localeCollationCaseAccentSensitive16}, + }; + + int rv = SQLITE_OK; + for (size_t i = 0; SQLITE_OK == rv && i < ArrayLength(collations); ++i) { + struct Collations *p = &collations[i]; + rv = ::sqlite3_create_collation(aDB, p->zName, p->enc, aService, + p->xCompare); + } + + return rv; +} + +//////////////////////////////////////////////////////////////////////////////// +//// SQL Collations + +int +localeCollation8(void *aService, + int aLen1, + const void *aStr1, + int aLen2, + const void *aStr2) +{ + return localeCollationHelper8(aService, aLen1, aStr1, aLen2, aStr2, + nsICollation::kCollationCaseInSensitive); +} + +int +localeCollationCaseSensitive8(void *aService, + int aLen1, + const void *aStr1, + int aLen2, + const void *aStr2) +{ + return localeCollationHelper8(aService, aLen1, aStr1, aLen2, aStr2, + nsICollation::kCollationAccentInsenstive); +} + +int +localeCollationAccentSensitive8(void *aService, + int aLen1, + const void *aStr1, + int aLen2, + const void *aStr2) +{ + return localeCollationHelper8(aService, aLen1, aStr1, aLen2, aStr2, + nsICollation::kCollationCaseInsensitiveAscii); +} + +int +localeCollationCaseAccentSensitive8(void *aService, + int aLen1, + const void *aStr1, + int aLen2, + const void *aStr2) +{ + return localeCollationHelper8(aService, aLen1, aStr1, aLen2, aStr2, + nsICollation::kCollationCaseSensitive); +} + +int +localeCollation16(void *aService, + int aLen1, + const void *aStr1, + int aLen2, + const void *aStr2) +{ + return localeCollationHelper16(aService, aLen1, aStr1, aLen2, aStr2, + nsICollation::kCollationCaseInSensitive); +} + +int +localeCollationCaseSensitive16(void *aService, + int aLen1, + const void *aStr1, + int aLen2, + const void *aStr2) +{ + return localeCollationHelper16(aService, aLen1, aStr1, aLen2, aStr2, + nsICollation::kCollationAccentInsenstive); +} + +int +localeCollationAccentSensitive16(void *aService, + int aLen1, + const void *aStr1, + int aLen2, + const void *aStr2) +{ + return localeCollationHelper16(aService, aLen1, aStr1, aLen2, aStr2, + nsICollation::kCollationCaseInsensitiveAscii); +} + +int +localeCollationCaseAccentSensitive16(void *aService, + int aLen1, + const void *aStr1, + int aLen2, + const void *aStr2) +{ + return localeCollationHelper16(aService, aLen1, aStr1, aLen2, aStr2, + nsICollation::kCollationCaseSensitive); +} + +} // namespace storage +} // namespace mozilla diff --git a/components/storage/src/SQLCollations.h b/components/storage/src/SQLCollations.h new file mode 100644 index 000000000..d6d0d4562 --- /dev/null +++ b/components/storage/src/SQLCollations.h @@ -0,0 +1,249 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_storage_SQLCollations_h +#define mozilla_storage_SQLCollations_h + +#include "mozStorageService.h" +#include "nscore.h" +#include "nsString.h" + +#include "sqlite3.h" + +namespace mozilla { +namespace storage { + +/** + * Registers the collating sequences declared here with the specified + * database and Service. + * + * @param aDB + * The database we'll be registering the collations with. + * @param aService + * The Service that owns the nsICollation used by our collations. + * @return the SQLite status code indicating success or failure. + */ +int registerCollations(sqlite3 *aDB, Service *aService); + +//////////////////////////////////////////////////////////////////////////////// +//// Predefined Functions + +/** + * Custom UTF-8 collating sequence that respects the application's locale. + * Comparison is case- and accent-insensitive. This is called by SQLite. + * + * @param aService + * The Service that owns the nsICollation used by this collation. + * @param aLen1 + * The number of bytes in aStr1. + * @param aStr1 + * The string to be compared against aStr2. It will be passed in by + * SQLite as a non-null-terminated char* buffer. + * @param aLen2 + * The number of bytes in aStr2. + * @param aStr2 + * The string to be compared against aStr1. It will be passed in by + * SQLite as a non-null-terminated char* buffer. + * @return aStr1 - aStr2. That is, if aStr1 < aStr2, returns a negative number. + * If aStr1 > aStr2, returns a positive number. If aStr1 == aStr2, + * returns 0. + */ +int localeCollation8(void *aService, + int aLen1, + const void *aStr1, + int aLen2, + const void *aStr2); + +/** + * Custom UTF-8 collating sequence that respects the application's locale. + * Comparison is case-sensitive and accent-insensitive. This is called by + * SQLite. + * + * @param aService + * The Service that owns the nsICollation used by this collation. + * @param aLen1 + * The number of bytes in aStr1. + * @param aStr1 + * The string to be compared against aStr2. It will be passed in by + * SQLite as a non-null-terminated char* buffer. + * @param aLen2 + * The number of bytes in aStr2. + * @param aStr2 + * The string to be compared against aStr1. It will be passed in by + * SQLite as a non-null-terminated char* buffer. + * @return aStr1 - aStr2. That is, if aStr1 < aStr2, returns a negative number. + * If aStr1 > aStr2, returns a positive number. If aStr1 == aStr2, + * returns 0. + */ +int localeCollationCaseSensitive8(void *aService, + int aLen1, + const void *aStr1, + int aLen2, + const void *aStr2); + +/** + * Custom UTF-8 collating sequence that respects the application's locale. + * Comparison is case-insensitive and accent-sensitive. This is called by + * SQLite. + * + * @param aService + * The Service that owns the nsICollation used by this collation. + * @param aLen1 + * The number of bytes in aStr1. + * @param aStr1 + * The string to be compared against aStr2. It will be passed in by + * SQLite as a non-null-terminated char* buffer. + * @param aLen2 + * The number of bytes in aStr2. + * @param aStr2 + * The string to be compared against aStr1. It will be passed in by + * SQLite as a non-null-terminated char* buffer. + * @return aStr1 - aStr2. That is, if aStr1 < aStr2, returns a negative number. + * If aStr1 > aStr2, returns a positive number. If aStr1 == aStr2, + * returns 0. + */ +int localeCollationAccentSensitive8(void *aService, + int aLen1, + const void *aStr1, + int aLen2, + const void *aStr2); + +/** + * Custom UTF-8 collating sequence that respects the application's locale. + * Comparison is case- and accent-sensitive. This is called by SQLite. + * + * @param aService + * The Service that owns the nsICollation used by this collation. + * @param aLen1 + * The number of bytes in aStr1. + * @param aStr1 + * The string to be compared against aStr2. It will be passed in by + * SQLite as a non-null-terminated char* buffer. + * @param aLen2 + * The number of bytes in aStr2. + * @param aStr2 + * The string to be compared against aStr1. It will be passed in by + * SQLite as a non-null-terminated char* buffer. + * @return aStr1 - aStr2. That is, if aStr1 < aStr2, returns a negative number. + * If aStr1 > aStr2, returns a positive number. If aStr1 == aStr2, + * returns 0. + */ +int localeCollationCaseAccentSensitive8(void *aService, + int aLen1, + const void *aStr1, + int aLen2, + const void *aStr2); + +/** + * Custom UTF-16 collating sequence that respects the application's locale. + * Comparison is case- and accent-insensitive. This is called by SQLite. + * + * @param aService + * The Service that owns the nsICollation used by this collation. + * @param aLen1 + * The number of bytes (not characters) in aStr1. + * @param aStr1 + * The string to be compared against aStr2. It will be passed in by + * SQLite as a non-null-terminated char16_t* buffer. + * @param aLen2 + * The number of bytes (not characters) in aStr2. + * @param aStr2 + * The string to be compared against aStr1. It will be passed in by + * SQLite as a non-null-terminated char16_t* buffer. + * @return aStr1 - aStr2. That is, if aStr1 < aStr2, returns a negative number. + * If aStr1 > aStr2, returns a positive number. If aStr1 == aStr2, + * returns 0. + */ +int localeCollation16(void *aService, + int aLen1, + const void *aStr1, + int aLen2, + const void *aStr2); + +/** + * Custom UTF-16 collating sequence that respects the application's locale. + * Comparison is case-sensitive and accent-insensitive. This is called by + * SQLite. + * + * @param aService + * The Service that owns the nsICollation used by this collation. + * @param aLen1 + * The number of bytes (not characters) in aStr1. + * @param aStr1 + * The string to be compared against aStr2. It will be passed in by + * SQLite as a non-null-terminated char16_t* buffer. + * @param aLen2 + * The number of bytes (not characters) in aStr2. + * @param aStr2 + * The string to be compared against aStr1. It will be passed in by + * SQLite as a non-null-terminated char16_t* buffer. + * @return aStr1 - aStr2. That is, if aStr1 < aStr2, returns a negative number. + * If aStr1 > aStr2, returns a positive number. If aStr1 == aStr2, + * returns 0. + */ +int localeCollationCaseSensitive16(void *aService, + int aLen1, + const void *aStr1, + int aLen2, + const void *aStr2); + +/** + * Custom UTF-16 collating sequence that respects the application's locale. + * Comparison is case-insensitive and accent-sensitive. This is called by + * SQLite. + * + * @param aService + * The Service that owns the nsICollation used by this collation. + * @param aLen1 + * The number of bytes (not characters) in aStr1. + * @param aStr1 + * The string to be compared against aStr2. It will be passed in by + * SQLite as a non-null-terminated char16_t* buffer. + * @param aLen2 + * The number of bytes (not characters) in aStr2. + * @param aStr2 + * The string to be compared against aStr1. It will be passed in by + * SQLite as a non-null-terminated char16_t* buffer. + * @return aStr1 - aStr2. That is, if aStr1 < aStr2, returns a negative number. + * If aStr1 > aStr2, returns a positive number. If aStr1 == aStr2, + * returns 0. + */ +int localeCollationAccentSensitive16(void *aService, + int aLen1, + const void *aStr1, + int aLen2, + const void *aStr2); + +/** + * Custom UTF-16 collating sequence that respects the application's locale. + * Comparison is case- and accent-sensitive. This is called by SQLite. + * + * @param aService + * The Service that owns the nsICollation used by this collation. + * @param aLen1 + * The number of bytes (not characters) in aStr1. + * @param aStr1 + * The string to be compared against aStr2. It will be passed in by + * SQLite as a non-null-terminated char16_t* buffer. + * @param aLen2 + * The number of bytes (not characters) in aStr2. + * @param aStr2 + * The string to be compared against aStr1. It will be passed in by + * SQLite as a non-null-terminated char16_t* buffer. + * @return aStr1 - aStr2. That is, if aStr1 < aStr2, returns a negative number. + * If aStr1 > aStr2, returns a positive number. If aStr1 == aStr2, + * returns 0. + */ +int localeCollationCaseAccentSensitive16(void *aService, + int aLen1, + const void *aStr1, + int aLen2, + const void *aStr2); + +} // namespace storage +} // namespace mozilla + +#endif // mozilla_storage_SQLCollations_h diff --git a/components/storage/src/SQLiteMutex.h b/components/storage/src/SQLiteMutex.h new file mode 100644 index 000000000..eaa69eab1 --- /dev/null +++ b/components/storage/src/SQLiteMutex.h @@ -0,0 +1,173 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_storage_SQLiteMutex_h_ +#define mozilla_storage_SQLiteMutex_h_ + +#include "mozilla/BlockingResourceBase.h" +#include "sqlite3.h" + +namespace mozilla { +namespace storage { + +/** + * Wrapper class for sqlite3_mutexes. To be used whenever we want to use a + * sqlite3_mutex. + * + * @warning Never EVER wrap the same sqlite3_mutex with a different SQLiteMutex. + * If you do this, you void the deadlock detector's warranty! + */ +class SQLiteMutex : private BlockingResourceBase +{ +public: + /** + * Constructs a wrapper for a sqlite3_mutex that has deadlock detecting. + * + * @param aName + * A name which can be used to reference this mutex. + */ + explicit SQLiteMutex(const char *aName) + : BlockingResourceBase(aName, eMutex) + , mMutex(nullptr) + { + } + + /** + * Sets the mutex that we are wrapping. We generally do not have access to + * our mutex at class construction, so we have to set it once we get access to + * it. + * + * @param aMutex + * The sqlite3_mutex that we are going to wrap. + */ + void initWithMutex(sqlite3_mutex *aMutex) + { + NS_ASSERTION(aMutex, "You must pass in a valid mutex!"); + NS_ASSERTION(!mMutex, "A mutex has already been set for this!"); + mMutex = aMutex; + } + +#if !defined(DEBUG) + /** + * Acquires the mutex. + */ + void lock() + { + sqlite3_mutex_enter(mMutex); + } + + /** + * Releases the mutex. + */ + void unlock() + { + sqlite3_mutex_leave(mMutex); + } + + /** + * Asserts that the current thread owns the mutex. + */ + void assertCurrentThreadOwns() + { + } + + /** + * Asserts that the current thread does not own the mutex. + */ + void assertNotCurrentThreadOwns() + { + } + +#else + void lock() + { + NS_ASSERTION(mMutex, "No mutex associated with this wrapper!"); + + // While SQLite Mutexes may be recursive, in our own code we do not want to + // treat them as such. + + CheckAcquire(); + sqlite3_mutex_enter(mMutex); + Acquire(); // Call is protected by us holding the mutex. + } + + void unlock() + { + NS_ASSERTION(mMutex, "No mutex associated with this wrapper!"); + + // While SQLite Mutexes may be recursive, in our own code we do not want to + // treat them as such. + Release(); // Call is protected by us holding the mutex. + sqlite3_mutex_leave(mMutex); + } + + void assertCurrentThreadOwns() + { + NS_ASSERTION(mMutex, "No mutex associated with this wrapper!"); + NS_ASSERTION(sqlite3_mutex_held(mMutex), + "Mutex is not held, but we expect it to be!"); + } + + void assertNotCurrentThreadOwns() + { + NS_ASSERTION(mMutex, "No mutex associated with this wrapper!"); + NS_ASSERTION(sqlite3_mutex_notheld(mMutex), + "Mutex is held, but we expect it to not be!"); + } +#endif // ifndef DEBUG + +private: + sqlite3_mutex *mMutex; +}; + +/** + * Automatically acquires the mutex when it enters scope, and releases it when + * it leaves scope. + */ +class MOZ_STACK_CLASS SQLiteMutexAutoLock +{ +public: + explicit SQLiteMutexAutoLock(SQLiteMutex &aMutex) + : mMutex(aMutex) + { + mMutex.lock(); + } + + ~SQLiteMutexAutoLock() + { + mMutex.unlock(); + } + +private: + SQLiteMutex &mMutex; +}; + +/** + * Automatically releases the mutex when it enters scope, and acquires it when + * it leaves scope. + */ +class MOZ_STACK_CLASS SQLiteMutexAutoUnlock +{ +public: + explicit SQLiteMutexAutoUnlock(SQLiteMutex &aMutex) + : mMutex(aMutex) + { + mMutex.unlock(); + } + + ~SQLiteMutexAutoUnlock() + { + mMutex.lock(); + } + +private: + SQLiteMutex &mMutex; +}; + +} // namespace storage +} // namespace mozilla + +#endif // mozilla_storage_SQLiteMutex_h_ diff --git a/components/storage/src/StatementCache.h b/components/storage/src/StatementCache.h new file mode 100644 index 000000000..ed7714799 --- /dev/null +++ b/components/storage/src/StatementCache.h @@ -0,0 +1,141 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_storage_StatementCache_h +#define mozilla_storage_StatementCache_h + +#include "mozIStorageConnection.h" +#include "mozIStorageStatement.h" +#include "mozIStorageAsyncStatement.h" + +#include "nsAutoPtr.h" +#include "nsHashKeys.h" +#include "nsInterfaceHashtable.h" + +namespace mozilla { +namespace storage { + +/** + * Class used to cache statements (mozIStorageStatement or + * mozIStorageAsyncStatement). + */ +template<typename StatementType> +class StatementCache { +public: + /** + * Constructor for the cache. + * + * @note a connection can have more than one cache. + * + * @param aConnection + * A reference to the nsCOMPtr for the connection this cache is to be + * used for. This nsCOMPtr must at least live as long as this class, + * otherwise crashes will happen. + */ + explicit StatementCache(nsCOMPtr<mozIStorageConnection>& aConnection) + : mConnection(aConnection) + { + } + + /** + * Obtains a cached statement. If this statement is not yet created, it will + * be created and stored for later use. + * + * @param aQuery + * The SQL string (either a const char [] or nsACString) to get a + * cached query for. + * @return the cached statement, or null upon error. + */ + inline + already_AddRefed<StatementType> + GetCachedStatement(const nsACString& aQuery) + { + nsCOMPtr<StatementType> stmt; + if (!mCachedStatements.Get(aQuery, getter_AddRefs(stmt))) { + stmt = CreateStatement(aQuery); + NS_ENSURE_TRUE(stmt, nullptr); + + mCachedStatements.Put(aQuery, stmt); + } + return stmt.forget(); + } + + template<int N> + MOZ_ALWAYS_INLINE already_AddRefed<StatementType> + GetCachedStatement(const char (&aQuery)[N]) + { + nsDependentCString query(aQuery, N - 1); + return GetCachedStatement(query); + } + + /** + * Finalizes all cached statements so the database can be safely closed. The + * behavior of this cache is unspecified after this method is called. + */ + inline + void + FinalizeStatements() + { + for (auto iter = mCachedStatements.Iter(); !iter.Done(); iter.Next()) { + (void)iter.Data()->Finalize(); + } + + // Clear the cache at this time too! + (void)mCachedStatements.Clear(); + } + +private: + inline + already_AddRefed<StatementType> + CreateStatement(const nsACString& aQuery); + + nsInterfaceHashtable<nsCStringHashKey, StatementType> mCachedStatements; + nsCOMPtr<mozIStorageConnection>& mConnection; +}; + +template< > +inline +already_AddRefed<mozIStorageStatement> +StatementCache<mozIStorageStatement>::CreateStatement(const nsACString& aQuery) +{ + NS_ENSURE_TRUE(mConnection, nullptr); + + nsCOMPtr<mozIStorageStatement> stmt; + nsresult rv = mConnection->CreateStatement(aQuery, getter_AddRefs(stmt)); + if (NS_FAILED(rv)) { + nsCString error; + error.AppendLiteral("The statement '"); + error.Append(aQuery); + error.AppendLiteral("' failed to compile with the error message '"); + nsCString msg; + (void)mConnection->GetLastErrorString(msg); + error.Append(msg); + error.AppendLiteral("'."); + NS_ERROR(error.get()); + } + NS_ENSURE_SUCCESS(rv, nullptr); + + return stmt.forget(); +} + +template< > +inline +already_AddRefed<mozIStorageAsyncStatement> +StatementCache<mozIStorageAsyncStatement>::CreateStatement(const nsACString& aQuery) +{ + NS_ENSURE_TRUE(mConnection, nullptr); + + nsCOMPtr<mozIStorageAsyncStatement> stmt; + nsresult rv = mConnection->CreateAsyncStatement(aQuery, getter_AddRefs(stmt)); + NS_ENSURE_SUCCESS(rv, nullptr); + + return stmt.forget(); +} + +} // namespace storage +} // namespace mozilla + +#endif // mozilla_storage_StatementCache_h diff --git a/components/storage/src/StorageBaseStatementInternal.cpp b/components/storage/src/StorageBaseStatementInternal.cpp new file mode 100644 index 000000000..d6545fcb4 --- /dev/null +++ b/components/storage/src/StorageBaseStatementInternal.cpp @@ -0,0 +1,221 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 sts=2 expandtab + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "StorageBaseStatementInternal.h" + +#include "nsProxyRelease.h" + +#include "mozStorageBindingParamsArray.h" +#include "mozStorageStatementData.h" +#include "mozStorageAsyncStatementExecution.h" + +namespace mozilla { +namespace storage { + +//////////////////////////////////////////////////////////////////////////////// +//// Local Classes + +/** + * Used to finalize an asynchronous statement on the background thread. + */ +class AsyncStatementFinalizer : public Runnable +{ +public: + /** + * Constructor for the event. + * + * @param aStatement + * We need the AsyncStatement to be able to get at the sqlite3_stmt; + * we only access/create it on the async thread. + * @param aConnection + * We need the connection to know what thread to release the statement + * on. We release the statement on that thread since releasing the + * statement might end up releasing the connection too. + */ + AsyncStatementFinalizer(StorageBaseStatementInternal *aStatement, + Connection *aConnection) + : mStatement(aStatement) + , mConnection(aConnection) + { + } + + NS_IMETHOD Run() override + { + if (mStatement->mAsyncStatement) { + sqlite3_finalize(mStatement->mAsyncStatement); + mStatement->mAsyncStatement = nullptr; + } + + nsCOMPtr<nsIThread> targetThread(mConnection->threadOpenedOn); + NS_ProxyRelease(targetThread, mStatement.forget()); + return NS_OK; + } +private: + RefPtr<StorageBaseStatementInternal> mStatement; + RefPtr<Connection> mConnection; +}; + +/** + * Finalize a sqlite3_stmt on the background thread for a statement whose + * destructor was invoked and the statement was non-null. + */ +class LastDitchSqliteStatementFinalizer : public Runnable +{ +public: + /** + * Event constructor. + * + * @param aConnection + * Used to keep the connection alive. If we failed to do this, it + * is possible that the statement going out of scope invoking us + * might have the last reference to the connection and so trigger + * an attempt to close the connection which is doomed to fail + * (because the asynchronous execution thread must exist which will + * trigger the failure case). + * @param aStatement + * The sqlite3_stmt to finalize. This object takes ownership / + * responsibility for the instance and all other references to it + * should be forgotten. + */ + LastDitchSqliteStatementFinalizer(RefPtr<Connection> &aConnection, + sqlite3_stmt *aStatement) + : mConnection(aConnection) + , mAsyncStatement(aStatement) + { + NS_PRECONDITION(aConnection, "You must provide a Connection"); + } + + NS_IMETHOD Run() override + { + (void)::sqlite3_finalize(mAsyncStatement); + mAsyncStatement = nullptr; + + nsCOMPtr<nsIThread> target(mConnection->threadOpenedOn); + (void)::NS_ProxyRelease(target, mConnection.forget()); + return NS_OK; + } +private: + RefPtr<Connection> mConnection; + sqlite3_stmt *mAsyncStatement; +}; + +//////////////////////////////////////////////////////////////////////////////// +//// StorageBaseStatementInternal + +StorageBaseStatementInternal::StorageBaseStatementInternal() +: mAsyncStatement(nullptr) +{ +} + +void +StorageBaseStatementInternal::asyncFinalize() +{ + nsIEventTarget *target = mDBConnection->getAsyncExecutionTarget(); + if (target) { + // Attempt to finalize asynchronously + nsCOMPtr<nsIRunnable> event = + new AsyncStatementFinalizer(this, mDBConnection); + + // Dispatch. Note that dispatching can fail, typically if + // we have a race condition with asyncClose(). It's ok, + // let asyncClose() win. + (void)target->Dispatch(event, NS_DISPATCH_NORMAL); + } + // If we cannot get the background thread, + // mozStorageConnection::AsyncClose() has already been called and + // the statement either has been or will be cleaned up by + // internalClose(). +} + +void +StorageBaseStatementInternal::destructorAsyncFinalize() +{ + if (!mAsyncStatement) + return; + + // If we reach this point, our owner has not finalized this + // statement, yet we are being destructed. If possible, we want to + // auto-finalize it early, to release the resources early. + nsIEventTarget *target = mDBConnection->getAsyncExecutionTarget(); + if (target) { + // If we can get the async execution target, we can indeed finalize + // the statement, as the connection is still open. + bool isAsyncThread = false; + (void)target->IsOnCurrentThread(&isAsyncThread); + + nsCOMPtr<nsIRunnable> event = + new LastDitchSqliteStatementFinalizer(mDBConnection, mAsyncStatement); + if (isAsyncThread) { + (void)event->Run(); + } else { + (void)target->Dispatch(event, NS_DISPATCH_NORMAL); + } + } + + // We might not be able to dispatch to the background thread, + // presumably because it is being shutdown. Since said shutdown will + // finalize the statement, we just need to clean-up around here. + mAsyncStatement = nullptr; +} + +NS_IMETHODIMP +StorageBaseStatementInternal::NewBindingParamsArray( + mozIStorageBindingParamsArray **_array +) +{ + nsCOMPtr<mozIStorageBindingParamsArray> array = new BindingParamsArray(this); + NS_ENSURE_TRUE(array, NS_ERROR_OUT_OF_MEMORY); + + array.forget(_array); + return NS_OK; +} + +NS_IMETHODIMP +StorageBaseStatementInternal::ExecuteAsync( + mozIStorageStatementCallback *aCallback, + mozIStoragePendingStatement **_stmt +) +{ + // We used to call Connection::ExecuteAsync but it takes a + // mozIStorageBaseStatement signature because it is also a public API. Since + // our 'this' has no static concept of mozIStorageBaseStatement and Connection + // would just QI it back across to a StorageBaseStatementInternal and the + // actual logic is very simple, we now roll our own. + nsTArray<StatementData> stmts(1); + StatementData data; + nsresult rv = getAsynchronousStatementData(data); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(stmts.AppendElement(data), NS_ERROR_OUT_OF_MEMORY); + + // Dispatch to the background + return AsyncExecuteStatements::execute(stmts, mDBConnection, + mNativeConnection, aCallback, _stmt); +} + +NS_IMETHODIMP +StorageBaseStatementInternal::EscapeStringForLIKE( + const nsAString &aValue, + const char16_t aEscapeChar, + nsAString &_escapedString +) +{ + const char16_t MATCH_ALL('%'); + const char16_t MATCH_ONE('_'); + + _escapedString.Truncate(0); + + for (uint32_t i = 0; i < aValue.Length(); i++) { + if (aValue[i] == aEscapeChar || aValue[i] == MATCH_ALL || + aValue[i] == MATCH_ONE) { + _escapedString += aEscapeChar; + } + _escapedString += aValue[i]; + } + return NS_OK; +} + +} // namespace storage +} // namespace mozilla diff --git a/components/storage/src/StorageBaseStatementInternal.h b/components/storage/src/StorageBaseStatementInternal.h new file mode 100644 index 000000000..97e68e6b5 --- /dev/null +++ b/components/storage/src/StorageBaseStatementInternal.h @@ -0,0 +1,353 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 sts=2 expandtab + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_storage_StorageBaseStatementInternal_h_ +#define mozilla_storage_StorageBaseStatementInternal_h_ + +#include "nsISupports.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" + +struct sqlite3; +struct sqlite3_stmt; +class mozIStorageBindingParamsArray; +class mozIStorageBindingParams; +class mozIStorageStatementCallback; +class mozIStoragePendingStatement; + +namespace mozilla { +namespace storage { + +#define STORAGEBASESTATEMENTINTERNAL_IID \ + {0xd18856c9, 0xbf07, 0x4ae2, {0x94, 0x5b, 0x1a, 0xdd, 0x49, 0x19, 0x55, 0x2a}} + +class Connection; +class StatementData; + +class AsyncStatementFinalizer; + +/** + * Implementation-only interface and shared logix mix-in corresponding to + * mozIStorageBaseStatement. Both Statement and AsyncStatement inherit from + * this. The interface aspect makes them look the same to implementation innards + * that aren't publicly accessible. The mix-in avoids code duplication in + * common implementations of mozIStorageBaseStatement, albeit with some minor + * performance/space overhead because we have to use defines to officially + * implement the methods on Statement/AsyncStatement (and proxy to this base + * class.) + */ +class StorageBaseStatementInternal : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(STORAGEBASESTATEMENTINTERNAL_IID) + + /** + * @return the connection that this statement belongs to. + */ + Connection *getOwner() + { + return mDBConnection; + } + + /** + * Return the asynchronous statement, creating it if required. + * + * This is for use by the asynchronous execution code for StatementData + * created by AsyncStatements. Statement internally uses this method to + * prepopulate StatementData with the sqlite3_stmt. + * + * @param[out] stmt + * The sqlite3_stmt for asynchronous use. + * @return The SQLite result code for creating the statement if created, + * SQLITE_OK if creation was not required. + */ + virtual int getAsyncStatement(sqlite3_stmt **_stmt) = 0; + + /** + * Obtains the StatementData needed for asynchronous execution. + * + * This is for use by Connection to retrieve StatementData from statements + * when executeAsync is invoked. + * + * @param[out] _data + * A reference to a StatementData object that will be populated + * upon successful execution of this method. + * @return NS_OK if we were able to assemble the data, failure otherwise. + */ + virtual nsresult getAsynchronousStatementData(StatementData &_data) = 0; + + /** + * Construct a new BindingParams to be owned by the provided binding params + * array. This method exists so that BindingParamsArray does not need + * factory logic to determine what type of BindingParams to instantiate. + * + * @param aOwner + * The binding params array to own the newly created binding params. + * @return The new mozIStorageBindingParams instance appropriate to the + * underlying statement type. + */ + virtual already_AddRefed<mozIStorageBindingParams> newBindingParams( + mozIStorageBindingParamsArray *aOwner + ) = 0; + +protected: // mix-in bits are protected + StorageBaseStatementInternal(); + + RefPtr<Connection> mDBConnection; + sqlite3 *mNativeConnection; + + /** + * Our asynchronous statement. + * + * For Statement this is populated by the first invocation to + * getAsyncStatement. + * + * For AsyncStatement, this is null at creation time and initialized by the + * async thread when it calls getAsyncStatement the first time the statement + * is executed. (Or in the event of badly formed SQL, every time.) + */ + sqlite3_stmt *mAsyncStatement; + + /** + * Initiate asynchronous finalization by dispatching an event to the + * asynchronous thread to finalize mAsyncStatement. This acquires a reference + * to this statement and proxies it back to the connection's owning thread + * for release purposes. + * + * In the event the asynchronous thread is already gone or we otherwise fail + * to dispatch an event to it we failover to invoking internalAsyncFinalize + * directly. (That's what the asynchronous finalizer would have called.) + * + * @note You must not call this method from your destructor because its + * operation assumes we are still alive. Call internalAsyncFinalize + * directly in that case. + */ + void asyncFinalize(); + + /** + * Cleanup the async sqlite3_stmt stored in mAsyncStatement if it exists by + * attempting to dispatch to the asynchronous thread if available, finalizing + * on this thread if it is not. + * + * @note Call this from your destructor, call asyncFinalize otherwise. + */ + void destructorAsyncFinalize(); + + NS_IMETHOD NewBindingParamsArray(mozIStorageBindingParamsArray **_array); + NS_IMETHOD ExecuteAsync(mozIStorageStatementCallback *aCallback, + mozIStoragePendingStatement **_stmt); + NS_IMETHOD EscapeStringForLIKE(const nsAString &aValue, + char16_t aEscapeChar, + nsAString &_escapedString); + + // Needs access to internalAsyncFinalize + friend class AsyncStatementFinalizer; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(StorageBaseStatementInternal, + STORAGEBASESTATEMENTINTERNAL_IID) + +#define NS_DECL_STORAGEBASESTATEMENTINTERNAL \ + virtual Connection *getOwner(); \ + virtual int getAsyncStatement(sqlite3_stmt **_stmt) override; \ + virtual nsresult getAsynchronousStatementData(StatementData &_data) override; \ + virtual already_AddRefed<mozIStorageBindingParams> newBindingParams( \ + mozIStorageBindingParamsArray *aOwner) override; + +/** + * Helper macro to implement the proxying implementations. Because we are + * implementing methods that are part of mozIStorageBaseStatement and the + * implementation classes already use NS_DECL_MOZISTORAGEBASESTATEMENT we don't + * need to provide declaration support. + */ +#define MIX_IMPL(_class, _optionalGuard, _method, _declArgs, _invokeArgs) \ + NS_IMETHODIMP _class::_method _declArgs \ + { \ + _optionalGuard \ + return StorageBaseStatementInternal::_method _invokeArgs; \ + } + + +/** + * Define proxying implementation for the given _class. If a state invariant + * needs to be checked and an early return possibly performed, pass the clause + * to use as _optionalGuard. + */ +#define MIXIN_IMPL_STORAGEBASESTATEMENTINTERNAL(_class, _optionalGuard) \ + MIX_IMPL(_class, _optionalGuard, \ + NewBindingParamsArray, \ + (mozIStorageBindingParamsArray **_array), \ + (_array)) \ + MIX_IMPL(_class, _optionalGuard, \ + ExecuteAsync, \ + (mozIStorageStatementCallback *aCallback, \ + mozIStoragePendingStatement **_stmt), \ + (aCallback, _stmt)) \ + MIX_IMPL(_class, _optionalGuard, \ + EscapeStringForLIKE, \ + (const nsAString &aValue, char16_t aEscapeChar, \ + nsAString &_escapedString), \ + (aValue, aEscapeChar, _escapedString)) + +/** + * Name-building helper for BIND_GEN_IMPL. + */ +#define BIND_NAME_CONCAT(_nameBit, _concatBit) \ + Bind##_nameBit##_concatBit + +/** + * We have type-specific convenience methods for C++ implementations in + * 3 different forms; 2 by index, 1 by name. The following macro allows + * us to avoid having to define repetitive things by hand. + * + * Because of limitations of macros and our desire to avoid requiring special + * permutations for the null and blob cases (whose argument count varies), + * we require that the argument declarations and corresponding invocation + * usages are passed in. + * + * @param _class + * The class name. + * @param _guard + * The guard clause to inject. + * @param _declName + * The argument list (with parens) for the ByName variants. + * @param _declIndex + * The argument list (with parens) for the index variants. + * @param _invArgs + * The invocation argumment list. + */ +#define BIND_GEN_IMPL(_class, _guard, _name, _declName, _declIndex, _invArgs) \ + NS_IMETHODIMP _class::BIND_NAME_CONCAT(_name, ByName) _declName \ + { \ + _guard \ + mozIStorageBindingParams *params = getParams(); \ + NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY); \ + return params->BIND_NAME_CONCAT(_name, ByName) _invArgs; \ + } \ + NS_IMETHODIMP _class::BIND_NAME_CONCAT(_name, ByIndex) _declIndex \ + { \ + _guard \ + mozIStorageBindingParams *params = getParams(); \ + NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY); \ + return params->BIND_NAME_CONCAT(_name, ByIndex) _invArgs; \ + } \ + NS_IMETHODIMP _class::BIND_NAME_CONCAT(_name, Parameter) _declIndex \ + { \ + _guard \ + mozIStorageBindingParams *params = getParams(); \ + NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY); \ + return params->BIND_NAME_CONCAT(_name, ByIndex) _invArgs; \ + } + +/** + * Implement BindByName/BindByIndex for the given class. + * + * @param _class The class name. + * @param _optionalGuard The guard clause to inject. + */ +#define BIND_BASE_IMPLS(_class, _optionalGuard) \ + NS_IMETHODIMP _class::BindByName(const nsACString &aName, \ + nsIVariant *aValue) \ + { \ + _optionalGuard \ + mozIStorageBindingParams *params = getParams(); \ + NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY); \ + return params->BindByName(aName, aValue); \ + } \ + NS_IMETHODIMP _class::BindByIndex(uint32_t aIndex, \ + nsIVariant *aValue) \ + { \ + _optionalGuard \ + mozIStorageBindingParams *params = getParams(); \ + NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY); \ + return params->BindByIndex(aIndex, aValue); \ + } + +/** + * Define the various Bind*Parameter, Bind*ByIndex, Bind*ByName stubs that just + * end up proxying to the params object. + */ +#define BOILERPLATE_BIND_PROXIES(_class, _optionalGuard) \ + BIND_BASE_IMPLS(_class, _optionalGuard) \ + BIND_GEN_IMPL(_class, _optionalGuard, \ + UTF8String, \ + (const nsACString &aWhere, \ + const nsACString &aValue), \ + (uint32_t aWhere, \ + const nsACString &aValue), \ + (aWhere, aValue)) \ + BIND_GEN_IMPL(_class, _optionalGuard, \ + String, \ + (const nsACString &aWhere, \ + const nsAString &aValue), \ + (uint32_t aWhere, \ + const nsAString &aValue), \ + (aWhere, aValue)) \ + BIND_GEN_IMPL(_class, _optionalGuard, \ + Double, \ + (const nsACString &aWhere, \ + double aValue), \ + (uint32_t aWhere, \ + double aValue), \ + (aWhere, aValue)) \ + BIND_GEN_IMPL(_class, _optionalGuard, \ + Int32, \ + (const nsACString &aWhere, \ + int32_t aValue), \ + (uint32_t aWhere, \ + int32_t aValue), \ + (aWhere, aValue)) \ + BIND_GEN_IMPL(_class, _optionalGuard, \ + Int64, \ + (const nsACString &aWhere, \ + int64_t aValue), \ + (uint32_t aWhere, \ + int64_t aValue), \ + (aWhere, aValue)) \ + BIND_GEN_IMPL(_class, _optionalGuard, \ + Null, \ + (const nsACString &aWhere), \ + (uint32_t aWhere), \ + (aWhere)) \ + BIND_GEN_IMPL(_class, _optionalGuard, \ + Blob, \ + (const nsACString &aWhere, \ + const uint8_t *aValue, \ + uint32_t aValueSize), \ + (uint32_t aWhere, \ + const uint8_t *aValue, \ + uint32_t aValueSize), \ + (aWhere, aValue, aValueSize)) \ + BIND_GEN_IMPL(_class, _optionalGuard, \ + StringAsBlob, \ + (const nsACString &aWhere, \ + const nsAString& aValue), \ + (uint32_t aWhere, \ + const nsAString& aValue), \ + (aWhere, aValue)) \ + BIND_GEN_IMPL(_class, _optionalGuard, \ + UTF8StringAsBlob, \ + (const nsACString &aWhere, \ + const nsACString& aValue), \ + (uint32_t aWhere, \ + const nsACString& aValue), \ + (aWhere, aValue)) \ + BIND_GEN_IMPL(_class, _optionalGuard, \ + AdoptedBlob, \ + (const nsACString &aWhere, \ + uint8_t *aValue, \ + uint32_t aValueSize), \ + (uint32_t aWhere, \ + uint8_t *aValue, \ + uint32_t aValueSize), \ + (aWhere, aValue, aValueSize)) + + + +} // namespace storage +} // namespace mozilla + +#endif // mozilla_storage_StorageBaseStatementInternal_h_ diff --git a/components/storage/src/TelemetryVFS.cpp b/components/storage/src/TelemetryVFS.cpp new file mode 100644 index 000000000..060255ba4 --- /dev/null +++ b/components/storage/src/TelemetryVFS.cpp @@ -0,0 +1,827 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* This is a passthrough module initially set up to record telemetry via + an IO interposer. While the interposing plumbing is still intact to + avoid storage issues, telemetry recording has been removed + MCFIXME: Rewrite calls to go directly to SQLite and no longer through + this plumbing... */ + +#include <string.h> +#include "mozilla/Preferences.h" +#include "sqlite3.h" +#include "nsThreadUtils.h" +#include "mozilla/dom/quota/PersistenceType.h" +#include "mozilla/dom/quota/QuotaManager.h" +#include "mozilla/dom/quota/QuotaObject.h" +#include "mozilla/IOInterposer.h" + +// The last VFS version for which this file has been updated. +#define LAST_KNOWN_VFS_VERSION 3 + +// The last io_methods version for which this file has been updated. +#define LAST_KNOWN_IOMETHODS_VERSION 3 + +/** + * This preference is a workaround to allow users/sysadmins to identify + * that the profile exists on an NFS share whose implementation + * is incompatible with SQLite's default locking implementation. + * Bug 433129 attempted to automatically identify such file-systems, + * but a reliable way was not found and it was determined that the fallback + * locking is slower than POSIX locking, so we do not want to do it by default. +*/ +#define PREF_NFS_FILESYSTEM "storage.nfs_filesystem" + +namespace { + +using namespace mozilla; +using namespace mozilla::dom::quota; + +struct Histograms { + const char *name; +}; + +#define SQLITE_TELEMETRY(FILENAME, HGRAM) \ + { FILENAME, \ + } + +Histograms gHistograms[] = { + SQLITE_TELEMETRY("places.sqlite", PLACES), + SQLITE_TELEMETRY("cookies.sqlite", COOKIES), + SQLITE_TELEMETRY("webappsstore.sqlite", WEBAPPS), + SQLITE_TELEMETRY(nullptr, OTHER) +}; +#undef SQLITE_TELEMETRY + +/** RAII class for measuring how long io takes on/off main thread + */ +class IOThreadAutoTimer { +public: + /** + * IOThreadAutoTimer measures time spent in IO. Additionally it + * automatically determines whether IO is happening on the main + * thread and picks an appropriate histogram. + * + * @param aOp optionally takes an IO operation to report through the + * IOInterposer. Filename will be reported as NULL, and reference will be + * either "sqlite-mainthread" or "sqlite-otherthread". + */ + explicit IOThreadAutoTimer(IOInterposeObserver::Operation aOp) + : start(TimeStamp::Now()), + op(aOp) + { + } + + ~IOThreadAutoTimer() + { + // We don't report SQLite I/O on Windows because we have a comprehensive + // mechanism for intercepting I/O on that platform that captures a superset + // of the data captured here. + } + +private: + const TimeStamp start; + IOInterposeObserver::Operation op; +}; + +struct telemetry_file { + // Base class. Must be first + sqlite3_file base; + + // histograms pertaining to this file + Histograms *histograms; + + // quota object for this file + RefPtr<QuotaObject> quotaObject; + + // The chunk size for this file. See the documentation for + // sqlite3_file_control() and FCNTL_CHUNK_SIZE. + int fileChunkSize; + + // This contains the vfs that actually does work + sqlite3_file pReal[1]; +}; + +const char* +DatabasePathFromWALPath(const char *zWALName) +{ + /** + * Do some sketchy pointer arithmetic to find the parameter key. The WAL + * filename is in the middle of a big allocated block that contains: + * + * - Random Values + * - Main Database Path + * - \0 + * - Multiple URI components consisting of: + * - Key + * - \0 + * - Value + * - \0 + * - \0 + * - Journal Path + * - \0 + * - WAL Path (zWALName) + * - \0 + * + * Because the main database path is preceded by a random value we have to be + * careful when trying to figure out when we should terminate this loop. + */ + MOZ_ASSERT(zWALName); + + nsDependentCSubstring dbPath(zWALName, strlen(zWALName)); + + // Chop off the "-wal" suffix. + NS_NAMED_LITERAL_CSTRING(kWALSuffix, "-wal"); + MOZ_ASSERT(StringEndsWith(dbPath, kWALSuffix)); + + dbPath.Rebind(zWALName, dbPath.Length() - kWALSuffix.Length()); + MOZ_ASSERT(!dbPath.IsEmpty()); + + // We want to scan to the end of the key/value URI pairs. Skip the preceding + // null and go to the last char of the journal path. + const char* cursor = zWALName - 2; + + // Make sure we just skipped a null. + MOZ_ASSERT(!*(cursor + 1)); + + // Walk backwards over the journal path. + while (*cursor) { + cursor--; + } + + // There should be another null here. + cursor--; + MOZ_ASSERT(!*cursor); + + // Back up one more char to the last char of the previous string. It may be + // the database path or it may be a key/value URI pair. + cursor--; + +#ifdef DEBUG + { + // Verify that we just walked over the journal path. Account for the two + // nulls we just skipped. + const char *journalStart = cursor + 3; + + nsDependentCSubstring journalPath(journalStart, + strlen(journalStart)); + + // Chop off the "-journal" suffix. + NS_NAMED_LITERAL_CSTRING(kJournalSuffix, "-journal"); + MOZ_ASSERT(StringEndsWith(journalPath, kJournalSuffix)); + + journalPath.Rebind(journalStart, + journalPath.Length() - kJournalSuffix.Length()); + MOZ_ASSERT(!journalPath.IsEmpty()); + + // Make sure that the database name is a substring of the journal name. + MOZ_ASSERT(journalPath == dbPath); + } +#endif + + // Now we're either at the end of the key/value URI pairs or we're at the + // end of the database path. Carefully walk backwards one character at a + // time to do this safely without running past the beginning of the database + // path. + const char *const dbPathStart = dbPath.BeginReading(); + const char *dbPathCursor = dbPath.EndReading() - 1; + bool isDBPath = true; + + while (true) { + MOZ_ASSERT(*dbPathCursor, "dbPathCursor should never see a null char!"); + + if (isDBPath) { + isDBPath = dbPathStart <= dbPathCursor && + *dbPathCursor == *cursor && + *cursor; + } + + if (!isDBPath) { + // This isn't the database path so it must be a value. Scan past it and + // the key also. + for (size_t stringCount = 0; stringCount < 2; stringCount++) { + // Scan past the string to the preceding null character. + while (*cursor) { + cursor--; + } + + // Back up one more char to the last char of preceding string. + cursor--; + } + + // Reset and start again. + dbPathCursor = dbPath.EndReading() - 1; + isDBPath = true; + + continue; + } + + MOZ_ASSERT(isDBPath); + MOZ_ASSERT(*cursor); + + if (dbPathStart == dbPathCursor) { + // Found the full database path, we're all done. + MOZ_ASSERT(nsDependentCString(cursor) == dbPath); + return cursor; + } + + // Change the cursors and go through the loop again. + cursor--; + dbPathCursor--; + } + + MOZ_CRASH("Should never get here!"); +} + +already_AddRefed<QuotaObject> +GetQuotaObjectFromNameAndParameters(const char *zName, + const char *zURIParameterKey) +{ + MOZ_ASSERT(zName); + MOZ_ASSERT(zURIParameterKey); + + const char *persistenceType = + sqlite3_uri_parameter(zURIParameterKey, "persistenceType"); + if (!persistenceType) { + return nullptr; + } + + const char *group = sqlite3_uri_parameter(zURIParameterKey, "group"); + if (!group) { + NS_WARNING("SQLite URI had 'persistenceType' but not 'group'?!"); + return nullptr; + } + + const char *origin = sqlite3_uri_parameter(zURIParameterKey, "origin"); + if (!origin) { + NS_WARNING("SQLite URI had 'persistenceType' and 'group' but not " + "'origin'?!"); + return nullptr; + } + + QuotaManager *quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + return quotaManager->GetQuotaObject( + PersistenceTypeFromText(nsDependentCString(persistenceType)), + nsDependentCString(group), + nsDependentCString(origin), + NS_ConvertUTF8toUTF16(zName)); +} + +void +MaybeEstablishQuotaControl(const char *zName, + telemetry_file *pFile, + int flags) +{ + MOZ_ASSERT(pFile); + MOZ_ASSERT(!pFile->quotaObject); + + if (!(flags & (SQLITE_OPEN_URI | SQLITE_OPEN_WAL))) { + return; + } + + MOZ_ASSERT(zName); + + const char *zURIParameterKey = (flags & SQLITE_OPEN_WAL) ? + DatabasePathFromWALPath(zName) : + zName; + + MOZ_ASSERT(zURIParameterKey); + + pFile->quotaObject = + GetQuotaObjectFromNameAndParameters(zName, zURIParameterKey); +} + +/* +** Close a telemetry_file. +*/ +int +xClose(sqlite3_file *pFile) +{ + telemetry_file *p = (telemetry_file *)pFile; + int rc; + { // Scope for IOThreadAutoTimer + IOThreadAutoTimer ioTimer(IOInterposeObserver::OpClose); + rc = p->pReal->pMethods->xClose(p->pReal); + } + if( rc==SQLITE_OK ){ + delete p->base.pMethods; + p->base.pMethods = nullptr; + p->quotaObject = nullptr; +#ifdef DEBUG + p->fileChunkSize = 0; +#endif + } + return rc; +} + +/* +** Read data from a telemetry_file. +*/ +int +xRead(sqlite3_file *pFile, void *zBuf, int iAmt, sqlite_int64 iOfst) +{ + telemetry_file *p = (telemetry_file *)pFile; + int rc; + rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst); + // sqlite likes to read from empty files, this is normal, ignore it. + return rc; +} + +/* +** Return the current file-size of a telemetry_file. +*/ +int +xFileSize(sqlite3_file *pFile, sqlite_int64 *pSize) +{ + IOThreadAutoTimer ioTimer(IOInterposeObserver::OpStat); + telemetry_file *p = (telemetry_file *)pFile; + int rc; + rc = p->pReal->pMethods->xFileSize(p->pReal, pSize); + return rc; +} + +/* +** Write data to a telemetry_file. +*/ +int +xWrite(sqlite3_file *pFile, const void *zBuf, int iAmt, sqlite_int64 iOfst) +{ + telemetry_file *p = (telemetry_file *)pFile; + int rc; + if (p->quotaObject) { + MOZ_ASSERT(INT64_MAX - iOfst >= iAmt); + if (!p->quotaObject->MaybeUpdateSize(iOfst + iAmt, /* aTruncate */ false)) { + return SQLITE_FULL; + } + } + rc = p->pReal->pMethods->xWrite(p->pReal, zBuf, iAmt, iOfst); + if (p->quotaObject && rc != SQLITE_OK) { + NS_WARNING("xWrite failed on a quota-controlled file, attempting to " + "update its current size..."); + sqlite_int64 currentSize; + if (xFileSize(pFile, ¤tSize) == SQLITE_OK) { + p->quotaObject->MaybeUpdateSize(currentSize, /* aTruncate */ true); + } + } + return rc; +} + +/* +** Truncate a telemetry_file. +*/ +int +xTruncate(sqlite3_file *pFile, sqlite_int64 size) +{ + telemetry_file *p = (telemetry_file *)pFile; + int rc; + if (p->quotaObject) { + if (p->fileChunkSize > 0) { + // Round up to the smallest multiple of the chunk size that will hold all + // the data. + size = + ((size + p->fileChunkSize - 1) / p->fileChunkSize) * p->fileChunkSize; + } + if (!p->quotaObject->MaybeUpdateSize(size, /* aTruncate */ true)) { + return SQLITE_FULL; + } + } + rc = p->pReal->pMethods->xTruncate(p->pReal, size); + if (p->quotaObject) { + if (rc != SQLITE_OK) { + NS_WARNING("xTruncate failed on a quota-controlled file, attempting to " + "update its current size..."); + if (xFileSize(pFile, &size) == SQLITE_OK) { + p->quotaObject->MaybeUpdateSize(size, /* aTruncate */ true); + } + } + } + return rc; +} + +/* +** Sync a telemetry_file. +*/ +int +xSync(sqlite3_file *pFile, int flags) +{ + telemetry_file *p = (telemetry_file *)pFile; + return p->pReal->pMethods->xSync(p->pReal, flags); +} + +/* +** Lock a telemetry_file. +*/ +int +xLock(sqlite3_file *pFile, int eLock) +{ + telemetry_file *p = (telemetry_file *)pFile; + int rc; + rc = p->pReal->pMethods->xLock(p->pReal, eLock); + return rc; +} + +/* +** Unlock a telemetry_file. +*/ +int +xUnlock(sqlite3_file *pFile, int eLock) +{ + telemetry_file *p = (telemetry_file *)pFile; + int rc; + rc = p->pReal->pMethods->xUnlock(p->pReal, eLock); + return rc; +} + +/* +** Check if another file-handle holds a RESERVED lock on a telemetry_file. +*/ +int +xCheckReservedLock(sqlite3_file *pFile, int *pResOut) +{ + telemetry_file *p = (telemetry_file *)pFile; + int rc = p->pReal->pMethods->xCheckReservedLock(p->pReal, pResOut); + return rc; +} + +/* +** File control method. For custom operations on a telemetry_file. +*/ +int +xFileControl(sqlite3_file *pFile, int op, void *pArg) +{ + telemetry_file *p = (telemetry_file *)pFile; + int rc; + // Hook SQLITE_FCNTL_SIZE_HINT for quota-controlled files and do the necessary + // work before passing to the SQLite VFS. + if (op == SQLITE_FCNTL_SIZE_HINT && p->quotaObject) { + sqlite3_int64 hintSize = *static_cast<sqlite3_int64*>(pArg); + sqlite3_int64 currentSize; + rc = xFileSize(pFile, ¤tSize); + if (rc != SQLITE_OK) { + return rc; + } + if (hintSize > currentSize) { + rc = xTruncate(pFile, hintSize); + if (rc != SQLITE_OK) { + return rc; + } + } + } + rc = p->pReal->pMethods->xFileControl(p->pReal, op, pArg); + // Grab the file chunk size after the SQLite VFS has approved. + if (op == SQLITE_FCNTL_CHUNK_SIZE && rc == SQLITE_OK) { + p->fileChunkSize = *static_cast<int*>(pArg); + } + return rc; +} + +/* +** Return the sector-size in bytes for a telemetry_file. +*/ +int +xSectorSize(sqlite3_file *pFile) +{ + telemetry_file *p = (telemetry_file *)pFile; + int rc; + rc = p->pReal->pMethods->xSectorSize(p->pReal); + return rc; +} + +/* +** Return the device characteristic flags supported by a telemetry_file. +*/ +int +xDeviceCharacteristics(sqlite3_file *pFile) +{ + telemetry_file *p = (telemetry_file *)pFile; + int rc; + rc = p->pReal->pMethods->xDeviceCharacteristics(p->pReal); + return rc; +} + +/* +** Shared-memory operations. +*/ +int +xShmLock(sqlite3_file *pFile, int ofst, int n, int flags) +{ + telemetry_file *p = (telemetry_file *)pFile; + return p->pReal->pMethods->xShmLock(p->pReal, ofst, n, flags); +} + +int +xShmMap(sqlite3_file *pFile, int iRegion, int szRegion, int isWrite, void volatile **pp) +{ + telemetry_file *p = (telemetry_file *)pFile; + int rc; + rc = p->pReal->pMethods->xShmMap(p->pReal, iRegion, szRegion, isWrite, pp); + return rc; +} + +void +xShmBarrier(sqlite3_file *pFile){ + telemetry_file *p = (telemetry_file *)pFile; + p->pReal->pMethods->xShmBarrier(p->pReal); +} + +int +xShmUnmap(sqlite3_file *pFile, int delFlag){ + telemetry_file *p = (telemetry_file *)pFile; + int rc; + rc = p->pReal->pMethods->xShmUnmap(p->pReal, delFlag); + return rc; +} + +int +xFetch(sqlite3_file *pFile, sqlite3_int64 iOff, int iAmt, void **pp) +{ + telemetry_file *p = (telemetry_file *)pFile; + MOZ_ASSERT(p->pReal->pMethods->iVersion >= 3); + return p->pReal->pMethods->xFetch(p->pReal, iOff, iAmt, pp); +} + +int +xUnfetch(sqlite3_file *pFile, sqlite3_int64 iOff, void *pResOut) +{ + telemetry_file *p = (telemetry_file *)pFile; + MOZ_ASSERT(p->pReal->pMethods->iVersion >= 3); + return p->pReal->pMethods->xUnfetch(p->pReal, iOff, pResOut); +} + +int +xOpen(sqlite3_vfs* vfs, const char *zName, sqlite3_file* pFile, + int flags, int *pOutFlags) +{ + sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); + int rc; + telemetry_file *p = (telemetry_file *)pFile; + Histograms *h = nullptr; + // check if the filename is one we are probing for + for(size_t i = 0;i < sizeof(gHistograms)/sizeof(gHistograms[0]);i++) { + h = &gHistograms[i]; + // last probe is the fallback probe + if (!h->name) + break; + if (!zName) + continue; + const char *match = strstr(zName, h->name); + if (!match) + continue; + char c = match[strlen(h->name)]; + // include -wal/-journal too + if (!c || c == '-') + break; + } + p->histograms = h; + + MaybeEstablishQuotaControl(zName, p, flags); + + rc = orig_vfs->xOpen(orig_vfs, zName, p->pReal, flags, pOutFlags); + if( rc != SQLITE_OK ) + return rc; + if( p->pReal->pMethods ){ + sqlite3_io_methods *pNew = new sqlite3_io_methods; + const sqlite3_io_methods *pSub = p->pReal->pMethods; + memset(pNew, 0, sizeof(*pNew)); + // If the io_methods version is higher than the last known one, you should + // update this VFS adding appropriate IO methods for any methods added in + // the version change. + pNew->iVersion = pSub->iVersion; + MOZ_ASSERT(pNew->iVersion <= LAST_KNOWN_IOMETHODS_VERSION); + pNew->xClose = xClose; + pNew->xRead = xRead; + pNew->xWrite = xWrite; + pNew->xTruncate = xTruncate; + pNew->xSync = xSync; + pNew->xFileSize = xFileSize; + pNew->xLock = xLock; + pNew->xUnlock = xUnlock; + pNew->xCheckReservedLock = xCheckReservedLock; + pNew->xFileControl = xFileControl; + pNew->xSectorSize = xSectorSize; + pNew->xDeviceCharacteristics = xDeviceCharacteristics; + if (pNew->iVersion >= 2) { + // Methods added in version 2. + pNew->xShmMap = pSub->xShmMap ? xShmMap : 0; + pNew->xShmLock = pSub->xShmLock ? xShmLock : 0; + pNew->xShmBarrier = pSub->xShmBarrier ? xShmBarrier : 0; + pNew->xShmUnmap = pSub->xShmUnmap ? xShmUnmap : 0; + } + if (pNew->iVersion >= 3) { + // Methods added in version 3. + // SQLite 3.7.17 calls these methods without checking for nullptr first, + // so we always define them. Verify that we're not going to call + // nullptrs, though. + MOZ_ASSERT(pSub->xFetch); + pNew->xFetch = xFetch; + MOZ_ASSERT(pSub->xUnfetch); + pNew->xUnfetch = xUnfetch; + } + pFile->pMethods = pNew; + } + return rc; +} + +int +xDelete(sqlite3_vfs* vfs, const char *zName, int syncDir) +{ + sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); + int rc; + RefPtr<QuotaObject> quotaObject; + + if (StringEndsWith(nsDependentCString(zName), NS_LITERAL_CSTRING("-wal"))) { + const char *zURIParameterKey = DatabasePathFromWALPath(zName); + MOZ_ASSERT(zURIParameterKey); + + quotaObject = GetQuotaObjectFromNameAndParameters(zName, zURIParameterKey); + } + + rc = orig_vfs->xDelete(orig_vfs, zName, syncDir); + if (rc == SQLITE_OK && quotaObject) { + MOZ_ALWAYS_TRUE(quotaObject->MaybeUpdateSize(0, /* aTruncate */ true)); + } + + return rc; +} + +int +xAccess(sqlite3_vfs *vfs, const char *zName, int flags, int *pResOut) +{ + sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); + return orig_vfs->xAccess(orig_vfs, zName, flags, pResOut); +} + +int +xFullPathname(sqlite3_vfs *vfs, const char *zName, int nOut, char *zOut) +{ + sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); + return orig_vfs->xFullPathname(orig_vfs, zName, nOut, zOut); +} + +void* +xDlOpen(sqlite3_vfs *vfs, const char *zFilename) +{ + sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); + return orig_vfs->xDlOpen(orig_vfs, zFilename); +} + +void +xDlError(sqlite3_vfs *vfs, int nByte, char *zErrMsg) +{ + sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); + orig_vfs->xDlError(orig_vfs, nByte, zErrMsg); +} + +void +(*xDlSym(sqlite3_vfs *vfs, void *pHdle, const char *zSym))(void){ + sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); + return orig_vfs->xDlSym(orig_vfs, pHdle, zSym); +} + +void +xDlClose(sqlite3_vfs *vfs, void *pHandle) +{ + sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); + orig_vfs->xDlClose(orig_vfs, pHandle); +} + +int +xRandomness(sqlite3_vfs *vfs, int nByte, char *zOut) +{ + sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); + return orig_vfs->xRandomness(orig_vfs, nByte, zOut); +} + +int +xSleep(sqlite3_vfs *vfs, int microseconds) +{ + sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); + return orig_vfs->xSleep(orig_vfs, microseconds); +} + +int +xCurrentTime(sqlite3_vfs *vfs, double *prNow) +{ + sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); + return orig_vfs->xCurrentTime(orig_vfs, prNow); +} + +int +xGetLastError(sqlite3_vfs *vfs, int nBuf, char *zBuf) +{ + sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); + return orig_vfs->xGetLastError(orig_vfs, nBuf, zBuf); +} + +int +xCurrentTimeInt64(sqlite3_vfs *vfs, sqlite3_int64 *piNow) +{ + sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); + return orig_vfs->xCurrentTimeInt64(orig_vfs, piNow); +} + +static +int +xSetSystemCall(sqlite3_vfs *vfs, const char *zName, sqlite3_syscall_ptr pFunc) +{ + sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); + return orig_vfs->xSetSystemCall(orig_vfs, zName, pFunc); +} + +static +sqlite3_syscall_ptr +xGetSystemCall(sqlite3_vfs *vfs, const char *zName) +{ + sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); + return orig_vfs->xGetSystemCall(orig_vfs, zName); +} + +static +const char * +xNextSystemCall(sqlite3_vfs *vfs, const char *zName) +{ + sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); + return orig_vfs->xNextSystemCall(orig_vfs, zName); +} + +} // namespace + +namespace mozilla { +namespace storage { + +sqlite3_vfs* ConstructTelemetryVFS() +{ +#if defined(XP_WIN) +#define EXPECTED_VFS "win32" +#define EXPECTED_VFS_NFS "win32" +#else +#define EXPECTED_VFS "unix" +#define EXPECTED_VFS_NFS "unix-excl" +#endif + + bool expected_vfs; + sqlite3_vfs *vfs; + if (Preferences::GetBool(PREF_NFS_FILESYSTEM)) { + vfs = sqlite3_vfs_find(EXPECTED_VFS_NFS); + expected_vfs = (vfs != nullptr); + } + else { + vfs = sqlite3_vfs_find(nullptr); + expected_vfs = vfs->zName && !strcmp(vfs->zName, EXPECTED_VFS); + } + if (!expected_vfs) { + return nullptr; + } + + sqlite3_vfs *tvfs = new ::sqlite3_vfs; + memset(tvfs, 0, sizeof(::sqlite3_vfs)); + // If the VFS version is higher than the last known one, you should update + // this VFS adding appropriate methods for any methods added in the version + // change. + tvfs->iVersion = vfs->iVersion; + MOZ_ASSERT(vfs->iVersion <= LAST_KNOWN_VFS_VERSION); + tvfs->szOsFile = sizeof(telemetry_file) - sizeof(sqlite3_file) + vfs->szOsFile; + tvfs->mxPathname = vfs->mxPathname; + tvfs->zName = "telemetry-vfs"; + tvfs->pAppData = vfs; + tvfs->xOpen = xOpen; + tvfs->xDelete = xDelete; + tvfs->xAccess = xAccess; + tvfs->xFullPathname = xFullPathname; + tvfs->xDlOpen = xDlOpen; + tvfs->xDlError = xDlError; + tvfs->xDlSym = xDlSym; + tvfs->xDlClose = xDlClose; + tvfs->xRandomness = xRandomness; + tvfs->xSleep = xSleep; + tvfs->xCurrentTime = xCurrentTime; + tvfs->xGetLastError = xGetLastError; + if (tvfs->iVersion >= 2) { + // Methods added in version 2. + tvfs->xCurrentTimeInt64 = xCurrentTimeInt64; + } + if (tvfs->iVersion >= 3) { + // Methods added in version 3. + tvfs->xSetSystemCall = xSetSystemCall; + tvfs->xGetSystemCall = xGetSystemCall; + tvfs->xNextSystemCall = xNextSystemCall; + } + return tvfs; +} + +already_AddRefed<QuotaObject> +GetQuotaObjectForFile(sqlite3_file *pFile) +{ + MOZ_ASSERT(pFile); + + telemetry_file *p = (telemetry_file *)pFile; + RefPtr<QuotaObject> result = p->quotaObject; + return result.forget(); +} + +} // namespace storage +} // namespace mozilla diff --git a/components/storage/src/VacuumManager.cpp b/components/storage/src/VacuumManager.cpp new file mode 100644 index 000000000..f35ded2d6 --- /dev/null +++ b/components/storage/src/VacuumManager.cpp @@ -0,0 +1,388 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/DebugOnly.h" + +#include "VacuumManager.h" + +#include "mozilla/Services.h" +#include "mozilla/Preferences.h" +#include "nsIObserverService.h" +#include "nsIFile.h" +#include "nsThreadUtils.h" +#include "mozilla/Logging.h" +#include "prtime.h" + +#include "mozStorageConnection.h" +#include "mozIStorageStatement.h" +#include "mozIStorageAsyncStatement.h" +#include "mozIStoragePendingStatement.h" +#include "mozIStorageError.h" +#include "mozStorageHelper.h" +#include "nsXULAppAPI.h" + +#define OBSERVER_TOPIC_IDLE_DAILY "idle-daily" +#define OBSERVER_TOPIC_XPCOM_SHUTDOWN "xpcom-shutdown" + +// Used to notify begin and end of a heavy IO task. +#define OBSERVER_TOPIC_HEAVY_IO "heavy-io-task" +#define OBSERVER_DATA_VACUUM_BEGIN NS_LITERAL_STRING("vacuum-begin") +#define OBSERVER_DATA_VACUUM_END NS_LITERAL_STRING("vacuum-end") + +// This preferences root will contain last vacuum timestamps (in seconds) for +// each database. The database filename is used as a key. +#define PREF_VACUUM_BRANCH "storage.vacuum.last." + +// Time between subsequent vacuum calls for a certain database. +#define VACUUM_INTERVAL_SECONDS 30 * 86400 // 30 days. + +extern mozilla::LazyLogModule gStorageLog; + +namespace mozilla { +namespace storage { + +namespace { + +//////////////////////////////////////////////////////////////////////////////// +//// BaseCallback + +class BaseCallback : public mozIStorageStatementCallback +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_MOZISTORAGESTATEMENTCALLBACK + BaseCallback() {} +protected: + virtual ~BaseCallback() {} +}; + +NS_IMETHODIMP +BaseCallback::HandleError(mozIStorageError *aError) +{ +#ifdef DEBUG + int32_t result; + nsresult rv = aError->GetResult(&result); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString message; + rv = aError->GetMessage(message); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString warnMsg; + warnMsg.AppendLiteral("An error occured during async execution: "); + warnMsg.AppendInt(result); + warnMsg.Append(' '); + warnMsg.Append(message); + NS_WARNING(warnMsg.get()); +#endif + return NS_OK; +} + +NS_IMETHODIMP +BaseCallback::HandleResult(mozIStorageResultSet *aResultSet) +{ + // We could get results from PRAGMA statements, but we don't mind them. + return NS_OK; +} + +NS_IMETHODIMP +BaseCallback::HandleCompletion(uint16_t aReason) +{ + // By default BaseCallback will just be silent on completion. + return NS_OK; +} + +NS_IMPL_ISUPPORTS( + BaseCallback +, mozIStorageStatementCallback +) + +//////////////////////////////////////////////////////////////////////////////// +//// Vacuumer declaration. + +class Vacuumer : public BaseCallback +{ +public: + NS_DECL_MOZISTORAGESTATEMENTCALLBACK + + explicit Vacuumer(mozIStorageVacuumParticipant *aParticipant); + + bool execute(); + nsresult notifyCompletion(bool aSucceeded); + +private: + nsCOMPtr<mozIStorageVacuumParticipant> mParticipant; + nsCString mDBFilename; + nsCOMPtr<mozIStorageConnection> mDBConn; +}; + +//////////////////////////////////////////////////////////////////////////////// +//// Vacuumer implementation. + +Vacuumer::Vacuumer(mozIStorageVacuumParticipant *aParticipant) + : mParticipant(aParticipant) +{ +} + +bool +Vacuumer::execute() +{ + MOZ_ASSERT(NS_IsMainThread(), "Must be running on the main thread!"); + + // Get the connection and check its validity. + nsresult rv = mParticipant->GetDatabaseConnection(getter_AddRefs(mDBConn)); + NS_ENSURE_SUCCESS(rv, false); + bool ready = false; + if (!mDBConn || NS_FAILED(mDBConn->GetConnectionReady(&ready)) || !ready) { + NS_WARNING("Unable to get a connection to vacuum database"); + return false; + } + + // Ask for the expected page size. Vacuum can change the page size, unless + // the database is using WAL journaling. + // TODO Bug 634374: figure out a strategy to fix page size with WAL. + int32_t expectedPageSize = 0; + rv = mParticipant->GetExpectedDatabasePageSize(&expectedPageSize); + if (NS_FAILED(rv) || !Service::pageSizeIsValid(expectedPageSize)) { + NS_WARNING("Invalid page size requested for database, will use default "); + NS_WARNING(mDBFilename.get()); + expectedPageSize = Service::getDefaultPageSize(); + } + + // Get the database filename. Last vacuum time is stored under this name + // in PREF_VACUUM_BRANCH. + nsCOMPtr<nsIFile> databaseFile; + mDBConn->GetDatabaseFile(getter_AddRefs(databaseFile)); + if (!databaseFile) { + NS_WARNING("Trying to vacuum a in-memory database!"); + return false; + } + nsAutoString databaseFilename; + rv = databaseFile->GetLeafName(databaseFilename); + NS_ENSURE_SUCCESS(rv, false); + mDBFilename = NS_ConvertUTF16toUTF8(databaseFilename); + MOZ_ASSERT(!mDBFilename.IsEmpty(), "Database filename cannot be empty"); + + // Check interval from last vacuum. + int32_t now = static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC); + int32_t lastVacuum; + nsAutoCString prefName(PREF_VACUUM_BRANCH); + prefName += mDBFilename; + rv = Preferences::GetInt(prefName.get(), &lastVacuum); + if (NS_SUCCEEDED(rv) && (now - lastVacuum) < VACUUM_INTERVAL_SECONDS) { + // This database was vacuumed recently, skip it. + return false; + } + + // Notify that we are about to start vacuuming. The participant can opt-out + // if it cannot handle a vacuum at this time, and then we'll move to the next + // one. + bool vacuumGranted = false; + rv = mParticipant->OnBeginVacuum(&vacuumGranted); + NS_ENSURE_SUCCESS(rv, false); + if (!vacuumGranted) { + return false; + } + + // Notify a heavy IO task is about to start. + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + rv = + os->NotifyObservers(nullptr, OBSERVER_TOPIC_HEAVY_IO, + OBSERVER_DATA_VACUUM_BEGIN.get()); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to notify"); + } + + // Execute the statements separately, since the pragma may conflict with the + // vacuum, if they are executed in the same transaction. + nsCOMPtr<mozIStorageAsyncStatement> pageSizeStmt; + nsAutoCString pageSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR + "PRAGMA page_size = "); + pageSizeQuery.AppendInt(expectedPageSize); + rv = mDBConn->CreateAsyncStatement(pageSizeQuery, + getter_AddRefs(pageSizeStmt)); + NS_ENSURE_SUCCESS(rv, false); + RefPtr<BaseCallback> callback = new BaseCallback(); + nsCOMPtr<mozIStoragePendingStatement> ps; + rv = pageSizeStmt->ExecuteAsync(callback, getter_AddRefs(ps)); + NS_ENSURE_SUCCESS(rv, false); + + nsCOMPtr<mozIStorageAsyncStatement> stmt; + rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING( + "VACUUM" + ), getter_AddRefs(stmt)); + NS_ENSURE_SUCCESS(rv, false); + rv = stmt->ExecuteAsync(this, getter_AddRefs(ps)); + NS_ENSURE_SUCCESS(rv, false); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +//// mozIStorageStatementCallback + +NS_IMETHODIMP +Vacuumer::HandleError(mozIStorageError *aError) +{ + int32_t result; + nsresult rv; + nsAutoCString message; + +#ifdef DEBUG + rv = aError->GetResult(&result); + NS_ENSURE_SUCCESS(rv, rv); + rv = aError->GetMessage(message); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString warnMsg; + warnMsg.AppendLiteral("Unable to vacuum database: "); + warnMsg.Append(mDBFilename); + warnMsg.AppendLiteral(" - "); + warnMsg.AppendInt(result); + warnMsg.Append(' '); + warnMsg.Append(message); + NS_WARNING(warnMsg.get()); +#endif + + if (MOZ_LOG_TEST(gStorageLog, LogLevel::Error)) { + rv = aError->GetResult(&result); + NS_ENSURE_SUCCESS(rv, rv); + rv = aError->GetMessage(message); + NS_ENSURE_SUCCESS(rv, rv); + MOZ_LOG(gStorageLog, LogLevel::Error, + ("Vacuum failed with error: %d '%s'. Database was: '%s'", + result, message.get(), mDBFilename.get())); + } + return NS_OK; +} + +NS_IMETHODIMP +Vacuumer::HandleResult(mozIStorageResultSet *aResultSet) +{ + NS_NOTREACHED("Got a resultset from a vacuum?"); + return NS_OK; +} + +NS_IMETHODIMP +Vacuumer::HandleCompletion(uint16_t aReason) +{ + if (aReason == REASON_FINISHED) { + // Update last vacuum time. + int32_t now = static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC); + MOZ_ASSERT(!mDBFilename.IsEmpty(), "Database filename cannot be empty"); + nsAutoCString prefName(PREF_VACUUM_BRANCH); + prefName += mDBFilename; + DebugOnly<nsresult> rv = Preferences::SetInt(prefName.get(), now); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to set a preference"); + } + + notifyCompletion(aReason == REASON_FINISHED); + + return NS_OK; +} + +nsresult +Vacuumer::notifyCompletion(bool aSucceeded) +{ + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + os->NotifyObservers(nullptr, OBSERVER_TOPIC_HEAVY_IO, + OBSERVER_DATA_VACUUM_END.get()); + } + + nsresult rv = mParticipant->OnEndVacuum(aSucceeded); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +//// VacuumManager + +NS_IMPL_ISUPPORTS( + VacuumManager +, nsIObserver +) + +VacuumManager * +VacuumManager::gVacuumManager = nullptr; + +VacuumManager * +VacuumManager::getSingleton() +{ + //Don't allocate it in the child Process. + if (!XRE_IsParentProcess()) { + return nullptr; + } + + if (gVacuumManager) { + NS_ADDREF(gVacuumManager); + return gVacuumManager; + } + gVacuumManager = new VacuumManager(); + if (gVacuumManager) { + NS_ADDREF(gVacuumManager); + } + return gVacuumManager; +} + +VacuumManager::VacuumManager() + : mParticipants("vacuum-participant") +{ + MOZ_ASSERT(!gVacuumManager, + "Attempting to create two instances of the service!"); + gVacuumManager = this; +} + +VacuumManager::~VacuumManager() +{ + // Remove the static reference to the service. Check to make sure its us + // in case somebody creates an extra instance of the service. + MOZ_ASSERT(gVacuumManager == this, + "Deleting a non-singleton instance of the service"); + if (gVacuumManager == this) { + gVacuumManager = nullptr; + } +} + +//////////////////////////////////////////////////////////////////////////////// +//// nsIObserver + +NS_IMETHODIMP +VacuumManager::Observe(nsISupports *aSubject, + const char *aTopic, + const char16_t *aData) +{ + if (strcmp(aTopic, OBSERVER_TOPIC_IDLE_DAILY) == 0) { + // Try to run vacuum on all registered entries. Will stop at the first + // successful one. + nsCOMArray<mozIStorageVacuumParticipant> entries; + mParticipants.GetEntries(entries); + // If there are more entries than what a month can contain, we could end up + // skipping some, since we run daily. So we use a starting index. + static const char* kPrefName = PREF_VACUUM_BRANCH "index"; + int32_t startIndex = Preferences::GetInt(kPrefName, 0); + if (startIndex >= entries.Count()) { + startIndex = 0; + } + int32_t index; + for (index = startIndex; index < entries.Count(); ++index) { + RefPtr<Vacuumer> vacuum = new Vacuumer(entries[index]); + // Only vacuum one database per day. + if (vacuum->execute()) { + break; + } + } + DebugOnly<nsresult> rv = Preferences::SetInt(kPrefName, index); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to set a preference"); + } + + return NS_OK; +} + +} // namespace storage +} // namespace mozilla diff --git a/components/storage/src/VacuumManager.h b/components/storage/src/VacuumManager.h new file mode 100644 index 000000000..12603deb6 --- /dev/null +++ b/components/storage/src/VacuumManager.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_storage_VacuumManager_h__ +#define mozilla_storage_VacuumManager_h__ + +#include "nsCOMPtr.h" +#include "nsIObserver.h" +#include "mozIStorageStatementCallback.h" +#include "mozIStorageVacuumParticipant.h" +#include "nsCategoryCache.h" +#include "mozilla/Attributes.h" + +namespace mozilla { +namespace storage { + +class VacuumManager final : public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + VacuumManager(); + + /** + * Obtains the VacuumManager object. + */ + static VacuumManager * getSingleton(); + +private: + ~VacuumManager(); + + static VacuumManager *gVacuumManager; + + // Cache of components registered in "vacuum-participant" category. + nsCategoryCache<mozIStorageVacuumParticipant> mParticipants; +}; + +} // namespace storage +} // namespace mozilla + +#endif diff --git a/components/storage/src/Variant.h b/components/storage/src/Variant.h new file mode 100644 index 000000000..265abb02a --- /dev/null +++ b/components/storage/src/Variant.h @@ -0,0 +1,446 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_storage_Variant_h__ +#define mozilla_storage_Variant_h__ + +#include <utility> + +#include "nsIVariant.h" +#include "nsString.h" +#include "nsTArray.h" + +#define VARIANT_BASE_IID \ +{ /* 78888042-0fa3-4f7a-8b19-7996f99bf1aa */ \ + 0x78888042, 0x0fa3, 0x4f7a, \ + { 0x8b, 0x19, 0x79, 0x96, 0xf9, 0x9b, 0xf1, 0xaa } \ +} + +/** + * This class is used by the storage module whenever an nsIVariant needs to be + * returned. We provide traits for the basic sqlite types to make use easier. + * The following types map to the indicated sqlite type: + * int64_t -> INTEGER (use IntegerVariant) + * double -> FLOAT (use FloatVariant) + * nsString -> TEXT (use TextVariant) + * nsCString -> TEXT (use UTF8TextVariant) + * uint8_t[] -> BLOB (use BlobVariant) + * nullptr -> NULL (use NullVariant) + */ + +namespace mozilla { +namespace storage { + +//////////////////////////////////////////////////////////////////////////////// +//// Base Class + +class Variant_base : public nsIVariant +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIVARIANT + NS_DECLARE_STATIC_IID_ACCESSOR(VARIANT_BASE_IID) + +protected: + virtual ~Variant_base() { } +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(Variant_base, + VARIANT_BASE_IID) + +//////////////////////////////////////////////////////////////////////////////// +//// Traits + +/** + * Generics + */ + +template <typename DataType> +struct variant_traits +{ + static inline uint16_t type() { return nsIDataType::VTYPE_EMPTY; } +}; + +template <typename DataType, bool Adopting=false> +struct variant_storage_traits +{ + typedef DataType ConstructorType; + typedef DataType StorageType; + static inline void storage_conversion(const ConstructorType aData, StorageType* _storage) + { + *_storage = aData; + } + + static inline void destroy(const StorageType& _storage) + { } +}; + +#define NO_CONVERSION return NS_ERROR_CANNOT_CONVERT_DATA; + +template <typename DataType, bool Adopting=false> +struct variant_integer_traits +{ + typedef typename variant_storage_traits<DataType, Adopting>::StorageType StorageType; + static inline nsresult asInt32(const StorageType &, int32_t *) { NO_CONVERSION } + static inline nsresult asInt64(const StorageType &, int64_t *) { NO_CONVERSION } +}; + +template <typename DataType, bool Adopting=false> +struct variant_float_traits +{ + typedef typename variant_storage_traits<DataType, Adopting>::StorageType StorageType; + static inline nsresult asDouble(const StorageType &, double *) { NO_CONVERSION } +}; + +template <typename DataType, bool Adopting=false> +struct variant_text_traits +{ + typedef typename variant_storage_traits<DataType, Adopting>::StorageType StorageType; + static inline nsresult asUTF8String(const StorageType &, nsACString &) { NO_CONVERSION } + static inline nsresult asString(const StorageType &, nsAString &) { NO_CONVERSION } +}; + +template <typename DataType, bool Adopting=false> +struct variant_blob_traits +{ + typedef typename variant_storage_traits<DataType, Adopting>::StorageType StorageType; + static inline nsresult asArray(const StorageType &, uint16_t *, uint32_t *, void **) + { NO_CONVERSION } +}; + +#undef NO_CONVERSION + +/** + * INTEGER types + */ + +template < > +struct variant_traits<int64_t> +{ + static inline uint16_t type() { return nsIDataType::VTYPE_INT64; } +}; +template < > +struct variant_integer_traits<int64_t> +{ + static inline nsresult asInt32(int64_t aValue, + int32_t *_result) + { + if (aValue > INT32_MAX || aValue < INT32_MIN) + return NS_ERROR_CANNOT_CONVERT_DATA; + + *_result = static_cast<int32_t>(aValue); + return NS_OK; + } + static inline nsresult asInt64(int64_t aValue, + int64_t *_result) + { + *_result = aValue; + return NS_OK; + } +}; +// xpcvariant just calls get double for integers... +template < > +struct variant_float_traits<int64_t> +{ + static inline nsresult asDouble(int64_t aValue, + double *_result) + { + *_result = double(aValue); + return NS_OK; + } +}; + +/** + * FLOAT types + */ + +template < > +struct variant_traits<double> +{ + static inline uint16_t type() { return nsIDataType::VTYPE_DOUBLE; } +}; +template < > +struct variant_float_traits<double> +{ + static inline nsresult asDouble(double aValue, + double *_result) + { + *_result = aValue; + return NS_OK; + } +}; + +/** + * TEXT types + */ + +template < > +struct variant_traits<nsString> +{ + static inline uint16_t type() { return nsIDataType::VTYPE_ASTRING; } +}; +template < > +struct variant_storage_traits<nsString> +{ + typedef const nsAString & ConstructorType; + typedef nsString StorageType; + static inline void storage_conversion(ConstructorType aText, StorageType* _outData) + { + *_outData = aText; + } + static inline void destroy(const StorageType& _outData) + { } +}; +template < > +struct variant_text_traits<nsString> +{ + static inline nsresult asUTF8String(const nsString &aValue, + nsACString &_result) + { + CopyUTF16toUTF8(aValue, _result); + return NS_OK; + } + static inline nsresult asString(const nsString &aValue, + nsAString &_result) + { + _result = aValue; + return NS_OK; + } +}; + +template < > +struct variant_traits<nsCString> +{ + static inline uint16_t type() { return nsIDataType::VTYPE_UTF8STRING; } +}; +template < > +struct variant_storage_traits<nsCString> +{ + typedef const nsACString & ConstructorType; + typedef nsCString StorageType; + static inline void storage_conversion(ConstructorType aText, StorageType* _outData) + { + *_outData = aText; + } + static inline void destroy(const StorageType &aData) + { } +}; +template < > +struct variant_text_traits<nsCString> +{ + static inline nsresult asUTF8String(const nsCString &aValue, + nsACString &_result) + { + _result = aValue; + return NS_OK; + } + static inline nsresult asString(const nsCString &aValue, + nsAString &_result) + { + CopyUTF8toUTF16(aValue, _result); + return NS_OK; + } +}; + +/** + * BLOB types + */ + +template < > +struct variant_traits<uint8_t[]> +{ + static inline uint16_t type() { return nsIDataType::VTYPE_ARRAY; } +}; +template < > +struct variant_storage_traits<uint8_t[], false> +{ + typedef std::pair<const void *, int> ConstructorType; + typedef FallibleTArray<uint8_t> StorageType; + static inline void storage_conversion(ConstructorType aBlob, StorageType* _outData) + { + _outData->Clear(); + (void)_outData->AppendElements(static_cast<const uint8_t *>(aBlob.first), + aBlob.second, fallible); + } + static inline void destroy(const StorageType& _outData) + { } +}; +template < > +struct variant_storage_traits<uint8_t[], true> +{ + typedef std::pair<uint8_t *, int> ConstructorType; + typedef std::pair<uint8_t *, int> StorageType; + static inline void storage_conversion(ConstructorType aBlob, StorageType* _outData) + { + *_outData = aBlob; + } + static inline void destroy(StorageType &aData) + { + if (aData.first) { + free(aData.first); + aData.first = nullptr; + } + } +}; +template < > +struct variant_blob_traits<uint8_t[], false> +{ + static inline nsresult asArray(FallibleTArray<uint8_t> &aData, + uint16_t *_type, + uint32_t *_size, + void **_result) + { + // For empty blobs, we return nullptr. + if (aData.Length() == 0) { + *_result = nullptr; + *_type = nsIDataType::VTYPE_UINT8; + *_size = 0; + return NS_OK; + } + + // Otherwise, we copy the array. + *_result = nsMemory::Clone(aData.Elements(), aData.Length() * sizeof(uint8_t)); + NS_ENSURE_TRUE(*_result, NS_ERROR_OUT_OF_MEMORY); + + // Set type and size + *_type = nsIDataType::VTYPE_UINT8; + *_size = aData.Length(); + return NS_OK; + } +}; + +template < > +struct variant_blob_traits<uint8_t[], true> +{ + static inline nsresult asArray(std::pair<uint8_t *, int> &aData, + uint16_t *_type, + uint32_t *_size, + void **_result) + { + // For empty blobs, we return nullptr. + if (aData.second == 0) { + *_result = nullptr; + *_type = nsIDataType::VTYPE_UINT8; + *_size = 0; + return NS_OK; + } + + // Otherwise, transfer the data out. + *_result = aData.first; + aData.first = nullptr; + MOZ_ASSERT(*_result); // We asked for it twice, better not use adopting! + + // Set type and size + *_type = nsIDataType::VTYPE_UINT8; + *_size = aData.second; + return NS_OK; + } +}; + +/** + * nullptr type + */ + +class NullVariant : public Variant_base +{ +public: + NS_IMETHOD GetDataType(uint16_t *_type) + { + NS_ENSURE_ARG_POINTER(_type); + *_type = nsIDataType::VTYPE_EMPTY; + return NS_OK; + } + + NS_IMETHOD GetAsAUTF8String(nsACString &_str) + { + // Return a void string. + _str.SetIsVoid(true); + return NS_OK; + } + + NS_IMETHOD GetAsAString(nsAString &_str) + { + // Return a void string. + _str.SetIsVoid(true); + return NS_OK; + } +}; + +//////////////////////////////////////////////////////////////////////////////// +//// Template Implementation + +template <typename DataType, bool Adopting=false> +class Variant final : public Variant_base +{ + ~Variant() + { + variant_storage_traits<DataType, Adopting>::destroy(mData); + } + +public: + explicit Variant(const typename variant_storage_traits<DataType, Adopting>::ConstructorType aData) + { + variant_storage_traits<DataType, Adopting>::storage_conversion(aData, &mData); + } + + NS_IMETHOD GetDataType(uint16_t *_type) + { + *_type = variant_traits<DataType>::type(); + return NS_OK; + } + NS_IMETHOD GetAsInt32(int32_t *_integer) + { + return variant_integer_traits<DataType, Adopting>::asInt32(mData, _integer); + } + + NS_IMETHOD GetAsInt64(int64_t *_integer) + { + return variant_integer_traits<DataType, Adopting>::asInt64(mData, _integer); + } + + NS_IMETHOD GetAsDouble(double *_double) + { + return variant_float_traits<DataType, Adopting>::asDouble(mData, _double); + } + + NS_IMETHOD GetAsAUTF8String(nsACString &_str) + { + return variant_text_traits<DataType, Adopting>::asUTF8String(mData, _str); + } + + NS_IMETHOD GetAsAString(nsAString &_str) + { + return variant_text_traits<DataType, Adopting>::asString(mData, _str); + } + + NS_IMETHOD GetAsArray(uint16_t *_type, + nsIID *, + uint32_t *_size, + void **_data) + { + return variant_blob_traits<DataType, Adopting>::asArray(mData, _type, _size, _data); + } + +private: + typename variant_storage_traits<DataType, Adopting>::StorageType mData; +}; + +//////////////////////////////////////////////////////////////////////////////// +//// Handy typedefs! Use these for the right mapping. + +typedef Variant<int64_t> IntegerVariant; +typedef Variant<double> FloatVariant; +typedef Variant<nsString> TextVariant; +typedef Variant<nsCString> UTF8TextVariant; +typedef Variant<uint8_t[], false> BlobVariant; +typedef Variant<uint8_t[], true> AdoptedBlobVariant; + +} // namespace storage +} // namespace mozilla + +#include "Variant_inl.h" + +#endif // mozilla_storage_Variant_h__ diff --git a/components/storage/src/Variant_inl.h b/components/storage/src/Variant_inl.h new file mode 100644 index 000000000..2e0571ae2 --- /dev/null +++ b/components/storage/src/Variant_inl.h @@ -0,0 +1,229 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Note: This file is included by Variant.h. + */ + +#ifndef mozilla_storage_Variant_h__ +#error "Do not include this file directly!" +#endif + +namespace mozilla { +namespace storage { + +//////////////////////////////////////////////////////////////////////////////// +//// Variant_base + +inline NS_IMPL_ADDREF(Variant_base) +inline NS_IMPL_RELEASE(Variant_base) +inline NS_IMPL_QUERY_INTERFACE( + Variant_base, + nsIVariant +) + +//////////////////////////////////////////////////////////////////////////////// +//// nsIVariant + +inline +NS_IMETHODIMP +Variant_base::GetDataType(uint16_t *_type) +{ + NS_ENSURE_ARG_POINTER(_type); + *_type = nsIDataType::VTYPE_VOID; + return NS_OK; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsInt32(int32_t *) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsInt64(int64_t *) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsDouble(double *) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsAUTF8String(nsACString &) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsAString(nsAString &) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsArray(uint16_t *, + nsIID *, + uint32_t *, + void **) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsInt8(uint8_t *) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsInt16(int16_t *) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsUint8(uint8_t *) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsUint16(uint16_t *) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsUint32(uint32_t *) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsUint64(uint64_t *) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsFloat(float *) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsBool(bool *) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsChar(char *) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsWChar(char16_t *) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsID(nsID *) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsDOMString(nsAString &) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsString(char **) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsWString(char16_t **) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsISupports(nsISupports **) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsInterface(nsIID **, + void **) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsACString(nsACString &) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsStringWithSize(uint32_t *, + char **) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsWStringWithSize(uint32_t *, + char16_t **) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +inline +NS_IMETHODIMP +Variant_base::GetAsJSVal(JS::MutableHandle<JS::Value>) +{ + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +} // namespace storage +} // namespace mozilla diff --git a/components/storage/src/mozStorageArgValueArray.cpp b/components/storage/src/mozStorageArgValueArray.cpp new file mode 100644 index 000000000..40d67a4cd --- /dev/null +++ b/components/storage/src/mozStorageArgValueArray.cpp @@ -0,0 +1,213 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsError.h" +#include "nsMemory.h" +#include "nsString.h" + +#include "mozStoragePrivateHelpers.h" +#include "mozStorageArgValueArray.h" + +namespace mozilla { +namespace storage { + +//////////////////////////////////////////////////////////////////////////////// +//// ArgValueArray + +ArgValueArray::ArgValueArray(int32_t aArgc, + sqlite3_value **aArgv) +: mArgc(aArgc) +, mArgv(aArgv) +{ +} + +NS_IMPL_ISUPPORTS( + ArgValueArray, + mozIStorageValueArray +) + +//////////////////////////////////////////////////////////////////////////////// +//// mozIStorageValueArray + +NS_IMETHODIMP +ArgValueArray::GetNumEntries(uint32_t *_size) +{ + *_size = mArgc; + return NS_OK; +} + +NS_IMETHODIMP +ArgValueArray::GetTypeOfIndex(uint32_t aIndex, + int32_t *_type) +{ + ENSURE_INDEX_VALUE(aIndex, mArgc); + + int t = ::sqlite3_value_type(mArgv[aIndex]); + switch (t) { + case SQLITE_INTEGER: + *_type = VALUE_TYPE_INTEGER; + break; + case SQLITE_FLOAT: + *_type = VALUE_TYPE_FLOAT; + break; + case SQLITE_TEXT: + *_type = VALUE_TYPE_TEXT; + break; + case SQLITE_BLOB: + *_type = VALUE_TYPE_BLOB; + break; + case SQLITE_NULL: + *_type = VALUE_TYPE_NULL; + break; + default: + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +ArgValueArray::GetInt32(uint32_t aIndex, + int32_t *_value) +{ + ENSURE_INDEX_VALUE(aIndex, mArgc); + + *_value = ::sqlite3_value_int(mArgv[aIndex]); + return NS_OK; +} + +NS_IMETHODIMP +ArgValueArray::GetInt64(uint32_t aIndex, + int64_t *_value) +{ + ENSURE_INDEX_VALUE(aIndex, mArgc); + + *_value = ::sqlite3_value_int64(mArgv[aIndex]); + return NS_OK; +} + +NS_IMETHODIMP +ArgValueArray::GetDouble(uint32_t aIndex, + double *_value) +{ + ENSURE_INDEX_VALUE(aIndex, mArgc); + + *_value = ::sqlite3_value_double(mArgv[aIndex]); + return NS_OK; +} + +NS_IMETHODIMP +ArgValueArray::GetUTF8String(uint32_t aIndex, + nsACString &_value) +{ + ENSURE_INDEX_VALUE(aIndex, mArgc); + + if (::sqlite3_value_type(mArgv[aIndex]) == SQLITE_NULL) { + // NULL columns should have IsVoid set to distinguish them from an empty + // string. + _value.SetIsVoid(true); + } + else { + _value.Assign(reinterpret_cast<const char *>(::sqlite3_value_text(mArgv[aIndex])), + ::sqlite3_value_bytes(mArgv[aIndex])); + } + return NS_OK; +} + +NS_IMETHODIMP +ArgValueArray::GetString(uint32_t aIndex, + nsAString &_value) +{ + ENSURE_INDEX_VALUE(aIndex, mArgc); + + if (::sqlite3_value_type(mArgv[aIndex]) == SQLITE_NULL) { + // NULL columns should have IsVoid set to distinguish them from an empty + // string. + _value.SetIsVoid(true); + } else { + _value.Assign(static_cast<const char16_t *>(::sqlite3_value_text16(mArgv[aIndex])), + ::sqlite3_value_bytes16(mArgv[aIndex]) / 2); + } + return NS_OK; +} + +NS_IMETHODIMP +ArgValueArray::GetBlob(uint32_t aIndex, + uint32_t *_size, + uint8_t **_blob) +{ + ENSURE_INDEX_VALUE(aIndex, mArgc); + + int size = ::sqlite3_value_bytes(mArgv[aIndex]); + void *blob = nsMemory::Clone(::sqlite3_value_blob(mArgv[aIndex]), size); + NS_ENSURE_TRUE(blob, NS_ERROR_OUT_OF_MEMORY); + + *_blob = static_cast<uint8_t *>(blob); + *_size = size; + return NS_OK; +} + +NS_IMETHODIMP +ArgValueArray::GetBlobAsString(uint32_t aIndex, nsAString& aValue) +{ + return DoGetBlobAsString(this, aIndex, aValue); +} + +NS_IMETHODIMP +ArgValueArray::GetBlobAsUTF8String(uint32_t aIndex, nsACString& aValue) +{ + return DoGetBlobAsString(this, aIndex, aValue); +} + +NS_IMETHODIMP +ArgValueArray::GetIsNull(uint32_t aIndex, + bool *_isNull) +{ + // GetTypeOfIndex will check aIndex for us, so we don't have to. + int32_t type; + nsresult rv = GetTypeOfIndex(aIndex, &type); + NS_ENSURE_SUCCESS(rv, rv); + + *_isNull = (type == VALUE_TYPE_NULL); + return NS_OK; +} + +NS_IMETHODIMP +ArgValueArray::GetSharedUTF8String(uint32_t aIndex, + uint32_t *_length, + const char **_string) +{ + if (_length) + *_length = ::sqlite3_value_bytes(mArgv[aIndex]); + + *_string = reinterpret_cast<const char *>(::sqlite3_value_text(mArgv[aIndex])); + return NS_OK; +} + +NS_IMETHODIMP +ArgValueArray::GetSharedString(uint32_t aIndex, + uint32_t *_length, + const char16_t **_string) +{ + if (_length) + *_length = ::sqlite3_value_bytes(mArgv[aIndex]); + + *_string = static_cast<const char16_t *>(::sqlite3_value_text16(mArgv[aIndex])); + return NS_OK; +} + +NS_IMETHODIMP +ArgValueArray::GetSharedBlob(uint32_t aIndex, + uint32_t *_size, + const uint8_t **_blob) +{ + *_size = ::sqlite3_value_bytes(mArgv[aIndex]); + *_blob = static_cast<const uint8_t *>(::sqlite3_value_blob(mArgv[aIndex])); + return NS_OK; +} + +} // namespace storage +} // namespace mozilla diff --git a/components/storage/src/mozStorageArgValueArray.h b/components/storage/src/mozStorageArgValueArray.h new file mode 100644 index 000000000..5a14957ba --- /dev/null +++ b/components/storage/src/mozStorageArgValueArray.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozStorageArgValueArray_h +#define mozStorageArgValueArray_h + +#include "mozIStorageValueArray.h" +#include "mozilla/Attributes.h" + +#include "sqlite3.h" + +namespace mozilla { +namespace storage { + +class ArgValueArray final : public mozIStorageValueArray +{ +public: + ArgValueArray(int32_t aArgc, sqlite3_value **aArgv); + + NS_DECL_ISUPPORTS + NS_DECL_MOZISTORAGEVALUEARRAY + +private: + ~ArgValueArray() {} + + uint32_t mArgc; + sqlite3_value **mArgv; +}; + +} // namespace storage +} // namespace mozilla + +#endif // mozStorageArgValueArray_h diff --git a/components/storage/src/mozStorageAsyncStatement.cpp b/components/storage/src/mozStorageAsyncStatement.cpp new file mode 100644 index 000000000..d0a3eec04 --- /dev/null +++ b/components/storage/src/mozStorageAsyncStatement.cpp @@ -0,0 +1,383 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <limits.h> +#include <stdio.h> + +#include "nsError.h" +#include "nsMemory.h" +#include "nsProxyRelease.h" +#include "nsThreadUtils.h" +#include "nsIClassInfoImpl.h" +#include "Variant.h" + +#include "mozIStorageError.h" + +#include "mozStorageBindingParams.h" +#include "mozStorageConnection.h" +#include "mozStorageAsyncStatementJSHelper.h" +#include "mozStorageAsyncStatementParams.h" +#include "mozStoragePrivateHelpers.h" +#include "mozStorageStatementRow.h" +#include "mozStorageStatement.h" +#include "nsDOMClassInfo.h" + +#include "mozilla/Logging.h" + +extern mozilla::LazyLogModule gStorageLog; + +namespace mozilla { +namespace storage { + +//////////////////////////////////////////////////////////////////////////////// +//// nsIClassInfo + +NS_IMPL_CI_INTERFACE_GETTER(AsyncStatement, + mozIStorageAsyncStatement, + mozIStorageBaseStatement, + mozIStorageBindingParams, + mozilla::storage::StorageBaseStatementInternal) + +class AsyncStatementClassInfo : public nsIClassInfo +{ +public: + constexpr AsyncStatementClassInfo() {} + + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHOD + GetInterfaces(uint32_t *_count, nsIID ***_array) override + { + return NS_CI_INTERFACE_GETTER_NAME(AsyncStatement)(_count, _array); + } + + NS_IMETHOD + GetScriptableHelper(nsIXPCScriptable **_helper) override + { + static AsyncStatementJSHelper sJSHelper; + *_helper = &sJSHelper; + return NS_OK; + } + + NS_IMETHOD + GetContractID(char **_contractID) override + { + *_contractID = nullptr; + return NS_OK; + } + + NS_IMETHOD + GetClassDescription(char **_desc) override + { + *_desc = nullptr; + return NS_OK; + } + + NS_IMETHOD + GetClassID(nsCID **_id) override + { + *_id = nullptr; + return NS_OK; + } + + NS_IMETHOD + GetFlags(uint32_t *_flags) override + { + *_flags = 0; + return NS_OK; + } + + NS_IMETHOD + GetClassIDNoAlloc(nsCID *_cid) override + { + return NS_ERROR_NOT_AVAILABLE; + } +}; + +NS_IMETHODIMP_(MozExternalRefCountType) AsyncStatementClassInfo::AddRef() { return 2; } +NS_IMETHODIMP_(MozExternalRefCountType) AsyncStatementClassInfo::Release() { return 1; } +NS_IMPL_QUERY_INTERFACE(AsyncStatementClassInfo, nsIClassInfo) + +static AsyncStatementClassInfo sAsyncStatementClassInfo; + +//////////////////////////////////////////////////////////////////////////////// +//// AsyncStatement + +AsyncStatement::AsyncStatement() +: StorageBaseStatementInternal() +, mFinalized(false) +{ +} + +nsresult +AsyncStatement::initialize(Connection *aDBConnection, + sqlite3 *aNativeConnection, + const nsACString &aSQLStatement) +{ + MOZ_ASSERT(aDBConnection, "No database connection given!"); + MOZ_ASSERT(!aDBConnection->isClosed(), "Database connection should be valid"); + MOZ_ASSERT(aNativeConnection, "No native connection given!"); + + mDBConnection = aDBConnection; + mNativeConnection = aNativeConnection; + mSQLString = aSQLStatement; + + MOZ_LOG(gStorageLog, LogLevel::Debug, ("Inited async statement '%s' (0x%p)", + mSQLString.get())); + +#ifdef DEBUG + // We want to try and test for LIKE and that consumers are using + // escapeStringForLIKE instead of just trusting user input. The idea to + // check to see if they are binding a parameter after like instead of just + // using a string. We only do this in debug builds because it's expensive! + const nsCaseInsensitiveCStringComparator c; + nsACString::const_iterator start, end, e; + aSQLStatement.BeginReading(start); + aSQLStatement.EndReading(end); + e = end; + while (::FindInReadable(NS_LITERAL_CSTRING(" LIKE"), start, e, c)) { + // We have a LIKE in here, so we perform our tests + // FindInReadable moves the iterator, so we have to get a new one for + // each test we perform. + nsACString::const_iterator s1, s2, s3; + s1 = s2 = s3 = start; + + if (!(::FindInReadable(NS_LITERAL_CSTRING(" LIKE ?"), s1, end, c) || + ::FindInReadable(NS_LITERAL_CSTRING(" LIKE :"), s2, end, c) || + ::FindInReadable(NS_LITERAL_CSTRING(" LIKE @"), s3, end, c))) { + // At this point, we didn't find a LIKE statement followed by ?, :, + // or @, all of which are valid characters for binding a parameter. + // We will warn the consumer that they may not be safely using LIKE. + NS_WARNING("Unsafe use of LIKE detected! Please ensure that you " + "are using mozIStorageAsyncStatement::escapeStringForLIKE " + "and that you are binding that result to the statement " + "to prevent SQL injection attacks."); + } + + // resetting start and e + start = e; + e = end; + } +#endif + + return NS_OK; +} + +mozIStorageBindingParams * +AsyncStatement::getParams() +{ + nsresult rv; + + // If we do not have an array object yet, make it. + if (!mParamsArray) { + nsCOMPtr<mozIStorageBindingParamsArray> array; + rv = NewBindingParamsArray(getter_AddRefs(array)); + NS_ENSURE_SUCCESS(rv, nullptr); + + mParamsArray = static_cast<BindingParamsArray *>(array.get()); + } + + // If there isn't already any rows added, we'll have to add one to use. + if (mParamsArray->length() == 0) { + RefPtr<AsyncBindingParams> params(new AsyncBindingParams(mParamsArray)); + NS_ENSURE_TRUE(params, nullptr); + + rv = mParamsArray->AddParams(params); + NS_ENSURE_SUCCESS(rv, nullptr); + + // We have to unlock our params because AddParams locks them. This is safe + // because no reference to the params object was, or ever will be given out. + params->unlock(nullptr); + + // We also want to lock our array at this point - we don't want anything to + // be added to it. + mParamsArray->lock(); + } + + return *mParamsArray->begin(); +} + +/** + * If we are here then we know there are no pending async executions relying on + * us (StatementData holds a reference to us; this also goes for our own + * AsyncStatementFinalizer which proxies its release to the calling thread) and + * so it is always safe to destroy our sqlite3_stmt if one exists. We can be + * destroyed on the caller thread by garbage-collection/reference counting or on + * the async thread by the last execution of a statement that already lost its + * main-thread refs. + */ +AsyncStatement::~AsyncStatement() +{ + destructorAsyncFinalize(); + + // If we are getting destroyed on the wrong thread, proxy the connection + // release to the right thread. I'm not sure why we do this. + bool onCallingThread = false; + (void)mDBConnection->threadOpenedOn->IsOnCurrentThread(&onCallingThread); + if (!onCallingThread) { + // NS_ProxyRelase only magic forgets for us if mDBConnection is an + // nsCOMPtr. Which it is not; it's an nsRefPtr. + nsCOMPtr<nsIThread> targetThread(mDBConnection->threadOpenedOn); + NS_ProxyRelease(targetThread, mDBConnection.forget()); + } +} + +//////////////////////////////////////////////////////////////////////////////// +//// nsISupports + +NS_IMPL_ADDREF(AsyncStatement) +NS_IMPL_RELEASE(AsyncStatement) + +NS_INTERFACE_MAP_BEGIN(AsyncStatement) + NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncStatement) + NS_INTERFACE_MAP_ENTRY(mozIStorageBaseStatement) + NS_INTERFACE_MAP_ENTRY(mozIStorageBindingParams) + NS_INTERFACE_MAP_ENTRY(mozilla::storage::StorageBaseStatementInternal) + if (aIID.Equals(NS_GET_IID(nsIClassInfo))) { + foundInterface = static_cast<nsIClassInfo *>(&sAsyncStatementClassInfo); + } + else + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageAsyncStatement) +NS_INTERFACE_MAP_END + + +//////////////////////////////////////////////////////////////////////////////// +//// StorageBaseStatementInternal + +Connection * +AsyncStatement::getOwner() +{ + return mDBConnection; +} + +int +AsyncStatement::getAsyncStatement(sqlite3_stmt **_stmt) +{ +#ifdef DEBUG + // Make sure we are never called on the connection's owning thread. + bool onOpenedThread = false; + (void)mDBConnection->threadOpenedOn->IsOnCurrentThread(&onOpenedThread); + NS_ASSERTION(!onOpenedThread, + "We should only be called on the async thread!"); +#endif + + if (!mAsyncStatement) { + int rc = mDBConnection->prepareStatement(mNativeConnection, mSQLString, + &mAsyncStatement); + if (rc != SQLITE_OK) { + MOZ_LOG(gStorageLog, LogLevel::Error, + ("Sqlite statement prepare error: %d '%s'", rc, + ::sqlite3_errmsg(mNativeConnection))); + MOZ_LOG(gStorageLog, LogLevel::Error, + ("Statement was: '%s'", mSQLString.get())); + *_stmt = nullptr; + return rc; + } + MOZ_LOG(gStorageLog, LogLevel::Debug, ("Initialized statement '%s' (0x%p)", + mSQLString.get(), + mAsyncStatement)); + } + + *_stmt = mAsyncStatement; + return SQLITE_OK; +} + +nsresult +AsyncStatement::getAsynchronousStatementData(StatementData &_data) +{ + if (mFinalized) + return NS_ERROR_UNEXPECTED; + + // Pass null for the sqlite3_stmt; it will be requested on demand from the + // async thread. + _data = StatementData(nullptr, bindingParamsArray(), this); + + return NS_OK; +} + +already_AddRefed<mozIStorageBindingParams> +AsyncStatement::newBindingParams(mozIStorageBindingParamsArray *aOwner) +{ + if (mFinalized) + return nullptr; + + nsCOMPtr<mozIStorageBindingParams> params(new AsyncBindingParams(aOwner)); + return params.forget(); +} + + +//////////////////////////////////////////////////////////////////////////////// +//// mozIStorageAsyncStatement + +// (nothing is specific to mozIStorageAsyncStatement) + +//////////////////////////////////////////////////////////////////////////////// +//// StorageBaseStatementInternal + +// proxy to StorageBaseStatementInternal using its define helper. +MIXIN_IMPL_STORAGEBASESTATEMENTINTERNAL( + AsyncStatement, + if (mFinalized) return NS_ERROR_UNEXPECTED;) + +NS_IMETHODIMP +AsyncStatement::Finalize() +{ + if (mFinalized) + return NS_OK; + + mFinalized = true; + + MOZ_LOG(gStorageLog, LogLevel::Debug, ("Finalizing statement '%s'", + mSQLString.get())); + + asyncFinalize(); + + // Release the params holder, so it can release the reference to us. + mStatementParamsHolder = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +AsyncStatement::BindParameters(mozIStorageBindingParamsArray *aParameters) +{ + if (mFinalized) + return NS_ERROR_UNEXPECTED; + + BindingParamsArray *array = static_cast<BindingParamsArray *>(aParameters); + if (array->getOwner() != this) + return NS_ERROR_UNEXPECTED; + + if (array->length() == 0) + return NS_ERROR_UNEXPECTED; + + mParamsArray = array; + mParamsArray->lock(); + + return NS_OK; +} + +NS_IMETHODIMP +AsyncStatement::GetState(int32_t *_state) +{ + if (mFinalized) + *_state = MOZ_STORAGE_STATEMENT_INVALID; + else + *_state = MOZ_STORAGE_STATEMENT_READY; + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +//// mozIStorageBindingParams + +BOILERPLATE_BIND_PROXIES( + AsyncStatement, + if (mFinalized) return NS_ERROR_UNEXPECTED; +) + +} // namespace storage +} // namespace mozilla diff --git a/components/storage/src/mozStorageAsyncStatement.h b/components/storage/src/mozStorageAsyncStatement.h new file mode 100644 index 000000000..4fac36d30 --- /dev/null +++ b/components/storage/src/mozStorageAsyncStatement.h @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_storage_mozStorageAsyncStatement_h_ +#define mozilla_storage_mozStorageAsyncStatement_h_ + +#include "nsAutoPtr.h" +#include "nsString.h" + +#include "nsTArray.h" + +#include "mozStorageBindingParamsArray.h" +#include "mozStorageStatementData.h" +#include "mozIStorageAsyncStatement.h" +#include "StorageBaseStatementInternal.h" +#include "mozilla/Attributes.h" + +class nsIXPConnectJSObjectHolder; + +namespace mozilla { +namespace storage { + +class AsyncStatementJSHelper; +class Connection; + +class AsyncStatement final : public mozIStorageAsyncStatement + , public StorageBaseStatementInternal +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_MOZISTORAGEASYNCSTATEMENT + NS_DECL_MOZISTORAGEBASESTATEMENT + NS_DECL_MOZISTORAGEBINDINGPARAMS + NS_DECL_STORAGEBASESTATEMENTINTERNAL + + AsyncStatement(); + + /** + * Initializes the object on aDBConnection by preparing the SQL statement + * given by aSQLStatement. + * + * @param aDBConnection + * The Connection object this statement is associated with. + * @param aNativeConnection + * The native Sqlite connection this statement is associated with. + * @param aSQLStatement + * The SQL statement to prepare that this object will represent. + */ + nsresult initialize(Connection *aDBConnection, + sqlite3 *aNativeConnection, + const nsACString &aSQLStatement); + + /** + * Obtains and transfers ownership of the array of parameters that are bound + * to this statment. This can be null. + */ + inline already_AddRefed<BindingParamsArray> bindingParamsArray() + { + return mParamsArray.forget(); + } + + +private: + ~AsyncStatement(); + + /** + * @return a pointer to the BindingParams object to use with our Bind* + * method. + */ + mozIStorageBindingParams *getParams(); + + /** + * The SQL string as passed by the user. We store it because we create the + * async statement on-demand on the async thread. + */ + nsCString mSQLString; + + /** + * Holds the array of parameters to bind to this statement when we execute + * it asynchronously. + */ + RefPtr<BindingParamsArray> mParamsArray; + + /** + * Caches the JS 'params' helper for this statement. + */ + nsMainThreadPtrHandle<nsIXPConnectJSObjectHolder> mStatementParamsHolder; + + /** + * Have we been explicitly finalized by the user? + */ + bool mFinalized; + + /** + * Required for access to private mStatementParamsHolder field by + * AsyncStatementJSHelper::getParams. + */ + friend class AsyncStatementJSHelper; +}; + +} // namespace storage +} // namespace mozilla + +#endif // mozilla_storage_mozStorageAsyncStatement_h_ diff --git a/components/storage/src/mozStorageAsyncStatementExecution.cpp b/components/storage/src/mozStorageAsyncStatementExecution.cpp new file mode 100644 index 000000000..ec9f380bf --- /dev/null +++ b/components/storage/src/mozStorageAsyncStatementExecution.cpp @@ -0,0 +1,580 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsAutoPtr.h" + +#include "sqlite3.h" + +#include "mozIStorageStatementCallback.h" +#include "mozStorageBindingParams.h" +#include "mozStorageHelper.h" +#include "mozStorageResultSet.h" +#include "mozStorageRow.h" +#include "mozStorageConnection.h" +#include "mozStorageError.h" +#include "mozStoragePrivateHelpers.h" +#include "mozStorageStatementData.h" +#include "mozStorageAsyncStatementExecution.h" + +#include "mozilla/DebugOnly.h" + +namespace mozilla { +namespace storage { + +/** + * The following constants help batch rows into result sets. + * MAX_MILLISECONDS_BETWEEN_RESULTS was chosen because any user-based task that + * takes less than 200 milliseconds is considered to feel instantaneous to end + * users. MAX_ROWS_PER_RESULT was arbitrarily chosen to reduce the number of + * dispatches to calling thread, while also providing reasonably-sized sets of + * data for consumers. Both of these constants are used because we assume that + * consumers are trying to avoid blocking their execution thread for long + * periods of time, and dispatching many small events to the calling thread will + * end up blocking it. + */ +#define MAX_MILLISECONDS_BETWEEN_RESULTS 75 +#define MAX_ROWS_PER_RESULT 15 + +//////////////////////////////////////////////////////////////////////////////// +//// AsyncExecuteStatements + +/* static */ +nsresult +AsyncExecuteStatements::execute(StatementDataArray &aStatements, + Connection *aConnection, + sqlite3 *aNativeConnection, + mozIStorageStatementCallback *aCallback, + mozIStoragePendingStatement **_stmt) +{ + // Create our event to run in the background + RefPtr<AsyncExecuteStatements> event = + new AsyncExecuteStatements(aStatements, aConnection, aNativeConnection, + aCallback); + NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY); + + // Dispatch it to the background + nsIEventTarget *target = aConnection->getAsyncExecutionTarget(); + + // If we don't have a valid target, this is a bug somewhere else. In the past, + // this assert found cases where a Run method would schedule a new statement + // without checking if asyncClose had been called. The caller must prevent + // that from happening or, if the work is not critical, just avoid creating + // the new statement during shutdown. See bug 718449 for an example. + MOZ_ASSERT(target); + if (!target) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + + // Return it as the pending statement object and track it. + event.forget(_stmt); + return NS_OK; +} + +AsyncExecuteStatements::AsyncExecuteStatements(StatementDataArray &aStatements, + Connection *aConnection, + sqlite3 *aNativeConnection, + mozIStorageStatementCallback *aCallback) +: mConnection(aConnection) +, mNativeConnection(aNativeConnection) +, mHasTransaction(false) +, mCallback(aCallback) +, mCallingThread(::do_GetCurrentThread()) +, mMaxWait(TimeDuration::FromMilliseconds(MAX_MILLISECONDS_BETWEEN_RESULTS)) +, mIntervalStart(TimeStamp::Now()) +, mState(PENDING) +, mCancelRequested(false) +, mMutex(aConnection->sharedAsyncExecutionMutex) +, mDBMutex(aConnection->sharedDBMutex) +, mRequestStartDate(TimeStamp::Now()) +{ + (void)mStatements.SwapElements(aStatements); + NS_ASSERTION(mStatements.Length(), "We weren't given any statements!"); +} + +AsyncExecuteStatements::~AsyncExecuteStatements() +{ + MOZ_ASSERT(!mCallback, "Never called the Completion callback!"); + MOZ_ASSERT(!mHasTransaction, "There should be no transaction at this point"); + if (mCallback) { + NS_ProxyRelease(mCallingThread, mCallback.forget()); + } +} + +bool +AsyncExecuteStatements::shouldNotify() +{ +#ifdef DEBUG + mMutex.AssertNotCurrentThreadOwns(); + + bool onCallingThread = false; + (void)mCallingThread->IsOnCurrentThread(&onCallingThread); + NS_ASSERTION(onCallingThread, "runEvent not running on the calling thread!"); +#endif + + // We do not need to acquire mMutex here because it can only ever be written + // to on the calling thread, and the only thread that can call us is the + // calling thread, so we know that our access is serialized. + return !mCancelRequested; +} + +bool +AsyncExecuteStatements::bindExecuteAndProcessStatement(StatementData &aData, + bool aLastStatement) +{ + mMutex.AssertNotCurrentThreadOwns(); + + sqlite3_stmt *aStatement = nullptr; + // This cannot fail; we are only called if it's available. + (void)aData.getSqliteStatement(&aStatement); + NS_ASSERTION(aStatement, "You broke the code; do not call here like that!"); + BindingParamsArray *paramsArray(aData); + + // Iterate through all of our parameters, bind them, and execute. + bool continueProcessing = true; + BindingParamsArray::iterator itr = paramsArray->begin(); + BindingParamsArray::iterator end = paramsArray->end(); + while (itr != end && continueProcessing) { + // Bind the data to our statement. + nsCOMPtr<IStorageBindingParamsInternal> bindingInternal = + do_QueryInterface(*itr); + nsCOMPtr<mozIStorageError> error = bindingInternal->bind(aStatement); + if (error) { + // Set our error state. + mState = ERROR; + + // And notify. + (void)notifyError(error); + return false; + } + + // Advance our iterator, execute, and then process the statement. + itr++; + bool lastStatement = aLastStatement && itr == end; + continueProcessing = executeAndProcessStatement(aStatement, lastStatement); + + // Always reset our statement. + (void)::sqlite3_reset(aStatement); + } + + return continueProcessing; +} + +bool +AsyncExecuteStatements::executeAndProcessStatement(sqlite3_stmt *aStatement, + bool aLastStatement) +{ + mMutex.AssertNotCurrentThreadOwns(); + + // Execute our statement + bool hasResults; + do { + hasResults = executeStatement(aStatement); + + // If we had an error, bail. + if (mState == ERROR) + return false; + + // If we have been canceled, there is no point in going on... + { + MutexAutoLock lockedScope(mMutex); + if (mCancelRequested) { + mState = CANCELED; + return false; + } + } + + // Build our result set and notify if we got anything back and have a + // callback to notify. + if (mCallback && hasResults && + NS_FAILED(buildAndNotifyResults(aStatement))) { + // We had an error notifying, so we notify on error and stop processing. + mState = ERROR; + + // Notify, and stop processing statements. + (void)notifyError(mozIStorageError::ERROR, + "An error occurred while notifying about results"); + + return false; + } + } while (hasResults); + +#ifndef MOZ_STORAGE_SORTWARNING_SQL_DUMP + if (MOZ_LOG_TEST(gStorageLog, LogLevel::Warning)) +#endif + { + // Check to make sure that this statement was smart about what it did. + checkAndLogStatementPerformance(aStatement); + } + + // If we are done, we need to set our state accordingly while we still hold + // our mutex. We would have already returned if we were canceled or had + // an error at this point. + if (aLastStatement) + mState = COMPLETED; + + return true; +} + +bool +AsyncExecuteStatements::executeStatement(sqlite3_stmt *aStatement) +{ + mMutex.AssertNotCurrentThreadOwns(); + while (true) { + // lock the sqlite mutex so sqlite3_errmsg cannot change + SQLiteMutexAutoLock lockedScope(mDBMutex); + + int rc = mConnection->stepStatement(mNativeConnection, aStatement); + // Stop if we have no more results. + if (rc == SQLITE_DONE) + { + return false; + } + + // If we got results, we can return now. + if (rc == SQLITE_ROW) + { + return true; + } + + // Some errors are not fatal, and we can handle them and continue. + if (rc == SQLITE_BUSY) { + // Don't hold the lock while we call outside our module. + SQLiteMutexAutoUnlock unlockedScope(mDBMutex); + + // Yield, and try again + (void)::PR_Sleep(PR_INTERVAL_NO_WAIT); + continue; + } + + // Set an error state. + mState = ERROR; + + // Construct the error message before giving up the mutex (which we cannot + // hold during the call to notifyError). + nsCOMPtr<mozIStorageError> errorObj( + new Error(rc, ::sqlite3_errmsg(mNativeConnection)) + ); + // We cannot hold the DB mutex while calling notifyError. + SQLiteMutexAutoUnlock unlockedScope(mDBMutex); + (void)notifyError(errorObj); + + // Finally, indicate that we should stop processing. + return false; + } +} + +nsresult +AsyncExecuteStatements::buildAndNotifyResults(sqlite3_stmt *aStatement) +{ + NS_ASSERTION(mCallback, "Trying to dispatch results without a callback!"); + mMutex.AssertNotCurrentThreadOwns(); + + // Build result object if we need it. + if (!mResultSet) + mResultSet = new ResultSet(); + NS_ENSURE_TRUE(mResultSet, NS_ERROR_OUT_OF_MEMORY); + + RefPtr<Row> row(new Row()); + NS_ENSURE_TRUE(row, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv = row->initialize(aStatement); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mResultSet->add(row); + NS_ENSURE_SUCCESS(rv, rv); + + // If we have hit our maximum number of allowed results, or if we have hit + // the maximum amount of time we want to wait for results, notify the + // calling thread about it. + TimeStamp now = TimeStamp::Now(); + TimeDuration delta = now - mIntervalStart; + if (mResultSet->rows() >= MAX_ROWS_PER_RESULT || delta > mMaxWait) { + // Notify the caller + rv = notifyResults(); + if (NS_FAILED(rv)) + return NS_OK; // we'll try again with the next result + + // Reset our start time + mIntervalStart = now; + } + + return NS_OK; +} + +nsresult +AsyncExecuteStatements::notifyComplete() +{ + mMutex.AssertNotCurrentThreadOwns(); + NS_ASSERTION(mState != PENDING, + "Still in a pending state when calling Complete!"); + + // Reset our statements before we try to commit or rollback. If we are + // canceling and have statements that think they have pending work, the + // rollback will fail. + for (uint32_t i = 0; i < mStatements.Length(); i++) + mStatements[i].reset(); + + // Release references to the statement data as soon as possible. If this + // is the last reference, statements will be finalized immediately on the + // async thread, hence avoiding several bounces between threads and possible + // race conditions with AsyncClose(). + mStatements.Clear(); + + // Handle our transaction, if we have one + if (mHasTransaction) { + if (mState == COMPLETED) { + nsresult rv = mConnection->commitTransactionInternal(mNativeConnection); + if (NS_FAILED(rv)) { + mState = ERROR; + (void)notifyError(mozIStorageError::ERROR, + "Transaction failed to commit"); + } + } + else { + DebugOnly<nsresult> rv = + mConnection->rollbackTransactionInternal(mNativeConnection); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Transaction failed to rollback"); + } + mHasTransaction = false; + } + + // This will take ownership of mCallback and make sure its destruction will + // happen on the owner thread. + Unused << mCallingThread->Dispatch( + NewRunnableMethod(this, &AsyncExecuteStatements::notifyCompleteOnCallingThread), + NS_DISPATCH_NORMAL); + + return NS_OK; +} + +nsresult +AsyncExecuteStatements::notifyCompleteOnCallingThread() { +#ifdef DEBUG + bool onCallingThread = false; + (void)mCallingThread->IsOnCurrentThread(&onCallingThread); + MOZ_ASSERT(onCallingThread); +#endif + // Take ownership of mCallback and responsibility for freeing it when we + // release it. Any notifyResultsOnCallingThread and notifyErrorOnCallingThread + // calls on the stack spinning the event loop have guaranteed their safety by + // creating their own strong reference before invoking the callback. + nsCOMPtr<mozIStorageStatementCallback> callback = mCallback.forget(); + if (callback) { + Unused << callback->HandleCompletion(mState); + } + return NS_OK; +} + +nsresult +AsyncExecuteStatements::notifyError(int32_t aErrorCode, + const char *aMessage) +{ + mMutex.AssertNotCurrentThreadOwns(); + mDBMutex.assertNotCurrentThreadOwns(); + + if (!mCallback) + return NS_OK; + + nsCOMPtr<mozIStorageError> errorObj(new Error(aErrorCode, aMessage)); + NS_ENSURE_TRUE(errorObj, NS_ERROR_OUT_OF_MEMORY); + + return notifyError(errorObj); +} + +nsresult +AsyncExecuteStatements::notifyError(mozIStorageError *aError) +{ + mMutex.AssertNotCurrentThreadOwns(); + mDBMutex.assertNotCurrentThreadOwns(); + + if (!mCallback) + return NS_OK; + + Unused << mCallingThread->Dispatch( + NewRunnableMethod<nsCOMPtr<mozIStorageError>>(this, &AsyncExecuteStatements::notifyErrorOnCallingThread, aError), + NS_DISPATCH_NORMAL); + + return NS_OK; +} + +nsresult +AsyncExecuteStatements::notifyErrorOnCallingThread(mozIStorageError *aError) { +#ifdef DEBUG + bool onCallingThread = false; + (void)mCallingThread->IsOnCurrentThread(&onCallingThread); + MOZ_ASSERT(onCallingThread); +#endif + // Acquire our own strong reference so that if the callback spins a nested + // event loop and notifyCompleteOnCallingThread is executed, forgetting + // mCallback, we still have a valid/strong reference that won't be freed until + // we exit. + nsCOMPtr<mozIStorageStatementCallback> callback = mCallback; + if (shouldNotify() && callback) { + Unused << callback->HandleError(aError); + } + return NS_OK; +} + +nsresult +AsyncExecuteStatements::notifyResults() +{ + mMutex.AssertNotCurrentThreadOwns(); + MOZ_ASSERT(mCallback, "notifyResults called without a callback!"); + + // This takes ownership of mResultSet, a new one will be generated in + // buildAndNotifyResults() when further results will arrive. + Unused << mCallingThread->Dispatch( + NewRunnableMethod<RefPtr<ResultSet>>(this, &AsyncExecuteStatements::notifyResultsOnCallingThread, mResultSet.forget()), + NS_DISPATCH_NORMAL); + + return NS_OK; +} + +nsresult +AsyncExecuteStatements::notifyResultsOnCallingThread(ResultSet *aResultSet) +{ +#ifdef DEBUG + bool onCallingThread = false; + (void)mCallingThread->IsOnCurrentThread(&onCallingThread); + MOZ_ASSERT(onCallingThread); +#endif + // Acquire our own strong reference so that if the callback spins a nested + // event loop and notifyCompleteOnCallingThread is executed, forgetting + // mCallback, we still have a valid/strong reference that won't be freed until + // we exit. + nsCOMPtr<mozIStorageStatementCallback> callback = mCallback; + if (shouldNotify() && callback) { + Unused << callback->HandleResult(aResultSet); + } + return NS_OK; +} + +NS_IMPL_ISUPPORTS( + AsyncExecuteStatements, + nsIRunnable, + mozIStoragePendingStatement +) + +bool +AsyncExecuteStatements::statementsNeedTransaction() +{ + // If there is more than one write statement, run in a transaction. + // Additionally, if we have only one statement but it needs a transaction, due + // to multiple BindingParams, we will wrap it in one. + for (uint32_t i = 0, transactionsCount = 0; i < mStatements.Length(); ++i) { + transactionsCount += mStatements[i].needsTransaction(); + if (transactionsCount > 1) { + return true; + } + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +//// mozIStoragePendingStatement + +NS_IMETHODIMP +AsyncExecuteStatements::Cancel() +{ +#ifdef DEBUG + bool onCallingThread = false; + (void)mCallingThread->IsOnCurrentThread(&onCallingThread); + NS_ASSERTION(onCallingThread, "Not canceling from the calling thread!"); +#endif + + // If we have already canceled, we have an error, but always indicate that + // we are trying to cancel. + NS_ENSURE_FALSE(mCancelRequested, NS_ERROR_UNEXPECTED); + + { + MutexAutoLock lockedScope(mMutex); + + // We need to indicate that we want to try and cancel now. + mCancelRequested = true; + } + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +//// nsIRunnable + +NS_IMETHODIMP +AsyncExecuteStatements::Run() +{ + MOZ_ASSERT(!mConnection->isClosed()); + + // Do not run if we have been canceled. + { + MutexAutoLock lockedScope(mMutex); + if (mCancelRequested) + mState = CANCELED; + } + if (mState == CANCELED) + return notifyComplete(); + + if (statementsNeedTransaction() && mConnection->getAutocommit()) { + if (NS_SUCCEEDED(mConnection->beginTransactionInternal(mNativeConnection, + mozIStorageConnection::TRANSACTION_IMMEDIATE))) { + mHasTransaction = true; + } +#ifdef DEBUG + else { + NS_WARNING("Unable to create a transaction for async execution."); + } +#endif + } + + // Execute each statement, giving the callback results if it returns any. + for (uint32_t i = 0; i < mStatements.Length(); i++) { + bool finished = (i == (mStatements.Length() - 1)); + + sqlite3_stmt *stmt; + { // lock the sqlite mutex so sqlite3_errmsg cannot change + SQLiteMutexAutoLock lockedScope(mDBMutex); + + int rc = mStatements[i].getSqliteStatement(&stmt); + if (rc != SQLITE_OK) { + // Set our error state. + mState = ERROR; + + // Build the error object; can't call notifyError with the lock held + nsCOMPtr<mozIStorageError> errorObj( + new Error(rc, ::sqlite3_errmsg(mNativeConnection)) + ); + { + // We cannot hold the DB mutex and call notifyError. + SQLiteMutexAutoUnlock unlockedScope(mDBMutex); + (void)notifyError(errorObj); + } + break; + } + } + + // If we have parameters to bind, bind them, execute, and process. + if (mStatements[i].hasParametersToBeBound()) { + if (!bindExecuteAndProcessStatement(mStatements[i], finished)) + break; + } + // Otherwise, just execute and process the statement. + else if (!executeAndProcessStatement(stmt, finished)) { + break; + } + } + + // If we still have results that we haven't notified about, take care of + // them now. + if (mResultSet) + (void)notifyResults(); + + // Notify about completion + return notifyComplete(); +} + +} // namespace storage +} // namespace mozilla diff --git a/components/storage/src/mozStorageAsyncStatementExecution.h b/components/storage/src/mozStorageAsyncStatementExecution.h new file mode 100644 index 000000000..14ea49c2d --- /dev/null +++ b/components/storage/src/mozStorageAsyncStatementExecution.h @@ -0,0 +1,253 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozStorageAsyncStatementExecution_h +#define mozStorageAsyncStatementExecution_h + +#include "nscore.h" +#include "nsTArray.h" +#include "nsAutoPtr.h" +#include "mozilla/Mutex.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Attributes.h" +#include "nsIRunnable.h" + +#include "SQLiteMutex.h" +#include "mozIStoragePendingStatement.h" +#include "mozIStorageStatementCallback.h" +#include "mozStorageHelper.h" + +struct sqlite3_stmt; + +namespace mozilla { +namespace storage { + +class Connection; +class ResultSet; +class StatementData; + +class AsyncExecuteStatements final : public nsIRunnable + , public mozIStoragePendingStatement +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIRUNNABLE + NS_DECL_MOZISTORAGEPENDINGSTATEMENT + + /** + * Describes the state of execution. + */ + enum ExecutionState { + PENDING = -1, + COMPLETED = mozIStorageStatementCallback::REASON_FINISHED, + CANCELED = mozIStorageStatementCallback::REASON_CANCELED, + ERROR = mozIStorageStatementCallback::REASON_ERROR + }; + + typedef nsTArray<StatementData> StatementDataArray; + + /** + * Executes a statement in the background, and passes results back to the + * caller. + * + * @param aStatements + * The statements to execute and possibly bind in the background. + * Ownership is transfered from the caller. + * @param aConnection + * The connection that created the statements to execute. + * @param aNativeConnection + * The native Sqlite connection that created the statements to execute. + * @param aCallback + * The callback that is notified of results, completion, and errors. + * @param _stmt + * The handle to control the execution of the statements. + */ + static nsresult execute(StatementDataArray &aStatements, + Connection *aConnection, + sqlite3 *aNativeConnection, + mozIStorageStatementCallback *aCallback, + mozIStoragePendingStatement **_stmt); + + /** + * Indicates when events on the calling thread should run or not. Certain + * events posted back to the calling thread should call this see if they + * should run or not. + * + * @pre mMutex is not held + * + * @returns true if the event should notify still, false otherwise. + */ + bool shouldNotify(); + + /** + * Used by notifyComplete(), notifyError() and notifyResults() to notify on + * the calling thread. + */ + nsresult notifyCompleteOnCallingThread(); + nsresult notifyErrorOnCallingThread(mozIStorageError *aError); + nsresult notifyResultsOnCallingThread(ResultSet *aResultSet); + +private: + AsyncExecuteStatements(StatementDataArray &aStatements, + Connection *aConnection, + sqlite3 *aNativeConnection, + mozIStorageStatementCallback *aCallback); + ~AsyncExecuteStatements(); + + /** + * Binds and then executes a given statement until completion, an error + * occurs, or we are canceled. If aLastStatement is true, we should set + * mState accordingly. + * + * @pre mMutex is not held + * + * @param aData + * The StatementData to bind, execute, and then process. + * @param aLastStatement + * Indicates if this is the last statement or not. If it is, we have + * to set the proper state. + * @returns true if we should continue to process statements, false otherwise. + */ + bool bindExecuteAndProcessStatement(StatementData &aData, + bool aLastStatement); + + /** + * Executes a given statement until completion, an error occurs, or we are + * canceled. If aLastStatement is true, we should set mState accordingly. + * + * @pre mMutex is not held + * + * @param aStatement + * The statement to execute and then process. + * @param aLastStatement + * Indicates if this is the last statement or not. If it is, we have + * to set the proper state. + * @returns true if we should continue to process statements, false otherwise. + */ + bool executeAndProcessStatement(sqlite3_stmt *aStatement, + bool aLastStatement); + + /** + * Executes a statement to completion, properly handling any error conditions. + * + * @pre mMutex is not held + * + * @param aStatement + * The statement to execute to completion. + * @returns true if results were obtained, false otherwise. + */ + bool executeStatement(sqlite3_stmt *aStatement); + + /** + * Builds a result set up with a row from a given statement. If we meet the + * right criteria, go ahead and notify about this results too. + * + * @pre mMutex is not held + * + * @param aStatement + * The statement to get the row data from. + */ + nsresult buildAndNotifyResults(sqlite3_stmt *aStatement); + + /** + * Notifies callback about completion, and does any necessary cleanup. + * + * @pre mMutex is not held + */ + nsresult notifyComplete(); + + /** + * Notifies callback about an error. + * + * @pre mMutex is not held + * @pre mDBMutex is not held + * + * @param aErrorCode + * The error code defined in mozIStorageError for the error. + * @param aMessage + * The error string, if any. + * @param aError + * The error object to notify the caller with. + */ + nsresult notifyError(int32_t aErrorCode, const char *aMessage); + nsresult notifyError(mozIStorageError *aError); + + /** + * Notifies the callback about a result set. + * + * @pre mMutex is not held + */ + nsresult notifyResults(); + + /** + * Tests whether the current statements should be wrapped in an explicit + * transaction. + * + * @return true if an explicit transaction is needed, false otherwise. + */ + bool statementsNeedTransaction(); + + StatementDataArray mStatements; + RefPtr<Connection> mConnection; + sqlite3 *mNativeConnection; + bool mHasTransaction; + // Note, this may not be a threadsafe object - never addref/release off + // the calling thread. We take a reference when this is created, and + // release it in the CompletionNotifier::Run() call back to this thread. + nsCOMPtr<mozIStorageStatementCallback> mCallback; + nsCOMPtr<nsIThread> mCallingThread; + RefPtr<ResultSet> mResultSet; + + /** + * The maximum amount of time we want to wait between results. Defined by + * MAX_MILLISECONDS_BETWEEN_RESULTS and set at construction. + */ + const TimeDuration mMaxWait; + + /** + * The start time since our last set of results. + */ + TimeStamp mIntervalStart; + + /** + * Indicates our state of execution. + */ + ExecutionState mState; + + /** + * Indicates if we should try to cancel at a cancelation point. + */ + bool mCancelRequested; + + /** + * This is the mutex that protects our state from changing between threads. + * This includes the following variables: + * - mCancelRequested is only set on the calling thread while the lock is + * held. It is always read from within the lock on the background thread, + * but not on the calling thread (see shouldNotify for why). + */ + Mutex &mMutex; + + /** + * The wrapped SQLite recursive connection mutex. We use it whenever we call + * sqlite3_step and care about having reliable error messages. By taking it + * prior to the call and holding it until the point where we no longer care + * about the error message, the user gets reliable error messages. + */ + SQLiteMutex &mDBMutex; + + /** + * The instant at which the request was started. + * + * Used by telemetry. + */ + TimeStamp mRequestStartDate; +}; + +} // namespace storage +} // namespace mozilla + +#endif // mozStorageAsyncStatementExecution_h diff --git a/components/storage/src/mozStorageAsyncStatementJSHelper.cpp b/components/storage/src/mozStorageAsyncStatementJSHelper.cpp new file mode 100644 index 000000000..321d37884 --- /dev/null +++ b/components/storage/src/mozStorageAsyncStatementJSHelper.cpp @@ -0,0 +1,153 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIXPConnect.h" +#include "mozStorageAsyncStatement.h" +#include "mozStorageService.h" + +#include "nsMemory.h" +#include "nsString.h" +#include "nsServiceManagerUtils.h" + +#include "mozStorageAsyncStatementJSHelper.h" + +#include "mozStorageAsyncStatementParams.h" + +#include "jsapi.h" + +#include "xpc_make_class.h" + +namespace mozilla { +namespace storage { + +//////////////////////////////////////////////////////////////////////////////// +//// AsyncStatementJSHelper + +nsresult +AsyncStatementJSHelper::getParams(AsyncStatement *aStatement, + JSContext *aCtx, + JSObject *aScopeObj, + JS::Value *_params) +{ + MOZ_ASSERT(NS_IsMainThread()); + nsresult rv; + +#ifdef DEBUG + int32_t state; + (void)aStatement->GetState(&state); + NS_ASSERTION(state == mozIStorageAsyncStatement::MOZ_STORAGE_STATEMENT_READY, + "Invalid state to get the params object - all calls will fail!"); +#endif + + if (!aStatement->mStatementParamsHolder) { + nsCOMPtr<mozIStorageStatementParams> params = + new AsyncStatementParams(aStatement); + NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY); + + JS::RootedObject scope(aCtx, aScopeObj); + nsCOMPtr<nsIXPConnectJSObjectHolder> holder; + nsCOMPtr<nsIXPConnect> xpc(Service::getXPConnect()); + rv = xpc->WrapNativeHolder( + aCtx, + ::JS_GetGlobalForObject(aCtx, scope), + params, + NS_GET_IID(mozIStorageStatementParams), + getter_AddRefs(holder) + ); + NS_ENSURE_SUCCESS(rv, rv); + RefPtr<AsyncStatementParamsHolder> paramsHolder = + new AsyncStatementParamsHolder(holder); + aStatement->mStatementParamsHolder = + new nsMainThreadPtrHolder<nsIXPConnectJSObjectHolder>(paramsHolder); + } + + JS::Rooted<JSObject*> obj(aCtx); + obj = aStatement->mStatementParamsHolder->GetJSObject(); + NS_ENSURE_STATE(obj); + + _params->setObject(*obj); + return NS_OK; +} + +NS_IMETHODIMP_(MozExternalRefCountType) AsyncStatementJSHelper::AddRef() { return 2; } +NS_IMETHODIMP_(MozExternalRefCountType) AsyncStatementJSHelper::Release() { return 1; } +NS_INTERFACE_MAP_BEGIN(AsyncStatementJSHelper) + NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +//////////////////////////////////////////////////////////////////////////////// +//// nsIXPCScriptable + +#define XPC_MAP_CLASSNAME AsyncStatementJSHelper +#define XPC_MAP_QUOTED_CLASSNAME "AsyncStatementJSHelper" +#define XPC_MAP_WANT_GETPROPERTY +#define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE +#include "xpc_map_end.h" + +NS_IMETHODIMP +AsyncStatementJSHelper::GetProperty(nsIXPConnectWrappedNative *aWrapper, + JSContext *aCtx, + JSObject *aScopeObj, + jsid aId, + JS::Value *_result, + bool *_retval) +{ + if (!JSID_IS_STRING(aId)) + return NS_OK; + + // Cast to async via mozI* since direct from nsISupports is ambiguous. + JS::RootedObject scope(aCtx, aScopeObj); + JS::RootedId id(aCtx, aId); + mozIStorageAsyncStatement *iAsyncStmt = + static_cast<mozIStorageAsyncStatement *>(aWrapper->Native()); + AsyncStatement *stmt = static_cast<AsyncStatement *>(iAsyncStmt); + +#ifdef DEBUG + { + nsISupports *supp = aWrapper->Native(); + nsCOMPtr<mozIStorageAsyncStatement> isStatement(do_QueryInterface(supp)); + NS_ASSERTION(isStatement, "How is this not an async statement?!"); + } +#endif + + if (::JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(id), "params")) + return getParams(stmt, aCtx, scope, _result); + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +//// AsyncStatementParamsHolder + +NS_IMPL_ISUPPORTS(AsyncStatementParamsHolder, nsIXPConnectJSObjectHolder); + +JSObject* +AsyncStatementParamsHolder::GetJSObject() +{ + return mHolder->GetJSObject(); +} + +AsyncStatementParamsHolder::AsyncStatementParamsHolder(nsIXPConnectJSObjectHolder* aHolder) + : mHolder(aHolder) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mHolder); +} + +AsyncStatementParamsHolder::~AsyncStatementParamsHolder() +{ + MOZ_ASSERT(NS_IsMainThread()); + // We are considered dead at this point, so any wrappers for row or params + // need to lose their reference to the statement. + nsCOMPtr<nsIXPConnectWrappedNative> wrapper = do_QueryInterface(mHolder); + nsCOMPtr<mozIStorageStatementParams> iObj = do_QueryWrappedNative(wrapper); + AsyncStatementParams *obj = static_cast<AsyncStatementParams *>(iObj.get()); + obj->mStatement = nullptr; +} + +} // namespace storage +} // namespace mozilla diff --git a/components/storage/src/mozStorageAsyncStatementJSHelper.h b/components/storage/src/mozStorageAsyncStatementJSHelper.h new file mode 100644 index 000000000..df28225de --- /dev/null +++ b/components/storage/src/mozStorageAsyncStatementJSHelper.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_storage_mozStorageAsyncStatementJSHelper_h_ +#define mozilla_storage_mozStorageAsyncStatementJSHelper_h_ + +#include "nsIXPCScriptable.h" +#include "nsIXPConnect.h" + +class AsyncStatement; + +namespace mozilla { +namespace storage { + +/** + * A modified version of StatementJSHelper that only exposes the async-specific + * 'params' helper. We do not expose 'row' or 'step' as they do not apply to + * us. + */ +class AsyncStatementJSHelper : public nsIXPCScriptable +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCSCRIPTABLE + +private: + nsresult getParams(AsyncStatement *, JSContext *, JSObject *, JS::Value *); +}; + +/** + * Wrapper used to clean up the references JS helpers hold to the statement. + * For cycle-avoidance reasons they do not hold reference-counted references, + * so it is important we do this. + */ +class AsyncStatementParamsHolder final : public nsIXPConnectJSObjectHolder +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCONNECTJSOBJECTHOLDER + + explicit AsyncStatementParamsHolder(nsIXPConnectJSObjectHolder* aHolder); + +private: + virtual ~AsyncStatementParamsHolder(); + nsCOMPtr<nsIXPConnectJSObjectHolder> mHolder; +}; + +} // namespace storage +} // namespace mozilla + +#endif // mozilla_storage_mozStorageAsyncStatementJSHelper_h_ diff --git a/components/storage/src/mozStorageAsyncStatementParams.cpp b/components/storage/src/mozStorageAsyncStatementParams.cpp new file mode 100644 index 000000000..5e2d8c604 --- /dev/null +++ b/components/storage/src/mozStorageAsyncStatementParams.cpp @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsMemory.h" +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsJSUtils.h" + +#include "jsapi.h" + +#include "mozStoragePrivateHelpers.h" +#include "mozStorageAsyncStatement.h" +#include "mozStorageAsyncStatementParams.h" +#include "mozIStorageStatement.h" + +#include "xpc_make_class.h" + +namespace mozilla { +namespace storage { + +//////////////////////////////////////////////////////////////////////////////// +//// AsyncStatementParams + +AsyncStatementParams::AsyncStatementParams(AsyncStatement *aStatement) +: mStatement(aStatement) +{ + NS_ASSERTION(mStatement != nullptr, "mStatement is null"); +} + +NS_IMPL_ISUPPORTS( + AsyncStatementParams +, mozIStorageStatementParams +, nsIXPCScriptable +) + +//////////////////////////////////////////////////////////////////////////////// +//// nsIXPCScriptable + +#define XPC_MAP_CLASSNAME AsyncStatementParams +#define XPC_MAP_QUOTED_CLASSNAME "AsyncStatementParams" +#define XPC_MAP_WANT_SETPROPERTY +#define XPC_MAP_WANT_RESOLVE +#define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE +#include "xpc_map_end.h" + +NS_IMETHODIMP +AsyncStatementParams::SetProperty( + nsIXPConnectWrappedNative *aWrapper, + JSContext *aCtx, + JSObject *aScopeObj, + jsid aId, + JS::Value *_vp, + bool *_retval +) +{ + NS_ENSURE_TRUE(mStatement, NS_ERROR_NOT_INITIALIZED); + + if (JSID_IS_INT(aId)) { + int idx = JSID_TO_INT(aId); + + nsCOMPtr<nsIVariant> variant(convertJSValToVariant(aCtx, *_vp)); + NS_ENSURE_TRUE(variant, NS_ERROR_UNEXPECTED); + nsresult rv = mStatement->BindByIndex(idx, variant); + NS_ENSURE_SUCCESS(rv, rv); + } + else if (JSID_IS_STRING(aId)) { + JSString *str = JSID_TO_STRING(aId); + nsAutoJSString autoStr; + if (!autoStr.init(aCtx, str)) { + return NS_ERROR_FAILURE; + } + + NS_ConvertUTF16toUTF8 name(autoStr); + + nsCOMPtr<nsIVariant> variant(convertJSValToVariant(aCtx, *_vp)); + NS_ENSURE_TRUE(variant, NS_ERROR_UNEXPECTED); + nsresult rv = mStatement->BindByName(name, variant); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + return NS_ERROR_INVALID_ARG; + } + + *_retval = true; + return NS_OK; +} + +NS_IMETHODIMP +AsyncStatementParams::Resolve(nsIXPConnectWrappedNative *aWrapper, + JSContext *aCtx, + JSObject *aScopeObj, + jsid aId, + bool *aResolvedp, + bool *_retval) +{ + JS::Rooted<JSObject*> scopeObj(aCtx, aScopeObj); + + NS_ENSURE_TRUE(mStatement, NS_ERROR_NOT_INITIALIZED); + // We do not throw at any point after this because we want to allow the + // prototype chain to be checked for the property. + + bool resolved = false; + bool ok = true; + if (JSID_IS_INT(aId)) { + uint32_t idx = JSID_TO_INT(aId); + // All indexes are good because we don't know how many parameters there + // really are. + ok = ::JS_DefineElement(aCtx, scopeObj, idx, JS::UndefinedHandleValue, + JSPROP_RESOLVING); + resolved = true; + } + else if (JSID_IS_STRING(aId)) { + // We are unable to tell if there's a parameter with this name and so + // we must assume that there is. This screws the rest of the prototype + // chain, but people really shouldn't be depending on this anyways. + JS::Rooted<jsid> id(aCtx, aId); + ok = ::JS_DefinePropertyById(aCtx, scopeObj, id, JS::UndefinedHandleValue, + JSPROP_RESOLVING); + resolved = true; + } + + *_retval = ok; + *aResolvedp = resolved && ok; + return NS_OK; +} + +} // namespace storage +} // namespace mozilla diff --git a/components/storage/src/mozStorageAsyncStatementParams.h b/components/storage/src/mozStorageAsyncStatementParams.h new file mode 100644 index 000000000..f753c6399 --- /dev/null +++ b/components/storage/src/mozStorageAsyncStatementParams.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_storage_mozStorageAsyncStatementParams_h_ +#define mozilla_storage_mozStorageAsyncStatementParams_h_ + +#include "mozIStorageStatementParams.h" +#include "nsIXPCScriptable.h" +#include "mozilla/Attributes.h" + +namespace mozilla { +namespace storage { + +class AsyncStatement; + +/* + * Since mozIStorageStatementParams is just a tagging interface we do not have + * an async variant. + */ +class AsyncStatementParams final : public mozIStorageStatementParams + , public nsIXPCScriptable +{ +public: + explicit AsyncStatementParams(AsyncStatement *aStatement); + + // interfaces + NS_DECL_ISUPPORTS + NS_DECL_MOZISTORAGESTATEMENTPARAMS + NS_DECL_NSIXPCSCRIPTABLE + +protected: + virtual ~AsyncStatementParams() {} + + AsyncStatement *mStatement; + + friend class AsyncStatementParamsHolder; +}; + +} // namespace storage +} // namespace mozilla + +#endif // mozilla_storage_mozStorageAsyncStatementParams_h_ diff --git a/components/storage/src/mozStorageBindingParams.cpp b/components/storage/src/mozStorageBindingParams.cpp new file mode 100644 index 000000000..98e114420 --- /dev/null +++ b/components/storage/src/mozStorageBindingParams.cpp @@ -0,0 +1,525 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <limits.h> + +#include "mozilla/UniquePtrExtensions.h" +#include "nsString.h" + +#include "mozStorageError.h" +#include "mozStoragePrivateHelpers.h" +#include "mozStorageBindingParams.h" +#include "mozStorageBindingParamsArray.h" +#include "Variant.h" + +namespace mozilla { +namespace storage { + +//////////////////////////////////////////////////////////////////////////////// +//// Local Helper Objects + +namespace { + +struct BindingColumnData +{ + BindingColumnData(sqlite3_stmt *aStmt, + int aColumn) + : stmt(aStmt) + , column(aColumn) + { + } + sqlite3_stmt *stmt; + int column; +}; + +//////////////////////////////////////////////////////////////////////////////// +//// Variant Specialization Functions (variantToSQLiteT) + +int +sqlite3_T_int(BindingColumnData aData, + int aValue) +{ + return ::sqlite3_bind_int(aData.stmt, aData.column + 1, aValue); +} + +int +sqlite3_T_int64(BindingColumnData aData, + sqlite3_int64 aValue) +{ + return ::sqlite3_bind_int64(aData.stmt, aData.column + 1, aValue); +} + +int +sqlite3_T_double(BindingColumnData aData, + double aValue) +{ + return ::sqlite3_bind_double(aData.stmt, aData.column + 1, aValue); +} + +int +sqlite3_T_text(BindingColumnData aData, + const nsCString& aValue) +{ + return ::sqlite3_bind_text(aData.stmt, + aData.column + 1, + aValue.get(), + aValue.Length(), + SQLITE_TRANSIENT); +} + +int +sqlite3_T_text16(BindingColumnData aData, + const nsString& aValue) +{ + return ::sqlite3_bind_text16(aData.stmt, + aData.column + 1, + aValue.get(), + aValue.Length() * 2, // Length in bytes! + SQLITE_TRANSIENT); +} + +int +sqlite3_T_null(BindingColumnData aData) +{ + return ::sqlite3_bind_null(aData.stmt, aData.column + 1); +} + +int +sqlite3_T_blob(BindingColumnData aData, + const void *aBlob, + int aSize) +{ + return ::sqlite3_bind_blob(aData.stmt, aData.column + 1, aBlob, aSize, + free); + +} + +#include "variantToSQLiteT_impl.h" + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +//// BindingParams + +BindingParams::BindingParams(mozIStorageBindingParamsArray *aOwningArray, + Statement *aOwningStatement) +: mLocked(false) +, mOwningArray(aOwningArray) +, mOwningStatement(aOwningStatement) +, mParamCount(0) +{ + (void)mOwningStatement->GetParameterCount(&mParamCount); + mParameters.SetCapacity(mParamCount); +} + +BindingParams::BindingParams(mozIStorageBindingParamsArray *aOwningArray) +: mLocked(false) +, mOwningArray(aOwningArray) +, mOwningStatement(nullptr) +, mParamCount(0) +{ +} + +AsyncBindingParams::AsyncBindingParams( + mozIStorageBindingParamsArray *aOwningArray +) +: BindingParams(aOwningArray) +{ +} + +void +BindingParams::lock() +{ + NS_ASSERTION(mLocked == false, "Parameters have already been locked!"); + mLocked = true; + + // We no longer need to hold a reference to our statement or our owning array. + // The array owns us at this point, and it will own a reference to the + // statement. + mOwningStatement = nullptr; + mOwningArray = nullptr; +} + +void +BindingParams::unlock(Statement *aOwningStatement) +{ + NS_ASSERTION(mLocked == true, "Parameters were not yet locked!"); + mLocked = false; + mOwningStatement = aOwningStatement; +} + +const mozIStorageBindingParamsArray * +BindingParams::getOwner() const +{ + return mOwningArray; +} + +//////////////////////////////////////////////////////////////////////////////// +//// nsISupports + +NS_IMPL_ISUPPORTS( + BindingParams +, mozIStorageBindingParams +, IStorageBindingParamsInternal +) + + +//////////////////////////////////////////////////////////////////////////////// +//// IStorageBindingParamsInternal + +already_AddRefed<mozIStorageError> +BindingParams::bind(sqlite3_stmt *aStatement) +{ + // Iterate through all of our stored data, and bind it. + for (size_t i = 0; i < mParameters.Length(); i++) { + int rc = variantToSQLiteT(BindingColumnData(aStatement, i), mParameters[i]); + if (rc != SQLITE_OK) { + // We had an error while trying to bind. Now we need to create an error + // object with the right message. Note that we special case + // SQLITE_MISMATCH, but otherwise get the message from SQLite. + const char *msg = "Could not covert nsIVariant to SQLite type."; + if (rc != SQLITE_MISMATCH) + msg = ::sqlite3_errmsg(::sqlite3_db_handle(aStatement)); + + nsCOMPtr<mozIStorageError> err(new Error(rc, msg)); + return err.forget(); + } + } + + return nullptr; +} + +already_AddRefed<mozIStorageError> +AsyncBindingParams::bind(sqlite3_stmt * aStatement) +{ + // We should bind by index using the super-class if there is nothing in our + // hashtable. + if (!mNamedParameters.Count()) + return BindingParams::bind(aStatement); + + nsCOMPtr<mozIStorageError> err; + + for (auto iter = mNamedParameters.Iter(); !iter.Done(); iter.Next()) { + const nsACString &key = iter.Key(); + + // We do not accept any forms of names other than ":name", but we need to + // add the colon for SQLite. + nsAutoCString name(":"); + name.Append(key); + int oneIdx = ::sqlite3_bind_parameter_index(aStatement, name.get()); + + if (oneIdx == 0) { + nsAutoCString errMsg(key); + errMsg.AppendLiteral(" is not a valid named parameter."); + err = new Error(SQLITE_RANGE, errMsg.get()); + break; + } + + // XPCVariant's AddRef and Release are not thread-safe and so we must not + // do anything that would invoke them here on the async thread. As such we + // can't cram aValue into mParameters using ReplaceObjectAt so that + // we can freeload off of the BindingParams::Bind implementation. + int rc = variantToSQLiteT(BindingColumnData(aStatement, oneIdx - 1), + iter.UserData()); + if (rc != SQLITE_OK) { + // We had an error while trying to bind. Now we need to create an error + // object with the right message. Note that we special case + // SQLITE_MISMATCH, but otherwise get the message from SQLite. + const char *msg = "Could not covert nsIVariant to SQLite type."; + if (rc != SQLITE_MISMATCH) { + msg = ::sqlite3_errmsg(::sqlite3_db_handle(aStatement)); + } + err = new Error(rc, msg); + break; + } + } + + return err.forget(); +} + + +/////////////////////////////////////////////////////////////////////////////// +//// mozIStorageBindingParams + +NS_IMETHODIMP +BindingParams::BindByName(const nsACString &aName, + nsIVariant *aValue) +{ + NS_ENSURE_FALSE(mLocked, NS_ERROR_UNEXPECTED); + + // Get the column index that we need to store this at. + uint32_t index; + nsresult rv = mOwningStatement->GetParameterIndex(aName, &index); + NS_ENSURE_SUCCESS(rv, rv); + + return BindByIndex(index, aValue); +} + +NS_IMETHODIMP +AsyncBindingParams::BindByName(const nsACString &aName, + nsIVariant *aValue) +{ + NS_ENSURE_FALSE(mLocked, NS_ERROR_UNEXPECTED); + + RefPtr<Variant_base> variant = convertVariantToStorageVariant(aValue); + if (!variant) + return NS_ERROR_UNEXPECTED; + + mNamedParameters.Put(aName, variant); + return NS_OK; +} + + +NS_IMETHODIMP +BindingParams::BindUTF8StringByName(const nsACString &aName, + const nsACString &aValue) +{ + nsCOMPtr<nsIVariant> value(new UTF8TextVariant(aValue)); + NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); + + return BindByName(aName, value); +} + +NS_IMETHODIMP +BindingParams::BindStringByName(const nsACString &aName, + const nsAString &aValue) +{ + nsCOMPtr<nsIVariant> value(new TextVariant(aValue)); + NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); + + return BindByName(aName, value); +} + +NS_IMETHODIMP +BindingParams::BindDoubleByName(const nsACString &aName, + double aValue) +{ + nsCOMPtr<nsIVariant> value(new FloatVariant(aValue)); + NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); + + return BindByName(aName, value); +} + +NS_IMETHODIMP +BindingParams::BindInt32ByName(const nsACString &aName, + int32_t aValue) +{ + nsCOMPtr<nsIVariant> value(new IntegerVariant(aValue)); + NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); + + return BindByName(aName, value); +} + +NS_IMETHODIMP +BindingParams::BindInt64ByName(const nsACString &aName, + int64_t aValue) +{ + nsCOMPtr<nsIVariant> value(new IntegerVariant(aValue)); + NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); + + return BindByName(aName, value); +} + +NS_IMETHODIMP +BindingParams::BindNullByName(const nsACString &aName) +{ + nsCOMPtr<nsIVariant> value(new NullVariant()); + NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); + + return BindByName(aName, value); +} + +NS_IMETHODIMP +BindingParams::BindBlobByName(const nsACString &aName, + const uint8_t *aValue, + uint32_t aValueSize) +{ + NS_ENSURE_ARG_MAX(aValueSize, INT_MAX); + std::pair<const void *, int> data( + static_cast<const void *>(aValue), + int(aValueSize) + ); + nsCOMPtr<nsIVariant> value(new BlobVariant(data)); + NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); + + return BindByName(aName, value); +} + +NS_IMETHODIMP +BindingParams::BindStringAsBlobByName(const nsACString& aName, + const nsAString& aValue) +{ + return DoBindStringAsBlobByName(this, aName, aValue); +} + +NS_IMETHODIMP +BindingParams::BindUTF8StringAsBlobByName(const nsACString& aName, + const nsACString& aValue) +{ + return DoBindStringAsBlobByName(this, aName, aValue); +} + + +NS_IMETHODIMP +BindingParams::BindAdoptedBlobByName(const nsACString &aName, + uint8_t *aValue, + uint32_t aValueSize) +{ + UniqueFreePtr<uint8_t> uniqueValue(aValue); + NS_ENSURE_ARG_MAX(aValueSize, INT_MAX); + std::pair<uint8_t *, int> data(uniqueValue.release(), int(aValueSize)); + nsCOMPtr<nsIVariant> value(new AdoptedBlobVariant(data)); + + return BindByName(aName, value); +} + +NS_IMETHODIMP +BindingParams::BindByIndex(uint32_t aIndex, + nsIVariant *aValue) +{ + NS_ENSURE_FALSE(mLocked, NS_ERROR_UNEXPECTED); + ENSURE_INDEX_VALUE(aIndex, mParamCount); + + // Store the variant for later use. + RefPtr<Variant_base> variant = convertVariantToStorageVariant(aValue); + if (!variant) + return NS_ERROR_UNEXPECTED; + if (mParameters.Length() <= aIndex) { + (void)mParameters.SetLength(aIndex); + (void)mParameters.AppendElement(variant); + } + else { + NS_ENSURE_TRUE(mParameters.ReplaceElementAt(aIndex, variant), + NS_ERROR_OUT_OF_MEMORY); + } + return NS_OK; +} + +NS_IMETHODIMP +AsyncBindingParams::BindByIndex(uint32_t aIndex, + nsIVariant *aValue) +{ + NS_ENSURE_FALSE(mLocked, NS_ERROR_UNEXPECTED); + // In the asynchronous case we do not know how many parameters there are to + // bind to, so we cannot check the validity of aIndex. + + RefPtr<Variant_base> variant = convertVariantToStorageVariant(aValue); + if (!variant) + return NS_ERROR_UNEXPECTED; + if (mParameters.Length() <= aIndex) { + mParameters.SetLength(aIndex); + mParameters.AppendElement(variant); + } + else { + NS_ENSURE_TRUE(mParameters.ReplaceElementAt(aIndex, variant), + NS_ERROR_OUT_OF_MEMORY); + } + return NS_OK; +} + +NS_IMETHODIMP +BindingParams::BindUTF8StringByIndex(uint32_t aIndex, + const nsACString &aValue) +{ + nsCOMPtr<nsIVariant> value(new UTF8TextVariant(aValue)); + NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); + + return BindByIndex(aIndex, value); +} + +NS_IMETHODIMP +BindingParams::BindStringByIndex(uint32_t aIndex, + const nsAString &aValue) +{ + nsCOMPtr<nsIVariant> value(new TextVariant(aValue)); + NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); + + return BindByIndex(aIndex, value); +} + +NS_IMETHODIMP +BindingParams::BindDoubleByIndex(uint32_t aIndex, + double aValue) +{ + nsCOMPtr<nsIVariant> value(new FloatVariant(aValue)); + NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); + + return BindByIndex(aIndex, value); +} + +NS_IMETHODIMP +BindingParams::BindInt32ByIndex(uint32_t aIndex, + int32_t aValue) +{ + nsCOMPtr<nsIVariant> value(new IntegerVariant(aValue)); + NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); + + return BindByIndex(aIndex, value); +} + +NS_IMETHODIMP +BindingParams::BindInt64ByIndex(uint32_t aIndex, + int64_t aValue) +{ + nsCOMPtr<nsIVariant> value(new IntegerVariant(aValue)); + NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); + + return BindByIndex(aIndex, value); +} + +NS_IMETHODIMP +BindingParams::BindNullByIndex(uint32_t aIndex) +{ + nsCOMPtr<nsIVariant> value(new NullVariant()); + NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); + + return BindByIndex(aIndex, value); +} + +NS_IMETHODIMP +BindingParams::BindBlobByIndex(uint32_t aIndex, + const uint8_t *aValue, + uint32_t aValueSize) +{ + NS_ENSURE_ARG_MAX(aValueSize, INT_MAX); + std::pair<const void *, int> data( + static_cast<const void *>(aValue), + int(aValueSize) + ); + nsCOMPtr<nsIVariant> value(new BlobVariant(data)); + NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); + + return BindByIndex(aIndex, value); +} + +NS_IMETHODIMP +BindingParams::BindStringAsBlobByIndex(uint32_t aIndex, const nsAString& aValue) +{ + return DoBindStringAsBlobByIndex(this, aIndex, aValue); +} + +NS_IMETHODIMP +BindingParams::BindUTF8StringAsBlobByIndex(uint32_t aIndex, + const nsACString& aValue) +{ + return DoBindStringAsBlobByIndex(this, aIndex, aValue); +} + +NS_IMETHODIMP +BindingParams::BindAdoptedBlobByIndex(uint32_t aIndex, + uint8_t *aValue, + uint32_t aValueSize) +{ + UniqueFreePtr<uint8_t> uniqueValue(aValue); + NS_ENSURE_ARG_MAX(aValueSize, INT_MAX); + std::pair<uint8_t *, int> data(uniqueValue.release(), int(aValueSize)); + nsCOMPtr<nsIVariant> value(new AdoptedBlobVariant(data)); + + return BindByIndex(aIndex, value); +} + +} // namespace storage +} // namespace mozilla diff --git a/components/storage/src/mozStorageBindingParams.h b/components/storage/src/mozStorageBindingParams.h new file mode 100644 index 000000000..86d00b02b --- /dev/null +++ b/components/storage/src/mozStorageBindingParams.h @@ -0,0 +1,113 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozStorageBindingParams_h +#define mozStorageBindingParams_h + +#include "nsCOMArray.h" +#include "nsIVariant.h" +#include "nsInterfaceHashtable.h" + +#include "mozStorageBindingParamsArray.h" +#include "mozStorageStatement.h" +#include "mozStorageAsyncStatement.h" +#include "Variant.h" + +#include "mozIStorageBindingParams.h" +#include "IStorageBindingParamsInternal.h" + +namespace mozilla { +namespace storage { + +class BindingParams : public mozIStorageBindingParams + , public IStorageBindingParamsInternal +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_MOZISTORAGEBINDINGPARAMS + NS_DECL_ISTORAGEBINDINGPARAMSINTERNAL + + /** + * Locks the parameters and prevents further modification to it (such as + * binding more elements to it). + */ + void lock(); + + /** + * Unlocks the parameters and allows modification to it again. + * + * @param aOwningStatement + * The statement that owns us. We cleared this when we were locked, + * and our invariant requires us to have this, so you need to tell us + * again. + */ + void unlock(Statement *aOwningStatement); + + /** + * @returns the pointer to the owning BindingParamsArray. Used by a + * BindingParamsArray to verify that we belong to it when added. + */ + const mozIStorageBindingParamsArray *getOwner() const; + + BindingParams(mozIStorageBindingParamsArray *aOwningArray, + Statement *aOwningStatement); +protected: + virtual ~BindingParams() {} + + explicit BindingParams(mozIStorageBindingParamsArray *aOwningArray); + // Note that this is managed as a sparse array, so particular caution should + // be used for out-of-bounds usage. + nsTArray<RefPtr<Variant_base> > mParameters; + bool mLocked; + +private: + + /** + * Track the BindingParamsArray that created us until we are added to it. + * (Once we are added we are locked and no one needs to look up our owner.) + * Ref-counted since there is no invariant that guarantees it stays alive + * otherwise. This keeps mOwningStatement alive for us too since the array + * also holds a reference. + */ + nsCOMPtr<mozIStorageBindingParamsArray> mOwningArray; + /** + * Used in the synchronous binding case to map parameter names to indices. + * Not reference-counted because this is only non-null as long as mOwningArray + * is non-null and mOwningArray also holds a statement reference. + */ + Statement *mOwningStatement; + uint32_t mParamCount; +}; + +/** + * Adds late resolution of named parameters so they don't get resolved until we + * try and bind the parameters on the async thread. We also stop checking + * parameter indices for being too big since we just just don't know how many + * there are. + * + * We support *either* binding by name or binding by index. Trying to do both + * results in only binding by name at sqlite3_stmt bind time. + */ +class AsyncBindingParams : public BindingParams +{ +public: + NS_IMETHOD BindByName(const nsACString & aName, + nsIVariant *aValue); + NS_IMETHOD BindByIndex(uint32_t aIndex, nsIVariant *aValue); + + virtual already_AddRefed<mozIStorageError> bind(sqlite3_stmt * aStatement); + + explicit AsyncBindingParams(mozIStorageBindingParamsArray *aOwningArray); + virtual ~AsyncBindingParams() {} + +private: + nsInterfaceHashtable<nsCStringHashKey, nsIVariant> mNamedParameters; +}; + +} // namespace storage +} // namespace mozilla + +#endif // mozStorageBindingParams_h diff --git a/components/storage/src/mozStorageBindingParamsArray.cpp b/components/storage/src/mozStorageBindingParamsArray.cpp new file mode 100644 index 000000000..fb7c9f14a --- /dev/null +++ b/components/storage/src/mozStorageBindingParamsArray.cpp @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozStorageBindingParamsArray.h" +#include "mozStorageBindingParams.h" +#include "StorageBaseStatementInternal.h" + +namespace mozilla { +namespace storage { + +//////////////////////////////////////////////////////////////////////////////// +//// BindingParamsArray + +BindingParamsArray::BindingParamsArray( + StorageBaseStatementInternal *aOwningStatement +) +: mOwningStatement(aOwningStatement) +, mLocked(false) +{ +} + +void +BindingParamsArray::lock() +{ + NS_ASSERTION(mLocked == false, "Array has already been locked!"); + mLocked = true; + + // We also no longer need to hold a reference to our statement since it owns + // us. + mOwningStatement = nullptr; +} + +const StorageBaseStatementInternal * +BindingParamsArray::getOwner() const +{ + return mOwningStatement; +} + +NS_IMPL_ISUPPORTS( + BindingParamsArray, + mozIStorageBindingParamsArray +) + +/////////////////////////////////////////////////////////////////////////////// +//// mozIStorageBindingParamsArray + +NS_IMETHODIMP +BindingParamsArray::NewBindingParams(mozIStorageBindingParams **_params) +{ + NS_ENSURE_FALSE(mLocked, NS_ERROR_UNEXPECTED); + + nsCOMPtr<mozIStorageBindingParams> params( + mOwningStatement->newBindingParams(this)); + NS_ENSURE_TRUE(params, NS_ERROR_UNEXPECTED); + + params.forget(_params); + return NS_OK; +} + +NS_IMETHODIMP +BindingParamsArray::AddParams(mozIStorageBindingParams *aParameters) +{ + NS_ENSURE_FALSE(mLocked, NS_ERROR_UNEXPECTED); + + BindingParams *params = static_cast<BindingParams *>(aParameters); + + // Check to make sure that this set of parameters was created with us. + if (params->getOwner() != this) + return NS_ERROR_UNEXPECTED; + + NS_ENSURE_TRUE(mArray.AppendElement(params), NS_ERROR_OUT_OF_MEMORY); + + // Lock the parameters only after we've successfully added them. + params->lock(); + + return NS_OK; +} + +NS_IMETHODIMP +BindingParamsArray::GetLength(uint32_t *_length) +{ + *_length = length(); + return NS_OK; +} + +} // namespace storage +} // namespace mozilla diff --git a/components/storage/src/mozStorageBindingParamsArray.h b/components/storage/src/mozStorageBindingParamsArray.h new file mode 100644 index 000000000..4626ab55f --- /dev/null +++ b/components/storage/src/mozStorageBindingParamsArray.h @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozStorageBindingParamsArray_h +#define mozStorageBindingParamsArray_h + +#include "nsAutoPtr.h" +#include "nsTArray.h" +#include "mozilla/Attributes.h" + +#include "mozIStorageBindingParamsArray.h" + +namespace mozilla { +namespace storage { + +class StorageBaseStatementInternal; + +class BindingParamsArray final : public mozIStorageBindingParamsArray +{ + typedef nsTArray< nsCOMPtr<mozIStorageBindingParams> > array_type; + + ~BindingParamsArray() {} + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_MOZISTORAGEBINDINGPARAMSARRAY + + explicit BindingParamsArray(StorageBaseStatementInternal *aOwningStatement); + + typedef array_type::size_type size_type; + + /** + * Locks the array and prevents further modification to it (such as adding + * more elements to it). + */ + void lock(); + + /** + * @return the pointer to the owning BindingParamsArray. + */ + const StorageBaseStatementInternal *getOwner() const; + + /** + * @return the number of elemets the array contains. + */ + size_type length() const { return mArray.Length(); } + + class iterator { + public: + iterator(BindingParamsArray *aArray, + uint32_t aIndex) + : mArray(aArray) + , mIndex(aIndex) + { + } + + iterator &operator++(int) + { + mIndex++; + return *this; + } + + bool operator==(const iterator &aOther) const + { + return mIndex == aOther.mIndex; + } + bool operator!=(const iterator &aOther) const + { + return !(*this == aOther); + } + mozIStorageBindingParams *operator*() + { + NS_ASSERTION(mIndex < mArray->length(), + "Dereferenceing an invalid value!"); + return mArray->mArray[mIndex].get(); + } + private: + void operator--() { } + BindingParamsArray *mArray; + uint32_t mIndex; + }; + + /** + * Obtains an iterator pointing to the beginning of the array. + */ + inline iterator begin() + { + NS_ASSERTION(length() != 0, + "Obtaining an iterator to the beginning with no elements!"); + return iterator(this, 0); + } + + /** + * Obtains an iterator pointing to the end of the array. + */ + inline iterator end() + { + NS_ASSERTION(mLocked, + "Obtaining an iterator to the end when we are not locked!"); + return iterator(this, length()); + } +private: + nsCOMPtr<StorageBaseStatementInternal> mOwningStatement; + array_type mArray; + bool mLocked; + + friend class iterator; +}; + +} // namespace storage +} // namespace mozilla + +#endif // mozStorageBindingParamsArray_h diff --git a/components/storage/src/mozStorageCID.h b/components/storage/src/mozStorageCID.h new file mode 100644 index 000000000..c682d07dd --- /dev/null +++ b/components/storage/src/mozStorageCID.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZSTORAGECID_H +#define MOZSTORAGECID_H + +#define MOZ_STORAGE_CONTRACTID_PREFIX "@mozilla.org/storage" + + +/* b71a1f84-3a70-4d37-a348-f1ba0e27eead */ +#define MOZ_STORAGE_CONNECTION_CID \ +{ 0xb71a1f84, 0x3a70, 0x4d37, {0xa3, 0x48, 0xf1, 0xba, 0x0e, 0x27, 0xee, 0xad} } + +#define MOZ_STORAGE_CONNECTION_CONTRACTID MOZ_STORAGE_CONTRACTID_PREFIX "/connection;1" + +/* bbbb1d61-438f-4436-92ed-8308e5830fb0 */ +#define MOZ_STORAGE_SERVICE_CID \ +{ 0xbbbb1d61, 0x438f, 0x4436, {0x92, 0xed, 0x83, 0x08, 0xe5, 0x83, 0x0f, 0xb0} } + +#define MOZ_STORAGE_SERVICE_CONTRACTID MOZ_STORAGE_CONTRACTID_PREFIX "/service;1" + +/* 3b667ee0-d2da-4ccc-9c3d-95f2ca6a8b4c */ +#define VACUUMMANAGER_CID \ +{ 0x3b667ee0, 0xd2da, 0x4ccc, { 0x9c, 0x3d, 0x95, 0xf2, 0xca, 0x6a, 0x8b, 0x4c } } + +#define VACUUMMANAGER_CONTRACTID MOZ_STORAGE_CONTRACTID_PREFIX "/vacuum;1" + +#endif /* MOZSTORAGECID_H */ diff --git a/components/storage/src/mozStorageConnection.cpp b/components/storage/src/mozStorageConnection.cpp new file mode 100644 index 000000000..c1d5374c7 --- /dev/null +++ b/components/storage/src/mozStorageConnection.cpp @@ -0,0 +1,1994 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdio.h> + +#include "nsError.h" +#include "nsIMutableArray.h" +#include "nsAutoPtr.h" +#include "nsIMemoryReporter.h" +#include "nsThreadUtils.h" +#include "nsIFile.h" +#include "nsIFileURL.h" +#include "mozilla/Mutex.h" +#include "mozilla/CondVar.h" +#include "mozilla/Attributes.h" +#include "mozilla/ErrorNames.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/quota/QuotaObject.h" + +#include "mozIStorageAggregateFunction.h" +#include "mozIStorageCompletionCallback.h" +#include "mozIStorageFunction.h" + +#include "mozStorageAsyncStatementExecution.h" +#include "mozStorageSQLFunctions.h" +#include "mozStorageConnection.h" +#include "mozStorageService.h" +#include "mozStorageStatement.h" +#include "mozStorageAsyncStatement.h" +#include "mozStorageArgValueArray.h" +#include "mozStoragePrivateHelpers.h" +#include "mozStorageStatementData.h" +#include "StorageBaseStatementInternal.h" +#include "SQLCollations.h" +#include "FileSystemModule.h" +#include "mozStorageHelper.h" +#include "GeckoProfiler.h" + +#include "mozilla/Logging.h" +#include "prprf.h" +#include "nsProxyRelease.h" +#include <algorithm> + +#define MIN_AVAILABLE_BYTES_PER_CHUNKED_GROWTH 524288000 // 500 MiB + +// Maximum size of the pages cache per connection. +#define MAX_CACHE_SIZE_KIBIBYTES 2048 // 2 MiB + +mozilla::LazyLogModule gStorageLog("mozStorage"); + +// Checks that the protected code is running on the main-thread only if the +// connection was also opened on it. +#ifdef DEBUG +#define CHECK_MAINTHREAD_ABUSE() \ + do { \ + nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); \ + NS_WARNING_ASSERTION( \ + threadOpenedOn == mainThread || !NS_IsMainThread(), \ + "Using Storage synchronous API on main-thread, but the connection was " \ + "opened on another thread."); \ + } while(0) +#else +#define CHECK_MAINTHREAD_ABUSE() do { /* Nothing */ } while(0) +#endif + +namespace mozilla { +namespace storage { + +using mozilla::dom::quota::QuotaObject; + +namespace { + +int +nsresultToSQLiteResult(nsresult aXPCOMResultCode) +{ + if (NS_SUCCEEDED(aXPCOMResultCode)) { + return SQLITE_OK; + } + + switch (aXPCOMResultCode) { + case NS_ERROR_FILE_CORRUPTED: + return SQLITE_CORRUPT; + case NS_ERROR_FILE_ACCESS_DENIED: + return SQLITE_CANTOPEN; + case NS_ERROR_STORAGE_BUSY: + return SQLITE_BUSY; + case NS_ERROR_FILE_IS_LOCKED: + return SQLITE_LOCKED; + case NS_ERROR_FILE_READ_ONLY: + return SQLITE_READONLY; + case NS_ERROR_STORAGE_IOERR: + return SQLITE_IOERR; + case NS_ERROR_FILE_NO_DEVICE_SPACE: + return SQLITE_FULL; + case NS_ERROR_OUT_OF_MEMORY: + return SQLITE_NOMEM; + case NS_ERROR_UNEXPECTED: + return SQLITE_MISUSE; + case NS_ERROR_ABORT: + return SQLITE_ABORT; + case NS_ERROR_STORAGE_CONSTRAINT: + return SQLITE_CONSTRAINT; + default: + return SQLITE_ERROR; + } + + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Must return in switch above!"); +} + +//////////////////////////////////////////////////////////////////////////////// +//// Variant Specialization Functions (variantToSQLiteT) + +int +sqlite3_T_int(sqlite3_context *aCtx, + int aValue) +{ + ::sqlite3_result_int(aCtx, aValue); + return SQLITE_OK; +} + +int +sqlite3_T_int64(sqlite3_context *aCtx, + sqlite3_int64 aValue) +{ + ::sqlite3_result_int64(aCtx, aValue); + return SQLITE_OK; +} + +int +sqlite3_T_double(sqlite3_context *aCtx, + double aValue) +{ + ::sqlite3_result_double(aCtx, aValue); + return SQLITE_OK; +} + +int +sqlite3_T_text(sqlite3_context *aCtx, + const nsCString &aValue) +{ + ::sqlite3_result_text(aCtx, + aValue.get(), + aValue.Length(), + SQLITE_TRANSIENT); + return SQLITE_OK; +} + +int +sqlite3_T_text16(sqlite3_context *aCtx, + const nsString &aValue) +{ + ::sqlite3_result_text16(aCtx, + aValue.get(), + aValue.Length() * 2, // Number of bytes. + SQLITE_TRANSIENT); + return SQLITE_OK; +} + +int +sqlite3_T_null(sqlite3_context *aCtx) +{ + ::sqlite3_result_null(aCtx); + return SQLITE_OK; +} + +int +sqlite3_T_blob(sqlite3_context *aCtx, + const void *aData, + int aSize) +{ + ::sqlite3_result_blob(aCtx, aData, aSize, free); + return SQLITE_OK; +} + +#include "variantToSQLiteT_impl.h" + +//////////////////////////////////////////////////////////////////////////////// +//// Modules + +struct Module +{ + const char* name; + int (*registerFunc)(sqlite3*, const char*); +}; + +Module gModules[] = { + { "filesystem", RegisterFileSystemModule } +}; + +//////////////////////////////////////////////////////////////////////////////// +//// Local Functions + +int tracefunc (unsigned aReason, void *aClosure, void *aP, void *aX) +{ + switch (aReason) { + case SQLITE_TRACE_STMT: { + // aP is a pointer to the prepared statement. + sqlite3_stmt* stmt = static_cast<sqlite3_stmt*>(aP); + // aX is a pointer to a string containing the unexpanded SQL or a comment, + // starting with "--"" in case of a trigger. + char* expanded = static_cast<char*>(aX); + // Simulate what sqlite_trace was doing. + if (!::strncmp(expanded, "--", 2)) { + MOZ_LOG(gStorageLog, LogLevel::Debug, + ("TRACE_STMT on %p: '%s'", aClosure, expanded)); + } else { + char* sql = ::sqlite3_expanded_sql(stmt); + MOZ_LOG(gStorageLog, LogLevel::Debug, + ("TRACE_STMT on %p: '%s'", aClosure, sql)); + ::sqlite3_free(sql); + } + break; + } + case SQLITE_TRACE_PROFILE: { + // aX is pointer to a 64bit integer containing nanoseconds it took to + // execute the last command. + sqlite_int64 time = *(static_cast<sqlite_int64*>(aX)) / 1000000; + if (time > 0) { + MOZ_LOG(gStorageLog, LogLevel::Debug, + ("TRACE_TIME on %p: %dms", aClosure, time)); + } + break; + } + } + return 0; +} + +void +basicFunctionHelper(sqlite3_context *aCtx, + int aArgc, + sqlite3_value **aArgv) +{ + void *userData = ::sqlite3_user_data(aCtx); + + mozIStorageFunction *func = static_cast<mozIStorageFunction *>(userData); + + RefPtr<ArgValueArray> arguments(new ArgValueArray(aArgc, aArgv)); + if (!arguments) + return; + + nsCOMPtr<nsIVariant> result; + nsresult rv = func->OnFunctionCall(arguments, getter_AddRefs(result)); + if (NS_FAILED(rv)) { + nsAutoCString errorMessage; + GetErrorName(rv, errorMessage); + errorMessage.InsertLiteral("User function returned ", 0); + errorMessage.Append('!'); + + NS_WARNING(errorMessage.get()); + + ::sqlite3_result_error(aCtx, errorMessage.get(), -1); + ::sqlite3_result_error_code(aCtx, nsresultToSQLiteResult(rv)); + return; + } + int retcode = variantToSQLiteT(aCtx, result); + if (retcode == SQLITE_IGNORE) { + ::sqlite3_result_int(aCtx, SQLITE_IGNORE); + } else if (retcode != SQLITE_OK) { + NS_WARNING("User function returned invalid data type!"); + ::sqlite3_result_error(aCtx, + "User function returned invalid data type", + -1); + } +} + +void +aggregateFunctionStepHelper(sqlite3_context *aCtx, + int aArgc, + sqlite3_value **aArgv) +{ + void *userData = ::sqlite3_user_data(aCtx); + mozIStorageAggregateFunction *func = + static_cast<mozIStorageAggregateFunction *>(userData); + + RefPtr<ArgValueArray> arguments(new ArgValueArray(aArgc, aArgv)); + if (!arguments) + return; + + if (NS_FAILED(func->OnStep(arguments))) + NS_WARNING("User aggregate step function returned error code!"); +} + +void +aggregateFunctionFinalHelper(sqlite3_context *aCtx) +{ + void *userData = ::sqlite3_user_data(aCtx); + mozIStorageAggregateFunction *func = + static_cast<mozIStorageAggregateFunction *>(userData); + + RefPtr<nsIVariant> result; + if (NS_FAILED(func->OnFinal(getter_AddRefs(result)))) { + NS_WARNING("User aggregate final function returned error code!"); + ::sqlite3_result_error(aCtx, + "User aggregate final function returned error code", + -1); + return; + } + + if (variantToSQLiteT(aCtx, result) != SQLITE_OK) { + NS_WARNING("User aggregate final function returned invalid data type!"); + ::sqlite3_result_error(aCtx, + "User aggregate final function returned invalid data type", + -1); + } +} + +/** + * This code is heavily based on the sample at: + * http://www.sqlite.org/unlock_notify.html + */ +class UnlockNotification +{ +public: + UnlockNotification() + : mMutex("UnlockNotification mMutex") + , mCondVar(mMutex, "UnlockNotification condVar") + , mSignaled(false) + { + } + + void Wait() + { + MutexAutoLock lock(mMutex); + while (!mSignaled) { + (void)mCondVar.Wait(); + } + } + + void Signal() + { + MutexAutoLock lock(mMutex); + mSignaled = true; + (void)mCondVar.Notify(); + } + +private: + Mutex mMutex; + CondVar mCondVar; + bool mSignaled; +}; + +void +UnlockNotifyCallback(void **aArgs, + int aArgsSize) +{ + for (int i = 0; i < aArgsSize; i++) { + UnlockNotification *notification = + static_cast<UnlockNotification *>(aArgs[i]); + notification->Signal(); + } +} + +int +WaitForUnlockNotify(sqlite3* aDatabase) +{ + UnlockNotification notification; + int srv = ::sqlite3_unlock_notify(aDatabase, UnlockNotifyCallback, + ¬ification); + MOZ_ASSERT(srv == SQLITE_LOCKED || srv == SQLITE_OK); + if (srv == SQLITE_OK) { + notification.Wait(); + } + + return srv; +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +//// Local Classes + +namespace { + +class AsyncCloseConnection final: public Runnable +{ +public: + AsyncCloseConnection(Connection *aConnection, + sqlite3 *aNativeConnection, + nsIRunnable *aCallbackEvent, + already_AddRefed<nsIThread> aAsyncExecutionThread) + : mConnection(aConnection) + , mNativeConnection(aNativeConnection) + , mCallbackEvent(aCallbackEvent) + , mAsyncExecutionThread(aAsyncExecutionThread) + { + } + + NS_IMETHOD Run() override + { +#ifdef DEBUG + // This code is executed on the background thread + bool onAsyncThread = false; + (void)mAsyncExecutionThread->IsOnCurrentThread(&onAsyncThread); + MOZ_ASSERT(onAsyncThread); +#endif // DEBUG + + nsCOMPtr<nsIRunnable> event = NewRunnableMethod<nsCOMPtr<nsIThread>> + (mConnection, &Connection::shutdownAsyncThread, mAsyncExecutionThread); + (void)NS_DispatchToMainThread(event); + + // Internal close. + (void)mConnection->internalClose(mNativeConnection); + + // Callback + if (mCallbackEvent) { + nsCOMPtr<nsIThread> thread; + (void)NS_GetMainThread(getter_AddRefs(thread)); + (void)thread->Dispatch(mCallbackEvent, NS_DISPATCH_NORMAL); + } + + return NS_OK; + } + + ~AsyncCloseConnection() { + NS_ReleaseOnMainThread(mConnection.forget()); + NS_ReleaseOnMainThread(mCallbackEvent.forget()); + } +private: + RefPtr<Connection> mConnection; + sqlite3 *mNativeConnection; + nsCOMPtr<nsIRunnable> mCallbackEvent; + nsCOMPtr<nsIThread> mAsyncExecutionThread; +}; + +/** + * An event used to initialize the clone of a connection. + * + * Must be executed on the clone's async execution thread. + */ +class AsyncInitializeClone final: public Runnable +{ +public: + /** + * @param aConnection The connection being cloned. + * @param aClone The clone. + * @param aReadOnly If |true|, the clone is read only. + * @param aCallback A callback to trigger once initialization + * is complete. This event will be called on + * aClone->threadOpenedOn. + */ + AsyncInitializeClone(Connection* aConnection, + Connection* aClone, + const bool aReadOnly, + mozIStorageCompletionCallback* aCallback) + : mConnection(aConnection) + , mClone(aClone) + , mReadOnly(aReadOnly) + , mCallback(aCallback) + { + MOZ_ASSERT(NS_IsMainThread()); + } + + NS_IMETHOD Run() override { + MOZ_ASSERT (NS_GetCurrentThread() == mConnection->getAsyncExecutionTarget()); + + nsresult rv = mConnection->initializeClone(mClone, mReadOnly); + if (NS_FAILED(rv)) { + return Dispatch(rv, nullptr); + } + return Dispatch(NS_OK, + NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, mClone)); + } + +private: + nsresult Dispatch(nsresult aResult, nsISupports* aValue) { + RefPtr<CallbackComplete> event = new CallbackComplete(aResult, + aValue, + mCallback.forget()); + return mClone->threadOpenedOn->Dispatch(event, NS_DISPATCH_NORMAL); + } + + ~AsyncInitializeClone() { + nsCOMPtr<nsIThread> thread; + DebugOnly<nsresult> rv = NS_GetMainThread(getter_AddRefs(thread)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + // Handle ambiguous nsISupports inheritance. + NS_ProxyRelease(thread, mConnection.forget()); + NS_ProxyRelease(thread, mClone.forget()); + + // Generally, the callback will be released by CallbackComplete. + // However, if for some reason Run() is not executed, we still + // need to ensure that it is released here. + NS_ProxyRelease(thread, mCallback.forget()); + } + + RefPtr<Connection> mConnection; + RefPtr<Connection> mClone; + const bool mReadOnly; + nsCOMPtr<mozIStorageCompletionCallback> mCallback; +}; + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +//// Connection + +Connection::Connection(Service *aService, + int aFlags, + bool aAsyncOnly, + bool aIgnoreLockingMode) +: sharedAsyncExecutionMutex("Connection::sharedAsyncExecutionMutex") +, sharedDBMutex("Connection::sharedDBMutex") +, threadOpenedOn(do_GetCurrentThread()) +, mDBConn(nullptr) +, mAsyncExecutionThreadShuttingDown(false) +#ifdef DEBUG +, mAsyncExecutionThreadIsAlive(false) +#endif +, mConnectionClosed(false) +, mTransactionInProgress(false) +, mProgressHandler(nullptr) +, mFlags(aFlags) +, mIgnoreLockingMode(aIgnoreLockingMode) +, mStorageService(aService) +, mAsyncOnly(aAsyncOnly) +{ + MOZ_ASSERT(!mIgnoreLockingMode || mFlags & SQLITE_OPEN_READONLY, + "Can't ignore locking for a non-readonly connection!"); + mStorageService->registerConnection(this); +} + +Connection::~Connection() +{ + (void)Close(); + + MOZ_ASSERT(!mAsyncExecutionThread, + "AsyncClose has not been invoked on this connection!"); + MOZ_ASSERT(!mAsyncExecutionThreadIsAlive, + "The async execution thread should have been shutdown!"); +} + +NS_IMPL_ADDREF(Connection) + +NS_INTERFACE_MAP_BEGIN(Connection) + NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncConnection) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(mozIStorageConnection, !mAsyncOnly) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageConnection) +NS_INTERFACE_MAP_END + +// This is identical to what NS_IMPL_RELEASE provides, but with the +// extra |1 == count| case. +NS_IMETHODIMP_(MozExternalRefCountType) Connection::Release(void) +{ + NS_PRECONDITION(0 != mRefCnt, "dup release"); + nsrefcnt count = --mRefCnt; + NS_LOG_RELEASE(this, count, "Connection"); + if (1 == count) { + // If the refcount is 1, the single reference must be from + // gService->mConnections (in class |Service|). Which means we can + // unregister it safely. + mStorageService->unregisterConnection(this); + } else if (0 == count) { + mRefCnt = 1; /* stabilize */ +#if 0 /* enable this to find non-threadsafe destructors: */ + NS_ASSERT_OWNINGTHREAD(Connection); +#endif + delete (this); + return 0; + } + return count; +} + +int32_t +Connection::getSqliteRuntimeStatus(int32_t aStatusOption, int32_t* aMaxValue) +{ + MOZ_ASSERT(mDBConn, "A connection must exist at this point"); + int curr = 0, max = 0; + DebugOnly<int> rc = ::sqlite3_db_status(mDBConn, aStatusOption, &curr, &max, 0); + MOZ_ASSERT(NS_SUCCEEDED(convertResultCode(rc))); + if (aMaxValue) + *aMaxValue = max; + return curr; +} + +nsIEventTarget * +Connection::getAsyncExecutionTarget() +{ + MutexAutoLock lockedScope(sharedAsyncExecutionMutex); + + // If we are shutting down the asynchronous thread, don't hand out any more + // references to the thread. + if (mAsyncExecutionThreadShuttingDown) + return nullptr; + + if (!mAsyncExecutionThread) { + nsresult rv = ::NS_NewThread(getter_AddRefs(mAsyncExecutionThread)); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to create async thread."); + return nullptr; + } + static nsThreadPoolNaming naming; + naming.SetThreadPoolName(NS_LITERAL_CSTRING("mozStorage"), + mAsyncExecutionThread); + } + +#ifdef DEBUG + mAsyncExecutionThreadIsAlive = true; +#endif + + return mAsyncExecutionThread; +} + +nsresult +Connection::initialize() +{ + NS_ASSERTION (!mDBConn, "Initialize called on already opened database!"); + MOZ_ASSERT(!mIgnoreLockingMode, "Can't ignore locking on an in-memory db."); + PROFILER_LABEL("mozStorageConnection", "initialize", + js::ProfileEntry::Category::STORAGE); + + // in memory database requested, sqlite uses a magic file name + int srv = ::sqlite3_open_v2(":memory:", &mDBConn, mFlags, nullptr); + if (srv != SQLITE_OK) { + mDBConn = nullptr; + return convertResultCode(srv); + } + + // Do not set mDatabaseFile or mFileURL here since this is a "memory" + // database. + + nsresult rv = initializeInternal(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +Connection::initialize(nsIFile *aDatabaseFile) +{ + NS_ASSERTION (aDatabaseFile, "Passed null file!"); + NS_ASSERTION (!mDBConn, "Initialize called on already opened database!"); + PROFILER_LABEL("mozStorageConnection", "initialize", + js::ProfileEntry::Category::STORAGE); + + mDatabaseFile = aDatabaseFile; + + nsAutoString path; + nsresult rv = aDatabaseFile->GetPath(path); + NS_ENSURE_SUCCESS(rv, rv); + +#ifdef XP_WIN + static const char* sIgnoreLockingVFS = "win32-none"; +#else + static const char* sIgnoreLockingVFS = "unix-none"; +#endif + const char* vfs = mIgnoreLockingMode ? sIgnoreLockingVFS : nullptr; + + int srv = ::sqlite3_open_v2(NS_ConvertUTF16toUTF8(path).get(), &mDBConn, + mFlags, vfs); + if (srv != SQLITE_OK) { + mDBConn = nullptr; + return convertResultCode(srv); + } + + // Do not set mFileURL here since this is database does not have an associated + // URL. + mDatabaseFile = aDatabaseFile; + + rv = initializeInternal(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +Connection::initialize(nsIFileURL *aFileURL) +{ + NS_ASSERTION (aFileURL, "Passed null file URL!"); + NS_ASSERTION (!mDBConn, "Initialize called on already opened database!"); + PROFILER_LABEL("mozStorageConnection", "initialize", + js::ProfileEntry::Category::STORAGE); + + nsCOMPtr<nsIFile> databaseFile; + nsresult rv = aFileURL->GetFile(getter_AddRefs(databaseFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString spec; + rv = aFileURL->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + int srv = ::sqlite3_open_v2(spec.get(), &mDBConn, mFlags, nullptr); + if (srv != SQLITE_OK) { + mDBConn = nullptr; + return convertResultCode(srv); + } + + // Set both mDatabaseFile and mFileURL here. + mFileURL = aFileURL; + mDatabaseFile = databaseFile; + + rv = initializeInternal(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +Connection::initializeInternal() +{ + MOZ_ASSERT(mDBConn); + + if (mFileURL) { + const char* dbPath = ::sqlite3_db_filename(mDBConn, "main"); + MOZ_ASSERT(dbPath); + } + + // Properly wrap the database handle's mutex. + sharedDBMutex.initWithMutex(sqlite3_db_mutex(mDBConn)); + + int64_t pageSize = Service::getDefaultPageSize(); + + // Set page_size to the preferred default value. This is effective only if + // the database has just been created, otherwise, if the database does not + // use WAL journal mode, a VACUUM operation will updated its page_size. + nsAutoCString pageSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR + "PRAGMA page_size = "); + pageSizeQuery.AppendInt(pageSize); + nsresult rv = ExecuteSimpleSQL(pageSizeQuery); + NS_ENSURE_SUCCESS(rv, rv); + + // Setting the cache_size forces the database open, verifying if it is valid + // or corrupt. So this is executed regardless it being actually needed. + // The cache_size is calculated from the actual page_size, to save memory. + nsAutoCString cacheSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR + "PRAGMA cache_size = "); + cacheSizeQuery.AppendInt(-MAX_CACHE_SIZE_KIBIBYTES); + int srv = executeSql(mDBConn, cacheSizeQuery.get()); + if (srv != SQLITE_OK) { + ::sqlite3_close(mDBConn); + mDBConn = nullptr; + return convertResultCode(srv); + } + +#if defined(MOZ_MEMORY_TEMP_STORE_PRAGMA) + (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA temp_store = 2;")); +#endif + + // Register our built-in SQL functions. + srv = registerFunctions(mDBConn); + if (srv != SQLITE_OK) { + ::sqlite3_close(mDBConn); + mDBConn = nullptr; + return convertResultCode(srv); + } + + // Register our built-in SQL collating sequences. + srv = registerCollations(mDBConn, mStorageService); + if (srv != SQLITE_OK) { + ::sqlite3_close(mDBConn); + mDBConn = nullptr; + return convertResultCode(srv); + } + + // Set the synchronous PRAGMA, according to the preference. + switch (Service::getSynchronousPref()) { + case 2: + (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "PRAGMA synchronous = FULL;")); + break; + case 0: + (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "PRAGMA synchronous = OFF;")); + break; + case 1: + default: + (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "PRAGMA synchronous = NORMAL;")); + break; + } + + return NS_OK; +} + +nsresult +Connection::databaseElementExists(enum DatabaseElementType aElementType, + const nsACString &aElementName, + bool *_exists) +{ + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + // When constructing the query, make sure to SELECT the correct db's sqlite_master + // if the user is prefixing the element with a specific db. ex: sample.test + nsCString query("SELECT name FROM (SELECT * FROM "); + nsDependentCSubstring element; + int32_t ind = aElementName.FindChar('.'); + if (ind == kNotFound) { + element.Assign(aElementName); + } + else { + nsDependentCSubstring db(Substring(aElementName, 0, ind + 1)); + element.Assign(Substring(aElementName, ind + 1, aElementName.Length())); + query.Append(db); + } + query.AppendLiteral("sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type = '"); + + switch (aElementType) { + case INDEX: + query.AppendLiteral("index"); + break; + case TABLE: + query.AppendLiteral("table"); + break; + } + query.AppendLiteral("' AND name ='"); + query.Append(element); + query.Append('\''); + + sqlite3_stmt *stmt; + int srv = prepareStatement(mDBConn, query, &stmt); + if (srv != SQLITE_OK) + return convertResultCode(srv); + + srv = stepStatement(mDBConn, stmt); + // we just care about the return value from step + (void)::sqlite3_finalize(stmt); + + if (srv == SQLITE_ROW) { + *_exists = true; + return NS_OK; + } + if (srv == SQLITE_DONE) { + *_exists = false; + return NS_OK; + } + + return convertResultCode(srv); +} + +bool +Connection::findFunctionByInstance(nsISupports *aInstance) +{ + sharedDBMutex.assertCurrentThreadOwns(); + + for (auto iter = mFunctions.Iter(); !iter.Done(); iter.Next()) { + if (iter.UserData().function == aInstance) { + return true; + } + } + return false; +} + +/* static */ int +Connection::sProgressHelper(void *aArg) +{ + Connection *_this = static_cast<Connection *>(aArg); + return _this->progressHandler(); +} + +int +Connection::progressHandler() +{ + sharedDBMutex.assertCurrentThreadOwns(); + if (mProgressHandler) { + bool result; + nsresult rv = mProgressHandler->OnProgress(this, &result); + if (NS_FAILED(rv)) return 0; // Don't break request + return result ? 1 : 0; + } + return 0; +} + +nsresult +Connection::setClosedState() +{ + // Ensure that we are on the correct thread to close the database. + bool onOpenedThread; + nsresult rv = threadOpenedOn->IsOnCurrentThread(&onOpenedThread); + NS_ENSURE_SUCCESS(rv, rv); + if (!onOpenedThread) { + NS_ERROR("Must close the database on the thread that you opened it with!"); + return NS_ERROR_UNEXPECTED; + } + + // Flag that we are shutting down the async thread, so that + // getAsyncExecutionTarget knows not to expose/create the async thread. + { + MutexAutoLock lockedScope(sharedAsyncExecutionMutex); + NS_ENSURE_FALSE(mAsyncExecutionThreadShuttingDown, NS_ERROR_UNEXPECTED); + mAsyncExecutionThreadShuttingDown = true; + } + + // Set the property to null before closing the connection, otherwise the other + // functions in the module may try to use the connection after it is closed. + mDBConn = nullptr; + + return NS_OK; +} + +bool +Connection::connectionReady() +{ + return mDBConn != nullptr; +} + +bool +Connection::isClosing() +{ + bool shuttingDown = false; + { + MutexAutoLock lockedScope(sharedAsyncExecutionMutex); + shuttingDown = mAsyncExecutionThreadShuttingDown; + } + return shuttingDown && !isClosed(); +} + +bool +Connection::isClosed() +{ + MutexAutoLock lockedScope(sharedAsyncExecutionMutex); + return mConnectionClosed; +} + +void +Connection::shutdownAsyncThread(nsIThread *aThread) { + MOZ_ASSERT(!mAsyncExecutionThread); + MOZ_ASSERT(mAsyncExecutionThreadIsAlive); + MOZ_ASSERT(mAsyncExecutionThreadShuttingDown); + + DebugOnly<nsresult> rv = aThread->Shutdown(); + MOZ_ASSERT(NS_SUCCEEDED(rv)); +#ifdef DEBUG + mAsyncExecutionThreadIsAlive = false; +#endif +} + +nsresult +Connection::internalClose(sqlite3 *aNativeConnection) +{ + // Sanity checks to make sure we are in the proper state before calling this. + // aNativeConnection can be null if OpenAsyncDatabase failed and is now just + // cleaning up the async thread. + MOZ_ASSERT(!isClosed()); + +#ifdef DEBUG + { // Make sure we have marked our async thread as shutting down. + MutexAutoLock lockedScope(sharedAsyncExecutionMutex); + NS_ASSERTION(mAsyncExecutionThreadShuttingDown, + "Did not call setClosedState!"); + } +#endif // DEBUG + + if (MOZ_LOG_TEST(gStorageLog, LogLevel::Debug)) { + nsAutoCString leafName(":memory"); + if (mDatabaseFile) + (void)mDatabaseFile->GetNativeLeafName(leafName); + MOZ_LOG(gStorageLog, LogLevel::Debug, ("Closing connection to '%s'", + leafName.get())); + } + + // At this stage, we may still have statements that need to be + // finalized. Attempt to close the database connection. This will + // always disconnect any virtual tables and cleanly finalize their + // internal statements. Once this is done, closing may fail due to + // unfinalized client statements, in which case we need to finalize + // these statements and close again. + { + MutexAutoLock lockedScope(sharedAsyncExecutionMutex); + mConnectionClosed = true; + } + + // Nothing else needs to be done if we don't have a connection here. + if (!aNativeConnection) + return NS_OK; + + int srv = sqlite3_close(aNativeConnection); + + if (srv == SQLITE_BUSY) { + // We still have non-finalized statements. Finalize them. + + sqlite3_stmt *stmt = nullptr; + while ((stmt = ::sqlite3_next_stmt(aNativeConnection, stmt))) { + MOZ_LOG(gStorageLog, LogLevel::Debug, + ("Auto-finalizing SQL statement '%s' (%x)", + ::sqlite3_sql(stmt), + stmt)); + +#ifdef DEBUG + char *msg = ::PR_smprintf("SQL statement '%s' (%x) should have been finalized before closing the connection", + ::sqlite3_sql(stmt), + stmt); + NS_WARNING(msg); + ::PR_smprintf_free(msg); + msg = nullptr; +#endif // DEBUG + + srv = ::sqlite3_finalize(stmt); + +#ifdef DEBUG + if (srv != SQLITE_OK) { + msg = ::PR_smprintf("Could not finalize SQL statement '%s' (%x)", + ::sqlite3_sql(stmt), + stmt); + NS_WARNING(msg); + ::PR_smprintf_free(msg); + msg = nullptr; + } +#endif // DEBUG + + // Ensure that the loop continues properly, whether closing has succeeded + // or not. + if (srv == SQLITE_OK) { + stmt = nullptr; + } + } + + // Now that all statements have been finalized, we + // should be able to close. + srv = ::sqlite3_close(aNativeConnection); + + } + + if (srv != SQLITE_OK) { + MOZ_ASSERT(srv == SQLITE_OK, + "sqlite3_close failed. There are probably outstanding statements that are listed above!"); + } + + return convertResultCode(srv); +} + +nsCString +Connection::getFilename() +{ + nsCString leafname(":memory:"); + if (mDatabaseFile) { + (void)mDatabaseFile->GetNativeLeafName(leafname); + } + return leafname; +} + +int +Connection::stepStatement(sqlite3 *aNativeConnection, sqlite3_stmt *aStatement) +{ + MOZ_ASSERT(aStatement); + bool checkedMainThread = false; + TimeStamp startTime = TimeStamp::Now(); + + // The connection may have been closed if the executing statement has been + // created and cached after a call to asyncClose() but before the actual + // sqlite3_close(). This usually happens when other tasks using cached + // statements are asynchronously scheduled for execution and any of them ends + // up after asyncClose. See bug 728653 for details. + if (isClosed()) + return SQLITE_MISUSE; + + (void)::sqlite3_extended_result_codes(aNativeConnection, 1); + + int srv; + while ((srv = ::sqlite3_step(aStatement)) == SQLITE_LOCKED_SHAREDCACHE) { + if (!checkedMainThread) { + checkedMainThread = true; + if (::NS_IsMainThread()) { + NS_WARNING("We won't allow blocking on the main thread!"); + break; + } + } + + srv = WaitForUnlockNotify(aNativeConnection); + if (srv != SQLITE_OK) { + break; + } + + ::sqlite3_reset(aStatement); + } + + (void)::sqlite3_extended_result_codes(aNativeConnection, 0); + // Drop off the extended result bits of the result code. + return srv & 0xFF; +} + +int +Connection::prepareStatement(sqlite3 *aNativeConnection, const nsCString &aSQL, + sqlite3_stmt **_stmt) +{ + // We should not even try to prepare statements after the connection has + // been closed. + if (isClosed()) + return SQLITE_MISUSE; + + bool checkedMainThread = false; + + (void)::sqlite3_extended_result_codes(aNativeConnection, 1); + + int srv; + while((srv = ::sqlite3_prepare_v2(aNativeConnection, + aSQL.get(), + -1, + _stmt, + nullptr)) == SQLITE_LOCKED_SHAREDCACHE) { + if (!checkedMainThread) { + checkedMainThread = true; + if (::NS_IsMainThread()) { + NS_WARNING("We won't allow blocking on the main thread!"); + break; + } + } + + srv = WaitForUnlockNotify(aNativeConnection); + if (srv != SQLITE_OK) { + break; + } + } + + if (srv != SQLITE_OK) { + nsCString warnMsg; + warnMsg.AppendLiteral("The SQL statement '"); + warnMsg.Append(aSQL); + warnMsg.AppendLiteral("' could not be compiled due to an error: "); + warnMsg.Append(::sqlite3_errmsg(aNativeConnection)); + +#ifdef DEBUG + NS_WARNING(warnMsg.get()); +#endif + MOZ_LOG(gStorageLog, LogLevel::Error, ("%s", warnMsg.get())); + } + + (void)::sqlite3_extended_result_codes(aNativeConnection, 0); + // Drop off the extended result bits of the result code. + int rc = srv & 0xFF; + // sqlite will return OK on a comment only string and set _stmt to nullptr. + // The callers of this function are used to only checking the return value, + // so it is safer to return an error code. + if (rc == SQLITE_OK && *_stmt == nullptr) { + return SQLITE_MISUSE; + } + + return rc; +} + + +int +Connection::executeSql(sqlite3 *aNativeConnection, const char *aSqlString) +{ + if (isClosed()) + return SQLITE_MISUSE; + + TimeStamp startTime = TimeStamp::Now(); + int srv = ::sqlite3_exec(aNativeConnection, aSqlString, nullptr, nullptr, + nullptr); + + return srv; +} + +//////////////////////////////////////////////////////////////////////////////// +//// nsIInterfaceRequestor + +NS_IMETHODIMP +Connection::GetInterface(const nsIID &aIID, + void **_result) +{ + if (aIID.Equals(NS_GET_IID(nsIEventTarget))) { + nsIEventTarget *background = getAsyncExecutionTarget(); + NS_IF_ADDREF(background); + *_result = background; + return NS_OK; + } + return NS_ERROR_NO_INTERFACE; +} + +//////////////////////////////////////////////////////////////////////////////// +//// mozIStorageConnection + +NS_IMETHODIMP +Connection::Close() +{ + if (!mDBConn) + return NS_ERROR_NOT_INITIALIZED; + + { // Make sure we have not executed any asynchronous statements. + // If this fails, the mDBConn will be left open, resulting in a leak. + // Ideally we'd schedule some code to destroy the mDBConn once all its + // async statements have finished executing; see bug 704030. + MutexAutoLock lockedScope(sharedAsyncExecutionMutex); + bool asyncCloseWasCalled = !mAsyncExecutionThread; + NS_ENSURE_TRUE(asyncCloseWasCalled, NS_ERROR_UNEXPECTED); + } + + // setClosedState nullifies our connection pointer, so we take a raw pointer + // off it, to pass it through the close procedure. + sqlite3 *nativeConn = mDBConn; + nsresult rv = setClosedState(); + NS_ENSURE_SUCCESS(rv, rv); + + return internalClose(nativeConn); +} + +NS_IMETHODIMP +Connection::AsyncClose(mozIStorageCompletionCallback *aCallback) +{ + if (!NS_IsMainThread()) { + return NS_ERROR_NOT_SAME_THREAD; + } + + // The two relevant factors at this point are whether we have a database + // connection and whether we have an async execution thread. Here's what the + // states mean and how we handle them: + // + // - (mDBConn && asyncThread): The expected case where we are either an + // async connection or a sync connection that has been used asynchronously. + // Either way the caller must call us and not Close(). Nothing surprising + // about this. We'll dispatch AsyncCloseConnection to the already-existing + // async thread. + // + // - (mDBConn && !asyncThread): A somewhat unusual case where the caller + // opened the connection synchronously and was planning to use it + // asynchronously, but never got around to using it asynchronously before + // needing to shutdown. This has been observed to happen for the cookie + // service in a case where Firefox shuts itself down almost immediately + // after startup (for unknown reasons). In the Firefox shutdown case, + // we may also fail to create a new async execution thread if one does not + // already exist. (nsThreadManager will refuse to create new threads when + // it has already been told to shutdown.) As such, we need to handle a + // failure to create the async execution thread by falling back to + // synchronous Close() and also dispatching the completion callback because + // at least Places likes to spin a nested event loop that depends on the + // callback being invoked. + // + // Note that we have considered not trying to spin up the async execution + // thread in this case if it does not already exist, but the overhead of + // thread startup (if successful) is significantly less expensive than the + // worst-case potential I/O hit of synchronously closing a database when we + // could close it asynchronously. + // + // - (!mDBConn && asyncThread): This happens in some but not all cases where + // OpenAsyncDatabase encountered a problem opening the database. If it + // happened in all cases AsyncInitDatabase would just shut down the thread + // directly and we would avoid this case. But it doesn't, so for simplicity + // and consistency AsyncCloseConnection knows how to handle this and we + // act like this was the (mDBConn && asyncThread) case in this method. + // + // - (!mDBConn && !asyncThread): The database was never successfully opened or + // Close() or AsyncClose() has already been called (at least) once. This is + // undeniably a misuse case by the caller. We could optimize for this + // case by adding an additional check of mAsyncExecutionThread without using + // getAsyncExecutionTarget() to avoid wastefully creating a thread just to + // shut it down. But this complicates the method for broken caller code + // whereas we're still correct and safe without the special-case. + nsIEventTarget *asyncThread = getAsyncExecutionTarget(); + + // Create our callback event if we were given a callback. This will + // eventually be dispatched in all cases, even if we fall back to Close() and + // the database wasn't open and we return an error. The rationale is that + // no existing consumer checks our return value and several of them like to + // spin nested event loops until the callback fires. Given that, it seems + // preferable for us to dispatch the callback in all cases. (Except the + // wrong thread misuse case we bailed on up above. But that's okay because + // that is statically wrong whereas these edge cases are dynamic.) + nsCOMPtr<nsIRunnable> completeEvent; + if (aCallback) { + completeEvent = newCompletionEvent(aCallback); + } + + if (!asyncThread) { + // We were unable to create an async thread, so we need to fall back to + // using normal Close(). Since there is no async thread, Close() will + // not complain about that. (Close() may, however, complain if the + // connection is closed, but that's okay.) + if (completeEvent) { + // Closing the database is more important than returning an error code + // about a failure to dispatch, especially because all existing native + // callers ignore our return value. + Unused << NS_DispatchToMainThread(completeEvent.forget()); + } + return Close(); + } + + // setClosedState nullifies our connection pointer, so we take a raw pointer + // off it, to pass it through the close procedure. + sqlite3 *nativeConn = mDBConn; + nsresult rv = setClosedState(); + NS_ENSURE_SUCCESS(rv, rv); + + // Create and dispatch our close event to the background thread. + nsCOMPtr<nsIRunnable> closeEvent; + { + // We need to lock because we're modifying mAsyncExecutionThread + MutexAutoLock lockedScope(sharedAsyncExecutionMutex); + closeEvent = new AsyncCloseConnection(this, + nativeConn, + completeEvent, + mAsyncExecutionThread.forget()); + } + + rv = asyncThread->Dispatch(closeEvent, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +Connection::AsyncClone(bool aReadOnly, + mozIStorageCompletionCallback *aCallback) +{ + PROFILER_LABEL("mozStorageConnection", "AsyncClone", + js::ProfileEntry::Category::STORAGE); + + if (!NS_IsMainThread()) { + return NS_ERROR_NOT_SAME_THREAD; + } + if (!mDBConn) + return NS_ERROR_NOT_INITIALIZED; + if (!mDatabaseFile) + return NS_ERROR_UNEXPECTED; + + int flags = mFlags; + if (aReadOnly) { + // Turn off SQLITE_OPEN_READWRITE, and set SQLITE_OPEN_READONLY. + flags = (~SQLITE_OPEN_READWRITE & flags) | SQLITE_OPEN_READONLY; + // Turn off SQLITE_OPEN_CREATE. + flags = (~SQLITE_OPEN_CREATE & flags); + } + + RefPtr<Connection> clone = new Connection(mStorageService, flags, + mAsyncOnly); + + RefPtr<AsyncInitializeClone> initEvent = + new AsyncInitializeClone(this, clone, aReadOnly, aCallback); + // Dispatch to our async thread, since the originating connection must remain + // valid and open for the whole cloning process. This also ensures we are + // properly serialized with a `close` operation, rather than race with it. + nsCOMPtr<nsIEventTarget> target = getAsyncExecutionTarget(); + if (!target) { + return NS_ERROR_UNEXPECTED; + } + return target->Dispatch(initEvent, NS_DISPATCH_NORMAL); +} + +nsresult +Connection::initializeClone(Connection* aClone, bool aReadOnly) +{ + nsresult rv = mFileURL ? aClone->initialize(mFileURL) + : aClone->initialize(mDatabaseFile); + if (NS_FAILED(rv)) { + return rv; + } + + // Re-attach on-disk databases that were attached to the original connection. + { + nsCOMPtr<mozIStorageStatement> stmt; + rv = CreateStatement(NS_LITERAL_CSTRING("PRAGMA database_list"), + getter_AddRefs(stmt)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + bool hasResult = false; + while (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) { + nsAutoCString name; + rv = stmt->GetUTF8String(1, name); + if (NS_SUCCEEDED(rv) && !name.Equals(NS_LITERAL_CSTRING("main")) && + !name.Equals(NS_LITERAL_CSTRING("temp"))) { + nsCString path; + rv = stmt->GetUTF8String(2, path); + if (NS_SUCCEEDED(rv) && !path.IsEmpty()) { + rv = aClone->ExecuteSimpleSQL(NS_LITERAL_CSTRING("ATTACH DATABASE '") + + path + NS_LITERAL_CSTRING("' AS ") + name); + MOZ_ASSERT(NS_SUCCEEDED(rv), "couldn't re-attach database to cloned connection"); + } + } + } + } + + // Copy over pragmas from the original connection. + static const char * pragmas[] = { + "cache_size", + "temp_store", + "foreign_keys", + "journal_size_limit", + "synchronous", + "wal_autocheckpoint", + "busy_timeout" + }; + for (uint32_t i = 0; i < ArrayLength(pragmas); ++i) { + // Read-only connections just need cache_size and temp_store pragmas. + if (aReadOnly && ::strcmp(pragmas[i], "cache_size") != 0 && + ::strcmp(pragmas[i], "temp_store") != 0) { + continue; + } + + nsAutoCString pragmaQuery("PRAGMA "); + pragmaQuery.Append(pragmas[i]); + nsCOMPtr<mozIStorageStatement> stmt; + rv = CreateStatement(pragmaQuery, getter_AddRefs(stmt)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + bool hasResult = false; + if (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) { + pragmaQuery.AppendLiteral(" = "); + pragmaQuery.AppendInt(stmt->AsInt32(0)); + rv = aClone->ExecuteSimpleSQL(pragmaQuery); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } + } + + // Copy any functions that have been added to this connection. + SQLiteMutexAutoLock lockedScope(sharedDBMutex); + for (auto iter = mFunctions.Iter(); !iter.Done(); iter.Next()) { + const nsACString &key = iter.Key(); + Connection::FunctionInfo data = iter.UserData(); + + MOZ_ASSERT(data.type == Connection::FunctionInfo::SIMPLE || + data.type == Connection::FunctionInfo::AGGREGATE, + "Invalid function type!"); + + if (data.type == Connection::FunctionInfo::SIMPLE) { + mozIStorageFunction *function = + static_cast<mozIStorageFunction *>(data.function.get()); + rv = aClone->CreateFunction(key, data.numArgs, function); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to copy function to cloned connection"); + } + + } else { + mozIStorageAggregateFunction *function = + static_cast<mozIStorageAggregateFunction *>(data.function.get()); + rv = aClone->CreateAggregateFunction(key, data.numArgs, function); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to copy aggregate function to cloned connection"); + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +Connection::Clone(bool aReadOnly, + mozIStorageConnection **_connection) +{ + MOZ_ASSERT(threadOpenedOn == NS_GetCurrentThread()); + + PROFILER_LABEL("mozStorageConnection", "Clone", + js::ProfileEntry::Category::STORAGE); + + if (!mDBConn) + return NS_ERROR_NOT_INITIALIZED; + if (!mDatabaseFile) + return NS_ERROR_UNEXPECTED; + + int flags = mFlags; + if (aReadOnly) { + // Turn off SQLITE_OPEN_READWRITE, and set SQLITE_OPEN_READONLY. + flags = (~SQLITE_OPEN_READWRITE & flags) | SQLITE_OPEN_READONLY; + // Turn off SQLITE_OPEN_CREATE. + flags = (~SQLITE_OPEN_CREATE & flags); + } + + RefPtr<Connection> clone = new Connection(mStorageService, flags, + mAsyncOnly); + + nsresult rv = initializeClone(clone, aReadOnly); + if (NS_FAILED(rv)) { + return rv; + } + + NS_IF_ADDREF(*_connection = clone); + return NS_OK; +} + +NS_IMETHODIMP +Connection::GetDefaultPageSize(int32_t *_defaultPageSize) +{ + *_defaultPageSize = Service::getDefaultPageSize(); + return NS_OK; +} + +NS_IMETHODIMP +Connection::GetConnectionReady(bool *_ready) +{ + *_ready = connectionReady(); + return NS_OK; +} + +NS_IMETHODIMP +Connection::GetDatabaseFile(nsIFile **_dbFile) +{ + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + NS_IF_ADDREF(*_dbFile = mDatabaseFile); + + return NS_OK; +} + +NS_IMETHODIMP +Connection::GetLastInsertRowID(int64_t *_id) +{ + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + sqlite_int64 id = ::sqlite3_last_insert_rowid(mDBConn); + *_id = id; + + return NS_OK; +} + +NS_IMETHODIMP +Connection::GetAffectedRows(int32_t *_rows) +{ + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + *_rows = ::sqlite3_changes(mDBConn); + + return NS_OK; +} + +NS_IMETHODIMP +Connection::GetLastError(int32_t *_error) +{ + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + *_error = ::sqlite3_errcode(mDBConn); + + return NS_OK; +} + +NS_IMETHODIMP +Connection::GetLastErrorString(nsACString &_errorString) +{ + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + const char *serr = ::sqlite3_errmsg(mDBConn); + _errorString.Assign(serr); + + return NS_OK; +} + +NS_IMETHODIMP +Connection::GetSchemaVersion(int32_t *_version) +{ + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + nsCOMPtr<mozIStorageStatement> stmt; + (void)CreateStatement(NS_LITERAL_CSTRING("PRAGMA user_version"), + getter_AddRefs(stmt)); + NS_ENSURE_TRUE(stmt, NS_ERROR_OUT_OF_MEMORY); + + *_version = 0; + bool hasResult; + if (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) + *_version = stmt->AsInt32(0); + + return NS_OK; +} + +NS_IMETHODIMP +Connection::SetSchemaVersion(int32_t aVersion) +{ + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + nsAutoCString stmt(NS_LITERAL_CSTRING("PRAGMA user_version = ")); + stmt.AppendInt(aVersion); + + return ExecuteSimpleSQL(stmt); +} + +NS_IMETHODIMP +Connection::CreateStatement(const nsACString &aSQLStatement, + mozIStorageStatement **_stmt) +{ + NS_ENSURE_ARG_POINTER(_stmt); + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + RefPtr<Statement> statement(new Statement()); + NS_ENSURE_TRUE(statement, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv = statement->initialize(this, mDBConn, aSQLStatement); + NS_ENSURE_SUCCESS(rv, rv); + + Statement *rawPtr; + statement.forget(&rawPtr); + *_stmt = rawPtr; + return NS_OK; +} + +NS_IMETHODIMP +Connection::CreateAsyncStatement(const nsACString &aSQLStatement, + mozIStorageAsyncStatement **_stmt) +{ + NS_ENSURE_ARG_POINTER(_stmt); + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + RefPtr<AsyncStatement> statement(new AsyncStatement()); + NS_ENSURE_TRUE(statement, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv = statement->initialize(this, mDBConn, aSQLStatement); + NS_ENSURE_SUCCESS(rv, rv); + + AsyncStatement *rawPtr; + statement.forget(&rawPtr); + *_stmt = rawPtr; + return NS_OK; +} + +NS_IMETHODIMP +Connection::ExecuteSimpleSQL(const nsACString &aSQLStatement) +{ + CHECK_MAINTHREAD_ABUSE(); + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + int srv = executeSql(mDBConn, PromiseFlatCString(aSQLStatement).get()); + return convertResultCode(srv); +} + +NS_IMETHODIMP +Connection::ExecuteAsync(mozIStorageBaseStatement **aStatements, + uint32_t aNumStatements, + mozIStorageStatementCallback *aCallback, + mozIStoragePendingStatement **_handle) +{ + nsTArray<StatementData> stmts(aNumStatements); + for (uint32_t i = 0; i < aNumStatements; i++) { + nsCOMPtr<StorageBaseStatementInternal> stmt = + do_QueryInterface(aStatements[i]); + + // Obtain our StatementData. + StatementData data; + nsresult rv = stmt->getAsynchronousStatementData(data); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(stmt->getOwner() == this, + "Statement must be from this database connection!"); + + // Now append it to our array. + NS_ENSURE_TRUE(stmts.AppendElement(data), NS_ERROR_OUT_OF_MEMORY); + } + + // Dispatch to the background + return AsyncExecuteStatements::execute(stmts, this, mDBConn, aCallback, + _handle); +} + +NS_IMETHODIMP +Connection::ExecuteSimpleSQLAsync(const nsACString &aSQLStatement, + mozIStorageStatementCallback *aCallback, + mozIStoragePendingStatement **_handle) +{ + if (!NS_IsMainThread()) { + return NS_ERROR_NOT_SAME_THREAD; + } + + nsCOMPtr<mozIStorageAsyncStatement> stmt; + nsresult rv = CreateAsyncStatement(aSQLStatement, getter_AddRefs(stmt)); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<mozIStoragePendingStatement> pendingStatement; + rv = stmt->ExecuteAsync(aCallback, getter_AddRefs(pendingStatement)); + if (NS_FAILED(rv)) { + return rv; + } + + pendingStatement.forget(_handle); + return rv; +} + +NS_IMETHODIMP +Connection::TableExists(const nsACString &aTableName, + bool *_exists) +{ + return databaseElementExists(TABLE, aTableName, _exists); +} + +NS_IMETHODIMP +Connection::IndexExists(const nsACString &aIndexName, + bool* _exists) +{ + return databaseElementExists(INDEX, aIndexName, _exists); +} + +NS_IMETHODIMP +Connection::GetTransactionInProgress(bool *_inProgress) +{ + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + SQLiteMutexAutoLock lockedScope(sharedDBMutex); + *_inProgress = mTransactionInProgress; + return NS_OK; +} + +NS_IMETHODIMP +Connection::BeginTransaction() +{ + return BeginTransactionAs(mozIStorageConnection::TRANSACTION_DEFERRED); +} + +NS_IMETHODIMP +Connection::BeginTransactionAs(int32_t aTransactionType) +{ + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + return beginTransactionInternal(mDBConn, aTransactionType); +} + +nsresult +Connection::beginTransactionInternal(sqlite3 *aNativeConnection, + int32_t aTransactionType) +{ + SQLiteMutexAutoLock lockedScope(sharedDBMutex); + if (mTransactionInProgress) + return NS_ERROR_FAILURE; + nsresult rv; + switch(aTransactionType) { + case TRANSACTION_DEFERRED: + rv = convertResultCode(executeSql(aNativeConnection, "BEGIN DEFERRED")); + break; + case TRANSACTION_IMMEDIATE: + rv = convertResultCode(executeSql(aNativeConnection, "BEGIN IMMEDIATE")); + break; + case TRANSACTION_EXCLUSIVE: + rv = convertResultCode(executeSql(aNativeConnection, "BEGIN EXCLUSIVE")); + break; + default: + return NS_ERROR_ILLEGAL_VALUE; + } + if (NS_SUCCEEDED(rv)) + mTransactionInProgress = true; + return rv; +} + +NS_IMETHODIMP +Connection::CommitTransaction() +{ + if (!mDBConn) + return NS_ERROR_NOT_INITIALIZED; + + return commitTransactionInternal(mDBConn); +} + +nsresult +Connection::commitTransactionInternal(sqlite3 *aNativeConnection) +{ + SQLiteMutexAutoLock lockedScope(sharedDBMutex); + if (!mTransactionInProgress) + return NS_ERROR_UNEXPECTED; + nsresult rv = + convertResultCode(executeSql(aNativeConnection, "COMMIT TRANSACTION")); + if (NS_SUCCEEDED(rv)) + mTransactionInProgress = false; + return rv; +} + +NS_IMETHODIMP +Connection::RollbackTransaction() +{ + if (!mDBConn) + return NS_ERROR_NOT_INITIALIZED; + + return rollbackTransactionInternal(mDBConn); +} + +nsresult +Connection::rollbackTransactionInternal(sqlite3 *aNativeConnection) +{ + SQLiteMutexAutoLock lockedScope(sharedDBMutex); + if (!mTransactionInProgress) + return NS_ERROR_UNEXPECTED; + + nsresult rv = + convertResultCode(executeSql(aNativeConnection, "ROLLBACK TRANSACTION")); + if (NS_SUCCEEDED(rv)) + mTransactionInProgress = false; + return rv; +} + +NS_IMETHODIMP +Connection::CreateTable(const char *aTableName, + const char *aTableSchema) +{ + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + char *buf = ::PR_smprintf("CREATE TABLE %s (%s)", aTableName, aTableSchema); + if (!buf) + return NS_ERROR_OUT_OF_MEMORY; + + int srv = executeSql(mDBConn, buf); + ::PR_smprintf_free(buf); + + return convertResultCode(srv); +} + +NS_IMETHODIMP +Connection::CreateFunction(const nsACString &aFunctionName, + int32_t aNumArguments, + mozIStorageFunction *aFunction) +{ + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + // Check to see if this function is already defined. We only check the name + // because a function can be defined with the same body but different names. + SQLiteMutexAutoLock lockedScope(sharedDBMutex); + NS_ENSURE_FALSE(mFunctions.Get(aFunctionName, nullptr), NS_ERROR_FAILURE); + + int srv = ::sqlite3_create_function(mDBConn, + nsPromiseFlatCString(aFunctionName).get(), + aNumArguments, + SQLITE_ANY, + aFunction, + basicFunctionHelper, + nullptr, + nullptr); + if (srv != SQLITE_OK) + return convertResultCode(srv); + + FunctionInfo info = { aFunction, + Connection::FunctionInfo::SIMPLE, + aNumArguments }; + mFunctions.Put(aFunctionName, info); + + return NS_OK; +} + +NS_IMETHODIMP +Connection::CreateAggregateFunction(const nsACString &aFunctionName, + int32_t aNumArguments, + mozIStorageAggregateFunction *aFunction) +{ + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + // Check to see if this function name is already defined. + SQLiteMutexAutoLock lockedScope(sharedDBMutex); + NS_ENSURE_FALSE(mFunctions.Get(aFunctionName, nullptr), NS_ERROR_FAILURE); + + // Because aggregate functions depend on state across calls, you cannot have + // the same instance use the same name. We want to enumerate all functions + // and make sure this instance is not already registered. + NS_ENSURE_FALSE(findFunctionByInstance(aFunction), NS_ERROR_FAILURE); + + int srv = ::sqlite3_create_function(mDBConn, + nsPromiseFlatCString(aFunctionName).get(), + aNumArguments, + SQLITE_ANY, + aFunction, + nullptr, + aggregateFunctionStepHelper, + aggregateFunctionFinalHelper); + if (srv != SQLITE_OK) + return convertResultCode(srv); + + FunctionInfo info = { aFunction, + Connection::FunctionInfo::AGGREGATE, + aNumArguments }; + mFunctions.Put(aFunctionName, info); + + return NS_OK; +} + +NS_IMETHODIMP +Connection::RemoveFunction(const nsACString &aFunctionName) +{ + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + SQLiteMutexAutoLock lockedScope(sharedDBMutex); + NS_ENSURE_TRUE(mFunctions.Get(aFunctionName, nullptr), NS_ERROR_FAILURE); + + int srv = ::sqlite3_create_function(mDBConn, + nsPromiseFlatCString(aFunctionName).get(), + 0, + SQLITE_ANY, + nullptr, + nullptr, + nullptr, + nullptr); + if (srv != SQLITE_OK) + return convertResultCode(srv); + + mFunctions.Remove(aFunctionName); + + return NS_OK; +} + +NS_IMETHODIMP +Connection::SetProgressHandler(int32_t aGranularity, + mozIStorageProgressHandler *aHandler, + mozIStorageProgressHandler **_oldHandler) +{ + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + // Return previous one + SQLiteMutexAutoLock lockedScope(sharedDBMutex); + NS_IF_ADDREF(*_oldHandler = mProgressHandler); + + if (!aHandler || aGranularity <= 0) { + aHandler = nullptr; + aGranularity = 0; + } + mProgressHandler = aHandler; + ::sqlite3_progress_handler(mDBConn, aGranularity, sProgressHelper, this); + + return NS_OK; +} + +NS_IMETHODIMP +Connection::RemoveProgressHandler(mozIStorageProgressHandler **_oldHandler) +{ + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + // Return previous one + SQLiteMutexAutoLock lockedScope(sharedDBMutex); + NS_IF_ADDREF(*_oldHandler = mProgressHandler); + + mProgressHandler = nullptr; + ::sqlite3_progress_handler(mDBConn, 0, nullptr, nullptr); + + return NS_OK; +} + +NS_IMETHODIMP +Connection::SetGrowthIncrement(int32_t aChunkSize, const nsACString &aDatabaseName) +{ + // Don't preallocate if less than 500MiB is available. + int64_t bytesAvailable; + nsresult rv = mDatabaseFile->GetDiskSpaceAvailable(&bytesAvailable); + NS_ENSURE_SUCCESS(rv, rv); + if (bytesAvailable < MIN_AVAILABLE_BYTES_PER_CHUNKED_GROWTH) { + return NS_ERROR_FILE_TOO_BIG; + } + + (void)::sqlite3_file_control(mDBConn, + aDatabaseName.Length() ? nsPromiseFlatCString(aDatabaseName).get() + : nullptr, + SQLITE_FCNTL_CHUNK_SIZE, + &aChunkSize); + + return NS_OK; +} + +NS_IMETHODIMP +Connection::EnableModule(const nsACString& aModuleName) +{ + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + for (size_t i = 0; i < ArrayLength(gModules); i++) { + struct Module* m = &gModules[i]; + if (aModuleName.Equals(m->name)) { + int srv = m->registerFunc(mDBConn, m->name); + if (srv != SQLITE_OK) + return convertResultCode(srv); + + return NS_OK; + } + } + + return NS_ERROR_FAILURE; +} + +// Implemented in TelemetryVFS.cpp +already_AddRefed<QuotaObject> +GetQuotaObjectForFile(sqlite3_file *pFile); + +NS_IMETHODIMP +Connection::GetQuotaObjects(QuotaObject** aDatabaseQuotaObject, + QuotaObject** aJournalQuotaObject) +{ + MOZ_ASSERT(aDatabaseQuotaObject); + MOZ_ASSERT(aJournalQuotaObject); + + if (!mDBConn) { + return NS_ERROR_NOT_INITIALIZED; + } + + sqlite3_file* file; + int srv = ::sqlite3_file_control(mDBConn, + nullptr, + SQLITE_FCNTL_FILE_POINTER, + &file); + if (srv != SQLITE_OK) { + return convertResultCode(srv); + } + + RefPtr<QuotaObject> databaseQuotaObject = GetQuotaObjectForFile(file); + if (NS_WARN_IF(!databaseQuotaObject)) { + return NS_ERROR_FAILURE; + } + + srv = ::sqlite3_file_control(mDBConn, + nullptr, + SQLITE_FCNTL_JOURNAL_POINTER, + &file); + if (srv != SQLITE_OK) { + return convertResultCode(srv); + } + + RefPtr<QuotaObject> journalQuotaObject = GetQuotaObjectForFile(file); + if (NS_WARN_IF(!journalQuotaObject)) { + return NS_ERROR_FAILURE; + } + + databaseQuotaObject.forget(aDatabaseQuotaObject); + journalQuotaObject.forget(aJournalQuotaObject); + return NS_OK; +} + +} // namespace storage +} // namespace mozilla diff --git a/components/storage/src/mozStorageConnection.h b/components/storage/src/mozStorageConnection.h new file mode 100644 index 000000000..979ac6436 --- /dev/null +++ b/components/storage/src/mozStorageConnection.h @@ -0,0 +1,439 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_storage_Connection_h +#define mozilla_storage_Connection_h + +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "mozilla/Mutex.h" +#include "nsProxyRelease.h" +#include "nsThreadUtils.h" +#include "nsIInterfaceRequestor.h" + +#include "nsDataHashtable.h" +#include "mozIStorageProgressHandler.h" +#include "SQLiteMutex.h" +#include "mozIStorageConnection.h" +#include "mozStorageService.h" +#include "mozIStorageAsyncConnection.h" +#include "mozIStorageCompletionCallback.h" + +#include "nsIMutableArray.h" +#include "mozilla/Attributes.h" + +#include "sqlite3.h" + +class nsIFile; +class nsIFileURL; +class nsIEventTarget; +class nsIThread; + +namespace mozilla { +namespace storage { + +class Connection final : public mozIStorageConnection + , public nsIInterfaceRequestor +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_MOZISTORAGEASYNCCONNECTION + NS_DECL_MOZISTORAGECONNECTION + NS_DECL_NSIINTERFACEREQUESTOR + + /** + * Structure used to describe user functions on the database connection. + */ + struct FunctionInfo { + enum FunctionType { + SIMPLE, + AGGREGATE + }; + + nsCOMPtr<nsISupports> function; + FunctionType type; + int32_t numArgs; + }; + + /** + * @param aService + * Pointer to the storage service. Held onto for the lifetime of the + * connection. + * @param aFlags + * The flags to pass to sqlite3_open_v2. + * @param aAsyncOnly + * If |true|, the Connection only implements asynchronous interface: + * - |mozIStorageAsyncConnection|; + * If |false|, the result also implements synchronous interface: + * - |mozIStorageConnection|. + * @param aIgnoreLockingMode + * If |true|, ignore locks in force on the file. Only usable with + * read-only connections. Defaults to false. + * Use with extreme caution. If sqlite ignores locks, reads may fail + * indicating database corruption (the database won't actually be + * corrupt) or produce wrong results without any indication that has + * happened. + */ + Connection(Service *aService, int aFlags, bool aAsyncOnly, + bool aIgnoreLockingMode = false); + + /** + * Creates the connection to an in-memory database. + */ + nsresult initialize(); + + /** + * Creates the connection to the database. + * + * @param aDatabaseFile + * The nsIFile of the location of the database to open, or create if it + * does not exist. + */ + nsresult initialize(nsIFile *aDatabaseFile); + + /** + * Creates the connection to the database. + * + * @param aFileURL + * The nsIFileURL of the location of the database to open, or create if it + * does not exist. + */ + nsresult initialize(nsIFileURL *aFileURL); + + /** + * Fetches runtime status information for this connection. + * + * @param aStatusOption One of the SQLITE_DBSTATUS options defined at + * http://www.sqlite.org/c3ref/c_dbstatus_options.html + * @param [optional] aMaxValue if provided, will be set to the highest + * istantaneous value. + * @return the current value for the specified option. + */ + int32_t getSqliteRuntimeStatus(int32_t aStatusOption, + int32_t* aMaxValue=nullptr); + /** + * Registers/unregisters a commit hook callback. + * + * @param aCallbackFn a callback function to be invoked on transactions + * commit. Pass nullptr to unregister the current callback. + * @param [optional] aData if provided, will be passed to the callback. + * @see http://sqlite.org/c3ref/commit_hook.html + */ + void setCommitHook(int (*aCallbackFn)(void *) , void *aData=nullptr) { + MOZ_ASSERT(mDBConn, "A connection must exist at this point"); + ::sqlite3_commit_hook(mDBConn, aCallbackFn, aData); + }; + + /** + * Gets autocommit status. + */ + bool getAutocommit() { + return mDBConn && static_cast<bool>(::sqlite3_get_autocommit(mDBConn)); + }; + + /** + * Lazily creates and returns a background execution thread. In the future, + * the thread may be re-claimed if left idle, so you should call this + * method just before you dispatch and not save the reference. + * + * @returns an event target suitable for asynchronous statement execution. + */ + nsIEventTarget *getAsyncExecutionTarget(); + + /** + * Mutex used by asynchronous statements to protect state. The mutex is + * declared on the connection object because there is no contention between + * asynchronous statements (they are serialized on mAsyncExecutionThread). + * Currently protects: + * - Connection.mAsyncExecutionThreadShuttingDown + * - Connection.mAsyncExecutionThread + * - Connection.mConnectionClosed + * - AsyncExecuteStatements.mCancelRequested + */ + Mutex sharedAsyncExecutionMutex; + + /** + * Wraps the mutex that SQLite gives us from sqlite3_db_mutex. This is public + * because we already expose the sqlite3* native connection and proper + * operation of the deadlock detector requires everyone to use the same single + * SQLiteMutex instance for correctness. + */ + SQLiteMutex sharedDBMutex; + + /** + * References the thread this database was opened on. This MUST be thread it is + * closed on. + */ + const nsCOMPtr<nsIThread> threadOpenedOn; + + /** + * Closes the SQLite database, and warns about any non-finalized statements. + */ + nsresult internalClose(sqlite3 *aDBConn); + + /** + * Shuts down the passed-in async thread. + */ + void shutdownAsyncThread(nsIThread *aAsyncThread); + + /** + * Obtains the filename of the connection. Useful for logging. + */ + nsCString getFilename(); + + /** + * Creates an sqlite3 prepared statement object from an SQL string. + * + * @param aNativeConnection + * The underlying Sqlite connection to prepare the statement with. + * @param aSQL + * The SQL statement string to compile. + * @param _stmt + * New sqlite3_stmt object. + * @return the result from sqlite3_prepare_v2. + */ + int prepareStatement(sqlite3* aNativeConnection, + const nsCString &aSQL, sqlite3_stmt **_stmt); + + /** + * Performs a sqlite3_step on aStatement, while properly handling SQLITE_LOCKED + * when not on the main thread by waiting until we are notified. + * + * @param aNativeConnection + * The underlying Sqlite connection to step the statement with. + * @param aStatement + * A pointer to a sqlite3_stmt object. + * @return the result from sqlite3_step. + */ + int stepStatement(sqlite3* aNativeConnection, sqlite3_stmt* aStatement); + + /** + * Raw connection transaction management. + * + * @see BeginTransactionAs, CommitTransaction, RollbackTransaction. + */ + nsresult beginTransactionInternal(sqlite3 *aNativeConnection, + int32_t aTransactionType=TRANSACTION_DEFERRED); + nsresult commitTransactionInternal(sqlite3 *aNativeConnection); + nsresult rollbackTransactionInternal(sqlite3 *aNativeConnection); + + bool connectionReady(); + + /** + * True if this connection is shutting down but not yet closed. + */ + bool isClosing(); + + /** + * True if the underlying connection is closed. + * Any sqlite resources may be lost when this returns true, so nothing should + * try to use them. + */ + bool isClosed(); + + nsresult initializeClone(Connection *aClone, bool aReadOnly); + +private: + ~Connection(); + nsresult initializeInternal(); + + /** + * Sets the database into a closed state so no further actions can be + * performed. + * + * @note mDBConn is set to nullptr in this method. + */ + nsresult setClosedState(); + + /** + * Helper for calls to sqlite3_exec. Reports long delays to Telemetry. + * + * @param aNativeConnection + * The underlying Sqlite connection to execute the query with. + * @param aSqlString + * SQL string to execute + * @return the result from sqlite3_exec. + */ + int executeSql(sqlite3 *aNativeConnection, const char *aSqlString); + + /** + * Describes a certain primitive type in the database. + * + * Possible Values Are: + * INDEX - To check for the existence of an index + * TABLE - To check for the existence of a table + */ + enum DatabaseElementType { + INDEX, + TABLE + }; + + /** + * Determines if the specified primitive exists. + * + * @param aElementType + * The type of element to check the existence of + * @param aElementName + * The name of the element to check for + * @returns true if element exists, false otherwise + */ + nsresult databaseElementExists(enum DatabaseElementType aElementType, + const nsACString& aElementName, + bool *_exists); + + bool findFunctionByInstance(nsISupports *aInstance); + + static int sProgressHelper(void *aArg); + // Generic progress handler + // Dispatch call to registered progress handler, + // if there is one. Do nothing in other cases. + int progressHandler(); + + sqlite3 *mDBConn; + nsCOMPtr<nsIFileURL> mFileURL; + nsCOMPtr<nsIFile> mDatabaseFile; + + /** + * The filename that will be reported to telemetry for this connection. By + * default this will be the leaf of the path to the database file. + */ + nsCString mTelemetryFilename; + + /** + * Lazily created thread for asynchronous statement execution. Consumers + * should use getAsyncExecutionTarget rather than directly accessing this + * field. + */ + nsCOMPtr<nsIThread> mAsyncExecutionThread; + + /** + * Set to true by Close() or AsyncClose() prior to shutdown. + * + * If false, we guarantee both that the underlying sqlite3 database + * connection is still open and that getAsyncExecutionTarget() can + * return a thread. Once true, either the sqlite3 database + * connection is being shutdown or it has been + * shutdown. Additionally, once true, getAsyncExecutionTarget() + * returns null. + * + * This variable should be accessed while holding the + * sharedAsyncExecutionMutex. + */ + bool mAsyncExecutionThreadShuttingDown; + + /** + * Tracks whether the async thread has been initialized and Shutdown() has + * not yet been invoked on it. + */ +#ifdef DEBUG + bool mAsyncExecutionThreadIsAlive; +#endif + + /** + * Set to true just prior to calling sqlite3_close on the + * connection. + * + * This variable should be accessed while holding the + * sharedAsyncExecutionMutex. + */ + bool mConnectionClosed; + + /** + * Tracks if we have a transaction in progress or not. Access protected by + * sharedDBMutex. + */ + bool mTransactionInProgress; + + /** + * Stores the mapping of a given function by name to its instance. Access is + * protected by sharedDBMutex. + */ + nsDataHashtable<nsCStringHashKey, FunctionInfo> mFunctions; + + /** + * Stores the registered progress handler for the database connection. Access + * is protected by sharedDBMutex. + */ + nsCOMPtr<mozIStorageProgressHandler> mProgressHandler; + + /** + * Stores the flags we passed to sqlite3_open_v2. + */ + const int mFlags; + + /** + * Stores whether we should ask sqlite3_open_v2 to ignore locking. + */ + const bool mIgnoreLockingMode; + + // This is here for two reasons: 1) It's used to make sure that the + // connections do not outlive the service. 2) Our custom collating functions + // call its localeCompareStrings() method. + RefPtr<Service> mStorageService; + + /** + * If |false|, this instance supports synchronous operations + * and it can be cast to |mozIStorageConnection|. + */ + const bool mAsyncOnly; +}; + + +/** + * A Runnable designed to call a mozIStorageCompletionCallback on + * the appropriate thread. + */ +class CallbackComplete final : public Runnable +{ +public: + /** + * @param aValue The result to pass to the callback. It must + * already be owned by the main thread. + * @param aCallback The callback. It must already be owned by the + * main thread. + */ + CallbackComplete(nsresult aStatus, + nsISupports* aValue, + already_AddRefed<mozIStorageCompletionCallback> aCallback) + : mStatus(aStatus) + , mValue(aValue) + , mCallback(aCallback) + { + } + + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread()); + nsresult rv = mCallback->Complete(mStatus, mValue); + + // Ensure that we release on the main thread + mValue = nullptr; + mCallback = nullptr; + return rv; + } + +private: + nsresult mStatus; + nsCOMPtr<nsISupports> mValue; + // This is a RefPtr<T> and not a nsCOMPtr<T> because + // nsCOMP<T> would cause an off-main thread QI, which + // is not a good idea (and crashes XPConnect). + RefPtr<mozIStorageCompletionCallback> mCallback; +}; + +} // namespace storage +} // namespace mozilla + +/** + * Casting Connection to nsISupports is ambiguous. + * This method handles that. + */ +inline nsISupports* +ToSupports(mozilla::storage::Connection* p) +{ + return NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, p); +} + +#endif // mozilla_storage_Connection_h diff --git a/components/storage/src/mozStorageError.cpp b/components/storage/src/mozStorageError.cpp new file mode 100644 index 000000000..1ddf25314 --- /dev/null +++ b/components/storage/src/mozStorageError.cpp @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozStorageError.h" + +namespace mozilla { +namespace storage { + +//////////////////////////////////////////////////////////////////////////////// +//// Error + +Error::Error(int aResult, + const char *aMessage) +: mResult(aResult) +, mMessage(aMessage) +{ +} + +/** + * Note: This object is only ever accessed on one thread at a time. It it not + * threadsafe, but it does need threadsafe AddRef and Release. + */ +NS_IMPL_ISUPPORTS( + Error, + mozIStorageError +) + +//////////////////////////////////////////////////////////////////////////////// +//// mozIStorageError + +NS_IMETHODIMP +Error::GetResult(int32_t *_result) +{ + *_result = mResult; + return NS_OK; +} + +NS_IMETHODIMP +Error::GetMessage(nsACString &_message) +{ + _message = mMessage; + return NS_OK; +} + +} // namespace storage +} // namespace mozilla diff --git a/components/storage/src/mozStorageError.h b/components/storage/src/mozStorageError.h new file mode 100644 index 000000000..07963cf13 --- /dev/null +++ b/components/storage/src/mozStorageError.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozStorageError_h +#define mozStorageError_h + +#include "mozIStorageError.h" +#include "nsString.h" +#include "mozilla/Attributes.h" + +namespace mozilla { +namespace storage { + +class Error final : public mozIStorageError +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_MOZISTORAGEERROR + + Error(int aResult, const char *aMessage); + +private: + ~Error() {} + + int mResult; + nsCString mMessage; +}; + +} // namespace storage +} // namespace mozilla + +#endif // mozStorageError_h diff --git a/components/storage/src/mozStorageHelper.h b/components/storage/src/mozStorageHelper.h new file mode 100644 index 000000000..1b4fde799 --- /dev/null +++ b/components/storage/src/mozStorageHelper.h @@ -0,0 +1,209 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZSTORAGEHELPER_H +#define MOZSTORAGEHELPER_H + +#include "nsAutoPtr.h" +#include "nsStringGlue.h" +#include "mozilla/DebugOnly.h" + +#include "mozIStorageAsyncConnection.h" +#include "mozIStorageConnection.h" +#include "mozIStorageStatement.h" +#include "mozIStoragePendingStatement.h" +#include "nsError.h" + +/** + * This class wraps a transaction inside a given C++ scope, guaranteeing that + * the transaction will be completed even if you have an exception or + * return early. + * + * A common use is to create an instance with aCommitOnComplete = false (rollback), + * then call Commit() on this object manually when your function completes + * successfully. + * + * @note nested transactions are not supported by Sqlite, so if a transaction + * is already in progress, this object does nothing. Note that in this case, + * you may not get the transaction type you asked for, and you won't be able + * to rollback. + * + * @param aConnection + * The connection to create the transaction on. + * @param aCommitOnComplete + * Controls whether the transaction is committed or rolled back when + * this object goes out of scope. + * @param aType [optional] + * The transaction type, as defined in mozIStorageConnection. Defaults + * to TRANSACTION_DEFERRED. + * @param aAsyncCommit [optional] + * Whether commit should be executed asynchronously on the helper thread. + * This is a special option introduced as an interim solution to reduce + * main-thread fsyncs in Places. Can only be used on main-thread. + * + * WARNING: YOU SHOULD _NOT_ WRITE NEW MAIN-THREAD CODE USING THIS! + * + * Notice that async commit might cause synchronous statements to fail + * with SQLITE_BUSY. A possible mitigation strategy is to use + * PRAGMA busy_timeout, but notice that might cause main-thread jank. + * Finally, if the database is using WAL journaling mode, other + * connections won't see the changes done in async committed transactions + * until commit is complete. + * + * For all of the above reasons, this should only be used as an interim + * solution and avoided completely if possible. + */ +class mozStorageTransaction +{ +public: + mozStorageTransaction(mozIStorageConnection* aConnection, + bool aCommitOnComplete, + int32_t aType = mozIStorageConnection::TRANSACTION_DEFERRED, + bool aAsyncCommit = false) + : mConnection(aConnection), + mHasTransaction(false), + mCommitOnComplete(aCommitOnComplete), + mCompleted(false), + mAsyncCommit(aAsyncCommit) + { + if (mConnection) { + nsAutoCString query("BEGIN"); + switch(aType) { + case mozIStorageConnection::TRANSACTION_IMMEDIATE: + query.AppendLiteral(" IMMEDIATE"); + break; + case mozIStorageConnection::TRANSACTION_EXCLUSIVE: + query.AppendLiteral(" EXCLUSIVE"); + break; + case mozIStorageConnection::TRANSACTION_DEFERRED: + query.AppendLiteral(" DEFERRED"); + break; + default: + MOZ_ASSERT(false, "Unknown transaction type"); + } + // If a transaction is already in progress, this will fail, since Sqlite + // doesn't support nested transactions. + mHasTransaction = NS_SUCCEEDED(mConnection->ExecuteSimpleSQL(query)); + } + } + + ~mozStorageTransaction() + { + if (mConnection && mHasTransaction && !mCompleted) { + if (mCommitOnComplete) { + mozilla::DebugOnly<nsresult> rv = Commit(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "A transaction didn't commit correctly"); + } + else { + mozilla::DebugOnly<nsresult> rv = Rollback(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "A transaction didn't rollback correctly"); + } + } + } + + /** + * Commits the transaction if one is in progress. If one is not in progress, + * this is a NOP since the actual owner of the transaction outside of our + * scope is in charge of finally committing or rolling back the transaction. + */ + nsresult Commit() + { + if (!mConnection || mCompleted || !mHasTransaction) + return NS_OK; + mCompleted = true; + + // TODO (bug 559659): this might fail with SQLITE_BUSY, but we don't handle + // it, thus the transaction might stay open until the next COMMIT. + nsresult rv; + if (mAsyncCommit) { + nsCOMPtr<mozIStoragePendingStatement> ps; + rv = mConnection->ExecuteSimpleSQLAsync(NS_LITERAL_CSTRING("COMMIT"), + nullptr, getter_AddRefs(ps)); + } + else { + rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("COMMIT")); + } + + if (NS_SUCCEEDED(rv)) + mHasTransaction = false; + + return rv; + } + + /** + * Rolls back the transaction if one is in progress. If one is not in progress, + * this is a NOP since the actual owner of the transaction outside of our + * scope is in charge of finally rolling back the transaction. + */ + nsresult Rollback() + { + if (!mConnection || mCompleted || !mHasTransaction) + return NS_OK; + mCompleted = true; + + // TODO (bug 1062823): from Sqlite 3.7.11 on, rollback won't ever return + // a busy error, so this handling can be removed. + nsresult rv = NS_OK; + do { + rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("ROLLBACK")); + if (rv == NS_ERROR_STORAGE_BUSY) + (void)PR_Sleep(PR_INTERVAL_NO_WAIT); + } while (rv == NS_ERROR_STORAGE_BUSY); + + if (NS_SUCCEEDED(rv)) + mHasTransaction = false; + + return rv; + } + +protected: + nsCOMPtr<mozIStorageConnection> mConnection; + bool mHasTransaction; + bool mCommitOnComplete; + bool mCompleted; + bool mAsyncCommit; +}; + +/** + * This class wraps a statement so that it is guaraneed to be reset when + * this object goes out of scope. + * + * Note that this always just resets the statement. If the statement doesn't + * need resetting, the reset operation is inexpensive. + */ +class MOZ_STACK_CLASS mozStorageStatementScoper +{ +public: + explicit mozStorageStatementScoper(mozIStorageStatement* aStatement) + : mStatement(aStatement) + { + } + ~mozStorageStatementScoper() + { + if (mStatement) + mStatement->Reset(); + } + + /** + * Call this to make the statement not reset. You might do this if you know + * that the statement has been reset. + */ + void Abandon() + { + mStatement = nullptr; + } + +protected: + nsCOMPtr<mozIStorageStatement> mStatement; +}; + +// Use this to make queries uniquely identifiable in telemetry +// statistics, especially PRAGMAs. We don't include __LINE__ so that +// queries are stable in the face of source code changes. +#define MOZ_STORAGE_UNIQUIFY_QUERY_STR "/* " __FILE__ " */ " + +#endif /* MOZSTORAGEHELPER_H */ diff --git a/components/storage/src/mozStorageModule.cpp b/components/storage/src/mozStorageModule.cpp new file mode 100644 index 000000000..ba77e4c62 --- /dev/null +++ b/components/storage/src/mozStorageModule.cpp @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "mozilla/ModuleUtils.h" + +#include "mozStorageService.h" +#include "mozStorageConnection.h" +#include "VacuumManager.h" + +#include "mozStorageCID.h" + +namespace mozilla { +namespace storage { + +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(Service, + Service::getSingleton) +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(VacuumManager, + VacuumManager::getSingleton) + +} // namespace storage +} // namespace mozilla + +NS_DEFINE_NAMED_CID(MOZ_STORAGE_SERVICE_CID); +NS_DEFINE_NAMED_CID(VACUUMMANAGER_CID); + +static const mozilla::Module::CIDEntry kStorageCIDs[] = { + { &kMOZ_STORAGE_SERVICE_CID, false, nullptr, mozilla::storage::ServiceConstructor }, + { &kVACUUMMANAGER_CID, false, nullptr, mozilla::storage::VacuumManagerConstructor }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kStorageContracts[] = { + { MOZ_STORAGE_SERVICE_CONTRACTID, &kMOZ_STORAGE_SERVICE_CID }, + { VACUUMMANAGER_CONTRACTID, &kVACUUMMANAGER_CID }, + { nullptr } +}; + +static const mozilla::Module::CategoryEntry kStorageCategories[] = { + { "idle-daily", "MozStorage Vacuum Manager", VACUUMMANAGER_CONTRACTID }, + { nullptr } +}; + +static const mozilla::Module kStorageModule = { + mozilla::Module::kVersion, + kStorageCIDs, + kStorageContracts, + kStorageCategories +}; + +NSMODULE_DEFN(mozStorageModule) = &kStorageModule; diff --git a/components/storage/src/mozStoragePrivateHelpers.cpp b/components/storage/src/mozStoragePrivateHelpers.cpp new file mode 100644 index 000000000..91924204f --- /dev/null +++ b/components/storage/src/mozStoragePrivateHelpers.cpp @@ -0,0 +1,277 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "sqlite3.h" + +#include "jsfriendapi.h" + +#include "nsPrintfCString.h" +#include "nsString.h" +#include "nsError.h" +#include "mozilla/Mutex.h" +#include "mozilla/CondVar.h" +#include "nsQueryObject.h" +#include "nsThreadUtils.h" +#include "nsJSUtils.h" + +#include "Variant.h" +#include "mozStoragePrivateHelpers.h" +#include "mozIStorageStatement.h" +#include "mozIStorageCompletionCallback.h" +#include "mozIStorageBindingParams.h" + +#include "mozilla/Logging.h" +extern mozilla::LazyLogModule gStorageLog; + +namespace mozilla { +namespace storage { + +nsresult +convertResultCode(int aSQLiteResultCode) +{ + // Drop off the extended result bits of the result code. + int rc = aSQLiteResultCode & 0xFF; + + switch (rc) { + case SQLITE_OK: + case SQLITE_ROW: + case SQLITE_DONE: + return NS_OK; + case SQLITE_CORRUPT: + case SQLITE_NOTADB: + return NS_ERROR_FILE_CORRUPTED; + case SQLITE_PERM: + case SQLITE_CANTOPEN: + return NS_ERROR_FILE_ACCESS_DENIED; + case SQLITE_BUSY: + return NS_ERROR_STORAGE_BUSY; + case SQLITE_LOCKED: + return NS_ERROR_FILE_IS_LOCKED; + case SQLITE_READONLY: + return NS_ERROR_FILE_READ_ONLY; + case SQLITE_IOERR: + return NS_ERROR_STORAGE_IOERR; + case SQLITE_FULL: + case SQLITE_TOOBIG: + return NS_ERROR_FILE_NO_DEVICE_SPACE; + case SQLITE_NOMEM: + return NS_ERROR_OUT_OF_MEMORY; + case SQLITE_MISUSE: + return NS_ERROR_UNEXPECTED; + case SQLITE_ABORT: + case SQLITE_INTERRUPT: + return NS_ERROR_ABORT; + case SQLITE_CONSTRAINT: + return NS_ERROR_STORAGE_CONSTRAINT; + } + + // generic error +#ifdef DEBUG + nsAutoCString message; + message.AppendLiteral("SQLite returned error code "); + message.AppendInt(rc); + message.AppendLiteral(" , Storage will convert it to NS_ERROR_FAILURE"); + NS_WARNING_ASSERTION(rc == SQLITE_ERROR, message.get()); +#endif + return NS_ERROR_FAILURE; +} + +void +checkAndLogStatementPerformance(sqlite3_stmt *aStatement) +{ + // Check to see if the query performed sorting operations or not. If it + // did, it may need to be optimized! + int count = ::sqlite3_stmt_status(aStatement, SQLITE_STMTSTATUS_SORT, 1); + if (count <= 0) + return; + + const char *sql = ::sqlite3_sql(aStatement); + + // Check to see if this is marked to not warn + if (::strstr(sql, "/* do not warn (bug ")) + return; + + // CREATE INDEX always sorts (sorting is a necessary step in creating + // an index). So ignore the warning there. + if (::strstr(sql, "CREATE INDEX") || ::strstr(sql, "CREATE UNIQUE INDEX")) + return; + + nsAutoCString message("Suboptimal indexes for the SQL statement "); +#ifdef MOZ_STORAGE_SORTWARNING_SQL_DUMP + message.Append('`'); + message.Append(sql); + message.AppendLiteral("` ["); + message.AppendInt(count); + message.AppendLiteral(" sort operation(s)]"); +#else + nsPrintfCString address("0x%p", aStatement); + message.Append(address); +#endif + message.AppendLiteral(" (http://mzl.la/1FuID0j)."); + NS_WARNING(message.get()); +} + +nsIVariant * +convertJSValToVariant( + JSContext *aCtx, + const JS::Value& aValue) +{ + if (aValue.isInt32()) + return new IntegerVariant(aValue.toInt32()); + + if (aValue.isDouble()) + return new FloatVariant(aValue.toDouble()); + + if (aValue.isString()) { + nsAutoJSString value; + if (!value.init(aCtx, aValue.toString())) + return nullptr; + return new TextVariant(value); + } + + if (aValue.isBoolean()) + return new IntegerVariant(aValue.isTrue() ? 1 : 0); + + if (aValue.isNull()) + return new NullVariant(); + + if (aValue.isObject()) { + JS::Rooted<JSObject*> obj(aCtx, &aValue.toObject()); + // We only support Date instances, all others fail. + bool valid; + if (!js::DateIsValid(aCtx, obj, &valid) || !valid) + return nullptr; + + double msecd; + if (!js::DateGetMsecSinceEpoch(aCtx, obj, &msecd)) + return nullptr; + + msecd *= 1000.0; + int64_t msec = msecd; + + return new IntegerVariant(msec); + } + + return nullptr; +} + +Variant_base * +convertVariantToStorageVariant(nsIVariant* aVariant) +{ + RefPtr<Variant_base> variant = do_QueryObject(aVariant); + if (variant) { + // JS helpers already convert the JS representation to a Storage Variant, + // in such a case there's nothing left to do here, so just pass-through. + return variant; + } + + if (!aVariant) + return new NullVariant(); + + uint16_t dataType; + nsresult rv = aVariant->GetDataType(&dataType); + NS_ENSURE_SUCCESS(rv, nullptr); + + switch (dataType) { + case nsIDataType::VTYPE_BOOL: + case nsIDataType::VTYPE_INT8: + case nsIDataType::VTYPE_INT16: + case nsIDataType::VTYPE_INT32: + case nsIDataType::VTYPE_UINT8: + case nsIDataType::VTYPE_UINT16: + case nsIDataType::VTYPE_UINT32: + case nsIDataType::VTYPE_INT64: + case nsIDataType::VTYPE_UINT64: { + int64_t v; + rv = aVariant->GetAsInt64(&v); + NS_ENSURE_SUCCESS(rv, nullptr); + return new IntegerVariant(v); + } + case nsIDataType::VTYPE_FLOAT: + case nsIDataType::VTYPE_DOUBLE: { + double v; + rv = aVariant->GetAsDouble(&v); + NS_ENSURE_SUCCESS(rv, nullptr); + return new FloatVariant(v); + } + case nsIDataType::VTYPE_CHAR: + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_CSTRING: { + nsCString v; + rv = aVariant->GetAsAUTF8String(v); + NS_ENSURE_SUCCESS(rv, nullptr); + return new UTF8TextVariant(v); + } + case nsIDataType::VTYPE_WCHAR: + case nsIDataType::VTYPE_DOMSTRING: + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + case nsIDataType::VTYPE_ASTRING: { + nsString v; + rv = aVariant->GetAsAString(v); + NS_ENSURE_SUCCESS(rv, nullptr); + return new TextVariant(v); + } + case nsIDataType::VTYPE_ARRAY: { + uint16_t type; + nsIID iid; + uint32_t len; + void *rawArray; + // Note this copies the array data. + rv = aVariant->GetAsArray(&type, &iid, &len, &rawArray); + NS_ENSURE_SUCCESS(rv, nullptr); + if (type == nsIDataType::VTYPE_UINT8) { + std::pair<uint8_t *, int> v(static_cast<uint8_t *>(rawArray), len); + // Take ownership of the data avoiding a further copy. + return new AdoptedBlobVariant(v); + } + MOZ_FALLTHROUGH; + } + case nsIDataType::VTYPE_EMPTY: + case nsIDataType::VTYPE_EMPTY_ARRAY: + case nsIDataType::VTYPE_VOID: + return new NullVariant(); + case nsIDataType::VTYPE_ID: + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + default: + NS_WARNING("Unsupported variant type"); + return nullptr; + } + + return nullptr; +} + +namespace { +class CallbackEvent : public Runnable +{ +public: + explicit CallbackEvent(mozIStorageCompletionCallback *aCallback) + : mCallback(aCallback) + { + } + + NS_IMETHOD Run() override + { + (void)mCallback->Complete(NS_OK, nullptr); + return NS_OK; + } +private: + nsCOMPtr<mozIStorageCompletionCallback> mCallback; +}; +} // namespace +already_AddRefed<nsIRunnable> +newCompletionEvent(mozIStorageCompletionCallback *aCallback) +{ + NS_ASSERTION(aCallback, "Passing a null callback is a no-no!"); + nsCOMPtr<nsIRunnable> event = new CallbackEvent(aCallback); + return event.forget(); +} + +} // namespace storage +} // namespace mozilla diff --git a/components/storage/src/mozStoragePrivateHelpers.h b/components/storage/src/mozStoragePrivateHelpers.h new file mode 100644 index 000000000..cfec6ff7f --- /dev/null +++ b/components/storage/src/mozStoragePrivateHelpers.h @@ -0,0 +1,143 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozStoragePrivateHelpers_h +#define mozStoragePrivateHelpers_h + +/** + * This file contains convenience methods for mozStorage. + */ + +#include "sqlite3.h" +#include "nsIVariant.h" +#include "nsError.h" +#include "nsAutoPtr.h" +#include "js/TypeDecls.h" +#include "Variant.h" + +class mozIStorageCompletionCallback; +class nsIRunnable; + +namespace mozilla { +namespace storage { + +//////////////////////////////////////////////////////////////////////////////// +//// Macros + +#define ENSURE_INDEX_VALUE(aIndex, aCount) \ + NS_ENSURE_TRUE(aIndex < aCount, NS_ERROR_INVALID_ARG) + +//////////////////////////////////////////////////////////////////////////////// +//// Functions + +/** + * Converts a SQLite return code to an nsresult return code. + * + * @param aSQLiteResultCode + * The SQLite return code to convert. + * @returns the corresponding nsresult code for aSQLiteResultCode. + */ +nsresult convertResultCode(int aSQLiteResultCode); + +/** + * Checks the performance of a SQLite statement and logs a warning with + * NS_WARNING. Currently this only checks the number of sort operations done + * on a statement, and if more than zero have been done, the statement can be + * made faster with the careful use of an index. + * + * @param aStatement + * The sqlite3_stmt object to check. + */ +void checkAndLogStatementPerformance(sqlite3_stmt *aStatement); + +/** + * Convert the provided JS::Value into a variant representation if possible. + * + * @param aCtx + * The JSContext the value is from. + * @param aValue + * The JavaScript value to convert. All primitive types are supported, + * but only Date objects are supported from the Date family. Date + * objects are coerced to PRTime (nanoseconds since epoch) values. + * @return the variant if conversion was successful, nullptr if conversion + * failed. The caller is responsible for addref'ing if non-null. + */ +nsIVariant *convertJSValToVariant(JSContext *aCtx, const JS::Value& aValue); + +/** + * Convert a provided nsIVariant implementation to our own thread-safe + * refcounting implementation, if needed. + * + * @param aValue + * The original nsIVariant to be converted. + * @return a thread-safe refcounting nsIVariant implementation. + */ +Variant_base *convertVariantToStorageVariant(nsIVariant *aVariant); + +/** + * Obtains an event that will notify a completion callback about completion. + * + * @param aCallback + * The callback to be notified. + * @return an nsIRunnable that can be dispatched to the calling thread. + */ +already_AddRefed<nsIRunnable> newCompletionEvent( + mozIStorageCompletionCallback *aCallback +); + +/** + * Utility method to get a Blob as a string value. The string expects + * the interface exposed by nsAString/nsACString/etc. + */ +template<class T, class V> +nsresult +DoGetBlobAsString(T* aThis, uint32_t aIndex, V& aValue) +{ + typedef typename V::char_type char_type; + + uint32_t size; + char_type* blob; + nsresult rv = + aThis->GetBlob(aIndex, &size, reinterpret_cast<uint8_t**>(&blob)); + NS_ENSURE_SUCCESS(rv, rv); + + aValue.Assign(blob, size / sizeof(char_type)); + delete[] blob; + return NS_OK; +} + +/** + * Utility method to bind a string value as a Blob. The string expects + * the interface exposed by nsAString/nsACString/etc. + */ +template<class T, class V> +nsresult +DoBindStringAsBlobByName(T* aThis, const nsACString& aName, const V& aValue) +{ + typedef typename V::char_type char_type; + return aThis->BindBlobByName(aName, + reinterpret_cast<const uint8_t*>(aValue.BeginReading()), + aValue.Length() * sizeof(char_type)); +} + +/** + * Utility method to bind a string value as a Blob. The string expects + * the interface exposed by nsAString/nsACString/etc. + */ +template<class T, class V> +nsresult +DoBindStringAsBlobByIndex(T* aThis, uint32_t aIndex, const V& aValue) +{ + typedef typename V::char_type char_type; + return aThis->BindBlobByIndex(aIndex, + reinterpret_cast<const uint8_t*>(aValue.BeginReading()), + aValue.Length() * sizeof(char_type)); +} + +} // namespace storage +} // namespace mozilla + +#endif // mozStoragePrivateHelpers_h diff --git a/components/storage/src/mozStorageResultSet.cpp b/components/storage/src/mozStorageResultSet.cpp new file mode 100644 index 000000000..e4f3d1f35 --- /dev/null +++ b/components/storage/src/mozStorageResultSet.cpp @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozStorageRow.h" +#include "mozStorageResultSet.h" + +namespace mozilla { +namespace storage { + +//////////////////////////////////////////////////////////////////////////////// +//// ResultSet + +ResultSet::ResultSet() +: mCurrentIndex(0) +{ +} + +ResultSet::~ResultSet() +{ + mData.Clear(); +} + +nsresult +ResultSet::add(mozIStorageRow *aRow) +{ + return mData.AppendObject(aRow) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +/** + * Note: This object is only ever accessed on one thread at a time. It it not + * threadsafe, but it does need threadsafe AddRef and Release. + */ +NS_IMPL_ISUPPORTS( + ResultSet, + mozIStorageResultSet +) + +//////////////////////////////////////////////////////////////////////////////// +//// mozIStorageResultSet + +NS_IMETHODIMP +ResultSet::GetNextRow(mozIStorageRow **_row) +{ + NS_ENSURE_ARG_POINTER(_row); + + if (mCurrentIndex >= mData.Count()) { + // Just return null here + return NS_OK; + } + + NS_ADDREF(*_row = mData.ObjectAt(mCurrentIndex++)); + return NS_OK; +} + +} // namespace storage +} // namespace mozilla diff --git a/components/storage/src/mozStorageResultSet.h b/components/storage/src/mozStorageResultSet.h new file mode 100644 index 000000000..07a861c52 --- /dev/null +++ b/components/storage/src/mozStorageResultSet.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozStorageResultSet_h +#define mozStorageResultSet_h + +#include "mozIStorageResultSet.h" +#include "nsCOMArray.h" +#include "mozilla/Attributes.h" +class mozIStorageRow; + +namespace mozilla { +namespace storage { + +class ResultSet final : public mozIStorageResultSet +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_MOZISTORAGERESULTSET + + ResultSet(); + + /** + * Adds a tuple to this result set. + */ + nsresult add(mozIStorageRow *aTuple); + + /** + * @returns the number of rows this result set holds. + */ + int32_t rows() const { return mData.Count(); } + +private: + ~ResultSet(); + + /** + * Stores the current index of the active result set. + */ + int32_t mCurrentIndex; + + /** + * Stores the tuples. + */ + nsCOMArray<mozIStorageRow> mData; +}; + +} // namespace storage +} // namespace mozilla + +#endif // mozStorageResultSet_h diff --git a/components/storage/src/mozStorageRow.cpp b/components/storage/src/mozStorageRow.cpp new file mode 100644 index 000000000..7bcac4c30 --- /dev/null +++ b/components/storage/src/mozStorageRow.cpp @@ -0,0 +1,247 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsString.h" + +#include "sqlite3.h" +#include "mozStoragePrivateHelpers.h" +#include "Variant.h" +#include "mozStorageRow.h" + +namespace mozilla { +namespace storage { + +//////////////////////////////////////////////////////////////////////////////// +//// Row + +nsresult +Row::initialize(sqlite3_stmt *aStatement) +{ + // Get the number of results + mNumCols = ::sqlite3_column_count(aStatement); + + // Start copying over values + for (uint32_t i = 0; i < mNumCols; i++) { + // Store the value + nsIVariant *variant = nullptr; + int type = ::sqlite3_column_type(aStatement, i); + switch (type) { + case SQLITE_INTEGER: + variant = new IntegerVariant(::sqlite3_column_int64(aStatement, i)); + break; + case SQLITE_FLOAT: + variant = new FloatVariant(::sqlite3_column_double(aStatement, i)); + break; + case SQLITE_TEXT: + { + nsDependentString str( + static_cast<const char16_t *>(::sqlite3_column_text16(aStatement, i)) + ); + variant = new TextVariant(str); + break; + } + case SQLITE_NULL: + variant = new NullVariant(); + break; + case SQLITE_BLOB: + { + int size = ::sqlite3_column_bytes(aStatement, i); + const void *data = ::sqlite3_column_blob(aStatement, i); + variant = new BlobVariant(std::pair<const void *, int>(data, size)); + break; + } + default: + return NS_ERROR_UNEXPECTED; + } + NS_ENSURE_TRUE(variant, NS_ERROR_OUT_OF_MEMORY); + + // Insert into our storage array + NS_ENSURE_TRUE(mData.InsertObjectAt(variant, i), NS_ERROR_OUT_OF_MEMORY); + + // Associate the name (if any) with the index + const char *name = ::sqlite3_column_name(aStatement, i); + if (!name) break; + nsAutoCString colName(name); + mNameHashtable.Put(colName, i); + } + + return NS_OK; +} + +/** + * Note: This object is only ever accessed on one thread at a time. It it not + * threadsafe, but it does need threadsafe AddRef and Release. + */ +NS_IMPL_ISUPPORTS( + Row, + mozIStorageRow, + mozIStorageValueArray +) + +//////////////////////////////////////////////////////////////////////////////// +//// mozIStorageRow + +NS_IMETHODIMP +Row::GetResultByIndex(uint32_t aIndex, + nsIVariant **_result) +{ + ENSURE_INDEX_VALUE(aIndex, mNumCols); + NS_ADDREF(*_result = mData.ObjectAt(aIndex)); + return NS_OK; +} + +NS_IMETHODIMP +Row::GetResultByName(const nsACString &aName, + nsIVariant **_result) +{ + uint32_t index; + NS_ENSURE_TRUE(mNameHashtable.Get(aName, &index), NS_ERROR_NOT_AVAILABLE); + return GetResultByIndex(index, _result); +} + +//////////////////////////////////////////////////////////////////////////////// +//// mozIStorageValueArray + +NS_IMETHODIMP +Row::GetNumEntries(uint32_t *_entries) +{ + *_entries = mNumCols; + return NS_OK; +} + +NS_IMETHODIMP +Row::GetTypeOfIndex(uint32_t aIndex, + int32_t *_type) +{ + ENSURE_INDEX_VALUE(aIndex, mNumCols); + + uint16_t type; + (void)mData.ObjectAt(aIndex)->GetDataType(&type); + switch (type) { + case nsIDataType::VTYPE_INT32: + case nsIDataType::VTYPE_INT64: + *_type = mozIStorageValueArray::VALUE_TYPE_INTEGER; + break; + case nsIDataType::VTYPE_DOUBLE: + *_type = mozIStorageValueArray::VALUE_TYPE_FLOAT; + break; + case nsIDataType::VTYPE_ASTRING: + *_type = mozIStorageValueArray::VALUE_TYPE_TEXT; + break; + case nsIDataType::VTYPE_ARRAY: + *_type = mozIStorageValueArray::VALUE_TYPE_BLOB; + break; + default: + *_type = mozIStorageValueArray::VALUE_TYPE_NULL; + break; + } + return NS_OK; +} + +NS_IMETHODIMP +Row::GetInt32(uint32_t aIndex, + int32_t *_value) +{ + ENSURE_INDEX_VALUE(aIndex, mNumCols); + return mData.ObjectAt(aIndex)->GetAsInt32(_value); +} + +NS_IMETHODIMP +Row::GetInt64(uint32_t aIndex, + int64_t *_value) +{ + ENSURE_INDEX_VALUE(aIndex, mNumCols); + return mData.ObjectAt(aIndex)->GetAsInt64(_value); +} + +NS_IMETHODIMP +Row::GetDouble(uint32_t aIndex, + double *_value) +{ + ENSURE_INDEX_VALUE(aIndex, mNumCols); + return mData.ObjectAt(aIndex)->GetAsDouble(_value); +} + +NS_IMETHODIMP +Row::GetUTF8String(uint32_t aIndex, + nsACString &_value) +{ + ENSURE_INDEX_VALUE(aIndex, mNumCols); + return mData.ObjectAt(aIndex)->GetAsAUTF8String(_value); +} + +NS_IMETHODIMP +Row::GetString(uint32_t aIndex, + nsAString &_value) +{ + ENSURE_INDEX_VALUE(aIndex, mNumCols); + return mData.ObjectAt(aIndex)->GetAsAString(_value); +} + +NS_IMETHODIMP +Row::GetBlob(uint32_t aIndex, + uint32_t *_size, + uint8_t **_blob) +{ + ENSURE_INDEX_VALUE(aIndex, mNumCols); + + uint16_t type; + nsIID interfaceIID; + return mData.ObjectAt(aIndex)->GetAsArray(&type, &interfaceIID, _size, + reinterpret_cast<void **>(_blob)); +} + +NS_IMETHODIMP +Row::GetBlobAsString(uint32_t aIndex, nsAString& aValue) +{ + return DoGetBlobAsString(this, aIndex, aValue); +} + +NS_IMETHODIMP +Row::GetBlobAsUTF8String(uint32_t aIndex, nsACString& aValue) +{ + return DoGetBlobAsString(this, aIndex, aValue); +} + +NS_IMETHODIMP +Row::GetIsNull(uint32_t aIndex, + bool *_isNull) +{ + ENSURE_INDEX_VALUE(aIndex, mNumCols); + NS_ENSURE_ARG_POINTER(_isNull); + + uint16_t type; + (void)mData.ObjectAt(aIndex)->GetDataType(&type); + *_isNull = type == nsIDataType::VTYPE_EMPTY; + return NS_OK; +} + +NS_IMETHODIMP +Row::GetSharedUTF8String(uint32_t, + uint32_t *, + char const **) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +Row::GetSharedString(uint32_t, + uint32_t *, + const char16_t **) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +Row::GetSharedBlob(uint32_t, + uint32_t *, + const uint8_t **) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +} // namespace storage +} // namespace mozilla diff --git a/components/storage/src/mozStorageRow.h b/components/storage/src/mozStorageRow.h new file mode 100644 index 000000000..9145c40a5 --- /dev/null +++ b/components/storage/src/mozStorageRow.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozStorageRow_h +#define mozStorageRow_h + +#include "mozIStorageRow.h" +#include "nsCOMArray.h" +#include "nsDataHashtable.h" +#include "mozilla/Attributes.h" +class nsIVariant; +struct sqlite3_stmt; + +namespace mozilla { +namespace storage { + +class Row final : public mozIStorageRow +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_MOZISTORAGEROW + NS_DECL_MOZISTORAGEVALUEARRAY + + Row() : mNumCols(0) {} + + /** + * Initializes the object with the given statement. Copies the values from + * the statement. + * + * @param aStatement + * The sqlite statement to pull results from. + */ + nsresult initialize(sqlite3_stmt *aStatement); + +private: + ~Row() {} + + /** + * The number of columns in this tuple. + */ + uint32_t mNumCols; + + /** + * Stores the data in the tuple. + */ + nsCOMArray<nsIVariant> mData; + + /** + * Maps a given name to a column index. + */ + nsDataHashtable<nsCStringHashKey, uint32_t> mNameHashtable; +}; + +} // namespace storage +} // namespace mozilla + +#endif // mozStorageRow_h diff --git a/components/storage/src/mozStorageSQLFunctions.cpp b/components/storage/src/mozStorageSQLFunctions.cpp new file mode 100644 index 000000000..8b3148e21 --- /dev/null +++ b/components/storage/src/mozStorageSQLFunctions.cpp @@ -0,0 +1,406 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" +#include "nsTArray.h" +#include "mozStorageSQLFunctions.h" +#include "nsUnicharUtils.h" +#include <algorithm> + +namespace mozilla { +namespace storage { + +//////////////////////////////////////////////////////////////////////////////// +//// Local Helper Functions + +namespace { + +/** + * Performs the LIKE comparison of a string against a pattern. For more detail + * see http://www.sqlite.org/lang_expr.html#like. + * + * @param aPatternItr + * An iterator at the start of the pattern to check for. + * @param aPatternEnd + * An iterator at the end of the pattern to check for. + * @param aStringItr + * An iterator at the start of the string to check for the pattern. + * @param aStringEnd + * An iterator at the end of the string to check for the pattern. + * @param aEscapeChar + * The character to use for escaping symbols in the pattern. + * @return 1 if the pattern is found, 0 otherwise. + */ +int +likeCompare(nsAString::const_iterator aPatternItr, + nsAString::const_iterator aPatternEnd, + nsAString::const_iterator aStringItr, + nsAString::const_iterator aStringEnd, + char16_t aEscapeChar) +{ + const char16_t MATCH_ALL('%'); + const char16_t MATCH_ONE('_'); + + bool lastWasEscape = false; + while (aPatternItr != aPatternEnd) { + /** + * What we do in here is take a look at each character from the input + * pattern, and do something with it. There are 4 possibilities: + * 1) character is an un-escaped match-all character + * 2) character is an un-escaped match-one character + * 3) character is an un-escaped escape character + * 4) character is not any of the above + */ + if (!lastWasEscape && *aPatternItr == MATCH_ALL) { + // CASE 1 + /** + * Now we need to skip any MATCH_ALL or MATCH_ONE characters that follow a + * MATCH_ALL character. For each MATCH_ONE character, skip one character + * in the pattern string. + */ + while (*aPatternItr == MATCH_ALL || *aPatternItr == MATCH_ONE) { + if (*aPatternItr == MATCH_ONE) { + // If we've hit the end of the string we are testing, no match + if (aStringItr == aStringEnd) + return 0; + aStringItr++; + } + aPatternItr++; + } + + // If we've hit the end of the pattern string, match + if (aPatternItr == aPatternEnd) + return 1; + + while (aStringItr != aStringEnd) { + if (likeCompare(aPatternItr, aPatternEnd, aStringItr, aStringEnd, + aEscapeChar)) { + // we've hit a match, so indicate this + return 1; + } + aStringItr++; + } + + // No match + return 0; + } + else if (!lastWasEscape && *aPatternItr == MATCH_ONE) { + // CASE 2 + if (aStringItr == aStringEnd) { + // If we've hit the end of the string we are testing, no match + return 0; + } + aStringItr++; + lastWasEscape = false; + } + else if (!lastWasEscape && *aPatternItr == aEscapeChar) { + // CASE 3 + lastWasEscape = true; + } + else { + // CASE 4 + if (::ToUpperCase(*aStringItr) != ::ToUpperCase(*aPatternItr)) { + // If we've hit a point where the strings don't match, there is no match + return 0; + } + aStringItr++; + lastWasEscape = false; + } + + aPatternItr++; + } + + return aStringItr == aStringEnd; +} + +/** + * Compute the Levenshtein Edit Distance between two strings. + * + * @param aStringS + * a string + * @param aStringT + * another string + * @param _result + * an outparam that will receive the edit distance between the arguments + * @return a Sqlite result code, e.g. SQLITE_OK, SQLITE_NOMEM, etc. + */ +int +levenshteinDistance(const nsAString &aStringS, + const nsAString &aStringT, + int *_result) +{ + // Set the result to a non-sensical value in case we encounter an error. + *_result = -1; + + const uint32_t sLen = aStringS.Length(); + const uint32_t tLen = aStringT.Length(); + + if (sLen == 0) { + *_result = tLen; + return SQLITE_OK; + } + if (tLen == 0) { + *_result = sLen; + return SQLITE_OK; + } + + // Notionally, Levenshtein Distance is computed in a matrix. If we + // assume s = "span" and t = "spam", the matrix would look like this: + // s --> + // t s p a n + // | 0 1 2 3 4 + // V s 1 * * * * + // p 2 * * * * + // a 3 * * * * + // m 4 * * * * + // + // Note that the row width is sLen + 1 and the column height is tLen + 1, + // where sLen is the length of the string "s" and tLen is the length of "t". + // The first row and the first column are initialized as shown, and + // the algorithm computes the remaining cells row-by-row, and + // left-to-right within each row. The computation only requires that + // we be able to see the current row and the previous one. + + // Allocate memory for two rows. + AutoTArray<int, nsAutoString::kDefaultStorageSize> row1; + AutoTArray<int, nsAutoString::kDefaultStorageSize> row2; + + // Declare the raw pointers that will actually be used to access the memory. + int *prevRow = row1.AppendElements(sLen + 1); + int *currRow = row2.AppendElements(sLen + 1); + + // Initialize the first row. + for (uint32_t i = 0; i <= sLen; i++) + prevRow[i] = i; + + const char16_t *s = aStringS.BeginReading(); + const char16_t *t = aStringT.BeginReading(); + + // Compute the empty cells in the "matrix" row-by-row, starting with + // the second row. + for (uint32_t ti = 1; ti <= tLen; ti++) { + + // Initialize the first cell in this row. + currRow[0] = ti; + + // Get the character from "t" that corresponds to this row. + const char16_t tch = t[ti - 1]; + + // Compute the remaining cells in this row, left-to-right, + // starting at the second column (and first character of "s"). + for (uint32_t si = 1; si <= sLen; si++) { + + // Get the character from "s" that corresponds to this column, + // compare it to the t-character, and compute the "cost". + const char16_t sch = s[si - 1]; + int cost = (sch == tch) ? 0 : 1; + + // ............ We want to calculate the value of cell "d" from + // ...ab....... the previously calculated (or initialized) cells + // ...cd....... "a", "b", and "c", where d = min(a', b', c'). + // ............ + int aPrime = prevRow[si - 1] + cost; + int bPrime = prevRow[si] + 1; + int cPrime = currRow[si - 1] + 1; + currRow[si] = std::min(aPrime, std::min(bPrime, cPrime)); + } + + // Advance to the next row. The current row becomes the previous + // row and we recycle the old previous row as the new current row. + // We don't need to re-initialize the new current row since we will + // rewrite all of its cells anyway. + int *oldPrevRow = prevRow; + prevRow = currRow; + currRow = oldPrevRow; + } + + // The final result is the value of the last cell in the last row. + // Note that that's now in the "previous" row, since we just swapped them. + *_result = prevRow[sLen]; + return SQLITE_OK; +} + +// This struct is used only by registerFunctions below, but ISO C++98 forbids +// instantiating a template dependent on a locally-defined type. Boo-urns! +struct Functions { + const char *zName; + int nArg; + int enc; + void *pContext; + void (*xFunc)(::sqlite3_context*, int, sqlite3_value**); +}; + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +//// Exposed Functions + +int +registerFunctions(sqlite3 *aDB) +{ + Functions functions[] = { + {"lower", + 1, + SQLITE_UTF16, + 0, + caseFunction}, + {"lower", + 1, + SQLITE_UTF8, + 0, + caseFunction}, + {"upper", + 1, + SQLITE_UTF16, + (void*)1, + caseFunction}, + {"upper", + 1, + SQLITE_UTF8, + (void*)1, + caseFunction}, + + {"like", + 2, + SQLITE_UTF16, + 0, + likeFunction}, + {"like", + 2, + SQLITE_UTF8, + 0, + likeFunction}, + {"like", + 3, + SQLITE_UTF16, + 0, + likeFunction}, + {"like", + 3, + SQLITE_UTF8, + 0, + likeFunction}, + + {"levenshteinDistance", + 2, + SQLITE_UTF16, + 0, + levenshteinDistanceFunction}, + {"levenshteinDistance", + 2, + SQLITE_UTF8, + 0, + levenshteinDistanceFunction}, + }; + + int rv = SQLITE_OK; + for (size_t i = 0; SQLITE_OK == rv && i < ArrayLength(functions); ++i) { + struct Functions *p = &functions[i]; + rv = ::sqlite3_create_function(aDB, p->zName, p->nArg, p->enc, p->pContext, + p->xFunc, nullptr, nullptr); + } + + return rv; +} + +//////////////////////////////////////////////////////////////////////////////// +//// SQL Functions + +void +caseFunction(sqlite3_context *aCtx, + int aArgc, + sqlite3_value **aArgv) +{ + NS_ASSERTION(1 == aArgc, "Invalid number of arguments!"); + + nsAutoString data(static_cast<const char16_t *>(::sqlite3_value_text16(aArgv[0]))); + bool toUpper = ::sqlite3_user_data(aCtx) ? true : false; + + if (toUpper) + ::ToUpperCase(data); + else + ::ToLowerCase(data); + + // Set the result. + ::sqlite3_result_text16(aCtx, data.get(), -1, SQLITE_TRANSIENT); +} + +/** + * This implements the like() SQL function. This is used by the LIKE operator. + * The SQL statement 'A LIKE B' is implemented as 'like(B, A)', and if there is + * an escape character, say E, it is implemented as 'like(B, A, E)'. + */ +void +likeFunction(sqlite3_context *aCtx, + int aArgc, + sqlite3_value **aArgv) +{ + NS_ASSERTION(2 == aArgc || 3 == aArgc, "Invalid number of arguments!"); + + if (::sqlite3_value_bytes(aArgv[0]) > SQLITE_MAX_LIKE_PATTERN_LENGTH) { + ::sqlite3_result_error(aCtx, "LIKE or GLOB pattern too complex", + SQLITE_TOOBIG); + return; + } + + if (!::sqlite3_value_text16(aArgv[0]) || !::sqlite3_value_text16(aArgv[1])) + return; + + nsDependentString A(static_cast<const char16_t *>(::sqlite3_value_text16(aArgv[1]))); + nsDependentString B(static_cast<const char16_t *>(::sqlite3_value_text16(aArgv[0]))); + NS_ASSERTION(!B.IsEmpty(), "LIKE string must not be null!"); + + char16_t E = 0; + if (3 == aArgc) + E = static_cast<const char16_t *>(::sqlite3_value_text16(aArgv[2]))[0]; + + nsAString::const_iterator itrString, endString; + A.BeginReading(itrString); + A.EndReading(endString); + nsAString::const_iterator itrPattern, endPattern; + B.BeginReading(itrPattern); + B.EndReading(endPattern); + ::sqlite3_result_int(aCtx, likeCompare(itrPattern, endPattern, itrString, + endString, E)); +} + +void levenshteinDistanceFunction(sqlite3_context *aCtx, + int aArgc, + sqlite3_value **aArgv) +{ + NS_ASSERTION(2 == aArgc, "Invalid number of arguments!"); + + // If either argument is a SQL NULL, then return SQL NULL. + if (::sqlite3_value_type(aArgv[0]) == SQLITE_NULL || + ::sqlite3_value_type(aArgv[1]) == SQLITE_NULL) { + ::sqlite3_result_null(aCtx); + return; + } + + int aLen = ::sqlite3_value_bytes16(aArgv[0]) / sizeof(char16_t); + const char16_t *a = static_cast<const char16_t *>(::sqlite3_value_text16(aArgv[0])); + + int bLen = ::sqlite3_value_bytes16(aArgv[1]) / sizeof(char16_t); + const char16_t *b = static_cast<const char16_t *>(::sqlite3_value_text16(aArgv[1])); + + // Compute the Levenshtein Distance, and return the result (or error). + int distance = -1; + const nsDependentString A(a, aLen); + const nsDependentString B(b, bLen); + int status = levenshteinDistance(A, B, &distance); + if (status == SQLITE_OK) { + ::sqlite3_result_int(aCtx, distance); + } + else if (status == SQLITE_NOMEM) { + ::sqlite3_result_error_nomem(aCtx); + } + else { + ::sqlite3_result_error(aCtx, "User function returned error code", -1); + } +} + +} // namespace storage +} // namespace mozilla diff --git a/components/storage/src/mozStorageSQLFunctions.h b/components/storage/src/mozStorageSQLFunctions.h new file mode 100644 index 000000000..556a4a7c1 --- /dev/null +++ b/components/storage/src/mozStorageSQLFunctions.h @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozStorageSQLFunctions_h +#define mozStorageSQLFunctions_h + +#include "sqlite3.h" +#include "nscore.h" + +namespace mozilla { +namespace storage { + +/** + * Registers the functions declared here with the specified database. + * + * @param aDB + * The database we'll be registering the functions with. + * @return the SQLite status code indicating success or failure. + */ +int registerFunctions(sqlite3 *aDB); + +//////////////////////////////////////////////////////////////////////////////// +//// Predefined Functions + +/** + * Overridden function to perform the SQL functions UPPER and LOWER. These + * support unicode, which the default implementations do not do. + * + * @param aCtx + * The sqlite_context that this function is being called on. + * @param aArgc + * The number of arguments the function is being called with. + * @param aArgv + * An array of the arguments the functions is being called with. + */ +void caseFunction(sqlite3_context *aCtx, + int aArgc, + sqlite3_value **aArgv); + +/** + * Overridden function to perform the SQL function LIKE. This supports unicode, + * which the default implementation does not do. + * + * @param aCtx + * The sqlite_context that this function is being called on. + * @param aArgc + * The number of arguments the function is being called with. + * @param aArgv + * An array of the arguments the functions is being called with. + */ +void likeFunction(sqlite3_context *aCtx, + int aArgc, + sqlite3_value **aArgv); + +/** + * An implementation of the Levenshtein Edit Distance algorithm for use in + * Sqlite queries. + * + * @param aCtx + * The sqlite_context that this function is being called on. + * @param aArgc + * The number of arguments the function is being called with. + * @param aArgv + * An array of the arguments the functions is being called with. + */ +void levenshteinDistanceFunction(sqlite3_context *aCtx, + int aArgc, + sqlite3_value **aArgv); + +} // namespace storage +} // namespace mozilla + +#endif // mozStorageSQLFunctions_h diff --git a/components/storage/src/mozStorageService.cpp b/components/storage/src/mozStorageService.cpp new file mode 100644 index 000000000..8c6f65232 --- /dev/null +++ b/components/storage/src/mozStorageService.cpp @@ -0,0 +1,930 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Attributes.h" +#include "mozilla/DebugOnly.h" + +#include "mozStorageService.h" +#include "mozStorageConnection.h" +#include "nsAutoPtr.h" +#include "nsCollationCID.h" +#include "nsEmbedCID.h" +#include "nsThreadUtils.h" +#include "mozStoragePrivateHelpers.h" +#include "nsILocale.h" +#include "nsILocaleService.h" +#include "nsIXPConnect.h" +#include "nsIObserverService.h" +#include "nsIPropertyBag2.h" +#include "mozilla/Services.h" +#include "mozilla/Preferences.h" +#include "mozilla/LateWriteChecks.h" +#include "mozIStorageCompletionCallback.h" +#include "mozIStoragePendingStatement.h" + +#include "sqlite3.h" + +#ifdef SQLITE_OS_WIN +// "windows.h" was included and it can #define lots of things we care about... +#undef CompareString +#endif + +#include "nsIPromptService.h" + +#ifdef MOZ_STORAGE_MEMORY +# include "mozmemory.h" +#endif + +//////////////////////////////////////////////////////////////////////////////// +//// Defines + +#define PREF_TS_SYNCHRONOUS "toolkit.storage.synchronous" +#define PREF_TS_SYNCHRONOUS_DEFAULT 1 + +#define PREF_TS_PAGESIZE "toolkit.storage.pageSize" + +// This value must be kept in sync with the value of SQLITE_DEFAULT_PAGE_SIZE in +// db/sqlite3/src/Makefile.in. +#define PREF_TS_PAGESIZE_DEFAULT 32768 + +namespace mozilla { +namespace storage { + +//////////////////////////////////////////////////////////////////////////////// +//// Memory Reporting + +static int64_t +StorageSQLiteDistinguishedAmount() +{ + return ::sqlite3_memory_used(); +} + +/** + * Passes a single SQLite memory statistic to a memory reporter callback. + * + * @param aHandleReport + * The callback. + * @param aData + * The data for the callback. + * @param aConn + * The SQLite connection. + * @param aPathHead + * Head of the path for the memory report. + * @param aKind + * The memory report statistic kind, one of "stmt", "cache" or + * "schema". + * @param aDesc + * The memory report description. + * @param aOption + * The SQLite constant for getting the measurement. + * @param aTotal + * The accumulator for the measurement. + */ +static void +ReportConn(nsIHandleReportCallback *aHandleReport, + nsISupports *aData, + Connection *aConn, + const nsACString &aPathHead, + const nsACString &aKind, + const nsACString &aDesc, + int32_t aOption, + size_t *aTotal) +{ + nsCString path(aPathHead); + path.Append(aKind); + path.AppendLiteral("-used"); + + int32_t val = aConn->getSqliteRuntimeStatus(aOption); + aHandleReport->Callback(EmptyCString(), path, + nsIMemoryReporter::KIND_HEAP, + nsIMemoryReporter::UNITS_BYTES, + int64_t(val), aDesc, aData); + *aTotal += val; +} + +// Warning: To get a Connection's measurements requires holding its lock. +// There may be a delay getting the lock if another thread is accessing the +// Connection. This isn't very nice if CollectReports is called from the main +// thread! But at the time of writing this function is only called when +// about:memory is loaded (not, for example, when telemetry pings occur) and +// any delays in that case aren't so bad. +NS_IMETHODIMP +Service::CollectReports(nsIHandleReportCallback *aHandleReport, + nsISupports *aData, bool aAnonymize) +{ + size_t totalConnSize = 0; + { + nsTArray<RefPtr<Connection> > connections; + getConnections(connections); + + for (uint32_t i = 0; i < connections.Length(); i++) { + RefPtr<Connection> &conn = connections[i]; + + // Someone may have closed the Connection, in which case we skip it. + bool isReady; + (void)conn->GetConnectionReady(&isReady); + if (!isReady) { + continue; + } + + nsCString pathHead("explicit/storage/sqlite/"); + // This filename isn't privacy-sensitive, and so is never anonymized. + pathHead.Append(conn->getFilename()); + pathHead.Append('/'); + + SQLiteMutexAutoLock lockedScope(conn->sharedDBMutex); + + NS_NAMED_LITERAL_CSTRING(stmtDesc, + "Memory (approximate) used by all prepared statements used by " + "connections to this database."); + ReportConn(aHandleReport, aData, conn, pathHead, + NS_LITERAL_CSTRING("stmt"), stmtDesc, + SQLITE_DBSTATUS_STMT_USED, &totalConnSize); + + NS_NAMED_LITERAL_CSTRING(cacheDesc, + "Memory (approximate) used by all pager caches used by connections " + "to this database."); + ReportConn(aHandleReport, aData, conn, pathHead, + NS_LITERAL_CSTRING("cache"), cacheDesc, + SQLITE_DBSTATUS_CACHE_USED_SHARED, &totalConnSize); + + NS_NAMED_LITERAL_CSTRING(schemaDesc, + "Memory (approximate) used to store the schema for all databases " + "associated with connections to this database."); + ReportConn(aHandleReport, aData, conn, pathHead, + NS_LITERAL_CSTRING("schema"), schemaDesc, + SQLITE_DBSTATUS_SCHEMA_USED, &totalConnSize); + } + } + + int64_t other = ::sqlite3_memory_used() - totalConnSize; + + MOZ_COLLECT_REPORT( + "explicit/storage/sqlite/other", KIND_HEAP, UNITS_BYTES, other, + "All unclassified sqlite memory."); + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +//// Service + +NS_IMPL_ISUPPORTS( + Service, + mozIStorageService, + nsIObserver, + nsIMemoryReporter +) + +Service *Service::gService = nullptr; + +Service * +Service::getSingleton() +{ + if (gService) { + NS_ADDREF(gService); + return gService; + } + + // Ensure that we are using the same version of SQLite that we compiled with + // or newer. Our configure check ensures we are using a new enough version + // at compile time. + if (SQLITE_VERSION_NUMBER > ::sqlite3_libversion_number()) { + nsCOMPtr<nsIPromptService> ps(do_GetService(NS_PROMPTSERVICE_CONTRACTID)); + if (ps) { + nsAutoString title, message; + title.AppendLiteral("SQLite Version Error"); + message.AppendLiteral("The application has been updated, but the SQLite " + "library wasn't updated properly and the application " + "cannot run. Please try to launch the application again. " + "If that should still fail, please try reinstalling " + "it, or visit https://support.mozilla.org/."); + (void)ps->Alert(nullptr, title.get(), message.get()); + } + MOZ_CRASH("SQLite Version Error"); + } + + // The first reference to the storage service must be obtained on the + // main thread. + NS_ENSURE_TRUE(NS_IsMainThread(), nullptr); + gService = new Service(); + if (gService) { + NS_ADDREF(gService); + if (NS_FAILED(gService->initialize())) + NS_RELEASE(gService); + } + + return gService; +} + +nsIXPConnect *Service::sXPConnect = nullptr; + +// static +already_AddRefed<nsIXPConnect> +Service::getXPConnect() +{ + NS_PRECONDITION(NS_IsMainThread(), + "Must only get XPConnect on the main thread!"); + NS_PRECONDITION(gService, + "Can not get XPConnect without an instance of our service!"); + + // If we've been shutdown, sXPConnect will be null. To prevent leaks, we do + // not cache the service after this point. + nsCOMPtr<nsIXPConnect> xpc(sXPConnect); + if (!xpc) + xpc = do_GetService(nsIXPConnect::GetCID()); + NS_ASSERTION(xpc, "Could not get XPConnect!"); + return xpc.forget(); +} + +int32_t Service::sSynchronousPref; + +// static +int32_t +Service::getSynchronousPref() +{ + return sSynchronousPref; +} + +int32_t Service::sDefaultPageSize = PREF_TS_PAGESIZE_DEFAULT; + +Service::Service() +: mMutex("Service::mMutex") +, mSqliteVFS(nullptr) +, mRegistrationMutex("Service::mRegistrationMutex") +, mConnections() +{ +} + +Service::~Service() +{ + mozilla::UnregisterWeakMemoryReporter(this); + mozilla::UnregisterStorageSQLiteDistinguishedAmount(); + + int rc = sqlite3_vfs_unregister(mSqliteVFS); + if (rc != SQLITE_OK) + NS_WARNING("Failed to unregister sqlite vfs wrapper."); + + // Shutdown the sqlite3 API. Warn if shutdown did not turn out okay, but + // there is nothing actionable we can do in that case. + rc = ::sqlite3_shutdown(); + if (rc != SQLITE_OK) + NS_WARNING("sqlite3 did not shutdown cleanly."); + + DebugOnly<bool> shutdownObserved = !sXPConnect; + NS_ASSERTION(shutdownObserved, "Shutdown was not observed!"); + + gService = nullptr; + delete mSqliteVFS; + mSqliteVFS = nullptr; +} + +void +Service::registerConnection(Connection *aConnection) +{ + mRegistrationMutex.AssertNotCurrentThreadOwns(); + MutexAutoLock mutex(mRegistrationMutex); + (void)mConnections.AppendElement(aConnection); +} + +void +Service::unregisterConnection(Connection *aConnection) +{ + // If this is the last Connection it might be the only thing keeping Service + // alive. So ensure that Service is destroyed only after the Connection is + // cleanly unregistered and destroyed. + RefPtr<Service> kungFuDeathGrip(this); + { + mRegistrationMutex.AssertNotCurrentThreadOwns(); + MutexAutoLock mutex(mRegistrationMutex); + + for (uint32_t i = 0 ; i < mConnections.Length(); ++i) { + if (mConnections[i] == aConnection) { + nsCOMPtr<nsIThread> thread = mConnections[i]->threadOpenedOn; + + // Ensure the connection is released on its opening thread. Note, we + // must use .forget().take() so that we can manually cast to an + // unambiguous nsISupports type. + NS_ProxyRelease(thread, mConnections[i].forget()); + + mConnections.RemoveElementAt(i); + return; + } + } + + MOZ_ASSERT_UNREACHABLE("Attempt to unregister unknown storage connection!"); + } +} + +void +Service::getConnections(/* inout */ nsTArray<RefPtr<Connection> >& aConnections) +{ + mRegistrationMutex.AssertNotCurrentThreadOwns(); + MutexAutoLock mutex(mRegistrationMutex); + aConnections.Clear(); + aConnections.AppendElements(mConnections); +} + +void +Service::minimizeMemory() +{ + nsTArray<RefPtr<Connection> > connections; + getConnections(connections); + + for (uint32_t i = 0; i < connections.Length(); i++) { + RefPtr<Connection> conn = connections[i]; + if (!conn->connectionReady()) + continue; + + NS_NAMED_LITERAL_CSTRING(shrinkPragma, "PRAGMA shrink_memory"); + nsCOMPtr<mozIStorageConnection> syncConn = do_QueryInterface( + NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, conn)); + bool onOpenedThread = false; + + if (!syncConn) { + // This is a mozIStorageAsyncConnection, it can only be used on the main + // thread, so we can do a straight API call. + nsCOMPtr<mozIStoragePendingStatement> ps; + DebugOnly<nsresult> rv = + conn->ExecuteSimpleSQLAsync(shrinkPragma, nullptr, getter_AddRefs(ps)); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Should have purged sqlite caches"); + } else if (NS_SUCCEEDED(conn->threadOpenedOn->IsOnCurrentThread(&onOpenedThread)) && + onOpenedThread) { + // We are on the opener thread, so we can just proceed. + conn->ExecuteSimpleSQL(shrinkPragma); + } else { + // We are on the wrong thread, the query should be executed on the + // opener thread, so we must dispatch to it. + nsCOMPtr<nsIRunnable> event = + NewRunnableMethod<const nsCString>( + conn, &Connection::ExecuteSimpleSQL, shrinkPragma); + conn->threadOpenedOn->Dispatch(event, NS_DISPATCH_NORMAL); + } + } +} + +void +Service::shutdown() +{ + NS_IF_RELEASE(sXPConnect); +} + +sqlite3_vfs *ConstructTelemetryVFS(); + +#ifdef MOZ_STORAGE_MEMORY + +namespace { + +// By default, SQLite tracks the size of all its heap blocks by adding an extra +// 8 bytes at the start of the block to hold the size. Unfortunately, this +// causes a lot of 2^N-sized allocations to be rounded up by jemalloc +// allocator, wasting memory. For example, a request for 1024 bytes has 8 +// bytes added, becoming a request for 1032 bytes, and jemalloc rounds this up +// to 2048 bytes, wasting 1012 bytes. (See bug 676189 for more details.) +// +// So we register jemalloc as the malloc implementation, which avoids this +// 8-byte overhead, and thus a lot of waste. This requires us to provide a +// function, sqliteMemRoundup(), which computes the actual size that will be +// allocated for a given request. SQLite uses this function before all +// allocations, and may be able to use any excess bytes caused by the rounding. +// +// Note: the wrappers for malloc, realloc and moz_malloc_usable_size are +// necessary because the sqlite_mem_methods type signatures differ slightly +// from the standard ones -- they use int instead of size_t. But we don't need +// a wrapper for free. + +static void *sqliteMemMalloc(int n) +{ + void* p = ::malloc(n); + return p; +} + +static void sqliteMemFree(void *p) +{ + ::free(p); +} + +static void *sqliteMemRealloc(void *p, int n) +{ + return ::realloc(p, n); +} + +static int sqliteMemSize(void *p) +{ + return ::moz_malloc_usable_size(p); +} + +static int sqliteMemRoundup(int n) +{ + n = malloc_good_size(n); + + // jemalloc can return blocks of size 2 and 4, but SQLite requires that all + // allocations be 8-aligned. So we round up sub-8 requests to 8. This + // wastes a small amount of memory but is obviously safe. + return n <= 8 ? 8 : n; +} + +static int sqliteMemInit(void *p) +{ + return 0; +} + +static void sqliteMemShutdown(void *p) +{ +} + +const sqlite3_mem_methods memMethods = { + &sqliteMemMalloc, + &sqliteMemFree, + &sqliteMemRealloc, + &sqliteMemSize, + &sqliteMemRoundup, + &sqliteMemInit, + &sqliteMemShutdown, + nullptr +}; + +} // namespace + +#endif // MOZ_STORAGE_MEMORY + +static const char* sObserverTopics[] = { + "memory-pressure", + "xpcom-shutdown", + "xpcom-shutdown-threads" +}; + +nsresult +Service::initialize() +{ + MOZ_ASSERT(NS_IsMainThread(), "Must be initialized on the main thread"); + + int rc; + +#ifdef MOZ_STORAGE_MEMORY + rc = ::sqlite3_config(SQLITE_CONFIG_MALLOC, &memMethods); + if (rc != SQLITE_OK) + return convertResultCode(rc); +#endif + + // TODO (bug 1191405): do not preallocate the connections caches until we + // have figured the impact on our consumers and memory. + sqlite3_config(SQLITE_CONFIG_PAGECACHE, NULL, 0, 0); + + // Explicitly initialize sqlite3. Although this is implicitly called by + // various sqlite3 functions (and the sqlite3_open calls in our case), + // the documentation suggests calling this directly. So we do. + rc = ::sqlite3_initialize(); + if (rc != SQLITE_OK) + return convertResultCode(rc); + + mSqliteVFS = ConstructTelemetryVFS(); + if (mSqliteVFS) { + rc = sqlite3_vfs_register(mSqliteVFS, 1); + if (rc != SQLITE_OK) + return convertResultCode(rc); + } else { + NS_WARNING("Failed to register telemetry VFS"); + } + + // Register for xpcom-shutdown so we can cleanup after ourselves. The + // observer service can only be used on the main thread. + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + NS_ENSURE_TRUE(os, NS_ERROR_FAILURE); + + for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) { + nsresult rv = os->AddObserver(this, sObserverTopics[i], false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + // We cache XPConnect for our language helpers. XPConnect can only be + // used on the main thread. + (void)CallGetService(nsIXPConnect::GetCID(), &sXPConnect); + + // We need to obtain the toolkit.storage.synchronous preferences on the main + // thread because the preference service can only be accessed there. This + // is cached in the service for all future Open[Unshared]Database calls. + sSynchronousPref = + Preferences::GetInt(PREF_TS_SYNCHRONOUS, PREF_TS_SYNCHRONOUS_DEFAULT); + + // We need to obtain the toolkit.storage.pageSize preferences on the main + // thread because the preference service can only be accessed there. This + // is cached in the service for all future Open[Unshared]Database calls. + sDefaultPageSize = + Preferences::GetInt(PREF_TS_PAGESIZE, PREF_TS_PAGESIZE_DEFAULT); + + mozilla::RegisterWeakMemoryReporter(this); + mozilla::RegisterStorageSQLiteDistinguishedAmount(StorageSQLiteDistinguishedAmount); + + return NS_OK; +} + +int +Service::localeCompareStrings(const nsAString &aStr1, + const nsAString &aStr2, + int32_t aComparisonStrength) +{ + // The implementation of nsICollation.CompareString() is platform-dependent. + // On Linux it's not thread-safe. It may not be on Windows and OS X either, + // but it's more difficult to tell. We therefore synchronize this method. + MutexAutoLock mutex(mMutex); + + nsICollation *coll = getLocaleCollation(); + if (!coll) { + NS_ERROR("Storage service has no collation"); + return 0; + } + + int32_t res; + nsresult rv = coll->CompareString(aComparisonStrength, aStr1, aStr2, &res); + if (NS_FAILED(rv)) { + NS_ERROR("Collation compare string failed"); + return 0; + } + + return res; +} + +nsICollation * +Service::getLocaleCollation() +{ + mMutex.AssertCurrentThreadOwns(); + + if (mLocaleCollation) + return mLocaleCollation; + + nsCOMPtr<nsILocaleService> svc(do_GetService(NS_LOCALESERVICE_CONTRACTID)); + if (!svc) { + NS_WARNING("Could not get locale service"); + return nullptr; + } + + nsCOMPtr<nsILocale> appLocale; + nsresult rv = svc->GetApplicationLocale(getter_AddRefs(appLocale)); + if (NS_FAILED(rv)) { + NS_WARNING("Could not get application locale"); + return nullptr; + } + + nsCOMPtr<nsICollationFactory> collFact = + do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID); + if (!collFact) { + NS_WARNING("Could not create collation factory"); + return nullptr; + } + + rv = collFact->CreateCollation(appLocale, getter_AddRefs(mLocaleCollation)); + if (NS_FAILED(rv)) { + NS_WARNING("Could not create collation"); + return nullptr; + } + + return mLocaleCollation; +} + +//////////////////////////////////////////////////////////////////////////////// +//// mozIStorageService + + +NS_IMETHODIMP +Service::OpenSpecialDatabase(const char *aStorageKey, + mozIStorageConnection **_connection) +{ + nsresult rv; + + nsCOMPtr<nsIFile> storageFile; + if (::strcmp(aStorageKey, "memory") == 0) { + // just fall through with nullptr storageFile, this will cause the storage + // connection to use a memory DB. + } + else { + return NS_ERROR_INVALID_ARG; + } + + RefPtr<Connection> msc = new Connection(this, SQLITE_OPEN_READWRITE, false); + + rv = storageFile ? msc->initialize(storageFile) : msc->initialize(); + NS_ENSURE_SUCCESS(rv, rv); + + msc.forget(_connection); + return NS_OK; + +} + +namespace { + +class AsyncInitDatabase final : public Runnable +{ +public: + AsyncInitDatabase(Connection* aConnection, + nsIFile* aStorageFile, + int32_t aGrowthIncrement, + mozIStorageCompletionCallback* aCallback) + : mConnection(aConnection) + , mStorageFile(aStorageFile) + , mGrowthIncrement(aGrowthIncrement) + , mCallback(aCallback) + { + MOZ_ASSERT(NS_IsMainThread()); + } + + NS_IMETHOD Run() override + { + MOZ_ASSERT(!NS_IsMainThread()); + nsresult rv = mStorageFile ? mConnection->initialize(mStorageFile) + : mConnection->initialize(); + if (NS_FAILED(rv)) { + nsCOMPtr<nsIRunnable> closeRunnable = + NewRunnableMethod<mozIStorageCompletionCallback*>( + mConnection.get(), + &Connection::AsyncClose, + nullptr); + MOZ_ASSERT(closeRunnable); + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(closeRunnable)); + + return DispatchResult(rv, nullptr); + } + + if (mGrowthIncrement >= 0) { + // Ignore errors. In the future, we might wish to log them. + (void)mConnection->SetGrowthIncrement(mGrowthIncrement, EmptyCString()); + } + + return DispatchResult(NS_OK, NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, + mConnection)); + } + +private: + nsresult DispatchResult(nsresult aStatus, nsISupports* aValue) { + RefPtr<CallbackComplete> event = + new CallbackComplete(aStatus, + aValue, + mCallback.forget()); + return NS_DispatchToMainThread(event); + } + + ~AsyncInitDatabase() + { + NS_ReleaseOnMainThread(mStorageFile.forget()); + NS_ReleaseOnMainThread(mConnection.forget()); + + // Generally, the callback will be released by CallbackComplete. + // However, if for some reason Run() is not executed, we still + // need to ensure that it is released here. + NS_ReleaseOnMainThread(mCallback.forget()); + } + + RefPtr<Connection> mConnection; + nsCOMPtr<nsIFile> mStorageFile; + int32_t mGrowthIncrement; + RefPtr<mozIStorageCompletionCallback> mCallback; +}; + +} // namespace + +NS_IMETHODIMP +Service::OpenAsyncDatabase(nsIVariant *aDatabaseStore, + nsIPropertyBag2 *aOptions, + mozIStorageCompletionCallback *aCallback) +{ + if (!NS_IsMainThread()) { + return NS_ERROR_NOT_SAME_THREAD; + } + NS_ENSURE_ARG(aDatabaseStore); + NS_ENSURE_ARG(aCallback); + + nsresult rv; + bool shared = false; + bool readOnly = false; + bool ignoreLockingMode = false; + int32_t growthIncrement = -1; + +#define FAIL_IF_SET_BUT_INVALID(rv)\ + if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) { \ + return NS_ERROR_INVALID_ARG; \ + } + + // Deal with options first: + if (aOptions) { + rv = aOptions->GetPropertyAsBool(NS_LITERAL_STRING("readOnly"), &readOnly); + FAIL_IF_SET_BUT_INVALID(rv); + + rv = aOptions->GetPropertyAsBool(NS_LITERAL_STRING("ignoreLockingMode"), + &ignoreLockingMode); + FAIL_IF_SET_BUT_INVALID(rv); + // Specifying ignoreLockingMode will force use of the readOnly flag: + if (ignoreLockingMode) { + readOnly = true; + } + + rv = aOptions->GetPropertyAsBool(NS_LITERAL_STRING("shared"), &shared); + FAIL_IF_SET_BUT_INVALID(rv); + + // NB: we re-set to -1 if we don't have a storage file later on. + rv = aOptions->GetPropertyAsInt32(NS_LITERAL_STRING("growthIncrement"), + &growthIncrement); + FAIL_IF_SET_BUT_INVALID(rv); + } + int flags = readOnly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE; + + nsCOMPtr<nsIFile> storageFile; + nsCOMPtr<nsISupports> dbStore; + rv = aDatabaseStore->GetAsISupports(getter_AddRefs(dbStore)); + if (NS_SUCCEEDED(rv)) { + // Generally, aDatabaseStore holds the database nsIFile. + storageFile = do_QueryInterface(dbStore, &rv); + if (NS_FAILED(rv)) { + return NS_ERROR_INVALID_ARG; + } + + rv = storageFile->Clone(getter_AddRefs(storageFile)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + if (!readOnly) { + // Ensure that SQLITE_OPEN_CREATE is passed in for compatibility reasons. + flags |= SQLITE_OPEN_CREATE; + } + + // Apply the shared-cache option. + flags |= shared ? SQLITE_OPEN_SHAREDCACHE : SQLITE_OPEN_PRIVATECACHE; + } else { + // Sometimes, however, it's a special database name. + nsAutoCString keyString; + rv = aDatabaseStore->GetAsACString(keyString); + if (NS_FAILED(rv) || !keyString.EqualsLiteral("memory")) { + return NS_ERROR_INVALID_ARG; + } + + // Just fall through with nullptr storageFile, this will cause the storage + // connection to use a memory DB. + } + + if (!storageFile && growthIncrement >= 0) { + return NS_ERROR_INVALID_ARG; + } + + // Create connection on this thread, but initialize it on its helper thread. + RefPtr<Connection> msc = new Connection(this, flags, true, + ignoreLockingMode); + nsCOMPtr<nsIEventTarget> target = msc->getAsyncExecutionTarget(); + MOZ_ASSERT(target, "Cannot initialize a connection that has been closed already"); + + RefPtr<AsyncInitDatabase> asyncInit = + new AsyncInitDatabase(msc, + storageFile, + growthIncrement, + aCallback); + return target->Dispatch(asyncInit, nsIEventTarget::DISPATCH_NORMAL); +} + +NS_IMETHODIMP +Service::OpenDatabase(nsIFile *aDatabaseFile, + mozIStorageConnection **_connection) +{ + NS_ENSURE_ARG(aDatabaseFile); + + // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility + // reasons. + int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE | + SQLITE_OPEN_CREATE; + RefPtr<Connection> msc = new Connection(this, flags, false); + + nsresult rv = msc->initialize(aDatabaseFile); + NS_ENSURE_SUCCESS(rv, rv); + + msc.forget(_connection); + return NS_OK; +} + +NS_IMETHODIMP +Service::OpenUnsharedDatabase(nsIFile *aDatabaseFile, + mozIStorageConnection **_connection) +{ + NS_ENSURE_ARG(aDatabaseFile); + + // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility + // reasons. + int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_PRIVATECACHE | + SQLITE_OPEN_CREATE; + RefPtr<Connection> msc = new Connection(this, flags, false); + + nsresult rv = msc->initialize(aDatabaseFile); + NS_ENSURE_SUCCESS(rv, rv); + + msc.forget(_connection); + return NS_OK; +} + +NS_IMETHODIMP +Service::OpenDatabaseWithFileURL(nsIFileURL *aFileURL, + mozIStorageConnection **_connection) +{ + NS_ENSURE_ARG(aFileURL); + + // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility + // reasons. + int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE | + SQLITE_OPEN_CREATE | SQLITE_OPEN_URI; + RefPtr<Connection> msc = new Connection(this, flags, false); + + nsresult rv = msc->initialize(aFileURL); + NS_ENSURE_SUCCESS(rv, rv); + + msc.forget(_connection); + return NS_OK; +} + +NS_IMETHODIMP +Service::BackupDatabaseFile(nsIFile *aDBFile, + const nsAString &aBackupFileName, + nsIFile *aBackupParentDirectory, + nsIFile **backup) +{ + nsresult rv; + nsCOMPtr<nsIFile> parentDir = aBackupParentDirectory; + if (!parentDir) { + // This argument is optional, and defaults to the same parent directory + // as the current file. + rv = aDBFile->GetParent(getter_AddRefs(parentDir)); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr<nsIFile> backupDB; + rv = parentDir->Clone(getter_AddRefs(backupDB)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = backupDB->Append(aBackupFileName); + NS_ENSURE_SUCCESS(rv, rv); + + rv = backupDB->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString fileName; + rv = backupDB->GetLeafName(fileName); + NS_ENSURE_SUCCESS(rv, rv); + + rv = backupDB->Remove(false); + NS_ENSURE_SUCCESS(rv, rv); + + backupDB.forget(backup); + + return aDBFile->CopyTo(parentDir, fileName); +} + +//////////////////////////////////////////////////////////////////////////////// +//// nsIObserver + +NS_IMETHODIMP +Service::Observe(nsISupports *, const char *aTopic, const char16_t *) +{ + if (strcmp(aTopic, "memory-pressure") == 0) { + minimizeMemory(); + } else if (strcmp(aTopic, "xpcom-shutdown") == 0) { + shutdown(); + } else if (strcmp(aTopic, "xpcom-shutdown-threads") == 0) { + nsCOMPtr<nsIObserverService> os = + mozilla::services::GetObserverService(); + + for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) { + (void)os->RemoveObserver(this, sObserverTopics[i]); + } + + bool anyOpen = false; + do { + nsTArray<RefPtr<Connection> > connections; + getConnections(connections); + anyOpen = false; + for (uint32_t i = 0; i < connections.Length(); i++) { + RefPtr<Connection> &conn = connections[i]; + if (conn->isClosing()) { + anyOpen = true; + break; + } + } + if (anyOpen) { + nsCOMPtr<nsIThread> thread = do_GetCurrentThread(); + NS_ProcessNextEvent(thread); + } + } while (anyOpen); + + if (gShutdownChecks == SCM_CRASH) { + nsTArray<RefPtr<Connection> > connections; + getConnections(connections); + for (uint32_t i = 0, n = connections.Length(); i < n; i++) { + if (!connections[i]->isClosed()) { + MOZ_CRASH(); + } + } + } + } + + return NS_OK; +} + +} // namespace storage +} // namespace mozilla diff --git a/components/storage/src/mozStorageService.h b/components/storage/src/mozStorageService.h new file mode 100644 index 000000000..effd330b1 --- /dev/null +++ b/components/storage/src/mozStorageService.h @@ -0,0 +1,197 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZSTORAGESERVICE_H +#define MOZSTORAGESERVICE_H + +#include "nsCOMPtr.h" +#include "nsICollation.h" +#include "nsIFile.h" +#include "nsIMemoryReporter.h" +#include "nsIObserver.h" +#include "nsTArray.h" +#include "mozilla/Mutex.h" + +#include "mozIStorageService.h" + +class nsIMemoryReporter; +class nsIXPConnect; +struct sqlite3_vfs; + +namespace mozilla { +namespace storage { + +class Connection; +class Service : public mozIStorageService + , public nsIObserver + , public nsIMemoryReporter +{ +public: + /** + * Initializes the service. This must be called before any other function! + */ + nsresult initialize(); + + /** + * Compares two strings using the Service's locale-aware collation. + * + * @param aStr1 + * The string to be compared against aStr2. + * @param aStr2 + * The string to be compared against aStr1. + * @param aComparisonStrength + * The sorting strength, one of the nsICollation constants. + * @return aStr1 - aStr2. That is, if aStr1 < aStr2, returns a negative + * number. If aStr1 > aStr2, returns a positive number. If + * aStr1 == aStr2, returns 0. + */ + int localeCompareStrings(const nsAString &aStr1, + const nsAString &aStr2, + int32_t aComparisonStrength); + + static Service *getSingleton(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_MOZISTORAGESERVICE + NS_DECL_NSIOBSERVER + NS_DECL_NSIMEMORYREPORTER + + /** + * Obtains an already AddRefed pointer to XPConnect. This is used by + * language helpers. + */ + static already_AddRefed<nsIXPConnect> getXPConnect(); + + /** + * Obtains the cached data for the toolkit.storage.synchronous preference. + */ + static int32_t getSynchronousPref(); + + /** + * Obtains the default page size for this platform. The default value is + * specified in the SQLite makefile (SQLITE_DEFAULT_PAGE_SIZE) but it may be + * overriden with the PREF_TS_PAGESIZE hidden preference. + */ + static int32_t getDefaultPageSize() + { + return sDefaultPageSize; + } + + /** + * Returns a boolean value indicating whether or not the given page size is + * valid (currently understood as a power of 2 between 512 and 65536). + */ + static bool pageSizeIsValid(int32_t aPageSize) + { + return aPageSize == 512 || aPageSize == 1024 || aPageSize == 2048 || + aPageSize == 4096 || aPageSize == 8192 || aPageSize == 16384 || + aPageSize == 32768 || aPageSize == 65536; + } + + /** + * Registers the connection with the storage service. Connections are + * registered so they can be iterated over. + * + * @pre mRegistrationMutex is not held + * + * @param aConnection + * The connection to register. + */ + void registerConnection(Connection *aConnection); + + /** + * Unregisters the connection with the storage service. + * + * @pre mRegistrationMutex is not held + * + * @param aConnection + * The connection to unregister. + */ + void unregisterConnection(Connection *aConnection); + + /** + * Gets the list of open connections. Note that you must test each + * connection with mozIStorageConnection::connectionReady before doing + * anything with it, and skip it if it's not ready. + * + * @pre mRegistrationMutex is not held + * + * @param aConnections + * An inout param; it is cleared and the connections are appended to + * it. + * @return The open connections. + */ + void getConnections(nsTArray<RefPtr<Connection> >& aConnections); + +private: + Service(); + virtual ~Service(); + + /** + * Used for 1) locking around calls when initializing connections so that we + * can ensure that the state of sqlite3_enable_shared_cache is sane and 2) + * synchronizing access to mLocaleCollation. + */ + Mutex mMutex; + + sqlite3_vfs *mSqliteVFS; + + /** + * Protects mConnections. + */ + Mutex mRegistrationMutex; + + /** + * The list of connections we have created. Modifications to it are + * protected by |mRegistrationMutex|. + */ + nsTArray<RefPtr<Connection> > mConnections; + + /** + * Frees as much heap memory as possible from all of the known open + * connections. + */ + void minimizeMemory(); + + /** + * Shuts down the storage service, freeing all of the acquired resources. + */ + void shutdown(); + + /** + * Lazily creates and returns a collation created from the application's + * locale that all statements of all Connections of this Service may use. + * Since the collation's lifetime is that of the Service and no statement may + * execute outside the lifetime of the Service, this method returns a raw + * pointer. + */ + nsICollation *getLocaleCollation(); + + /** + * Lazily created collation that all statements of all Connections of this + * Service may use. The collation is created from the application's locale. + * + * @note Collation implementations are platform-dependent and in general not + * thread-safe. Access to this collation should be synchronized. + */ + nsCOMPtr<nsICollation> mLocaleCollation; + + nsCOMPtr<nsIFile> mProfileStorageFile; + + nsCOMPtr<nsIMemoryReporter> mStorageSQLiteReporter; + + static Service *gService; + + static nsIXPConnect *sXPConnect; + + static int32_t sSynchronousPref; + static int32_t sDefaultPageSize; +}; + +} // namespace storage +} // namespace mozilla + +#endif /* MOZSTORAGESERVICE_H */ diff --git a/components/storage/src/mozStorageStatement.cpp b/components/storage/src/mozStorageStatement.cpp new file mode 100644 index 000000000..7210274d0 --- /dev/null +++ b/components/storage/src/mozStorageStatement.cpp @@ -0,0 +1,889 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <limits.h> +#include <stdio.h> + +#include "nsError.h" +#include "nsMemory.h" +#include "nsThreadUtils.h" +#include "nsIClassInfoImpl.h" +#include "Variant.h" + +#include "mozIStorageError.h" + +#include "mozStorageBindingParams.h" +#include "mozStorageConnection.h" +#include "mozStorageStatementJSHelper.h" +#include "mozStoragePrivateHelpers.h" +#include "mozStorageStatementParams.h" +#include "mozStorageStatementRow.h" +#include "mozStorageStatement.h" +#include "GeckoProfiler.h" +#include "nsDOMClassInfo.h" + +#include "mozilla/Logging.h" + + +extern mozilla::LazyLogModule gStorageLog; + +namespace mozilla { +namespace storage { + +//////////////////////////////////////////////////////////////////////////////// +//// nsIClassInfo + +NS_IMPL_CI_INTERFACE_GETTER(Statement, + mozIStorageStatement, + mozIStorageBaseStatement, + mozIStorageBindingParams, + mozIStorageValueArray, + mozilla::storage::StorageBaseStatementInternal) + +class StatementClassInfo : public nsIClassInfo +{ +public: + constexpr StatementClassInfo() {} + + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHOD + GetInterfaces(uint32_t *_count, nsIID ***_array) override + { + return NS_CI_INTERFACE_GETTER_NAME(Statement)(_count, _array); + } + + NS_IMETHOD + GetScriptableHelper(nsIXPCScriptable **_helper) override + { + static StatementJSHelper sJSHelper; + *_helper = &sJSHelper; + return NS_OK; + } + + NS_IMETHOD + GetContractID(char **_contractID) override + { + *_contractID = nullptr; + return NS_OK; + } + + NS_IMETHOD + GetClassDescription(char **_desc) override + { + *_desc = nullptr; + return NS_OK; + } + + NS_IMETHOD + GetClassID(nsCID **_id) override + { + *_id = nullptr; + return NS_OK; + } + + NS_IMETHOD + GetFlags(uint32_t *_flags) override + { + *_flags = 0; + return NS_OK; + } + + NS_IMETHOD + GetClassIDNoAlloc(nsCID *_cid) override + { + return NS_ERROR_NOT_AVAILABLE; + } +}; + +NS_IMETHODIMP_(MozExternalRefCountType) StatementClassInfo::AddRef() { return 2; } +NS_IMETHODIMP_(MozExternalRefCountType) StatementClassInfo::Release() { return 1; } +NS_IMPL_QUERY_INTERFACE(StatementClassInfo, nsIClassInfo) + +static StatementClassInfo sStatementClassInfo; + +//////////////////////////////////////////////////////////////////////////////// +//// Statement + +Statement::Statement() +: StorageBaseStatementInternal() +, mDBStatement(nullptr) +, mColumnNames() +, mExecuting(false) +{ +} + +nsresult +Statement::initialize(Connection *aDBConnection, + sqlite3 *aNativeConnection, + const nsACString &aSQLStatement) +{ + MOZ_ASSERT(aDBConnection, "No database connection given!"); + MOZ_ASSERT(!aDBConnection->isClosed(), "Database connection should be valid"); + MOZ_ASSERT(!mDBStatement, "Statement already initialized!"); + MOZ_ASSERT(aNativeConnection, "No native connection given!"); + + int srv = aDBConnection->prepareStatement(aNativeConnection, + PromiseFlatCString(aSQLStatement), + &mDBStatement); + if (srv != SQLITE_OK) { + MOZ_LOG(gStorageLog, LogLevel::Error, + ("Sqlite statement prepare error: %d '%s'", srv, + ::sqlite3_errmsg(aNativeConnection))); + MOZ_LOG(gStorageLog, LogLevel::Error, + ("Statement was: '%s'", PromiseFlatCString(aSQLStatement).get())); + return NS_ERROR_FAILURE; + } + + MOZ_LOG(gStorageLog, LogLevel::Debug, ("Initialized statement '%s' (0x%p)", + PromiseFlatCString(aSQLStatement).get(), + mDBStatement)); + + mDBConnection = aDBConnection; + mNativeConnection = aNativeConnection; + mParamCount = ::sqlite3_bind_parameter_count(mDBStatement); + mResultColumnCount = ::sqlite3_column_count(mDBStatement); + mColumnNames.Clear(); + + nsCString* columnNames = mColumnNames.AppendElements(mResultColumnCount); + for (uint32_t i = 0; i < mResultColumnCount; i++) { + const char *name = ::sqlite3_column_name(mDBStatement, i); + columnNames[i].Assign(name); + } + +#ifdef DEBUG + // We want to try and test for LIKE and that consumers are using + // escapeStringForLIKE instead of just trusting user input. The idea to + // check to see if they are binding a parameter after like instead of just + // using a string. We only do this in debug builds because it's expensive! + const nsCaseInsensitiveCStringComparator c; + nsACString::const_iterator start, end, e; + aSQLStatement.BeginReading(start); + aSQLStatement.EndReading(end); + e = end; + while (::FindInReadable(NS_LITERAL_CSTRING(" LIKE"), start, e, c)) { + // We have a LIKE in here, so we perform our tests + // FindInReadable moves the iterator, so we have to get a new one for + // each test we perform. + nsACString::const_iterator s1, s2, s3; + s1 = s2 = s3 = start; + + if (!(::FindInReadable(NS_LITERAL_CSTRING(" LIKE ?"), s1, end, c) || + ::FindInReadable(NS_LITERAL_CSTRING(" LIKE :"), s2, end, c) || + ::FindInReadable(NS_LITERAL_CSTRING(" LIKE @"), s3, end, c))) { + // At this point, we didn't find a LIKE statement followed by ?, :, + // or @, all of which are valid characters for binding a parameter. + // We will warn the consumer that they may not be safely using LIKE. + NS_WARNING("Unsafe use of LIKE detected! Please ensure that you " + "are using mozIStorageStatement::escapeStringForLIKE " + "and that you are binding that result to the statement " + "to prevent SQL injection attacks."); + } + + // resetting start and e + start = e; + e = end; + } +#endif + + return NS_OK; +} + +mozIStorageBindingParams * +Statement::getParams() +{ + nsresult rv; + + // If we do not have an array object yet, make it. + if (!mParamsArray) { + nsCOMPtr<mozIStorageBindingParamsArray> array; + rv = NewBindingParamsArray(getter_AddRefs(array)); + NS_ENSURE_SUCCESS(rv, nullptr); + + mParamsArray = static_cast<BindingParamsArray *>(array.get()); + } + + // If there isn't already any rows added, we'll have to add one to use. + if (mParamsArray->length() == 0) { + RefPtr<BindingParams> params(new BindingParams(mParamsArray, this)); + NS_ENSURE_TRUE(params, nullptr); + + rv = mParamsArray->AddParams(params); + NS_ENSURE_SUCCESS(rv, nullptr); + + // We have to unlock our params because AddParams locks them. This is safe + // because no reference to the params object was, or ever will be given out. + params->unlock(this); + + // We also want to lock our array at this point - we don't want anything to + // be added to it. Nothing has, or will ever get a reference to it, but we + // will get additional safety checks via assertions by doing this. + mParamsArray->lock(); + } + + return *mParamsArray->begin(); +} + +Statement::~Statement() +{ + (void)internalFinalize(true); +} + +//////////////////////////////////////////////////////////////////////////////// +//// nsISupports + +NS_IMPL_ADDREF(Statement) +NS_IMPL_RELEASE(Statement) + +NS_INTERFACE_MAP_BEGIN(Statement) + NS_INTERFACE_MAP_ENTRY(mozIStorageStatement) + NS_INTERFACE_MAP_ENTRY(mozIStorageBaseStatement) + NS_INTERFACE_MAP_ENTRY(mozIStorageBindingParams) + NS_INTERFACE_MAP_ENTRY(mozIStorageValueArray) + NS_INTERFACE_MAP_ENTRY(mozilla::storage::StorageBaseStatementInternal) + if (aIID.Equals(NS_GET_IID(nsIClassInfo))) { + foundInterface = static_cast<nsIClassInfo *>(&sStatementClassInfo); + } + else + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageStatement) +NS_INTERFACE_MAP_END + + +//////////////////////////////////////////////////////////////////////////////// +//// StorageBaseStatementInternal + +Connection * +Statement::getOwner() +{ + return mDBConnection; +} + +int +Statement::getAsyncStatement(sqlite3_stmt **_stmt) +{ + // If we have no statement, we shouldn't be calling this method! + NS_ASSERTION(mDBStatement != nullptr, "We have no statement to clone!"); + + // If we do not yet have a cached async statement, clone our statement now. + if (!mAsyncStatement) { + nsDependentCString sql(::sqlite3_sql(mDBStatement)); + int rc = mDBConnection->prepareStatement(mNativeConnection, sql, + &mAsyncStatement); + if (rc != SQLITE_OK) { + *_stmt = nullptr; + return rc; + } + + MOZ_LOG(gStorageLog, LogLevel::Debug, + ("Cloned statement 0x%p to 0x%p", mDBStatement, mAsyncStatement)); + } + + *_stmt = mAsyncStatement; + return SQLITE_OK; +} + +nsresult +Statement::getAsynchronousStatementData(StatementData &_data) +{ + if (!mDBStatement) + return NS_ERROR_UNEXPECTED; + + sqlite3_stmt *stmt; + int rc = getAsyncStatement(&stmt); + if (rc != SQLITE_OK) + return convertResultCode(rc); + + _data = StatementData(stmt, bindingParamsArray(), this); + + return NS_OK; +} + +already_AddRefed<mozIStorageBindingParams> +Statement::newBindingParams(mozIStorageBindingParamsArray *aOwner) +{ + nsCOMPtr<mozIStorageBindingParams> params = new BindingParams(aOwner, this); + return params.forget(); +} + + +//////////////////////////////////////////////////////////////////////////////// +//// mozIStorageStatement + +// proxy to StorageBaseStatementInternal using its define helper. +MIXIN_IMPL_STORAGEBASESTATEMENTINTERNAL(Statement, (void)0;) + +NS_IMETHODIMP +Statement::Clone(mozIStorageStatement **_statement) +{ + RefPtr<Statement> statement(new Statement()); + NS_ENSURE_TRUE(statement, NS_ERROR_OUT_OF_MEMORY); + + nsAutoCString sql(::sqlite3_sql(mDBStatement)); + nsresult rv = statement->initialize(mDBConnection, mNativeConnection, sql); + NS_ENSURE_SUCCESS(rv, rv); + + statement.forget(_statement); + return NS_OK; +} + +NS_IMETHODIMP +Statement::Finalize() +{ + return internalFinalize(false); +} + +nsresult +Statement::internalFinalize(bool aDestructing) +{ + if (!mDBStatement) + return NS_OK; + + int srv = SQLITE_OK; + + if (!mDBConnection->isClosed()) { + // + // The connection is still open. While statement finalization and + // closing may, in some cases, take place in two distinct threads, + // we have a guarantee that the connection will remain open until + // this method terminates: + // + // a. The connection will be closed synchronously. In this case, + // there is no race condition, as everything takes place on the + // same thread. + // + // b. The connection is closed asynchronously and this code is + // executed on the opener thread. In this case, asyncClose() has + // not been called yet and will not be called before we return + // from this function. + // + // c. The connection is closed asynchronously and this code is + // executed on the async execution thread. In this case, + // AsyncCloseConnection::Run() has not been called yet and will + // not be called before we return from this function. + // + // In either case, the connection is still valid, hence closing + // here is safe. + // + MOZ_LOG(gStorageLog, LogLevel::Debug, ("Finalizing statement '%s' during garbage-collection", + ::sqlite3_sql(mDBStatement))); + srv = ::sqlite3_finalize(mDBStatement); + } +#ifdef DEBUG + else { + // + // The database connection is either closed or closing. The sqlite + // statement has either been finalized already by the connection + // or is about to be finalized by the connection. + // + // Finalizing it here would be useless and segfaultish. + // + + char *msg = ::PR_smprintf("SQL statement (%x) should have been finalized" + " before garbage-collection. For more details on this statement, set" + " NSPR_LOG_MESSAGES=mozStorage:5 .", + mDBStatement); + + // + // Note that we can't display the statement itself, as the data structure + // is not valid anymore. However, the address shown here should help + // developers correlate with the more complete debug message triggered + // by AsyncClose(). + // + +#if 0 + // Deactivate the warning until we have fixed the exising culprit + // (see bug 914070). + NS_WARNING(msg); +#endif // 0 + + MOZ_LOG(gStorageLog, LogLevel::Warning, (msg)); + + ::PR_smprintf_free(msg); + } + +#endif + + mDBStatement = nullptr; + + if (mAsyncStatement) { + // If the destructor called us, there are no pending async statements (they + // hold a reference to us) and we can/must just kill the statement directly. + if (aDestructing) + destructorAsyncFinalize(); + else + asyncFinalize(); + } + + // Release the holders, so they can release the reference to us. + mStatementParamsHolder = nullptr; + mStatementRowHolder = nullptr; + + return convertResultCode(srv); +} + +NS_IMETHODIMP +Statement::GetParameterCount(uint32_t *_parameterCount) +{ + if (!mDBStatement) + return NS_ERROR_NOT_INITIALIZED; + + *_parameterCount = mParamCount; + return NS_OK; +} + +NS_IMETHODIMP +Statement::GetParameterName(uint32_t aParamIndex, + nsACString &_name) +{ + if (!mDBStatement) + return NS_ERROR_NOT_INITIALIZED; + ENSURE_INDEX_VALUE(aParamIndex, mParamCount); + + const char *name = ::sqlite3_bind_parameter_name(mDBStatement, + aParamIndex + 1); + if (name == nullptr) { + // this thing had no name, so fake one + nsAutoCString fakeName(":"); + fakeName.AppendInt(aParamIndex); + _name.Assign(fakeName); + } + else { + _name.Assign(nsDependentCString(name)); + } + + return NS_OK; +} + +NS_IMETHODIMP +Statement::GetParameterIndex(const nsACString &aName, + uint32_t *_index) +{ + if (!mDBStatement) + return NS_ERROR_NOT_INITIALIZED; + + // We do not accept any forms of names other than ":name", but we need to add + // the colon for SQLite. + nsAutoCString name(":"); + name.Append(aName); + int ind = ::sqlite3_bind_parameter_index(mDBStatement, name.get()); + if (ind == 0) // Named parameter not found. + return NS_ERROR_INVALID_ARG; + + *_index = ind - 1; // SQLite indexes are 1-based, we are 0-based. + + return NS_OK; +} + +NS_IMETHODIMP +Statement::GetColumnCount(uint32_t *_columnCount) +{ + if (!mDBStatement) + return NS_ERROR_NOT_INITIALIZED; + + *_columnCount = mResultColumnCount; + return NS_OK; +} + +NS_IMETHODIMP +Statement::GetColumnName(uint32_t aColumnIndex, + nsACString &_name) +{ + if (!mDBStatement) + return NS_ERROR_NOT_INITIALIZED; + ENSURE_INDEX_VALUE(aColumnIndex, mResultColumnCount); + + const char *cname = ::sqlite3_column_name(mDBStatement, aColumnIndex); + _name.Assign(nsDependentCString(cname)); + + return NS_OK; +} + +NS_IMETHODIMP +Statement::GetColumnIndex(const nsACString &aName, + uint32_t *_index) +{ + if (!mDBStatement) + return NS_ERROR_NOT_INITIALIZED; + + // Surprisingly enough, SQLite doesn't provide an API for this. We have to + // determine it ourselves sadly. + for (uint32_t i = 0; i < mResultColumnCount; i++) { + if (mColumnNames[i].Equals(aName)) { + *_index = i; + return NS_OK; + } + } + + return NS_ERROR_INVALID_ARG; +} + +NS_IMETHODIMP +Statement::Reset() +{ + if (!mDBStatement) + return NS_ERROR_NOT_INITIALIZED; + +#ifdef DEBUG + MOZ_LOG(gStorageLog, LogLevel::Debug, ("Resetting statement: '%s'", + ::sqlite3_sql(mDBStatement))); + + checkAndLogStatementPerformance(mDBStatement); +#endif + + mParamsArray = nullptr; + (void)sqlite3_reset(mDBStatement); + (void)sqlite3_clear_bindings(mDBStatement); + + mExecuting = false; + + return NS_OK; +} + +NS_IMETHODIMP +Statement::BindParameters(mozIStorageBindingParamsArray *aParameters) +{ + NS_ENSURE_ARG_POINTER(aParameters); + + if (!mDBStatement) + return NS_ERROR_NOT_INITIALIZED; + + BindingParamsArray *array = static_cast<BindingParamsArray *>(aParameters); + if (array->getOwner() != this) + return NS_ERROR_UNEXPECTED; + + if (array->length() == 0) + return NS_ERROR_UNEXPECTED; + + mParamsArray = array; + mParamsArray->lock(); + + return NS_OK; +} + +NS_IMETHODIMP +Statement::Execute() +{ + if (!mDBStatement) + return NS_ERROR_NOT_INITIALIZED; + + bool ret; + nsresult rv = ExecuteStep(&ret); + nsresult rv2 = Reset(); + + return NS_FAILED(rv) ? rv : rv2; +} + +NS_IMETHODIMP +Statement::ExecuteStep(bool *_moreResults) +{ + PROFILER_LABEL("Statement", "ExecuteStep", + js::ProfileEntry::Category::STORAGE); + + if (!mDBStatement) + return NS_ERROR_NOT_INITIALIZED; + + // Bind any parameters first before executing. + if (mParamsArray) { + // If we have more than one row of parameters to bind, they shouldn't be + // calling this method (and instead use executeAsync). + if (mParamsArray->length() != 1) + return NS_ERROR_UNEXPECTED; + + BindingParamsArray::iterator row = mParamsArray->begin(); + nsCOMPtr<IStorageBindingParamsInternal> bindingInternal = + do_QueryInterface(*row); + nsCOMPtr<mozIStorageError> error = bindingInternal->bind(mDBStatement); + if (error) { + int32_t srv; + (void)error->GetResult(&srv); + return convertResultCode(srv); + } + + // We have bound, so now we can clear our array. + mParamsArray = nullptr; + } + int srv = mDBConnection->stepStatement(mNativeConnection, mDBStatement); + + if (srv != SQLITE_ROW && srv != SQLITE_DONE && MOZ_LOG_TEST(gStorageLog, LogLevel::Debug)) { + nsAutoCString errStr; + (void)mDBConnection->GetLastErrorString(errStr); + MOZ_LOG(gStorageLog, LogLevel::Debug, + ("Statement::ExecuteStep error: %s", errStr.get())); + } + + // SQLITE_ROW and SQLITE_DONE are non-errors + if (srv == SQLITE_ROW) { + // we got a row back + mExecuting = true; + *_moreResults = true; + return NS_OK; + } + else if (srv == SQLITE_DONE) { + // statement is done (no row returned) + mExecuting = false; + *_moreResults = false; + return NS_OK; + } + else if (srv == SQLITE_BUSY || srv == SQLITE_MISUSE) { + mExecuting = false; + } + else if (mExecuting) { + MOZ_LOG(gStorageLog, LogLevel::Error, + ("SQLite error after mExecuting was true!")); + mExecuting = false; + } + + return convertResultCode(srv); +} + +NS_IMETHODIMP +Statement::GetState(int32_t *_state) +{ + if (!mDBStatement) + *_state = MOZ_STORAGE_STATEMENT_INVALID; + else if (mExecuting) + *_state = MOZ_STORAGE_STATEMENT_EXECUTING; + else + *_state = MOZ_STORAGE_STATEMENT_READY; + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +//// mozIStorageValueArray (now part of mozIStorageStatement too) + +NS_IMETHODIMP +Statement::GetNumEntries(uint32_t *_length) +{ + *_length = mResultColumnCount; + return NS_OK; +} + +NS_IMETHODIMP +Statement::GetTypeOfIndex(uint32_t aIndex, + int32_t *_type) +{ + if (!mDBStatement) + return NS_ERROR_NOT_INITIALIZED; + + ENSURE_INDEX_VALUE(aIndex, mResultColumnCount); + + if (!mExecuting) + return NS_ERROR_UNEXPECTED; + + int t = ::sqlite3_column_type(mDBStatement, aIndex); + switch (t) { + case SQLITE_INTEGER: + *_type = mozIStorageStatement::VALUE_TYPE_INTEGER; + break; + case SQLITE_FLOAT: + *_type = mozIStorageStatement::VALUE_TYPE_FLOAT; + break; + case SQLITE_TEXT: + *_type = mozIStorageStatement::VALUE_TYPE_TEXT; + break; + case SQLITE_BLOB: + *_type = mozIStorageStatement::VALUE_TYPE_BLOB; + break; + case SQLITE_NULL: + *_type = mozIStorageStatement::VALUE_TYPE_NULL; + break; + default: + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +Statement::GetInt32(uint32_t aIndex, + int32_t *_value) +{ + if (!mDBStatement) + return NS_ERROR_NOT_INITIALIZED; + + ENSURE_INDEX_VALUE(aIndex, mResultColumnCount); + + if (!mExecuting) + return NS_ERROR_UNEXPECTED; + + *_value = ::sqlite3_column_int(mDBStatement, aIndex); + return NS_OK; +} + +NS_IMETHODIMP +Statement::GetInt64(uint32_t aIndex, + int64_t *_value) +{ + if (!mDBStatement) + return NS_ERROR_NOT_INITIALIZED; + + ENSURE_INDEX_VALUE(aIndex, mResultColumnCount); + + if (!mExecuting) + return NS_ERROR_UNEXPECTED; + + *_value = ::sqlite3_column_int64(mDBStatement, aIndex); + + return NS_OK; +} + +NS_IMETHODIMP +Statement::GetDouble(uint32_t aIndex, + double *_value) +{ + if (!mDBStatement) + return NS_ERROR_NOT_INITIALIZED; + + ENSURE_INDEX_VALUE(aIndex, mResultColumnCount); + + if (!mExecuting) + return NS_ERROR_UNEXPECTED; + + *_value = ::sqlite3_column_double(mDBStatement, aIndex); + + return NS_OK; +} + +NS_IMETHODIMP +Statement::GetUTF8String(uint32_t aIndex, + nsACString &_value) +{ + // Get type of Index will check aIndex for us, so we don't have to. + int32_t type; + nsresult rv = GetTypeOfIndex(aIndex, &type); + NS_ENSURE_SUCCESS(rv, rv); + if (type == mozIStorageStatement::VALUE_TYPE_NULL) { + // NULL columns should have IsVoid set to distinguish them from the empty + // string. + _value.SetIsVoid(true); + } + else { + const char *value = + reinterpret_cast<const char *>(::sqlite3_column_text(mDBStatement, + aIndex)); + _value.Assign(value, ::sqlite3_column_bytes(mDBStatement, aIndex)); + } + return NS_OK; +} + +NS_IMETHODIMP +Statement::GetString(uint32_t aIndex, + nsAString &_value) +{ + // Get type of Index will check aIndex for us, so we don't have to. + int32_t type; + nsresult rv = GetTypeOfIndex(aIndex, &type); + NS_ENSURE_SUCCESS(rv, rv); + if (type == mozIStorageStatement::VALUE_TYPE_NULL) { + // NULL columns should have IsVoid set to distinguish them from the empty + // string. + _value.SetIsVoid(true); + } else { + const char16_t *value = + static_cast<const char16_t *>(::sqlite3_column_text16(mDBStatement, + aIndex)); + _value.Assign(value, ::sqlite3_column_bytes16(mDBStatement, aIndex) / 2); + } + return NS_OK; +} + +NS_IMETHODIMP +Statement::GetBlob(uint32_t aIndex, + uint32_t *_size, + uint8_t **_blob) +{ + if (!mDBStatement) + return NS_ERROR_NOT_INITIALIZED; + + ENSURE_INDEX_VALUE(aIndex, mResultColumnCount); + + if (!mExecuting) + return NS_ERROR_UNEXPECTED; + + int size = ::sqlite3_column_bytes(mDBStatement, aIndex); + void *blob = nullptr; + if (size) { + blob = nsMemory::Clone(::sqlite3_column_blob(mDBStatement, aIndex), size); + NS_ENSURE_TRUE(blob, NS_ERROR_OUT_OF_MEMORY); + } + + *_blob = static_cast<uint8_t *>(blob); + *_size = size; + return NS_OK; +} + +NS_IMETHODIMP +Statement::GetBlobAsString(uint32_t aIndex, nsAString& aValue) +{ + return DoGetBlobAsString(this, aIndex, aValue); +} + +NS_IMETHODIMP +Statement::GetBlobAsUTF8String(uint32_t aIndex, nsACString& aValue) +{ + return DoGetBlobAsString(this, aIndex, aValue); +} + +NS_IMETHODIMP +Statement::GetSharedUTF8String(uint32_t aIndex, + uint32_t *_length, + const char **_value) +{ + if (_length) + *_length = ::sqlite3_column_bytes(mDBStatement, aIndex); + + *_value = reinterpret_cast<const char *>(::sqlite3_column_text(mDBStatement, + aIndex)); + return NS_OK; +} + +NS_IMETHODIMP +Statement::GetSharedString(uint32_t aIndex, + uint32_t *_length, + const char16_t **_value) +{ + if (_length) + *_length = ::sqlite3_column_bytes16(mDBStatement, aIndex); + + *_value = static_cast<const char16_t *>(::sqlite3_column_text16(mDBStatement, + aIndex)); + return NS_OK; +} + +NS_IMETHODIMP +Statement::GetSharedBlob(uint32_t aIndex, + uint32_t *_size, + const uint8_t **_blob) +{ + *_size = ::sqlite3_column_bytes(mDBStatement, aIndex); + *_blob = static_cast<const uint8_t *>(::sqlite3_column_blob(mDBStatement, + aIndex)); + return NS_OK; +} + +NS_IMETHODIMP +Statement::GetIsNull(uint32_t aIndex, + bool *_isNull) +{ + // Get type of Index will check aIndex for us, so we don't have to. + int32_t type; + nsresult rv = GetTypeOfIndex(aIndex, &type); + NS_ENSURE_SUCCESS(rv, rv); + *_isNull = (type == mozIStorageStatement::VALUE_TYPE_NULL); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +//// mozIStorageBindingParams + +BOILERPLATE_BIND_PROXIES( + Statement, + if (!mDBStatement) return NS_ERROR_NOT_INITIALIZED; +) + +} // namespace storage +} // namespace mozilla diff --git a/components/storage/src/mozStorageStatement.h b/components/storage/src/mozStorageStatement.h new file mode 100644 index 000000000..69b69c58d --- /dev/null +++ b/components/storage/src/mozStorageStatement.h @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozStorageStatement_h +#define mozStorageStatement_h + +#include "nsAutoPtr.h" +#include "nsString.h" + +#include "nsTArray.h" + +#include "mozStorageBindingParamsArray.h" +#include "mozStorageStatementData.h" +#include "mozIStorageStatement.h" +#include "mozIStorageValueArray.h" +#include "StorageBaseStatementInternal.h" +#include "mozilla/Attributes.h" + +class nsIXPConnectJSObjectHolder; +struct sqlite3_stmt; + +namespace mozilla { +namespace storage { +class StatementJSHelper; +class Connection; + +class Statement final : public mozIStorageStatement + , public mozIStorageValueArray + , public StorageBaseStatementInternal +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_MOZISTORAGESTATEMENT + NS_DECL_MOZISTORAGEBASESTATEMENT + NS_DECL_MOZISTORAGEBINDINGPARAMS + // NS_DECL_MOZISTORAGEVALUEARRAY (methods in mozIStorageStatement) + NS_DECL_STORAGEBASESTATEMENTINTERNAL + + Statement(); + + /** + * Initializes the object on aDBConnection by preparing the SQL statement + * given by aSQLStatement. + * + * @param aDBConnection + * The Connection object this statement is associated with. + * @param aNativeConnection + * The native Sqlite connection this statement is associated with. + * @param aSQLStatement + * The SQL statement to prepare that this object will represent. + */ + nsresult initialize(Connection *aDBConnection, + sqlite3* aNativeConnection, + const nsACString &aSQLStatement); + + + /** + * Obtains the native statement pointer. + */ + inline sqlite3_stmt *nativeStatement() { return mDBStatement; } + + /** + * Obtains and transfers ownership of the array of parameters that are bound + * to this statment. This can be null. + */ + inline already_AddRefed<BindingParamsArray> bindingParamsArray() + { + return mParamsArray.forget(); + } + +private: + ~Statement(); + + sqlite3_stmt *mDBStatement; + uint32_t mParamCount; + uint32_t mResultColumnCount; + nsTArray<nsCString> mColumnNames; + bool mExecuting; + + /** + * @return a pointer to the BindingParams object to use with our Bind* + * method. + */ + mozIStorageBindingParams *getParams(); + + /** + * Holds the array of parameters to bind to this statement when we execute + * it asynchronously. + */ + RefPtr<BindingParamsArray> mParamsArray; + + /** + * The following two members are only used with the JS helper. They cache + * the row and params objects. + */ + nsMainThreadPtrHandle<nsIXPConnectJSObjectHolder> mStatementParamsHolder; + nsMainThreadPtrHandle<nsIXPConnectJSObjectHolder> mStatementRowHolder; + + /** + * Internal version of finalize that allows us to tell it if it is being + * called from the destructor so it can know not to dispatch events that + * require a reference to us. + * + * @param aDestructing + * Is the destructor calling? + */ + nsresult internalFinalize(bool aDestructing); + + friend class StatementJSHelper; +}; + +} // namespace storage +} // namespace mozilla + +#endif // mozStorageStatement_h diff --git a/components/storage/src/mozStorageStatementData.h b/components/storage/src/mozStorageStatementData.h new file mode 100644 index 000000000..8baaf2fa7 --- /dev/null +++ b/components/storage/src/mozStorageStatementData.h @@ -0,0 +1,150 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 sts=2 et + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozStorageStatementData_h +#define mozStorageStatementData_h + +#include "sqlite3.h" + +#include "nsAutoPtr.h" +#include "nsTArray.h" +#include "nsIEventTarget.h" +#include "MainThreadUtils.h" + +#include "mozStorageBindingParamsArray.h" +#include "mozIStorageBaseStatement.h" +#include "mozStorageConnection.h" +#include "StorageBaseStatementInternal.h" + +struct sqlite3_stmt; + +namespace mozilla { +namespace storage { + +class StatementData +{ +public: + StatementData(sqlite3_stmt *aStatement, + already_AddRefed<BindingParamsArray> aParamsArray, + StorageBaseStatementInternal *aStatementOwner) + : mStatement(aStatement) + , mParamsArray(aParamsArray) + , mStatementOwner(aStatementOwner) + { + NS_PRECONDITION(mStatementOwner, "Must have a statement owner!"); + } + StatementData(const StatementData &aSource) + : mStatement(aSource.mStatement) + , mParamsArray(aSource.mParamsArray) + , mStatementOwner(aSource.mStatementOwner) + { + NS_PRECONDITION(mStatementOwner, "Must have a statement owner!"); + } + StatementData() + : mStatement(nullptr) + { + } + ~StatementData() + { + // We need to ensure that mParamsArray is released on the main thread, + // as the binding arguments may be XPConnect values, which are safe + // to release only on the main thread. + NS_ReleaseOnMainThread(mParamsArray.forget()); + } + + /** + * Return the sqlite statement, fetching it from the storage statement. In + * the case of AsyncStatements this may actually create the statement + */ + inline int getSqliteStatement(sqlite3_stmt **_stmt) + { + if (!mStatement) { + int rc = mStatementOwner->getAsyncStatement(&mStatement); + NS_ENSURE_TRUE(rc == SQLITE_OK, rc); + } + *_stmt = mStatement; + return SQLITE_OK; + } + + operator BindingParamsArray *() const { return mParamsArray; } + + /** + * NULLs out our sqlite3_stmt (it is held by the owner) after reseting it and + * clear all bindings to it. This is expected to occur on the async thread. + */ + inline void reset() + { + NS_PRECONDITION(mStatementOwner, "Must have a statement owner!"); +#ifdef DEBUG + { + nsCOMPtr<nsIEventTarget> asyncThread = + mStatementOwner->getOwner()->getAsyncExecutionTarget(); + // It's possible that we are shutting down the async thread, and this + // method would return nullptr as a result. + if (asyncThread) { + bool onAsyncThread; + NS_ASSERTION(NS_SUCCEEDED(asyncThread->IsOnCurrentThread(&onAsyncThread)) && onAsyncThread, + "This should only be running on the async thread!"); + } + } +#endif + // In the AsyncStatement case we may never have populated mStatement if the + // AsyncExecuteStatements got canceled or a failure occurred in constructing + // the statement. + if (mStatement) { + (void)::sqlite3_reset(mStatement); + (void)::sqlite3_clear_bindings(mStatement); + mStatement = nullptr; + } + } + + /** + * Indicates if this statement has parameters to be bound before it is + * executed. + * + * @return true if the statement has parameters to bind against, false + * otherwise. + */ + inline bool hasParametersToBeBound() const { return !!mParamsArray; } + /** + * Indicates the number of implicit statements generated by this statement + * requiring a transaction for execution. For example a single statement + * with N BindingParams will execute N implicit staments. + * + * @return number of statements requiring a transaction for execution. + * + * @note In the case of AsyncStatements this may actually create the + * statement. + */ + inline uint32_t needsTransaction() + { + MOZ_ASSERT(!NS_IsMainThread()); + // Be sure to use the getSqliteStatement helper, since sqlite3_stmt_readonly + // can only analyze prepared statements and AsyncStatements are prepared + // lazily. + sqlite3_stmt *stmt; + int rc = getSqliteStatement(&stmt); + if (SQLITE_OK != rc || ::sqlite3_stmt_readonly(stmt)) { + return 0; + } + return mParamsArray ? mParamsArray->length() : 1; + } + +private: + sqlite3_stmt *mStatement; + RefPtr<BindingParamsArray> mParamsArray; + + /** + * We hold onto a reference of the statement's owner so it doesn't get + * destroyed out from under us. + */ + nsCOMPtr<StorageBaseStatementInternal> mStatementOwner; +}; + +} // namespace storage +} // namespace mozilla + +#endif // mozStorageStatementData_h diff --git a/components/storage/src/mozStorageStatementJSHelper.cpp b/components/storage/src/mozStorageStatementJSHelper.cpp new file mode 100644 index 000000000..37e3bf517 --- /dev/null +++ b/components/storage/src/mozStorageStatementJSHelper.cpp @@ -0,0 +1,287 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIXPConnect.h" +#include "mozStorageStatement.h" +#include "mozStorageService.h" + +#include "nsMemory.h" +#include "nsString.h" +#include "nsServiceManagerUtils.h" + +#include "mozStorageStatementJSHelper.h" + +#include "mozStorageStatementRow.h" +#include "mozStorageStatementParams.h" + +#include "jsapi.h" + +#include "xpc_make_class.h" + +namespace mozilla { +namespace storage { + +//////////////////////////////////////////////////////////////////////////////// +//// Global Functions + +static +bool +stepFunc(JSContext *aCtx, + uint32_t, + JS::Value *_vp) +{ + nsCOMPtr<nsIXPConnect> xpc(Service::getXPConnect()); + nsCOMPtr<nsIXPConnectWrappedNative> wrapper; + JSObject *obj = JS_THIS_OBJECT(aCtx, _vp); + if (!obj) { + return false; + } + + nsresult rv = + xpc->GetWrappedNativeOfJSObject(aCtx, obj, getter_AddRefs(wrapper)); + if (NS_FAILED(rv)) { + ::JS_ReportErrorASCII(aCtx, "mozIStorageStatement::step() could not obtain native statement"); + return false; + } + +#ifdef DEBUG + { + nsCOMPtr<mozIStorageStatement> isStatement( + do_QueryInterface(wrapper->Native()) + ); + NS_ASSERTION(isStatement, "How is this not a statement?!"); + } +#endif + + Statement *stmt = static_cast<Statement *>( + static_cast<mozIStorageStatement *>(wrapper->Native()) + ); + + bool hasMore = false; + rv = stmt->ExecuteStep(&hasMore); + if (NS_SUCCEEDED(rv) && !hasMore) { + _vp->setBoolean(false); + (void)stmt->Reset(); + return true; + } + + if (NS_FAILED(rv)) { + ::JS_ReportErrorASCII(aCtx, "mozIStorageStatement::step() returned an error"); + return false; + } + + _vp->setBoolean(hasMore); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +//// StatementJSHelper + +nsresult +StatementJSHelper::getRow(Statement *aStatement, + JSContext *aCtx, + JSObject *aScopeObj, + JS::Value *_row) +{ + MOZ_ASSERT(NS_IsMainThread()); + nsresult rv; + +#ifdef DEBUG + int32_t state; + (void)aStatement->GetState(&state); + NS_ASSERTION(state == mozIStorageStatement::MOZ_STORAGE_STATEMENT_EXECUTING, + "Invalid state to get the row object - all calls will fail!"); +#endif + + if (!aStatement->mStatementRowHolder) { + JS::RootedObject scope(aCtx, aScopeObj); + nsCOMPtr<mozIStorageStatementRow> row(new StatementRow(aStatement)); + NS_ENSURE_TRUE(row, NS_ERROR_OUT_OF_MEMORY); + + nsCOMPtr<nsIXPConnectJSObjectHolder> holder; + nsCOMPtr<nsIXPConnect> xpc(Service::getXPConnect()); + rv = xpc->WrapNativeHolder( + aCtx, + ::JS_GetGlobalForObject(aCtx, scope), + row, + NS_GET_IID(mozIStorageStatementRow), + getter_AddRefs(holder) + ); + NS_ENSURE_SUCCESS(rv, rv); + RefPtr<StatementRowHolder> rowHolder = new StatementRowHolder(holder); + aStatement->mStatementRowHolder = + new nsMainThreadPtrHolder<nsIXPConnectJSObjectHolder>(rowHolder); + } + + JS::Rooted<JSObject*> obj(aCtx); + obj = aStatement->mStatementRowHolder->GetJSObject(); + NS_ENSURE_STATE(obj); + + _row->setObject(*obj); + return NS_OK; +} + +nsresult +StatementJSHelper::getParams(Statement *aStatement, + JSContext *aCtx, + JSObject *aScopeObj, + JS::Value *_params) +{ + MOZ_ASSERT(NS_IsMainThread()); + nsresult rv; + +#ifdef DEBUG + int32_t state; + (void)aStatement->GetState(&state); + NS_ASSERTION(state == mozIStorageStatement::MOZ_STORAGE_STATEMENT_READY, + "Invalid state to get the params object - all calls will fail!"); +#endif + + if (!aStatement->mStatementParamsHolder) { + JS::RootedObject scope(aCtx, aScopeObj); + nsCOMPtr<mozIStorageStatementParams> params = + new StatementParams(aStatement); + NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY); + + nsCOMPtr<nsIXPConnectJSObjectHolder> holder; + nsCOMPtr<nsIXPConnect> xpc(Service::getXPConnect()); + rv = xpc->WrapNativeHolder( + aCtx, + ::JS_GetGlobalForObject(aCtx, scope), + params, + NS_GET_IID(mozIStorageStatementParams), + getter_AddRefs(holder) + ); + NS_ENSURE_SUCCESS(rv, rv); + RefPtr<StatementParamsHolder> paramsHolder = + new StatementParamsHolder(holder); + aStatement->mStatementParamsHolder = + new nsMainThreadPtrHolder<nsIXPConnectJSObjectHolder>(paramsHolder); + } + + JS::Rooted<JSObject*> obj(aCtx); + obj = aStatement->mStatementParamsHolder->GetJSObject(); + NS_ENSURE_STATE(obj); + + _params->setObject(*obj); + return NS_OK; +} + +NS_IMETHODIMP_(MozExternalRefCountType) StatementJSHelper::AddRef() { return 2; } +NS_IMETHODIMP_(MozExternalRefCountType) StatementJSHelper::Release() { return 1; } +NS_INTERFACE_MAP_BEGIN(StatementJSHelper) + NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +//////////////////////////////////////////////////////////////////////////////// +//// nsIXPCScriptable + +#define XPC_MAP_CLASSNAME StatementJSHelper +#define XPC_MAP_QUOTED_CLASSNAME "StatementJSHelper" +#define XPC_MAP_WANT_GETPROPERTY +#define XPC_MAP_WANT_RESOLVE +#define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE +#include "xpc_map_end.h" + +NS_IMETHODIMP +StatementJSHelper::GetProperty(nsIXPConnectWrappedNative *aWrapper, + JSContext *aCtx, + JSObject *aScopeObj, + jsid aId, + JS::Value *_result, + bool *_retval) +{ + if (!JSID_IS_STRING(aId)) + return NS_OK; + + JS::Rooted<JSObject*> scope(aCtx, aScopeObj); + JS::Rooted<jsid> id(aCtx, aId); + +#ifdef DEBUG + { + nsCOMPtr<mozIStorageStatement> isStatement( + do_QueryInterface(aWrapper->Native())); + NS_ASSERTION(isStatement, "How is this not a statement?!"); + } +#endif + + Statement *stmt = static_cast<Statement *>( + static_cast<mozIStorageStatement *>(aWrapper->Native()) + ); + + JSFlatString *str = JSID_TO_FLAT_STRING(id); + if (::JS_FlatStringEqualsAscii(str, "row")) + return getRow(stmt, aCtx, scope, _result); + + if (::JS_FlatStringEqualsAscii(str, "params")) + return getParams(stmt, aCtx, scope, _result); + + return NS_OK; +} + + +NS_IMETHODIMP +StatementJSHelper::Resolve(nsIXPConnectWrappedNative *aWrapper, + JSContext *aCtx, JSObject *aScopeObj, + jsid aId, bool *aResolvedp, + bool *_retval) +{ + if (!JSID_IS_STRING(aId)) + return NS_OK; + + JS::RootedObject scope(aCtx, aScopeObj); + if (::JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(aId), "step")) { + *_retval = ::JS_DefineFunction(aCtx, scope, "step", stepFunc, + 0, JSPROP_RESOLVING) != nullptr; + *aResolvedp = true; + return NS_OK; + } + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +//// StatementJSObjectHolder + +NS_IMPL_ISUPPORTS(StatementJSObjectHolder, nsIXPConnectJSObjectHolder); + +JSObject* +StatementJSObjectHolder::GetJSObject() +{ + return mHolder->GetJSObject(); +} + +StatementJSObjectHolder::StatementJSObjectHolder(nsIXPConnectJSObjectHolder* aHolder) + : mHolder(aHolder) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mHolder); +} + +StatementParamsHolder::~StatementParamsHolder() +{ + MOZ_ASSERT(NS_IsMainThread()); + // We are considered dead at this point, so any wrappers for row or params + // need to lose their reference to the statement. + nsCOMPtr<nsIXPConnectWrappedNative> wrapper = do_QueryInterface(mHolder); + nsCOMPtr<mozIStorageStatementParams> iObj = do_QueryWrappedNative(wrapper); + StatementParams *obj = static_cast<StatementParams *>(iObj.get()); + obj->mStatement = nullptr; +} + +StatementRowHolder::~StatementRowHolder() +{ + MOZ_ASSERT(NS_IsMainThread()); + // We are considered dead at this point, so any wrappers for row or params + // need to lose their reference to the statement. + nsCOMPtr<nsIXPConnectWrappedNative> wrapper = do_QueryInterface(mHolder); + nsCOMPtr<mozIStorageStatementRow> iObj = do_QueryWrappedNative(wrapper); + StatementRow *obj = static_cast<StatementRow *>(iObj.get()); + obj->mStatement = nullptr; +} + +} // namespace storage +} // namespace mozilla diff --git a/components/storage/src/mozStorageStatementJSHelper.h b/components/storage/src/mozStorageStatementJSHelper.h new file mode 100644 index 000000000..c7948bfa8 --- /dev/null +++ b/components/storage/src/mozStorageStatementJSHelper.h @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZSTORAGESTATEMENTJSHELPER_H +#define MOZSTORAGESTATEMENTJSHELPER_H + +#include "nsIXPCScriptable.h" +#include "nsIXPConnect.h" + +class Statement; + +namespace mozilla { +namespace storage { + +class StatementJSHelper : public nsIXPCScriptable +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCSCRIPTABLE + +private: + nsresult getRow(Statement *, JSContext *, JSObject *, JS::Value *); + nsresult getParams(Statement *, JSContext *, JSObject *, JS::Value *); +}; + +/** + * Wrappers used to clean up the references JS helpers hold to the statement. + * For cycle-avoidance reasons they do not hold reference-counted references, + * so it is important we do this. + */ +class StatementJSObjectHolder : public nsIXPConnectJSObjectHolder +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCONNECTJSOBJECTHOLDER + + explicit StatementJSObjectHolder(nsIXPConnectJSObjectHolder* aHolder); + +protected: + virtual ~StatementJSObjectHolder() {}; + nsCOMPtr<nsIXPConnectJSObjectHolder> mHolder; +}; + +class StatementParamsHolder final: public StatementJSObjectHolder { +public: + explicit StatementParamsHolder(nsIXPConnectJSObjectHolder* aHolder) + : StatementJSObjectHolder(aHolder) { + } + +private: + virtual ~StatementParamsHolder(); +}; + +class StatementRowHolder final: public StatementJSObjectHolder { +public: + explicit StatementRowHolder(nsIXPConnectJSObjectHolder* aHolder) + : StatementJSObjectHolder(aHolder) { + } + +private: + virtual ~StatementRowHolder(); +}; + +} // namespace storage +} // namespace mozilla + +#endif // MOZSTORAGESTATEMENTJSHELPER_H diff --git a/components/storage/src/mozStorageStatementParams.cpp b/components/storage/src/mozStorageStatementParams.cpp new file mode 100644 index 000000000..de4ace78a --- /dev/null +++ b/components/storage/src/mozStorageStatementParams.cpp @@ -0,0 +1,184 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsJSUtils.h" +#include "nsMemory.h" +#include "nsString.h" + +#include "jsapi.h" + +#include "mozStoragePrivateHelpers.h" +#include "mozStorageStatementParams.h" +#include "mozIStorageStatement.h" + +#include "xpc_make_class.h" + +namespace mozilla { +namespace storage { + +//////////////////////////////////////////////////////////////////////////////// +//// StatementParams + +StatementParams::StatementParams(mozIStorageStatement *aStatement) : + mStatement(aStatement), + mParamCount(0) +{ + NS_ASSERTION(mStatement != nullptr, "mStatement is null"); + (void)mStatement->GetParameterCount(&mParamCount); +} + +NS_IMPL_ISUPPORTS( + StatementParams, + mozIStorageStatementParams, + nsIXPCScriptable +) + +//////////////////////////////////////////////////////////////////////////////// +//// nsIXPCScriptable + +#define XPC_MAP_CLASSNAME StatementParams +#define XPC_MAP_QUOTED_CLASSNAME "StatementParams" +#define XPC_MAP_WANT_SETPROPERTY +#define XPC_MAP_WANT_NEWENUMERATE +#define XPC_MAP_WANT_RESOLVE +#define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE +#include "xpc_map_end.h" + +NS_IMETHODIMP +StatementParams::SetProperty(nsIXPConnectWrappedNative *aWrapper, + JSContext *aCtx, + JSObject *aScopeObj, + jsid aId, + JS::Value *_vp, + bool *_retval) +{ + NS_ENSURE_TRUE(mStatement, NS_ERROR_NOT_INITIALIZED); + + if (JSID_IS_INT(aId)) { + int idx = JSID_TO_INT(aId); + + nsCOMPtr<nsIVariant> variant(convertJSValToVariant(aCtx, *_vp)); + NS_ENSURE_TRUE(variant, NS_ERROR_UNEXPECTED); + nsresult rv = mStatement->BindByIndex(idx, variant); + NS_ENSURE_SUCCESS(rv, rv); + } + else if (JSID_IS_STRING(aId)) { + JSString *str = JSID_TO_STRING(aId); + nsAutoJSString autoStr; + if (!autoStr.init(aCtx, str)) { + return NS_ERROR_FAILURE; + } + + NS_ConvertUTF16toUTF8 name(autoStr); + + // check to see if there's a parameter with this name + nsCOMPtr<nsIVariant> variant(convertJSValToVariant(aCtx, *_vp)); + NS_ENSURE_TRUE(variant, NS_ERROR_UNEXPECTED); + nsresult rv = mStatement->BindByName(name, variant); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + return NS_ERROR_INVALID_ARG; + } + + *_retval = true; + return NS_OK; +} + +NS_IMETHODIMP +StatementParams::NewEnumerate(nsIXPConnectWrappedNative *aWrapper, + JSContext *aCtx, + JSObject *aScopeObj, + JS::AutoIdVector &aProperties, + bool *_retval) +{ + NS_ENSURE_TRUE(mStatement, NS_ERROR_NOT_INITIALIZED); + JS::RootedObject scope(aCtx, aScopeObj); + + if (!aProperties.reserve(mParamCount)) { + *_retval = false; + return NS_OK; + } + + for (uint32_t i = 0; i < mParamCount; i++) { + // Get the name of our parameter. + nsAutoCString name; + nsresult rv = mStatement->GetParameterName(i, name); + NS_ENSURE_SUCCESS(rv, rv); + + // But drop the first character, which is going to be a ':'. + JS::RootedString jsname(aCtx, ::JS_NewStringCopyN(aCtx, &(name.get()[1]), + name.Length() - 1)); + NS_ENSURE_TRUE(jsname, NS_ERROR_OUT_OF_MEMORY); + + // Set our name. + JS::Rooted<jsid> id(aCtx); + if (!::JS_StringToId(aCtx, jsname, &id)) { + *_retval = false; + return NS_OK; + } + + aProperties.infallibleAppend(id); + } + + return NS_OK; +} + +NS_IMETHODIMP +StatementParams::Resolve(nsIXPConnectWrappedNative *aWrapper, + JSContext *aCtx, + JSObject *aScopeObj, + jsid aId, + bool *resolvedp, + bool *_retval) +{ + NS_ENSURE_TRUE(mStatement, NS_ERROR_NOT_INITIALIZED); + // We do not throw at any point after this unless our index is out of range + // because we want to allow the prototype chain to be checked for the + // property. + + JS::RootedObject scope(aCtx, aScopeObj); + JS::RootedId id(aCtx, aId); + bool resolved = false; + bool ok = true; + if (JSID_IS_INT(id)) { + uint32_t idx = JSID_TO_INT(id); + + // Ensure that our index is within range. We do not care about the + // prototype chain being checked here. + if (idx >= mParamCount) + return NS_ERROR_INVALID_ARG; + + ok = ::JS_DefineElement(aCtx, scope, idx, JS::UndefinedHandleValue, + JSPROP_ENUMERATE | JSPROP_RESOLVING); + resolved = true; + } + else if (JSID_IS_STRING(id)) { + JSString *str = JSID_TO_STRING(id); + nsAutoJSString autoStr; + if (!autoStr.init(aCtx, str)) { + return NS_ERROR_FAILURE; + } + + // Check to see if there's a parameter with this name, and if not, let + // the rest of the prototype chain be checked. + NS_ConvertUTF16toUTF8 name(autoStr); + uint32_t idx; + nsresult rv = mStatement->GetParameterIndex(name, &idx); + if (NS_SUCCEEDED(rv)) { + ok = ::JS_DefinePropertyById(aCtx, scope, id, JS::UndefinedHandleValue, + JSPROP_ENUMERATE | JSPROP_RESOLVING); + resolved = true; + } + } + + *_retval = ok; + *resolvedp = resolved && ok; + return NS_OK; +} + +} // namespace storage +} // namespace mozilla diff --git a/components/storage/src/mozStorageStatementParams.h b/components/storage/src/mozStorageStatementParams.h new file mode 100644 index 000000000..2627f8aa1 --- /dev/null +++ b/components/storage/src/mozStorageStatementParams.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZSTORAGESTATEMENTPARAMS_H +#define MOZSTORAGESTATEMENTPARAMS_H + +#include "mozIStorageStatementParams.h" +#include "nsIXPCScriptable.h" +#include "mozilla/Attributes.h" + +class mozIStorageStatement; + +namespace mozilla { +namespace storage { + +class StatementParams final : public mozIStorageStatementParams + , public nsIXPCScriptable +{ +public: + explicit StatementParams(mozIStorageStatement *aStatement); + + // interfaces + NS_DECL_ISUPPORTS + NS_DECL_MOZISTORAGESTATEMENTPARAMS + NS_DECL_NSIXPCSCRIPTABLE + +protected: + ~StatementParams() {} + + mozIStorageStatement *mStatement; + uint32_t mParamCount; + + friend class StatementParamsHolder; + friend class StatementRowHolder; +}; + +} // namespace storage +} // namespace mozilla + +#endif /* MOZSTORAGESTATEMENTPARAMS_H */ diff --git a/components/storage/src/mozStorageStatementRow.cpp b/components/storage/src/mozStorageStatementRow.cpp new file mode 100644 index 000000000..6ace04bbf --- /dev/null +++ b/components/storage/src/mozStorageStatementRow.cpp @@ -0,0 +1,157 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsMemory.h" +#include "nsString.h" + +#include "mozStorageStatementRow.h" +#include "mozStorageStatement.h" + +#include "jsapi.h" + +#include "xpc_make_class.h" + +namespace mozilla { +namespace storage { + +//////////////////////////////////////////////////////////////////////////////// +//// StatementRow + +StatementRow::StatementRow(Statement *aStatement) +: mStatement(aStatement) +{ +} + +NS_IMPL_ISUPPORTS( + StatementRow, + mozIStorageStatementRow, + nsIXPCScriptable +) + +//////////////////////////////////////////////////////////////////////////////// +//// nsIXPCScriptable + +#define XPC_MAP_CLASSNAME StatementRow +#define XPC_MAP_QUOTED_CLASSNAME "StatementRow" +#define XPC_MAP_WANT_GETPROPERTY +#define XPC_MAP_WANT_RESOLVE +#define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE +#include "xpc_map_end.h" + +NS_IMETHODIMP +StatementRow::GetProperty(nsIXPConnectWrappedNative *aWrapper, + JSContext *aCtx, + JSObject *aScopeObj, + jsid aId, + JS::Value *_vp, + bool *_retval) +{ + NS_ENSURE_TRUE(mStatement, NS_ERROR_NOT_INITIALIZED); + + JS::RootedObject scope(aCtx, aScopeObj); + if (JSID_IS_STRING(aId)) { + ::JSAutoByteString idBytes(aCtx, JSID_TO_STRING(aId)); + NS_ENSURE_TRUE(!!idBytes, NS_ERROR_OUT_OF_MEMORY); + nsDependentCString jsid(idBytes.ptr()); + + uint32_t idx; + nsresult rv = mStatement->GetColumnIndex(jsid, &idx); + NS_ENSURE_SUCCESS(rv, rv); + int32_t type; + rv = mStatement->GetTypeOfIndex(idx, &type); + NS_ENSURE_SUCCESS(rv, rv); + + if (type == mozIStorageValueArray::VALUE_TYPE_INTEGER || + type == mozIStorageValueArray::VALUE_TYPE_FLOAT) { + double dval; + rv = mStatement->GetDouble(idx, &dval); + NS_ENSURE_SUCCESS(rv, rv); + *_vp = ::JS_NumberValue(dval); + } + else if (type == mozIStorageValueArray::VALUE_TYPE_TEXT) { + uint32_t bytes; + const char16_t *sval = reinterpret_cast<const char16_t *>( + static_cast<mozIStorageStatement *>(mStatement)-> + AsSharedWString(idx, &bytes) + ); + JSString *str = ::JS_NewUCStringCopyN(aCtx, sval, bytes / 2); + if (!str) { + *_retval = false; + return NS_OK; + } + _vp->setString(str); + } + else if (type == mozIStorageValueArray::VALUE_TYPE_BLOB) { + uint32_t length; + const uint8_t *blob = static_cast<mozIStorageStatement *>(mStatement)-> + AsSharedBlob(idx, &length); + JSObject *obj = ::JS_NewArrayObject(aCtx, length); + if (!obj) { + *_retval = false; + return NS_OK; + } + _vp->setObject(*obj); + + // Copy the blob over to the JS array. + for (uint32_t i = 0; i < length; i++) { + if (!::JS_DefineElement(aCtx, scope, i, blob[i], JSPROP_ENUMERATE)) { + *_retval = false; + return NS_OK; + } + } + } + else if (type == mozIStorageValueArray::VALUE_TYPE_NULL) { + _vp->setNull(); + } + else { + NS_ERROR("unknown column type returned, what's going on?"); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +StatementRow::Resolve(nsIXPConnectWrappedNative *aWrapper, + JSContext *aCtx, + JSObject *aScopeObj, + jsid aId, + bool *aResolvedp, + bool *_retval) +{ + JS::Rooted<JSObject*> scopeObj(aCtx, aScopeObj); + + NS_ENSURE_TRUE(mStatement, NS_ERROR_NOT_INITIALIZED); + // We do not throw at any point after this because we want to allow the + // prototype chain to be checked for the property. + + if (JSID_IS_STRING(aId)) { + ::JSAutoByteString idBytes(aCtx, JSID_TO_STRING(aId)); + NS_ENSURE_TRUE(!!idBytes, NS_ERROR_OUT_OF_MEMORY); + nsDependentCString name(idBytes.ptr()); + + uint32_t idx; + nsresult rv = mStatement->GetColumnIndex(name, &idx); + if (NS_FAILED(rv)) { + // It's highly likely that the name doesn't exist, so let the JS engine + // check the prototype chain and throw if that doesn't have the property + // either. + *aResolvedp = false; + return NS_OK; + } + + JS::Rooted<jsid> id(aCtx, aId); + *_retval = ::JS_DefinePropertyById(aCtx, scopeObj, id, JS::UndefinedHandleValue, + JSPROP_RESOLVING); + *aResolvedp = true; + return NS_OK; + } + + return NS_OK; +} + +} // namespace storage +} // namespace mozilla diff --git a/components/storage/src/mozStorageStatementRow.h b/components/storage/src/mozStorageStatementRow.h new file mode 100644 index 000000000..ea9e40348 --- /dev/null +++ b/components/storage/src/mozStorageStatementRow.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZSTORAGESTATEMENTROW_H +#define MOZSTORAGESTATEMENTROW_H + +#include "mozIStorageStatementRow.h" +#include "nsIXPCScriptable.h" +#include "mozilla/Attributes.h" + +namespace mozilla { +namespace storage { + +class Statement; + +class StatementRow final : public mozIStorageStatementRow + , public nsIXPCScriptable +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_MOZISTORAGESTATEMENTROW + NS_DECL_NSIXPCSCRIPTABLE + + explicit StatementRow(Statement *aStatement); +protected: + + ~StatementRow() {} + + Statement *mStatement; + + friend class StatementRowHolder; +}; + +} // namespace storage +} // namespace mozilla + +#endif /* MOZSTORAGESTATEMENTROW_H */ diff --git a/components/storage/src/storage.h b/components/storage/src/storage.h new file mode 100644 index 000000000..ec8037983 --- /dev/null +++ b/components/storage/src/storage.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_storage_h_ +#define mozilla_storage_h_ + +//////////////////////////////////////////////////////////////////////////////// +//// Public Interfaces + +#include "mozStorageCID.h" +#include "mozIStorageAggregateFunction.h" +#include "mozIStorageConnection.h" +#include "mozIStorageError.h" +#include "mozIStorageFunction.h" +#include "mozIStoragePendingStatement.h" +#include "mozIStorageProgressHandler.h" +#include "mozIStorageResultSet.h" +#include "mozIStorageRow.h" +#include "mozIStorageService.h" +#include "mozIStorageStatement.h" +#include "mozIStorageStatementCallback.h" +#include "mozIStorageBindingParamsArray.h" +#include "mozIStorageBindingParams.h" +#include "mozIStorageVacuumParticipant.h" +#include "mozIStorageCompletionCallback.h" +#include "mozIStorageAsyncStatement.h" +#include "mozIStorageAsyncConnection.h" + +//////////////////////////////////////////////////////////////////////////////// +//// Native Language Helpers + +#include "mozStorageHelper.h" +#include "mozilla/storage/StatementCache.h" +#include "mozilla/storage/Variant.h" + +#endif // mozilla_storage_h_ diff --git a/components/storage/src/variantToSQLiteT_impl.h b/components/storage/src/variantToSQLiteT_impl.h new file mode 100644 index 000000000..5e55a261f --- /dev/null +++ b/components/storage/src/variantToSQLiteT_impl.h @@ -0,0 +1,127 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Note: we are already in the namepace mozilla::storage + +// Note 2: whoever #includes this file must provide implementations of +// sqlite3_T_* prior. + +//////////////////////////////////////////////////////////////////////////////// +//// variantToSQLiteT Implementation + +template <typename T> +int +variantToSQLiteT(T aObj, + nsIVariant *aValue) +{ + // Allow to return nullptr not wrapped to nsIVariant for speed. + if (!aValue) + return sqlite3_T_null(aObj); + + uint16_t valueType; + aValue->GetDataType(&valueType); + switch (valueType) { + case nsIDataType::VTYPE_INT8: + case nsIDataType::VTYPE_INT16: + case nsIDataType::VTYPE_INT32: + case nsIDataType::VTYPE_UINT8: + case nsIDataType::VTYPE_UINT16: + { + int32_t value; + nsresult rv = aValue->GetAsInt32(&value); + NS_ENSURE_SUCCESS(rv, SQLITE_MISMATCH); + return sqlite3_T_int(aObj, value); + } + case nsIDataType::VTYPE_UINT32: // Try to preserve full range + case nsIDataType::VTYPE_INT64: + // Data loss possible, but there is no unsigned types in SQLite + case nsIDataType::VTYPE_UINT64: + { + int64_t value; + nsresult rv = aValue->GetAsInt64(&value); + NS_ENSURE_SUCCESS(rv, SQLITE_MISMATCH); + return sqlite3_T_int64(aObj, value); + } + case nsIDataType::VTYPE_FLOAT: + case nsIDataType::VTYPE_DOUBLE: + { + double value; + nsresult rv = aValue->GetAsDouble(&value); + NS_ENSURE_SUCCESS(rv, SQLITE_MISMATCH); + return sqlite3_T_double(aObj, value); + } + case nsIDataType::VTYPE_BOOL: + { + bool value; + nsresult rv = aValue->GetAsBool(&value); + NS_ENSURE_SUCCESS(rv, SQLITE_MISMATCH); + return sqlite3_T_int(aObj, value ? 1 : 0); + } + case nsIDataType::VTYPE_CHAR: + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_CSTRING: + { + nsAutoCString value; + // GetAsAUTF8String should never perform conversion when coming from + // 8-bit string types, and thus can accept strings with arbitrary encoding + // (including UTF8 and ASCII). + nsresult rv = aValue->GetAsAUTF8String(value); + NS_ENSURE_SUCCESS(rv, SQLITE_MISMATCH); + return sqlite3_T_text(aObj, value); + } + case nsIDataType::VTYPE_WCHAR: + case nsIDataType::VTYPE_DOMSTRING: + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + case nsIDataType::VTYPE_ASTRING: + { + nsAutoString value; + // GetAsAString does proper conversion to UCS2 from all string-like types. + // It can be used universally without problems (unless someone implements + // their own variant, but that's their problem). + nsresult rv = aValue->GetAsAString(value); + NS_ENSURE_SUCCESS(rv, SQLITE_MISMATCH); + return sqlite3_T_text16(aObj, value); + } + case nsIDataType::VTYPE_VOID: + case nsIDataType::VTYPE_EMPTY: + case nsIDataType::VTYPE_EMPTY_ARRAY: + return sqlite3_T_null(aObj); + case nsIDataType::VTYPE_ARRAY: + { + uint16_t arrayType; + nsIID iid; + uint32_t count; + void *data; + nsresult rv = aValue->GetAsArray(&arrayType, &iid, &count, &data); + NS_ENSURE_SUCCESS(rv, SQLITE_MISMATCH); + + // Check to make sure it's a supported type. + NS_ASSERTION(arrayType == nsIDataType::VTYPE_UINT8, + "Invalid type passed! You may leak!"); + if (arrayType != nsIDataType::VTYPE_UINT8) { + // Technically this could leak with certain data types, but somebody was + // being stupid passing us this anyway. + free(data); + return SQLITE_MISMATCH; + } + + // Finally do our thing. The function should free the array accordingly! + int rc = sqlite3_T_blob(aObj, data, count); + return rc; + } + // Maybe, it'll be possible to convert these + // in future too. + case nsIDataType::VTYPE_ID: + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + default: + return SQLITE_MISMATCH; + } + return SQLITE_OK; +} diff --git a/components/storage/style.txt b/components/storage/style.txt new file mode 100644 index 000000000..03652e606 --- /dev/null +++ b/components/storage/style.txt @@ -0,0 +1,141 @@ +Storage Module Style Guidelines + +These guidelines should be followed for all new code in this module. Reviewers +will be enforcing them, so please obey them! + +* All code should be contained within the namespace mozilla::storage at a + minimum. The use of namespaces is strongly encouraged. + +* All functions being called in the global namespace should be prefixed with + "::" to indicate that they are in the global namespace. + +* The indentation level to use in source code is two spaces. No tabs, please! + +* All files should have the following emacs and vim mode lines: + -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + +* All functions that are not XPCOM should start with a lowercase letter. + +* Function arguments that are not out parameters should be prefixed with a (for + pArameter), and use CamelCase. + +* Function arguments that are out parameters should be prefixed with an + underscore and have a descriptive name. + +* Function declarations should include javadoc style comments. + +* Javadoc @param tags should have the parameter description start on a new line + aligned with the variable name. See the example below. + +* Javadoc @return (note: non-plural) continuation lines should be lined up with + the initial comment. See the example below. + +* Javadoc @throws, like @param, should have the exception type on the same line + as the @throws and the description on a new line indented to line up with + the type of the exception. + +* For function implementations, each argument should be on its own line. + +* All variables should use camelCase. + +* The use of bool is encouraged whenever the variable does not have the + potential to go through xpconnect. + +* For pointer variable types, include a space after the type before the asterisk + and no space between the asterisk and variable name. + +* If any part of an if-else block requires braces, all blocks need braces. + +* Every else should be on a newline after a brace. + +* Bracing should start on the line after a function and class definition. This + goes for JavaScript code as well as C++ code. + +* If a return value is not going to be checked, the return value should be + explicitly casted to void (C style cast). + + +BIG EXAMPLE: + +*** Header *** + +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_storage_FILENAME_h_ +#define mozilla_storage_FILENAME_h_ + +namespace mozilla { +namespace storage { + +class Foo : public Bar + , public Baz +{ +public: + /** + * Brief function summary. + * + * @param aArg1 + * Description description description description description etc etc + * next line of description. + * @param aArg2 + * Description description description. + * @return Description description description description description etc etc + * next line of description. + * + * @throws NS_ERROR_FAILURE + * Okay, so this is for JavaScript code, but you probably get the + * idea. + */ + int chew(int aArg1, int aArg2); +}; + +} // storage +} // mozilla + +#endif // mozilla_storage_FILENAME_h_ + + +*** Implementation *** + +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +NS_IMPL_ISUPPORTS( + Foo +, IBar +, IBaz +) + +Foo::Foo( + LongArgumentLineThatWouldOtherwiseOverflow *aArgument1 +) +: mField1(0) +, mField2(0) +{ + someMethodWithLotsOfParamsOrJustLongParameters( + mLongFieldNameThatIsJustified, + mMaybeThisOneIsLessJustifiedButBoyIsItLong, + 15 + ); +} + +//////////////////////////////////////////////////////////////////////////////// +//// Separate sections of the file like this + +int +Foo::chew(int aArg1, int aArg2) +{ + (void)functionReturningAnIgnoredValue(); + + ::functionFromGlobalNamespaceWithVoidReturnValue(); + + return 0; +} |