/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Indexed Database. * * The Initial Developer of the Original Code is * The Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Ben Turner * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "IDBObjectStore.h" #include "nsIJSContextStack.h" #include "jsclone.h" #include "mozilla/dom/StructuredCloneTags.h" #include "mozilla/storage.h" #include "nsCharSeparatedTokenizer.h" #include "nsContentUtils.h" #include "nsDOMClassInfo.h" #include "nsDOMFile.h" #include "nsDOMLists.h" #include "nsEventDispatcher.h" #include "nsJSUtils.h" #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" #include "snappy/snappy.h" #include "test_quota.h" #include "AsyncConnectionHelper.h" #include "IDBCursor.h" #include "IDBEvents.h" #include "IDBIndex.h" #include "IDBKeyRange.h" #include "IDBTransaction.h" #include "DatabaseInfo.h" #define FILE_COPY_BUFFER_SIZE 32768 USING_INDEXEDDB_NAMESPACE namespace { class AddHelper : public AsyncConnectionHelper { public: AddHelper(IDBTransaction* aTransaction, IDBRequest* aRequest, IDBObjectStore* aObjectStore, StructuredCloneWriteInfo& aCloneWriteInfo, const Key& aKey, bool aOverwrite, nsTArray& aIndexUpdateInfo) : AsyncConnectionHelper(aTransaction, aRequest), mObjectStore(aObjectStore), mKey(aKey), mOverwrite(aOverwrite) { mCloneWriteInfo.Swap(aCloneWriteInfo); mIndexUpdateInfo.SwapElements(aIndexUpdateInfo); } ~AddHelper() { IDBObjectStore::ClearStructuredCloneBuffer(mCloneWriteInfo.mCloneBuffer); } nsresult DoDatabaseWork(mozIStorageConnection* aConnection); nsresult GetSuccessResult(JSContext* aCx, jsval* aVal); void ReleaseMainThreadObjects() { mObjectStore = nsnull; IDBObjectStore::ClearStructuredCloneBuffer(mCloneWriteInfo.mCloneBuffer); AsyncConnectionHelper::ReleaseMainThreadObjects(); } private: // In-params. nsRefPtr mObjectStore; // These may change in the autoincrement case. StructuredCloneWriteInfo mCloneWriteInfo; Key mKey; const bool mOverwrite; nsTArray mIndexUpdateInfo; }; class GetHelper : public AsyncConnectionHelper { public: GetHelper(IDBTransaction* aTransaction, IDBRequest* aRequest, IDBObjectStore* aObjectStore, IDBKeyRange* aKeyRange) : AsyncConnectionHelper(aTransaction, aRequest), mObjectStore(aObjectStore), mKeyRange(aKeyRange) { } ~GetHelper() { IDBObjectStore::ClearStructuredCloneBuffer(mCloneReadInfo.mCloneBuffer); } nsresult DoDatabaseWork(mozIStorageConnection* aConnection); nsresult GetSuccessResult(JSContext* aCx, jsval* aVal); void ReleaseMainThreadObjects() { mObjectStore = nsnull; mKeyRange = nsnull; IDBObjectStore::ClearStructuredCloneBuffer(mCloneReadInfo.mCloneBuffer); AsyncConnectionHelper::ReleaseMainThreadObjects(); } protected: // In-params. nsRefPtr mObjectStore; nsRefPtr mKeyRange; private: // Out-params. StructuredCloneReadInfo mCloneReadInfo; }; class DeleteHelper : public GetHelper { public: DeleteHelper(IDBTransaction* aTransaction, IDBRequest* aRequest, IDBObjectStore* aObjectStore, IDBKeyRange* aKeyRange) : GetHelper(aTransaction, aRequest, aObjectStore, aKeyRange) { } nsresult DoDatabaseWork(mozIStorageConnection* aConnection); nsresult GetSuccessResult(JSContext* aCx, jsval* aVal); }; class ClearHelper : public AsyncConnectionHelper { public: ClearHelper(IDBTransaction* aTransaction, IDBRequest* aRequest, IDBObjectStore* aObjectStore) : AsyncConnectionHelper(aTransaction, aRequest), mObjectStore(aObjectStore) { } nsresult DoDatabaseWork(mozIStorageConnection* aConnection); void ReleaseMainThreadObjects() { mObjectStore = nsnull; AsyncConnectionHelper::ReleaseMainThreadObjects(); } protected: // In-params. nsRefPtr mObjectStore; }; class OpenCursorHelper : public AsyncConnectionHelper { public: OpenCursorHelper(IDBTransaction* aTransaction, IDBRequest* aRequest, IDBObjectStore* aObjectStore, IDBKeyRange* aKeyRange, PRUint16 aDirection) : AsyncConnectionHelper(aTransaction, aRequest), mObjectStore(aObjectStore), mKeyRange(aKeyRange), mDirection(aDirection) { } ~OpenCursorHelper() { IDBObjectStore::ClearStructuredCloneBuffer(mCloneReadInfo.mCloneBuffer); } nsresult DoDatabaseWork(mozIStorageConnection* aConnection); nsresult GetSuccessResult(JSContext* aCx, jsval* aVal); void ReleaseMainThreadObjects() { mObjectStore = nsnull; mKeyRange = nsnull; IDBObjectStore::ClearStructuredCloneBuffer(mCloneReadInfo.mCloneBuffer); AsyncConnectionHelper::ReleaseMainThreadObjects(); } private: // In-params. nsRefPtr mObjectStore; nsRefPtr mKeyRange; const PRUint16 mDirection; // Out-params. Key mKey; StructuredCloneReadInfo mCloneReadInfo; nsCString mContinueQuery; nsCString mContinueToQuery; Key mRangeKey; }; class CreateIndexHelper : public AsyncConnectionHelper { public: CreateIndexHelper(IDBTransaction* aTransaction, IDBIndex* aIndex); nsresult DoDatabaseWork(mozIStorageConnection* aConnection); nsresult OnSuccess() { return NS_OK; } void OnError() { NS_ASSERTION(mTransaction->IsAborted(), "How else can this fail?!"); } void ReleaseMainThreadObjects() { mIndex = nsnull; AsyncConnectionHelper::ReleaseMainThreadObjects(); } private: nsresult InsertDataFromObjectStore(mozIStorageConnection* aConnection); static void DestroyTLSEntry(void* aPtr); static PRUintn sTLSIndex; // In-params. nsRefPtr mIndex; }; PRUintn CreateIndexHelper::sTLSIndex = PRUintn(BAD_TLS_INDEX); class DeleteIndexHelper : public AsyncConnectionHelper { public: DeleteIndexHelper(IDBTransaction* aTransaction, const nsAString& aName, IDBObjectStore* aObjectStore) : AsyncConnectionHelper(aTransaction, nsnull), mName(aName), mObjectStore(aObjectStore) { } nsresult DoDatabaseWork(mozIStorageConnection* aConnection); nsresult OnSuccess() { return NS_OK; } void OnError() { NS_ASSERTION(mTransaction->IsAborted(), "How else can this fail?!"); } void ReleaseMainThreadObjects() { mObjectStore = nsnull; AsyncConnectionHelper::ReleaseMainThreadObjects(); } private: // In-params nsString mName; nsRefPtr mObjectStore; }; class GetAllHelper : public AsyncConnectionHelper { public: GetAllHelper(IDBTransaction* aTransaction, IDBRequest* aRequest, IDBObjectStore* aObjectStore, IDBKeyRange* aKeyRange, const PRUint32 aLimit) : AsyncConnectionHelper(aTransaction, aRequest), mObjectStore(aObjectStore), mKeyRange(aKeyRange), mLimit(aLimit) { } ~GetAllHelper() { for (PRUint32 index = 0; index < mCloneReadInfos.Length(); index++) { IDBObjectStore::ClearStructuredCloneBuffer( mCloneReadInfos[index].mCloneBuffer); } } nsresult DoDatabaseWork(mozIStorageConnection* aConnection); nsresult GetSuccessResult(JSContext* aCx, jsval* aVal); void ReleaseMainThreadObjects() { mObjectStore = nsnull; mKeyRange = nsnull; for (PRUint32 index = 0; index < mCloneReadInfos.Length(); index++) { IDBObjectStore::ClearStructuredCloneBuffer( mCloneReadInfos[index].mCloneBuffer); } AsyncConnectionHelper::ReleaseMainThreadObjects(); } protected: // In-params. nsRefPtr mObjectStore; nsRefPtr mKeyRange; const PRUint32 mLimit; private: // Out-params. nsTArray mCloneReadInfos; }; class CountHelper : public AsyncConnectionHelper { public: CountHelper(IDBTransaction* aTransaction, IDBRequest* aRequest, IDBObjectStore* aObjectStore, IDBKeyRange* aKeyRange) : AsyncConnectionHelper(aTransaction, aRequest), mObjectStore(aObjectStore), mKeyRange(aKeyRange), mCount(0) { } nsresult DoDatabaseWork(mozIStorageConnection* aConnection); nsresult GetSuccessResult(JSContext* aCx, jsval* aVal); void ReleaseMainThreadObjects() { mObjectStore = nsnull; mKeyRange = nsnull; AsyncConnectionHelper::ReleaseMainThreadObjects(); } protected: nsRefPtr mObjectStore; nsRefPtr mKeyRange; private: PRUint64 mCount; }; NS_STACK_CLASS class AutoRemoveIndex { public: AutoRemoveIndex(IDBDatabase* aDatabase, const nsAString& aObjectStoreName, const nsAString& aIndexName) : mDatabase(aDatabase), mObjectStoreName(aObjectStoreName), mIndexName(aIndexName) { } ~AutoRemoveIndex() { if (mDatabase) { ObjectStoreInfo* info; if (mDatabase->Info()->GetObjectStore(mObjectStoreName, &info)) { for (PRUint32 index = 0; index < info->indexes.Length(); index++) { if (info->indexes[index].name == mIndexName) { info->indexes.RemoveElementAt(index); break; } } } } } void forget() { mDatabase = nsnull; } private: IDBDatabase* mDatabase; nsString mObjectStoreName; nsString mIndexName; }; inline bool IgnoreWhitespace(PRUnichar c) { return false; } typedef nsCharSeparatedTokenizerTemplate KeyPathTokenizer; inline nsresult GetJSValFromKeyPath(JSContext* aCx, jsval aVal, const nsAString& aKeyPath, jsval& aKey) { NS_ASSERTION(aCx, "Null pointer!"); // aVal can be primitive iff the key path is empty. NS_ASSERTION(IDBObjectStore::IsValidKeyPath(aCx, aKeyPath), "This will explode!"); KeyPathTokenizer tokenizer(aKeyPath, '.'); jsval intermediate = aVal; while (tokenizer.hasMoreTokens()) { const nsDependentSubstring& token = tokenizer.nextToken(); NS_ASSERTION(!token.IsEmpty(), "Should be a valid keypath"); const jschar* keyPathChars = token.BeginReading(); const size_t keyPathLen = token.Length(); if (JSVAL_IS_PRIMITIVE(intermediate)) { intermediate = JSVAL_VOID; break; } JSBool ok = JS_GetUCProperty(aCx, JSVAL_TO_OBJECT(intermediate), keyPathChars, keyPathLen, &intermediate); NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } aKey = intermediate; return NS_OK; } inline nsresult GetKeyFromValue(JSContext* aCx, jsval aVal, const nsAString& aKeyPath, Key& aKey) { jsval key; nsresult rv = GetJSValFromKeyPath(aCx, aVal, aKeyPath, key); NS_ENSURE_SUCCESS(rv, rv); if (NS_FAILED(aKey.SetFromJSVal(aCx, key))) { aKey.Unset(); } return NS_OK; } inline already_AddRefed GenerateRequest(IDBObjectStore* aObjectStore) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); IDBDatabase* database = aObjectStore->Transaction()->Database(); return IDBRequest::Create(aObjectStore, database->ScriptContext(), database->Owner(), aObjectStore->Transaction()); } JSClass gDummyPropClass = { "dummy", 0, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, JSCLASS_NO_OPTIONAL_MEMBERS }; } // anonymous namespace // static already_AddRefed IDBObjectStore::Create(IDBTransaction* aTransaction, const ObjectStoreInfo* aStoreInfo) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); nsRefPtr objectStore = new IDBObjectStore(); objectStore->mScriptContext = aTransaction->Database()->ScriptContext(); objectStore->mOwner = aTransaction->Database()->Owner(); objectStore->mTransaction = aTransaction; objectStore->mName = aStoreInfo->name; objectStore->mId = aStoreInfo->id; objectStore->mKeyPath = aStoreInfo->keyPath; objectStore->mAutoIncrement = aStoreInfo->autoIncrement; objectStore->mDatabaseId = aStoreInfo->databaseId; return objectStore.forget(); } // static bool IDBObjectStore::IsValidKeyPath(JSContext* aCx, const nsAString& aKeyPath) { NS_ASSERTION(!aKeyPath.IsVoid(), "What?"); KeyPathTokenizer tokenizer(aKeyPath, '.'); while (tokenizer.hasMoreTokens()) { nsString token(tokenizer.nextToken()); if (!token.Length()) { return false; } jsval stringVal; if (!xpc_qsStringToJsval(aCx, token, &stringVal)) { return false; } NS_ASSERTION(JSVAL_IS_STRING(stringVal), "This should never happen"); JSString* str = JSVAL_TO_STRING(stringVal); JSBool isIdentifier = JS_FALSE; if (!JS_IsIdentifier(aCx, str, &isIdentifier) || !isIdentifier) { return false; } } // If the very last character was a '.', the tokenizer won't give us an empty // token, but the keyPath is still invalid. if (!aKeyPath.IsEmpty() && aKeyPath.CharAt(aKeyPath.Length() - 1) == '.') { return false; } return true; } // static nsresult IDBObjectStore::AppendIndexUpdateInfo(PRInt64 aIndexID, const nsAString& aKeyPath, bool aUnique, bool aMultiEntry, JSContext* aCx, jsval aObject, nsTArray& aUpdateInfoArray) { jsval key; nsresult rv = GetJSValFromKeyPath(aCx, aObject, aKeyPath, key); NS_ENSURE_SUCCESS(rv, rv); if (aMultiEntry && !JSVAL_IS_PRIMITIVE(key) && JS_IsArrayObject(aCx, JSVAL_TO_OBJECT(key))) { JSObject* array = JSVAL_TO_OBJECT(key); jsuint arrayLength; if (!JS_GetArrayLength(aCx, array, &arrayLength)) { return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } for (jsuint arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) { jsval arrayItem; if (!JS_GetElement(aCx, array, arrayIndex, &arrayItem)) { return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } Key value; if (NS_FAILED(value.SetFromJSVal(aCx, arrayItem)) || value.IsUnset()) { // Not a value we can do anything with, ignore it. continue; } IndexUpdateInfo* updateInfo = aUpdateInfoArray.AppendElement(); updateInfo->indexId = aIndexID; updateInfo->indexUnique = aUnique; updateInfo->value = value; } } else { Key value; if (NS_FAILED(value.SetFromJSVal(aCx, key)) || value.IsUnset()) { // Not a value we can do anything with, ignore it. return NS_OK; } IndexUpdateInfo* updateInfo = aUpdateInfoArray.AppendElement(); updateInfo->indexId = aIndexID; updateInfo->indexUnique = aUnique; updateInfo->value = value; } return NS_OK; } // static nsresult IDBObjectStore::UpdateIndexes(IDBTransaction* aTransaction, PRInt64 aObjectStoreId, const Key& aObjectStoreKey, bool aAutoIncrement, bool aOverwrite, PRInt64 aObjectDataId, const nsTArray& aUpdateInfoArray) { NS_ASSERTION(!aAutoIncrement || aObjectDataId != LL_MININT, "Bad objectData id!"); nsCOMPtr stmt; nsresult rv; if (aObjectDataId == LL_MININT) { stmt = aTransaction->GetCachedStatement( "SELECT id " "FROM object_data " "WHERE object_store_id = :osid " "AND key_value = :key_value" ); NS_ENSURE_TRUE(stmt, NS_ERROR_FAILURE); mozStorageStatementScoper scoper(stmt); rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), aObjectStoreId); NS_ENSURE_SUCCESS(rv, rv); NS_ASSERTION(!aObjectStoreKey.IsUnset(), "This shouldn't happen!"); rv = aObjectStoreKey.BindToStatement(stmt, NS_LITERAL_CSTRING("key_value")); NS_ENSURE_SUCCESS(rv, rv); bool hasResult; rv = stmt->ExecuteStep(&hasResult); NS_ENSURE_SUCCESS(rv, rv); if (!hasResult) { NS_ERROR("This is bad, we just added this value! Where'd it go?!"); return NS_ERROR_FAILURE; } aObjectDataId = stmt->AsInt64(0); } NS_ASSERTION(aObjectDataId != LL_MININT, "Bad objectData id!"); NS_NAMED_LITERAL_CSTRING(indexId, "index_id"); NS_NAMED_LITERAL_CSTRING(objectDataId, "object_data_id"); NS_NAMED_LITERAL_CSTRING(objectDataKey, "object_data_key"); NS_NAMED_LITERAL_CSTRING(value, "value"); if (aOverwrite) { stmt = aTransaction->IndexDataDeleteStatement(aAutoIncrement, false); NS_ENSURE_TRUE(stmt, NS_ERROR_FAILURE); mozStorageStatementScoper scoper2(stmt); rv = stmt->BindInt64ByName(objectDataId, aObjectDataId); NS_ENSURE_SUCCESS(rv, rv); rv = stmt->Execute(); NS_ENSURE_SUCCESS(rv, rv); stmt = aTransaction->IndexDataDeleteStatement(aAutoIncrement, true); NS_ENSURE_TRUE(stmt, NS_ERROR_FAILURE); mozStorageStatementScoper scoper3(stmt); rv = stmt->BindInt64ByName(objectDataId, aObjectDataId); NS_ENSURE_SUCCESS(rv, rv); rv = stmt->Execute(); NS_ENSURE_SUCCESS(rv, rv); } PRUint32 infoCount = aUpdateInfoArray.Length(); for (PRUint32 i = 0; i < infoCount; i++) { const IndexUpdateInfo& updateInfo = aUpdateInfoArray[i]; // Insert new values. stmt = aTransaction->IndexDataInsertStatement(aAutoIncrement, updateInfo.indexUnique); NS_ENSURE_TRUE(stmt, NS_ERROR_FAILURE); mozStorageStatementScoper scoper4(stmt); rv = stmt->BindInt64ByName(indexId, updateInfo.indexId); NS_ENSURE_SUCCESS(rv, rv); rv = stmt->BindInt64ByName(objectDataId, aObjectDataId); NS_ENSURE_SUCCESS(rv, rv); if (!aAutoIncrement) { rv = aObjectStoreKey.BindToStatement(stmt, objectDataKey); NS_ENSURE_SUCCESS(rv, rv); } rv = updateInfo.value.BindToStatement(stmt, value); NS_ENSURE_SUCCESS(rv, rv); rv = stmt->Execute(); if (rv == NS_ERROR_STORAGE_CONSTRAINT && updateInfo.indexUnique) { // If we're inserting multiple entries for the same unique index, then // we might have failed to insert due to colliding with another entry for // the same index in which case we should ignore it. for (PRInt32 j = (PRInt32)i - 1; j >= 0 && aUpdateInfoArray[j].indexId == updateInfo.indexId; --j) { if (updateInfo.value == aUpdateInfoArray[j].value) { // We found a key with the same value for the same index. So we // must have had a collision with a value we just inserted. rv = NS_OK; break; } } } if (NS_FAILED(rv)) { return rv; } } return NS_OK; } // static nsresult IDBObjectStore::GetStructuredCloneReadInfoFromStatement( mozIStorageStatement* aStatement, PRUint32 aDataIndex, PRUint32 aFileIdsIndex, FileManager* aFileManager, StructuredCloneReadInfo& aInfo) { #ifdef DEBUG { PRInt32 type; NS_ASSERTION(NS_SUCCEEDED(aStatement->GetTypeOfIndex(aDataIndex, &type)) && type == mozIStorageStatement::VALUE_TYPE_BLOB, "Bad value type!"); } #endif const PRUint8* blobData; PRUint32 blobDataLength; nsresult rv = aStatement->GetSharedBlob(aDataIndex, &blobDataLength, &blobData); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); const char* compressed = reinterpret_cast(blobData); size_t compressedLength = size_t(blobDataLength); size_t uncompressedLength; if (!snappy::GetUncompressedLength(compressed, compressedLength, &uncompressedLength)) { NS_WARNING("Snappy can't determine uncompressed length!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } nsAutoArrayPtr uncompressed(new char[uncompressedLength]); if (!snappy::RawUncompress(compressed, compressedLength, uncompressed.get())) { NS_WARNING("Snappy can't determine uncompressed length!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } JSAutoStructuredCloneBuffer& buffer = aInfo.mCloneBuffer; if (!buffer.copy(reinterpret_cast(uncompressed.get()), uncompressedLength)) { return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } bool isNull; rv = aStatement->GetIsNull(aFileIdsIndex, &isNull); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (isNull) { return NS_OK; } nsString ids; rv = aStatement->GetString(aFileIdsIndex, ids); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); nsAutoTArray array; rv = ConvertFileIdsToArray(ids, array); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); for (PRUint32 i = 0; i < array.Length(); i++) { const PRInt64& id = array.ElementAt(i); nsRefPtr fileInfo = aFileManager->GetFileInfo(id); NS_ASSERTION(fileInfo, "Null file info!"); aInfo.mFileInfos.AppendElement(fileInfo); } return NS_OK; } // static void IDBObjectStore::ClearStructuredCloneBuffer(JSAutoStructuredCloneBuffer& aBuffer) { if (aBuffer.data()) { aBuffer.clear(); } } // static bool IDBObjectStore::DeserializeValue(JSContext* aCx, StructuredCloneReadInfo& aCloneReadInfo, jsval* aValue) { NS_ASSERTION(NS_IsMainThread(), "Should only be deserializing on the main thread!"); NS_ASSERTION(aCx, "A JSContext is required!"); JSAutoStructuredCloneBuffer& buffer = aCloneReadInfo.mCloneBuffer; if (!buffer.data()) { *aValue = JSVAL_VOID; return true; } JSAutoRequest ar(aCx); JSStructuredCloneCallbacks callbacks = { IDBObjectStore::StructuredCloneReadCallback, nsnull, nsnull }; return buffer.read(aCx, aValue, &callbacks, &aCloneReadInfo); } // static bool IDBObjectStore::SerializeValue(JSContext* aCx, StructuredCloneWriteInfo& aCloneWriteInfo, jsval aValue) { NS_ASSERTION(NS_IsMainThread(), "Should only be serializing on the main thread!"); NS_ASSERTION(aCx, "A JSContext is required!"); JSAutoRequest ar(aCx); JSStructuredCloneCallbacks callbacks = { nsnull, StructuredCloneWriteCallback, nsnull }; JSAutoStructuredCloneBuffer& buffer = aCloneWriteInfo.mCloneBuffer; return buffer.write(aCx, aValue, &callbacks, &aCloneWriteInfo); } static inline PRUint32 SwapBytes(PRUint32 u) { #ifdef IS_BIG_ENDIAN return ((u & 0x000000ffU) << 24) | ((u & 0x0000ff00U) << 8) | ((u & 0x00ff0000U) >> 8) | ((u & 0xff000000U) >> 24); #else return u; #endif } static inline jsdouble SwapBytes(PRUint64 u) { #ifdef IS_BIG_ENDIAN return ((u & 0x00000000000000ffLLU) << 56) | ((u & 0x000000000000ff00LLU) << 40) | ((u & 0x0000000000ff0000LLU) << 24) | ((u & 0x00000000ff000000LLU) << 8) | ((u & 0x000000ff00000000LLU) >> 8) | ((u & 0x0000ff0000000000LLU) >> 24) | ((u & 0x00ff000000000000LLU) >> 40) | ((u & 0xff00000000000000LLU) >> 56); #else return jsdouble(u); #endif } static inline bool StructuredCloneReadString(JSStructuredCloneReader* aReader, nsCString& aString) { PRUint32 length; if (!JS_ReadBytes(aReader, &length, sizeof(PRUint32))) { NS_WARNING("Failed to read length!"); return false; } length = SwapBytes(length); if (!EnsureStringLength(aString, length)) { NS_WARNING("Out of memory?"); return false; } char* buffer = aString.BeginWriting(); if (!JS_ReadBytes(aReader, buffer, length)) { NS_WARNING("Failed to read type!"); return false; } return true; } JSObject* IDBObjectStore::StructuredCloneReadCallback(JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag, uint32_t aData, void* aClosure) { if (aTag == SCTAG_DOM_BLOB || aTag == SCTAG_DOM_FILE) { StructuredCloneReadInfo* cloneReadInfo = reinterpret_cast(aClosure); NS_ASSERTION(aData < cloneReadInfo->mFileInfos.Length(), "Bad blob index!"); nsRefPtr fileInfo = cloneReadInfo->mFileInfos[aData]; nsRefPtr fileManager = fileInfo->Manager(); nsCOMPtr directory = fileManager->GetDirectory(); if (!directory) { return nsnull; } nsCOMPtr nativeFile = fileManager->GetFileForId(directory, fileInfo->Id()); if (!nativeFile) { return nsnull; } PRUint64 size; if (!JS_ReadBytes(aReader, &size, sizeof(PRUint64))) { NS_WARNING("Failed to read size!"); return nsnull; } size = SwapBytes(size); nsCString type; if (!StructuredCloneReadString(aReader, type)) { return nsnull; } NS_ConvertUTF8toUTF16 convType(type); if (aTag == SCTAG_DOM_BLOB) { nsCOMPtr blob = new nsDOMFileFile(convType, size, nativeFile, fileInfo); jsval wrappedBlob; nsresult rv = nsContentUtils::WrapNative(aCx, JS_GetGlobalForScopeChain(aCx), blob, &NS_GET_IID(nsIDOMBlob), &wrappedBlob); if (NS_FAILED(rv)) { NS_WARNING("Failed to wrap native!"); return nsnull; } return JSVAL_TO_OBJECT(wrappedBlob); } nsCString name; if (!StructuredCloneReadString(aReader, name)) { return nsnull; } NS_ConvertUTF8toUTF16 convName(name); nsCOMPtr file = new nsDOMFileFile(convName, convType, size, nativeFile, fileInfo); jsval wrappedFile; nsresult rv = nsContentUtils::WrapNative(aCx, JS_GetGlobalForScopeChain(aCx), file, &NS_GET_IID(nsIDOMFile), &wrappedFile); if (NS_FAILED(rv)) { NS_WARNING("Failed to wrap native!"); return nsnull; } return JSVAL_TO_OBJECT(wrappedFile); } const JSStructuredCloneCallbacks* runtimeCallbacks = aCx->runtime->structuredCloneCallbacks; if (runtimeCallbacks) { return runtimeCallbacks->read(aCx, aReader, aTag, aData, nsnull); } return nsnull; } JSBool IDBObjectStore::StructuredCloneWriteCallback(JSContext* aCx, JSStructuredCloneWriter* aWriter, JSObject* aObj, void* aClosure) { StructuredCloneWriteInfo* cloneWriteInfo = reinterpret_cast(aClosure); if (JS_GET_CLASS(aCx, aObj) == &gDummyPropClass) { NS_ASSERTION(cloneWriteInfo->mOffsetToKeyProp == 0, "We should not have been here before!"); cloneWriteInfo->mOffsetToKeyProp = js_GetSCOffset(aWriter); PRUint64 value = 0; return JS_WriteBytes(aWriter, &value, sizeof(value)); } nsCOMPtr wrappedNative; nsContentUtils::XPConnect()-> GetWrappedNativeOfJSObject(aCx, aObj, getter_AddRefs(wrappedNative)); if (wrappedNative) { nsISupports* supports = wrappedNative->Native(); nsCOMPtr blob = do_QueryInterface(supports); if (blob) { nsCOMPtr file = do_QueryInterface(blob); PRUint64 size; if (NS_FAILED(blob->GetSize(&size))) { return false; } size = SwapBytes(size); nsString type; if (NS_FAILED(blob->GetType(type))) { return false; } NS_ConvertUTF16toUTF8 convType(type); PRUint32 convTypeLength = SwapBytes(convType.Length()); if (!JS_WriteUint32Pair(aWriter, file ? SCTAG_DOM_FILE : SCTAG_DOM_BLOB, cloneWriteInfo->mBlobs.Length()) || !JS_WriteBytes(aWriter, &size, sizeof(PRUint64)) || !JS_WriteBytes(aWriter, &convTypeLength, sizeof(PRUint32)) || !JS_WriteBytes(aWriter, convType.get(), convType.Length())) { return false; } if (file) { nsString name; if (NS_FAILED(file->GetName(name))) { return false; } NS_ConvertUTF16toUTF8 convName(name); PRUint32 convNameLength = SwapBytes(convName.Length()); if (!JS_WriteBytes(aWriter, &convNameLength, sizeof(PRUint32)) || !JS_WriteBytes(aWriter, convName.get(), convName.Length())) { return false; } } cloneWriteInfo->mBlobs.AppendElement(blob); return true; } } // try using the runtime callbacks const JSStructuredCloneCallbacks* runtimeCallbacks = aCx->runtime->structuredCloneCallbacks; if (runtimeCallbacks) { return runtimeCallbacks->write(aCx, aWriter, aObj, nsnull); } return false; } nsresult IDBObjectStore::ConvertFileIdsToArray(const nsAString& aFileIds, nsTArray& aResult) { nsCharSeparatedTokenizerTemplate tokenizer(aFileIds, ' '); while (tokenizer.hasMoreTokens()) { nsString token(tokenizer.nextToken()); NS_ASSERTION(!token.IsEmpty(), "Should be a valid id!"); nsresult rv; PRInt32 id = token.ToInteger(&rv); NS_ENSURE_SUCCESS(rv, rv); PRInt64* element = aResult.AppendElement(); *element = id; } return NS_OK; } nsresult IDBObjectStore::ModifyValueForNewKey(StructuredCloneWriteInfo& aCloneWriteInfo, Key& aKey) { NS_ASSERTION(IsAutoIncrement() && aKey.IsInteger(), "Don't call me!"); NS_ASSERTION(!NS_IsMainThread(), "Wrong thread"); // This is a duplicate of the js engine's byte munging here union { jsdouble d; PRUint64 u; } pun; pun.d = SwapBytes(static_cast(aKey.ToInteger())); JSAutoStructuredCloneBuffer& buffer = aCloneWriteInfo.mCloneBuffer; PRUint64 offsetToKeyProp = aCloneWriteInfo.mOffsetToKeyProp; memcpy((char*)buffer.data() + offsetToKeyProp, &pun.u, sizeof(PRUint64)); return NS_OK; } IDBObjectStore::IDBObjectStore() : mId(LL_MININT), mAutoIncrement(false) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); } IDBObjectStore::~IDBObjectStore() { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); } nsresult IDBObjectStore::GetAddInfo(JSContext* aCx, jsval aValue, jsval aKeyVal, StructuredCloneWriteInfo& aCloneWriteInfo, Key& aKey, nsTArray& aUpdateInfoArray) { nsresult rv; // Return DATA_ERR if a key was passed in and this objectStore uses inline // keys. if (!JSVAL_IS_VOID(aKeyVal) && HasKeyPath()) { return NS_ERROR_DOM_INDEXEDDB_DATA_ERR; } JSAutoRequest ar(aCx); if (!HasKeyPath()) { // Out-of-line keys must be passed in. rv = aKey.SetFromJSVal(aCx, aKeyVal); NS_ENSURE_SUCCESS(rv, rv); } else if (!mAutoIncrement) { // Inline keys live on the object. Make sure that the value passed in is an // object. rv = GetKeyFromValue(aCx, aValue, mKeyPath, aKey); NS_ENSURE_SUCCESS(rv, rv); } // Return DATA_ERR if no key was specified this isn't an autoIncrement // objectStore. if (aKey.IsUnset() && !mAutoIncrement) { return NS_ERROR_DOM_INDEXEDDB_DATA_ERR; } // Figure out indexes and the index values to update here. ObjectStoreInfo* info; if (!mTransaction->Database()->Info()->GetObjectStore(mName, &info)) { NS_ERROR("This should never fail!"); } PRUint32 count = info->indexes.Length(); aUpdateInfoArray.SetCapacity(count); // Pretty good estimate for (PRUint32 indexesIndex = 0; indexesIndex < count; indexesIndex++) { const IndexInfo& indexInfo = info->indexes[indexesIndex]; rv = AppendIndexUpdateInfo(indexInfo.id, indexInfo.keyPath, indexInfo.unique, indexInfo.multiEntry, aCx, aValue, aUpdateInfoArray); NS_ENSURE_SUCCESS(rv, rv); } nsString targetObjectPropName; JSObject* targetObject = nsnull; rv = NS_OK; if (mAutoIncrement && HasKeyPath()) { NS_ASSERTION(aKey.IsUnset(), "Shouldn't have gotten the key yet!"); if (JSVAL_IS_PRIMITIVE(aValue)) { return NS_ERROR_DOM_INDEXEDDB_DATA_ERR; } KeyPathTokenizer tokenizer(mKeyPath, '.'); NS_ASSERTION(tokenizer.hasMoreTokens(), "Shouldn't have empty keypath and autoincrement"); JSObject* obj = JSVAL_TO_OBJECT(aValue); while (tokenizer.hasMoreTokens()) { const nsDependentSubstring& token = tokenizer.nextToken(); NS_ASSERTION(!token.IsEmpty(), "Should be a valid keypath"); const jschar* keyPathChars = token.BeginReading(); const size_t keyPathLen = token.Length(); JSBool hasProp; if (!targetObject) { // We're still walking the chain of existing objects JSBool ok = JS_HasUCProperty(aCx, obj, keyPathChars, keyPathLen, &hasProp); NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (hasProp) { // Get if the property exists... jsval intermediate; JSBool ok = JS_GetUCProperty(aCx, obj, keyPathChars, keyPathLen, &intermediate); NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (tokenizer.hasMoreTokens()) { // ...and walk to it if there are more steps... if (JSVAL_IS_PRIMITIVE(intermediate)) { return NS_ERROR_DOM_INDEXEDDB_DATA_ERR; } obj = JSVAL_TO_OBJECT(intermediate); } else { // ...otherwise use it as key aKey.SetFromJSVal(aCx, intermediate); if (aKey.IsUnset()) { return NS_ERROR_DOM_INDEXEDDB_DATA_ERR; } } } else { // If the property doesn't exist, fall into below path of starting // to define properties targetObject = obj; targetObjectPropName = token; } } if (targetObject) { // We have started inserting new objects or are about to just insert // the first one. if (tokenizer.hasMoreTokens()) { // If we're not at the end, we need to add a dummy object to the chain. JSObject* dummy = JS_NewObject(aCx, nsnull, nsnull, nsnull); if (!dummy) { rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; break; } if (!JS_DefineUCProperty(aCx, obj, token.BeginReading(), token.Length(), OBJECT_TO_JSVAL(dummy), nsnull, nsnull, JSPROP_ENUMERATE)) { rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; break; } obj = dummy; } else { JSObject* dummy = JS_NewObject(aCx, &gDummyPropClass, nsnull, nsnull); if (!dummy) { rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; break; } if (!JS_DefineUCProperty(aCx, obj, token.BeginReading(), token.Length(), OBJECT_TO_JSVAL(dummy), nsnull, nsnull, JSPROP_ENUMERATE)) { rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; break; } } } } } aCloneWriteInfo.mOffsetToKeyProp = 0; // We guard on rv being a success because we need to run the property // deletion code below even if we should not be serializing the value if (NS_SUCCEEDED(rv) && !IDBObjectStore::SerializeValue(aCx, aCloneWriteInfo, aValue)) { rv = NS_ERROR_DOM_DATA_CLONE_ERR; } if (targetObject) { // If this fails, we lose, and the web page sees a magical property // appear on the object :-( jsval succeeded; if (!JS_DeleteUCProperty2(aCx, targetObject, targetObjectPropName.get(), targetObjectPropName.Length(), &succeeded)) { return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } NS_ASSERTION(JSVAL_IS_BOOLEAN(succeeded), "Wtf?"); NS_ENSURE_TRUE(JSVAL_TO_BOOLEAN(succeeded), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } return rv; } nsresult IDBObjectStore::AddOrPut(const jsval& aValue, const jsval& aKey, JSContext* aCx, PRUint8 aOptionalArgCount, nsIIDBRequest** _retval, bool aOverwrite) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (!mTransaction->IsOpen()) { return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR; } if (!IsWriteAllowed()) { return NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR; } jsval keyval = (aOptionalArgCount >= 1) ? aKey : JSVAL_VOID; StructuredCloneWriteInfo cloneWriteInfo; Key key; nsTArray updateInfo; nsresult rv = GetAddInfo(aCx, aValue, keyval, cloneWriteInfo, key, updateInfo); if (NS_FAILED(rv)) { return rv; } // Put requires a key, unless this is an autoIncrementing objectStore. if (aOverwrite && !mAutoIncrement && key.IsUnset()) { return NS_ERROR_DOM_INDEXEDDB_DATA_ERR; } nsRefPtr request = GenerateRequest(this); NS_ENSURE_TRUE(request, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); nsRefPtr helper = new AddHelper(mTransaction, request, this, cloneWriteInfo, key, aOverwrite, updateInfo); rv = helper->DispatchToTransactionPool(); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); request.forget(_retval); return NS_OK; } NS_IMPL_CYCLE_COLLECTION_CLASS(IDBObjectStore) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IDBObjectStore) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mTransaction, nsIDOMEventTarget) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOwner) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mScriptContext) for (PRUint32 i = 0; i < tmp->mCreatedIndexes.Length(); i++) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCreatedIndexes[i]"); cb.NoteXPCOMChild(static_cast(tmp->mCreatedIndexes[i].get())); } NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IDBObjectStore) // Don't unlink mTransaction! NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOwner) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mScriptContext) tmp->mCreatedIndexes.Clear(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBObjectStore) NS_INTERFACE_MAP_ENTRY(nsIIDBObjectStore) NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(IDBObjectStore) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(IDBObjectStore) NS_IMPL_CYCLE_COLLECTING_RELEASE(IDBObjectStore) DOMCI_DATA(IDBObjectStore, IDBObjectStore) NS_IMETHODIMP IDBObjectStore::GetName(nsAString& aName) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); aName.Assign(mName); return NS_OK; } NS_IMETHODIMP IDBObjectStore::GetKeyPath(nsAString& aKeyPath) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); aKeyPath.Assign(mKeyPath); return NS_OK; } NS_IMETHODIMP IDBObjectStore::GetTransaction(nsIIDBTransaction** aTransaction) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); nsCOMPtr transaction(mTransaction); transaction.forget(aTransaction); return NS_OK; } NS_IMETHODIMP IDBObjectStore::GetIndexNames(nsIDOMDOMStringList** aIndexNames) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); ObjectStoreInfo* info; if (!mTransaction->Database()->Info()->GetObjectStore(mName, &info)) { NS_ERROR("This should never fail!"); } nsRefPtr list(new nsDOMStringList()); PRUint32 count = info->indexes.Length(); for (PRUint32 index = 0; index < count; index++) { NS_ENSURE_TRUE(list->Add(info->indexes[index].name), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } list.forget(aIndexNames); return NS_OK; } NS_IMETHODIMP IDBObjectStore::Get(const jsval& aKey, JSContext* aCx, nsIIDBRequest** _retval) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (!mTransaction->IsOpen()) { return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR; } nsRefPtr keyRange; nsresult rv = IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange)); NS_ENSURE_SUCCESS(rv, rv); if (!keyRange) { // Must specify a key or keyRange for get(). return NS_ERROR_DOM_INDEXEDDB_DATA_ERR; } nsRefPtr request = GenerateRequest(this); NS_ENSURE_TRUE(request, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); nsRefPtr helper = new GetHelper(mTransaction, request, this, keyRange); rv = helper->DispatchToTransactionPool(); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); request.forget(_retval); return NS_OK; } NS_IMETHODIMP IDBObjectStore::GetAll(const jsval& aKey, PRUint32 aLimit, JSContext* aCx, PRUint8 aOptionalArgCount, nsIIDBRequest** _retval) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (!mTransaction->IsOpen()) { return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR; } nsresult rv; nsRefPtr keyRange; if (aOptionalArgCount) { rv = IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange)); NS_ENSURE_SUCCESS(rv, rv); } if (aOptionalArgCount < 2) { aLimit = PR_UINT32_MAX; } nsRefPtr request = GenerateRequest(this); NS_ENSURE_TRUE(request, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); nsRefPtr helper = new GetAllHelper(mTransaction, request, this, keyRange, aLimit); rv = helper->DispatchToTransactionPool(); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); request.forget(_retval); return NS_OK; } NS_IMETHODIMP IDBObjectStore::Add(const jsval& aValue, const jsval& aKey, JSContext* aCx, PRUint8 aOptionalArgCount, nsIIDBRequest** _retval) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); return AddOrPut(aValue, aKey, aCx, aOptionalArgCount, _retval, false); } NS_IMETHODIMP IDBObjectStore::Put(const jsval& aValue, const jsval& aKey, JSContext* aCx, PRUint8 aOptionalArgCount, nsIIDBRequest** _retval) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); return AddOrPut(aValue, aKey, aCx, aOptionalArgCount, _retval, true); } NS_IMETHODIMP IDBObjectStore::Delete(const jsval& aKey, JSContext* aCx, nsIIDBRequest** _retval) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (!mTransaction->IsOpen()) { return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR; } if (!IsWriteAllowed()) { return NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR; } nsRefPtr keyRange; nsresult rv = IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange)); NS_ENSURE_SUCCESS(rv, rv); if (!keyRange) { // Must specify a key or keyRange for delete(). return NS_ERROR_DOM_INDEXEDDB_DATA_ERR; } nsRefPtr request = GenerateRequest(this); NS_ENSURE_TRUE(request, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); nsRefPtr helper = new DeleteHelper(mTransaction, request, this, keyRange); rv = helper->DispatchToTransactionPool(); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); request.forget(_retval); return NS_OK; } NS_IMETHODIMP IDBObjectStore::Clear(nsIIDBRequest** _retval) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (!mTransaction->IsOpen()) { return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR; } if (!IsWriteAllowed()) { return NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR; } nsRefPtr request = GenerateRequest(this); NS_ENSURE_TRUE(request, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); nsRefPtr helper(new ClearHelper(mTransaction, request, this)); nsresult rv = helper->DispatchToTransactionPool(); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); request.forget(_retval); return NS_OK; } NS_IMETHODIMP IDBObjectStore::OpenCursor(const jsval& aKey, PRUint16 aDirection, JSContext* aCx, PRUint8 aOptionalArgCount, nsIIDBRequest** _retval) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (!mTransaction->IsOpen()) { return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR; } nsresult rv; nsRefPtr keyRange; if (aOptionalArgCount) { rv = IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange)); NS_ENSURE_SUCCESS(rv, rv); if (aOptionalArgCount >= 2) { if (aDirection != nsIIDBCursor::NEXT && aDirection != nsIIDBCursor::NEXT_NO_DUPLICATE && aDirection != nsIIDBCursor::PREV && aDirection != nsIIDBCursor::PREV_NO_DUPLICATE) { return NS_ERROR_DOM_INDEXEDDB_NON_TRANSIENT_ERR; } } else { aDirection = nsIIDBCursor::NEXT; } } nsRefPtr request = GenerateRequest(this); NS_ENSURE_TRUE(request, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); nsRefPtr helper = new OpenCursorHelper(mTransaction, request, this, keyRange, aDirection); rv = helper->DispatchToTransactionPool(); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); request.forget(_retval); return NS_OK; } NS_IMETHODIMP IDBObjectStore::CreateIndex(const nsAString& aName, const nsAString& aKeyPath, const jsval& aOptions, JSContext* aCx, nsIIDBIndex** _retval) { NS_PRECONDITION(NS_IsMainThread(), "Wrong thread!"); if (!IsValidKeyPath(aCx, aKeyPath)) { return NS_ERROR_DOM_SYNTAX_ERR; } IDBTransaction* transaction = AsyncConnectionHelper::GetCurrentTransaction(); if (!transaction || transaction != mTransaction || mTransaction->Mode() != nsIIDBTransaction::VERSION_CHANGE) { return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR; } ObjectStoreInfo* info; if (!mTransaction->Database()->Info()->GetObjectStore(mName, &info)) { NS_ERROR("This should never fail!"); } bool found = false; PRUint32 indexCount = info->indexes.Length(); for (PRUint32 index = 0; index < indexCount; index++) { if (info->indexes[index].name == aName) { found = true; break; } } if (found) { return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR; } NS_ASSERTION(mTransaction->IsOpen(), "Impossible!"); bool unique = false; bool multiEntry = false; // Get optional arguments. if (!JSVAL_IS_VOID(aOptions) && !JSVAL_IS_NULL(aOptions)) { if (JSVAL_IS_PRIMITIVE(aOptions)) { // XXX Update spec for a real code here return NS_ERROR_DOM_TYPE_ERR; } NS_ASSERTION(JSVAL_IS_OBJECT(aOptions), "Huh?!"); JSObject* options = JSVAL_TO_OBJECT(aOptions); jsval val; if (!JS_GetPropertyById(aCx, options, nsDOMClassInfo::sUnique_id, &val)) { NS_WARNING("JS_GetPropertyById failed!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } JSBool boolVal; if (!JS_ValueToBoolean(aCx, val, &boolVal)) { NS_WARNING("JS_ValueToBoolean failed!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } unique = !!boolVal; if (!JS_GetPropertyById(aCx, options, nsDOMClassInfo::sMultiEntry_id, &val)) { NS_WARNING("JS_GetPropertyById failed!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } if (!JS_ValueToBoolean(aCx, val, &boolVal)) { NS_WARNING("JS_ValueToBoolean failed!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } multiEntry = !!boolVal; } DatabaseInfo* databaseInfo = mTransaction->Database()->Info(); IndexInfo* indexInfo = info->indexes.AppendElement(); if (!indexInfo) { NS_WARNING("Out of memory!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } indexInfo->id = databaseInfo->nextIndexId++; indexInfo->name = aName; indexInfo->keyPath = aKeyPath; indexInfo->unique = unique; indexInfo->multiEntry = multiEntry; indexInfo->autoIncrement = mAutoIncrement; // Don't leave this in the list if we fail below! AutoRemoveIndex autoRemove(mTransaction->Database(), mName, aName); #ifdef DEBUG for (PRUint32 index = 0; index < mCreatedIndexes.Length(); index++) { if (mCreatedIndexes[index]->Name() == aName) { NS_ERROR("Already created this one!"); } } #endif nsRefPtr index(IDBIndex::Create(this, indexInfo)); if (!mCreatedIndexes.AppendElement(index)) { NS_WARNING("Out of memory!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } nsRefPtr helper = new CreateIndexHelper(mTransaction, index); nsresult rv = helper->DispatchToTransactionPool(); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); autoRemove.forget(); index.forget(_retval); return NS_OK; } NS_IMETHODIMP IDBObjectStore::Index(const nsAString& aName, nsIIDBIndex** _retval) { NS_PRECONDITION(NS_IsMainThread(), "Wrong thread!"); if (!mTransaction->IsOpen()) { return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR; } ObjectStoreInfo* info; if (!mTransaction->Database()->Info()->GetObjectStore(mName, &info)) { NS_ERROR("This should never fail!"); } IndexInfo* indexInfo = nsnull; PRUint32 indexCount = info->indexes.Length(); for (PRUint32 index = 0; index < indexCount; index++) { if (info->indexes[index].name == aName) { indexInfo = &(info->indexes[index]); break; } } if (!indexInfo) { return NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR; } nsRefPtr retval; for (PRUint32 i = 0; i < mCreatedIndexes.Length(); i++) { nsRefPtr& index = mCreatedIndexes[i]; if (index->Name() == aName) { retval = index; break; } } if (!retval) { retval = IDBIndex::Create(this, indexInfo); NS_ENSURE_TRUE(retval, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (!mCreatedIndexes.AppendElement(retval)) { NS_WARNING("Out of memory!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } } retval.forget(_retval); return NS_OK; } NS_IMETHODIMP IDBObjectStore::DeleteIndex(const nsAString& aName) { NS_PRECONDITION(NS_IsMainThread(), "Wrong thread!"); IDBTransaction* transaction = AsyncConnectionHelper::GetCurrentTransaction(); if (!transaction || transaction != mTransaction || mTransaction->Mode() != nsIIDBTransaction::VERSION_CHANGE) { return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR; } NS_ASSERTION(mTransaction->IsOpen(), "Impossible!"); ObjectStoreInfo* info; if (!mTransaction->Database()->Info()->GetObjectStore(mName, &info)) { NS_ERROR("This should never fail!"); } PRUint32 index = 0; for (; index < info->indexes.Length(); index++) { if (info->indexes[index].name == aName) { break; } } if (index == info->indexes.Length()) { return NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR; } nsRefPtr helper = new DeleteIndexHelper(mTransaction, aName, this); nsresult rv = helper->DispatchToTransactionPool(); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); info->indexes.RemoveElementAt(index); for (PRUint32 i = 0; i < mCreatedIndexes.Length(); i++) { if (mCreatedIndexes[i]->Name() == aName) { mCreatedIndexes.RemoveElementAt(i); break; } } return NS_OK; } NS_IMETHODIMP IDBObjectStore::Count(const jsval& aKey, JSContext* aCx, PRUint8 aOptionalArgCount, nsIIDBRequest** _retval) { if (!mTransaction->IsOpen()) { return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR; } nsresult rv; nsRefPtr keyRange; if (aOptionalArgCount) { rv = IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange)); NS_ENSURE_SUCCESS(rv, rv); } nsRefPtr request = GenerateRequest(this); NS_ENSURE_TRUE(request, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); nsRefPtr helper = new CountHelper(mTransaction, request, this, keyRange); rv = helper->DispatchToTransactionPool(); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); request.forget(_retval); return NS_OK; } inline nsresult CopyData(nsIInputStream* aStream, quota_FILE* aFile) { do { char copyBuffer[FILE_COPY_BUFFER_SIZE]; PRUint32 numRead; nsresult rv = aStream->Read(copyBuffer, FILE_COPY_BUFFER_SIZE, &numRead); NS_ENSURE_SUCCESS(rv, rv); if (numRead <= 0) { break; } size_t numWrite = sqlite3_quota_fwrite(copyBuffer, 1, numRead, aFile); NS_ENSURE_TRUE(numWrite == numRead, NS_ERROR_FAILURE); } while (true); // Flush and sync NS_ENSURE_TRUE(sqlite3_quota_fflush(aFile, 1) == 0, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return NS_OK; } nsresult AddHelper::DoDatabaseWork(mozIStorageConnection* aConnection) { NS_PRECONDITION(aConnection, "Passed a null connection!"); nsresult rv; bool mayOverwrite = mOverwrite; bool unsetKey = mKey.IsUnset(); bool autoIncrement = mObjectStore->IsAutoIncrement(); PRInt64 osid = mObjectStore->Id(); const nsString& keyPath = mObjectStore->KeyPath(); if (unsetKey) { NS_ASSERTION(autoIncrement, "Must have a key for non-autoIncrement!"); // Will need to add first and then set the key later. mayOverwrite = false; } if (autoIncrement && !unsetKey) { mayOverwrite = true; } nsCOMPtr stmt; if (!mOverwrite && !unsetKey) { // Make sure the key doesn't exist already NS_NAMED_LITERAL_CSTRING(id, "id"); NS_NAMED_LITERAL_CSTRING(osidStr, "osid"); nsCString table; nsCString value; if (autoIncrement) { table.AssignLiteral("ai_object_data"); value = id; } else { table.AssignLiteral("object_data"); value.AssignLiteral("key_value"); } nsCString query = NS_LITERAL_CSTRING("SELECT data FROM ") + table + NS_LITERAL_CSTRING(" WHERE ") + value + NS_LITERAL_CSTRING(" = :") + id + NS_LITERAL_CSTRING(" AND object_store_id = :") + osidStr; stmt = mTransaction->GetCachedStatement(query); NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); rv = stmt->BindInt64ByName(osidStr, osid); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = mKey.BindToStatement(stmt, NS_LITERAL_CSTRING("id")); NS_ENSURE_SUCCESS(rv, rv); bool hasResult; rv = stmt->ExecuteStep(&hasResult); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (hasResult) { return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR; } } nsRefPtr fileManager = mDatabase->Manager(); nsCOMPtr directory = fileManager->GetDirectory(); NS_ENSURE_TRUE(directory, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); nsAutoString fileIds; for (PRUint32 index = 0; index < mCloneWriteInfo.mBlobs.Length(); index++) { nsCOMPtr& domBlob = mCloneWriteInfo.mBlobs[index]; PRInt64 id = -1; // Check if it is a blob created from this db or the blob was already // stored in this db nsRefPtr fileInfo = domBlob->GetFileInfo(fileManager); if (fileInfo) { id = fileInfo->Id(); } if (id == -1) { fileInfo = fileManager->GetNewFileInfo(); NS_ENSURE_TRUE(fileInfo, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); id = fileInfo->Id(); mTransaction->OnNewFileInfo(fileInfo); // Copy it nsCOMPtr inputStream; rv = domBlob->GetInternalStream(getter_AddRefs(inputStream)); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); nsCOMPtr nativeFile = fileManager->GetFileForId(directory, id); NS_ENSURE_TRUE(nativeFile, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); nsString nativeFilePath; rv = nativeFile->GetPath(nativeFilePath); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); quota_FILE* file = sqlite3_quota_fopen(NS_ConvertUTF16toUTF8(nativeFilePath).get(), "wb"); NS_ENSURE_TRUE(file, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = CopyData(inputStream, file); NS_ENSURE_TRUE(sqlite3_quota_fclose(file) == 0, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); domBlob->AddFileInfo(fileInfo); } if (index) { fileIds.Append(NS_LITERAL_STRING(" ")); } fileIds.AppendInt(id); } // Now we add it to the database (or update, depending on our variables). stmt = mTransaction->AddStatement(true, mayOverwrite, autoIncrement); NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); NS_NAMED_LITERAL_CSTRING(keyValue, "key_value"); rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), osid); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (!autoIncrement || mayOverwrite) { NS_ASSERTION(!mKey.IsUnset(), "This shouldn't happen!"); rv = mKey.BindToStatement(stmt, keyValue); NS_ENSURE_SUCCESS(rv, rv); } NS_NAMED_LITERAL_CSTRING(data, "data"); // This will hold our compressed data until the end of the method. The // BindBlobByName function will copy it. nsAutoArrayPtr compressed; // This points to the compressed buffer. const PRUint8* dataBuffer = nsnull; size_t dataBufferLength = 0; // If we're going to modify the buffer later to add a key property on an // autoIncrement objectStore then we will wait to compress our data until we // have the appropriate key value. if (autoIncrement && !mOverwrite && !keyPath.IsEmpty() && unsetKey) { rv = stmt->BindInt32ByName(data, 0); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } else { // Compress the bytes before adding into the database. const char* uncompressed = reinterpret_cast(mCloneWriteInfo.mCloneBuffer.data()); size_t uncompressedLength = mCloneWriteInfo.mCloneBuffer.nbytes(); size_t compressedLength = snappy::MaxCompressedLength(uncompressedLength); compressed = new char[compressedLength]; snappy::RawCompress(uncompressed, uncompressedLength, compressed.get(), &compressedLength); dataBuffer = reinterpret_cast(compressed.get()); dataBufferLength = compressedLength; rv = stmt->BindBlobByName(data, dataBuffer, dataBufferLength); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } if (fileIds.IsEmpty()) { rv = stmt->BindNullByName(NS_LITERAL_CSTRING("file_ids")); } else { rv = stmt->BindStringByName(NS_LITERAL_CSTRING("file_ids"), fileIds); } NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = stmt->Execute(); if (NS_FAILED(rv)) { if (mayOverwrite && rv == NS_ERROR_STORAGE_CONSTRAINT) { scoper.Abandon(); rv = stmt->Reset(); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); stmt = mTransaction->AddStatement(false, true, autoIncrement); NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper2(stmt); rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), osid); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); NS_ASSERTION(!mKey.IsUnset(), "This shouldn't happen!"); rv = mKey.BindToStatement(stmt, keyValue); NS_ENSURE_SUCCESS(rv, rv); NS_ASSERTION(dataBuffer && dataBufferLength, "These should be set!"); rv = stmt->BindBlobByName(data, dataBuffer, dataBufferLength); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (fileIds.IsEmpty()) { rv = stmt->BindNullByName(NS_LITERAL_CSTRING("file_ids")); } else { rv = stmt->BindStringByName(NS_LITERAL_CSTRING("file_ids"), fileIds); } NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = stmt->Execute(); } if (NS_FAILED(rv)) { return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR; } } // If we are supposed to generate a key, get the new id. if (autoIncrement && !mayOverwrite) { #ifdef DEBUG PRInt64 oldKey = unsetKey ? 0 : mKey.ToInteger(); #endif PRInt64 newIntKey; rv = aConnection->GetLastInsertRowID(&newIntKey); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = mKey.SetFromInteger(newIntKey); NS_ENSURE_SUCCESS(rv, rv); #ifdef DEBUG NS_ASSERTION(mKey.IsInteger(), "Bad key value!"); if (!unsetKey) { NS_ASSERTION(mKey.ToInteger() == oldKey, "Something went haywire!"); } #endif if (!keyPath.IsEmpty() && unsetKey) { // Special case where someone put an object into an autoIncrement'ing // objectStore with no key in its keyPath set. We needed to figure out // which row id we would get above before we could set that properly. rv = mObjectStore->ModifyValueForNewKey(mCloneWriteInfo, mKey); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); scoper.Abandon(); rv = stmt->Reset(); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); stmt = mTransaction->AddStatement(false, true, true); NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper2(stmt); rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), osid); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = mKey.BindToStatement(stmt, keyValue); NS_ENSURE_SUCCESS(rv, rv); NS_ASSERTION(!dataBuffer && !dataBufferLength, "These should be unset!"); const char* uncompressed = reinterpret_cast(mCloneWriteInfo.mCloneBuffer.data()); size_t uncompressedLength = mCloneWriteInfo.mCloneBuffer.nbytes(); size_t compressedLength = snappy::MaxCompressedLength(uncompressedLength); compressed = new char[compressedLength]; snappy::RawCompress(uncompressed, uncompressedLength, compressed.get(), &compressedLength); dataBuffer = reinterpret_cast(compressed.get()); dataBufferLength = compressedLength; rv = stmt->BindBlobByName(data, dataBuffer, dataBufferLength); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (fileIds.IsEmpty()) { rv = stmt->BindNullByName(NS_LITERAL_CSTRING("file_ids")); } else { rv = stmt->BindStringByName(NS_LITERAL_CSTRING("file_ids"), fileIds); } NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = stmt->Execute(); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } } // Update our indexes if needed. if (mOverwrite || !mIndexUpdateInfo.IsEmpty()) { PRInt64 objectDataId = autoIncrement ? mKey.ToInteger() : LL_MININT; rv = IDBObjectStore::UpdateIndexes(mTransaction, osid, mKey, autoIncrement, mOverwrite, objectDataId, mIndexUpdateInfo); if (rv == NS_ERROR_STORAGE_CONSTRAINT) { return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR; } NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } return NS_OK; } nsresult AddHelper::GetSuccessResult(JSContext* aCx, jsval* aVal) { NS_ASSERTION(!mKey.IsUnset(), "Badness!"); mCloneWriteInfo.mCloneBuffer.clear(); return mKey.ToJSVal(aCx, aVal); } nsresult GetHelper::DoDatabaseWork(mozIStorageConnection* /* aConnection */) { NS_ASSERTION(mKeyRange, "Must have a key range here!"); nsCString table; nsCString value; if (mObjectStore->IsAutoIncrement()) { table.AssignLiteral("ai_object_data"); value.AssignLiteral("id"); } else { table.AssignLiteral("object_data"); value.AssignLiteral("key_value"); } nsCString keyRangeClause; mKeyRange->GetBindingClause(value, keyRangeClause); NS_ASSERTION(!keyRangeClause.IsEmpty(), "Huh?!"); NS_NAMED_LITERAL_CSTRING(osid, "osid"); nsCString query = NS_LITERAL_CSTRING("SELECT data, file_ids FROM ") + table + NS_LITERAL_CSTRING(" WHERE object_store_id = :") + osid + keyRangeClause + NS_LITERAL_CSTRING(" LIMIT 1"); nsCOMPtr stmt = mTransaction->GetCachedStatement(query); NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); nsresult rv = stmt->BindInt64ByName(osid, mObjectStore->Id()); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = mKeyRange->BindToStatement(stmt); NS_ENSURE_SUCCESS(rv, rv); bool hasResult; rv = stmt->ExecuteStep(&hasResult); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (hasResult) { rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(stmt, 0, 1, mDatabase->Manager(), mCloneReadInfo); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } nsresult GetHelper::GetSuccessResult(JSContext* aCx, jsval* aVal) { bool result = IDBObjectStore::DeserializeValue(aCx, mCloneReadInfo, aVal); mCloneReadInfo.mCloneBuffer.clear(); NS_ENSURE_TRUE(result, NS_ERROR_FAILURE); return NS_OK; } nsresult DeleteHelper::DoDatabaseWork(mozIStorageConnection* /*aConnection */) { NS_ASSERTION(mKeyRange, "Must have a key range here!"); nsCString table; nsCString value; if (mObjectStore->IsAutoIncrement()) { table.AssignLiteral("ai_object_data"); value.AssignLiteral("id"); } else { table.AssignLiteral("object_data"); value.AssignLiteral("key_value"); } nsCString keyRangeClause; mKeyRange->GetBindingClause(value, keyRangeClause); NS_ASSERTION(!keyRangeClause.IsEmpty(), "Huh?!"); NS_NAMED_LITERAL_CSTRING(osid, "osid"); nsCString query = NS_LITERAL_CSTRING("DELETE FROM ") + table + NS_LITERAL_CSTRING(" WHERE object_store_id = :") + osid + keyRangeClause; nsCOMPtr stmt = mTransaction->GetCachedStatement(query); NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); nsresult rv = stmt->BindInt64ByName(osid, mObjectStore->Id()); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = mKeyRange->BindToStatement(stmt); NS_ENSURE_SUCCESS(rv, rv); rv = stmt->Execute(); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return NS_OK; } nsresult DeleteHelper::GetSuccessResult(JSContext* aCx, jsval* aVal) { *aVal = JSVAL_VOID; return NS_OK; } nsresult ClearHelper::DoDatabaseWork(mozIStorageConnection* aConnection) { NS_PRECONDITION(aConnection, "Passed a null connection!"); nsCString table; if (mObjectStore->IsAutoIncrement()) { table.AssignLiteral("ai_object_data"); } else { table.AssignLiteral("object_data"); } nsCString query = NS_LITERAL_CSTRING("DELETE FROM ") + table + NS_LITERAL_CSTRING(" WHERE object_store_id = :osid"); nsCOMPtr stmt = mTransaction->GetCachedStatement(query); NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), mObjectStore->Id()); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = stmt->Execute(); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return NS_OK; } nsresult OpenCursorHelper::DoDatabaseWork(mozIStorageConnection* aConnection) { nsCString table; nsCString keyColumn; if (mObjectStore->IsAutoIncrement()) { table.AssignLiteral("ai_object_data"); keyColumn.AssignLiteral("id"); } else { table.AssignLiteral("object_data"); keyColumn.AssignLiteral("key_value"); } NS_NAMED_LITERAL_CSTRING(id, "id"); nsCString keyRangeClause; if (mKeyRange) { mKeyRange->GetBindingClause(keyColumn, keyRangeClause); } nsCAutoString directionClause = NS_LITERAL_CSTRING(" ORDER BY ") + keyColumn; switch (mDirection) { case nsIIDBCursor::NEXT: case nsIIDBCursor::NEXT_NO_DUPLICATE: directionClause += NS_LITERAL_CSTRING(" ASC"); break; case nsIIDBCursor::PREV: case nsIIDBCursor::PREV_NO_DUPLICATE: directionClause += NS_LITERAL_CSTRING(" DESC"); break; default: NS_NOTREACHED("Unknown direction type!"); } nsCString firstQuery = NS_LITERAL_CSTRING("SELECT ") + keyColumn + NS_LITERAL_CSTRING(", data, file_ids FROM ") + table + NS_LITERAL_CSTRING(" WHERE object_store_id = :") + id + keyRangeClause + directionClause + NS_LITERAL_CSTRING(" LIMIT 1"); nsCOMPtr stmt = mTransaction->GetCachedStatement(firstQuery); NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); nsresult rv = stmt->BindInt64ByName(id, mObjectStore->Id()); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (mKeyRange) { rv = mKeyRange->BindToStatement(stmt); NS_ENSURE_SUCCESS(rv, rv); } bool hasResult; rv = stmt->ExecuteStep(&hasResult); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (!hasResult) { mKey.Unset(); return NS_OK; } rv = mKey.SetFromStatement(stmt, 0); NS_ENSURE_SUCCESS(rv, rv); rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(stmt, 1, 2, mDatabase->Manager(), mCloneReadInfo); NS_ENSURE_SUCCESS(rv, rv); // Now we need to make the query to get the next match. keyRangeClause.Truncate(); nsCAutoString continueToKeyRangeClause; NS_NAMED_LITERAL_CSTRING(currentKey, "current_key"); NS_NAMED_LITERAL_CSTRING(rangeKey, "range_key"); switch (mDirection) { case nsIIDBCursor::NEXT: case nsIIDBCursor::NEXT_NO_DUPLICATE: AppendConditionClause(keyColumn, currentKey, false, false, keyRangeClause); AppendConditionClause(keyColumn, currentKey, false, true, continueToKeyRangeClause); if (mKeyRange && !mKeyRange->Upper().IsUnset()) { AppendConditionClause(keyColumn, rangeKey, true, !mKeyRange->IsUpperOpen(), keyRangeClause); AppendConditionClause(keyColumn, rangeKey, true, !mKeyRange->IsUpperOpen(), continueToKeyRangeClause); mRangeKey = mKeyRange->Upper(); } break; case nsIIDBCursor::PREV: case nsIIDBCursor::PREV_NO_DUPLICATE: AppendConditionClause(keyColumn, currentKey, true, false, keyRangeClause); AppendConditionClause(keyColumn, currentKey, true, true, continueToKeyRangeClause); if (mKeyRange && !mKeyRange->Lower().IsUnset()) { AppendConditionClause(keyColumn, rangeKey, false, !mKeyRange->IsLowerOpen(), keyRangeClause); AppendConditionClause(keyColumn, rangeKey, false, !mKeyRange->IsLowerOpen(), continueToKeyRangeClause); mRangeKey = mKeyRange->Lower(); } break; default: NS_NOTREACHED("Unknown direction type!"); } mContinueQuery = NS_LITERAL_CSTRING("SELECT ") + keyColumn + NS_LITERAL_CSTRING(", data, file_ids FROM ") + table + NS_LITERAL_CSTRING(" WHERE object_store_id = :") + id + keyRangeClause + directionClause + NS_LITERAL_CSTRING(" LIMIT "); mContinueToQuery = NS_LITERAL_CSTRING("SELECT ") + keyColumn + NS_LITERAL_CSTRING(", data, file_ids FROM ") + table + NS_LITERAL_CSTRING(" WHERE object_store_id = :") + id + continueToKeyRangeClause + directionClause + NS_LITERAL_CSTRING(" LIMIT "); return NS_OK; } nsresult OpenCursorHelper::GetSuccessResult(JSContext* aCx, jsval* aVal) { if (mKey.IsUnset()) { *aVal = JSVAL_VOID; return NS_OK; } nsRefPtr cursor = IDBCursor::Create(mRequest, mTransaction, mObjectStore, mDirection, mRangeKey, mContinueQuery, mContinueToQuery, mKey, mCloneReadInfo); NS_ENSURE_TRUE(cursor, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); NS_ASSERTION(!mCloneReadInfo.mCloneBuffer.data(), "Should have swapped!"); return WrapNative(aCx, cursor, aVal); } class ThreadLocalJSRuntime { JSRuntime* mRuntime; JSContext* mContext; JSObject* mGlobal; static JSClass sGlobalClass; static const unsigned sRuntimeHeapSize = 256 * 1024; // should be enough for anyone ThreadLocalJSRuntime() : mRuntime(NULL), mContext(NULL), mGlobal(NULL) { MOZ_COUNT_CTOR(ThreadLocalJSRuntime); } nsresult Init() { mRuntime = JS_NewRuntime(sRuntimeHeapSize); NS_ENSURE_TRUE(mRuntime, NS_ERROR_OUT_OF_MEMORY); mContext = JS_NewContext(mRuntime, 0); NS_ENSURE_TRUE(mContext, NS_ERROR_OUT_OF_MEMORY); JSAutoRequest ar(mContext); mGlobal = JS_NewCompartmentAndGlobalObject(mContext, &sGlobalClass, NULL); NS_ENSURE_TRUE(mGlobal, NS_ERROR_OUT_OF_MEMORY); JS_SetGlobalObject(mContext, mGlobal); return NS_OK; } public: static ThreadLocalJSRuntime *Create() { ThreadLocalJSRuntime *entry = new ThreadLocalJSRuntime(); NS_ENSURE_TRUE(entry, nsnull); if (NS_FAILED(entry->Init())) { delete entry; return nsnull; } return entry; } JSContext *Context() const { return mContext; } ~ThreadLocalJSRuntime() { MOZ_COUNT_DTOR(ThreadLocalJSRuntime); if (mContext) { JS_DestroyContext(mContext); } if (mRuntime) { JS_DestroyRuntime(mRuntime); } } }; JSClass ThreadLocalJSRuntime::sGlobalClass = { "IndexedDBTransactionThreadGlobal", JSCLASS_GLOBAL_FLAGS, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub }; CreateIndexHelper::CreateIndexHelper(IDBTransaction* aTransaction, IDBIndex* aIndex) : AsyncConnectionHelper(aTransaction, nsnull), mIndex(aIndex) { if (sTLSIndex == BAD_TLS_INDEX) { PR_NewThreadPrivateIndex(&sTLSIndex, DestroyTLSEntry); } } void CreateIndexHelper::DestroyTLSEntry(void* aPtr) { delete reinterpret_cast(aPtr); } nsresult CreateIndexHelper::DoDatabaseWork(mozIStorageConnection* aConnection) { // Insert the data into the database. nsCOMPtr stmt = mTransaction->GetCachedStatement( "INSERT INTO object_store_index (id, name, key_path, unique_index, " "multientry, object_store_id, object_store_autoincrement) " "VALUES (:id, :name, :key_path, :unique, :multientry, :osid, " ":os_auto_increment)" ); NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), mIndex->Id()); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mIndex->Name()); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key_path"), mIndex->KeyPath()); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("unique"), mIndex->IsUnique() ? 1 : 0); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("multientry"), mIndex->IsMultiEntry() ? 1 : 0); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), mIndex->ObjectStore()->Id()); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("os_auto_increment"), mIndex->IsAutoIncrement() ? 1 : 0); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (NS_FAILED(stmt->Execute())) { return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR; } #ifdef DEBUG { PRInt64 id; aConnection->GetLastInsertRowID(&id); NS_ASSERTION(mIndex->Id() == id, "Bad index id!"); } #endif // Now we need to populate the index with data from the object store. rv = InsertDataFromObjectStore(aConnection); if (NS_FAILED(rv)) { return rv; } return NS_OK; } nsresult CreateIndexHelper::InsertDataFromObjectStore(mozIStorageConnection* aConnection) { nsCAutoString table; nsCAutoString columns; if (mIndex->IsAutoIncrement()) { table.AssignLiteral("ai_object_data"); columns.AssignLiteral("id, data, file_ids"); } else { table.AssignLiteral("object_data"); columns.AssignLiteral("id, data, file_ids, key_value"); } nsCString query = NS_LITERAL_CSTRING("SELECT ") + columns + NS_LITERAL_CSTRING(" FROM ") + table + NS_LITERAL_CSTRING(" WHERE object_store_id = :osid"); nsCOMPtr stmt = mTransaction->GetCachedStatement(query); NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), mIndex->ObjectStore()->Id()); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); NS_ENSURE_TRUE(sTLSIndex != BAD_TLS_INDEX, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); bool hasResult; rv = stmt->ExecuteStep(&hasResult); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (!hasResult) { // Bail early if we have no data to avoid creating the below runtime return NS_OK; } ThreadLocalJSRuntime* tlsEntry = reinterpret_cast(PR_GetThreadPrivate(sTLSIndex)); if (!tlsEntry) { tlsEntry = ThreadLocalJSRuntime::Create(); NS_ENSURE_TRUE(tlsEntry, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); PR_SetThreadPrivate(sTLSIndex, tlsEntry); } JSContext* cx = tlsEntry->Context(); JSAutoRequest ar(cx); do { StructuredCloneReadInfo cloneReadInfo; rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(stmt, 1, 2, mDatabase->Manager(), cloneReadInfo); NS_ENSURE_SUCCESS(rv, rv); JSAutoStructuredCloneBuffer& buffer = cloneReadInfo.mCloneBuffer; JSStructuredCloneCallbacks callbacks = { IDBObjectStore::StructuredCloneReadCallback, nsnull, nsnull }; jsval clone; if (!buffer.read(cx, &clone, &callbacks, &cloneReadInfo)) { NS_WARNING("Failed to deserialize structured clone data!"); return NS_ERROR_DOM_DATA_CLONE_ERR; } nsTArray updateInfo; rv = IDBObjectStore::AppendIndexUpdateInfo(mIndex->Id(), mIndex->KeyPath(), mIndex->IsUnique(), mIndex->IsMultiEntry(), tlsEntry->Context(), clone, updateInfo); NS_ENSURE_SUCCESS(rv, rv); PRInt64 objectDataID = stmt->AsInt64(0); Key key; if (!mIndex->IsAutoIncrement()) { rv = key.SetFromStatement(stmt, 3); NS_ENSURE_SUCCESS(rv, rv); } else { key.SetFromInteger(objectDataID); } rv = IDBObjectStore::UpdateIndexes(mTransaction, mIndex->Id(), key, mIndex->IsAutoIncrement(), false, objectDataID, updateInfo); NS_ENSURE_SUCCESS(rv, rv); } while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&hasResult)) && hasResult); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return NS_OK; } nsresult DeleteIndexHelper::DoDatabaseWork(mozIStorageConnection* aConnection) { NS_PRECONDITION(!NS_IsMainThread(), "Wrong thread!"); nsCOMPtr stmt = mTransaction->GetCachedStatement( "DELETE FROM object_store_index " "WHERE name = :name " ); NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); nsresult rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mName); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (NS_FAILED(stmt->Execute())) { return NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR; } return NS_OK; } nsresult GetAllHelper::DoDatabaseWork(mozIStorageConnection* aConnection) { nsCString table; nsCString keyColumn; if (mObjectStore->IsAutoIncrement()) { table.AssignLiteral("ai_object_data"); keyColumn.AssignLiteral("id"); } else { table.AssignLiteral("object_data"); keyColumn.AssignLiteral("key_value"); } NS_NAMED_LITERAL_CSTRING(osid, "osid"); NS_NAMED_LITERAL_CSTRING(lowerKeyName, "lower_key"); NS_NAMED_LITERAL_CSTRING(upperKeyName, "upper_key"); nsCAutoString keyRangeClause; if (mKeyRange) { if (!mKeyRange->Lower().IsUnset()) { keyRangeClause = NS_LITERAL_CSTRING(" AND ") + keyColumn; if (mKeyRange->IsLowerOpen()) { keyRangeClause.AppendLiteral(" > :"); } else { keyRangeClause.AppendLiteral(" >= :"); } keyRangeClause.Append(lowerKeyName); } if (!mKeyRange->Upper().IsUnset()) { keyRangeClause += NS_LITERAL_CSTRING(" AND ") + keyColumn; if (mKeyRange->IsUpperOpen()) { keyRangeClause.AppendLiteral(" < :"); } else { keyRangeClause.AppendLiteral(" <= :"); } keyRangeClause.Append(upperKeyName); } } nsCAutoString limitClause; if (mLimit != PR_UINT32_MAX) { limitClause.AssignLiteral(" LIMIT "); limitClause.AppendInt(mLimit); } nsCString query = NS_LITERAL_CSTRING("SELECT data, file_ids FROM ") + table + NS_LITERAL_CSTRING(" WHERE object_store_id = :") + osid + keyRangeClause + NS_LITERAL_CSTRING(" ORDER BY ") + keyColumn + NS_LITERAL_CSTRING(" ASC") + limitClause; mCloneReadInfos.SetCapacity(50); nsCOMPtr stmt = mTransaction->GetCachedStatement(query); NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); nsresult rv = stmt->BindInt64ByName(osid, mObjectStore->Id()); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (mKeyRange) { if (!mKeyRange->Lower().IsUnset()) { rv = mKeyRange->Lower().BindToStatement(stmt, lowerKeyName); NS_ENSURE_SUCCESS(rv, rv); } if (!mKeyRange->Upper().IsUnset()) { rv = mKeyRange->Upper().BindToStatement(stmt, upperKeyName); NS_ENSURE_SUCCESS(rv, rv); } } bool hasResult; while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) { if (mCloneReadInfos.Capacity() == mCloneReadInfos.Length()) { if (!mCloneReadInfos.SetCapacity(mCloneReadInfos.Capacity() * 2)) { NS_ERROR("Out of memory!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } } StructuredCloneReadInfo* readInfo = mCloneReadInfos.AppendElement(); NS_ASSERTION(readInfo, "Shouldn't fail if SetCapacity succeeded!"); rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(stmt, 0, 1, mDatabase->Manager(), *readInfo); NS_ENSURE_SUCCESS(rv, rv); } NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return NS_OK; } nsresult GetAllHelper::GetSuccessResult(JSContext* aCx, jsval* aVal) { NS_ASSERTION(mCloneReadInfos.Length() <= mLimit, "Too many results!"); nsresult rv = ConvertCloneReadInfosToArray(aCx, mCloneReadInfos, aVal); for (PRUint32 index = 0; index < mCloneReadInfos.Length(); index++) { mCloneReadInfos[index].mCloneBuffer.clear(); } NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } nsresult CountHelper::DoDatabaseWork(mozIStorageConnection* aConnection) { nsCString table; nsCString keyColumn; if (mObjectStore->IsAutoIncrement()) { table.AssignLiteral("ai_object_data"); keyColumn.AssignLiteral("id"); } else { table.AssignLiteral("object_data"); keyColumn.AssignLiteral("key_value"); } NS_NAMED_LITERAL_CSTRING(osid, "osid"); NS_NAMED_LITERAL_CSTRING(lowerKeyName, "lower_key"); NS_NAMED_LITERAL_CSTRING(upperKeyName, "upper_key"); nsCAutoString keyRangeClause; if (mKeyRange) { if (!mKeyRange->Lower().IsUnset()) { keyRangeClause = NS_LITERAL_CSTRING(" AND ") + keyColumn; if (mKeyRange->IsLowerOpen()) { keyRangeClause.AppendLiteral(" > :"); } else { keyRangeClause.AppendLiteral(" >= :"); } keyRangeClause.Append(lowerKeyName); } if (!mKeyRange->Upper().IsUnset()) { keyRangeClause += NS_LITERAL_CSTRING(" AND ") + keyColumn; if (mKeyRange->IsUpperOpen()) { keyRangeClause.AppendLiteral(" < :"); } else { keyRangeClause.AppendLiteral(" <= :"); } keyRangeClause.Append(upperKeyName); } } nsCString query = NS_LITERAL_CSTRING("SELECT count(*) FROM ") + table + NS_LITERAL_CSTRING(" WHERE object_store_id = :") + osid + keyRangeClause; nsCOMPtr stmt = mTransaction->GetCachedStatement(query); NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); nsresult rv = stmt->BindInt64ByName(osid, mObjectStore->Id()); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (mKeyRange) { if (!mKeyRange->Lower().IsUnset()) { rv = mKeyRange->Lower().BindToStatement(stmt, lowerKeyName); NS_ENSURE_SUCCESS(rv, rv); } if (!mKeyRange->Upper().IsUnset()) { rv = mKeyRange->Upper().BindToStatement(stmt, upperKeyName); NS_ENSURE_SUCCESS(rv, rv); } } bool hasResult; rv = stmt->ExecuteStep(&hasResult); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); NS_ENSURE_TRUE(hasResult, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mCount = stmt->AsInt64(0); return NS_OK; } nsresult CountHelper::GetSuccessResult(JSContext* aCx, jsval* aVal) { return JS_NewNumberValue(aCx, static_cast(mCount), aVal); }