/* -*- 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 "IDBCursor.h" #include "nsIVariant.h" #include "jscntxt.h" #include "mozilla/storage.h" #include "nsComponentManagerUtils.h" #include "nsContentUtils.h" #include "nsDOMClassInfo.h" #include "nsEventDispatcher.h" #include "nsJSON.h" #include "nsJSUtils.h" #include "nsThreadUtils.h" #include "AsyncConnectionHelper.h" #include "DatabaseInfo.h" #include "IDBEvents.h" #include "IDBIndex.h" #include "IDBObjectStore.h" #include "IDBTransaction.h" #include "TransactionThreadPool.h" USING_INDEXEDDB_NAMESPACE namespace { class UpdateHelper : public AsyncConnectionHelper { public: UpdateHelper(IDBTransaction* aTransaction, IDBRequest* aRequest, PRInt64 aObjectStoreID, const nsAString& aValue, const Key& aKey, bool aAutoIncrement, nsTArray& aIndexUpdateInfo) : AsyncConnectionHelper(aTransaction, aRequest), mOSID(aObjectStoreID), mValue(aValue), mKey(aKey), mAutoIncrement(aAutoIncrement) { mIndexUpdateInfo.SwapElements(aIndexUpdateInfo); } nsresult DoDatabaseWork(mozIStorageConnection* aConnection); nsresult GetSuccessResult(nsIWritableVariant* aResult); private: // In-params. const PRInt64 mOSID; const nsString mValue; const Key mKey; const bool mAutoIncrement; nsTArray mIndexUpdateInfo; }; class DeleteHelper : public AsyncConnectionHelper { public: DeleteHelper(IDBTransaction* aTransaction, IDBRequest* aRequest, PRInt64 aObjectStoreID, const Key& aKey, bool aAutoIncrement) : AsyncConnectionHelper(aTransaction, aRequest), mOSID(aObjectStoreID), mKey(aKey), mAutoIncrement(aAutoIncrement) { } nsresult DoDatabaseWork(mozIStorageConnection* aConnection); nsresult GetSuccessResult(nsIWritableVariant* aResult); private: // In-params. const PRInt64 mOSID; const nsString mValue; const Key mKey; const bool mAutoIncrement; }; inline already_AddRefed GenerateRequest(IDBCursor* aCursor) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); IDBDatabase* database = aCursor->Transaction()->Database(); return IDBRequest::Create(aCursor, database->ScriptContext(), database->Owner(), aCursor->Transaction()); } } // anonymous namespace BEGIN_INDEXEDDB_NAMESPACE class ContinueHelper : public AsyncConnectionHelper { public: ContinueHelper(IDBCursor* aCursor) : AsyncConnectionHelper(aCursor->mTransaction, aCursor->mRequest), mCursor(aCursor) { } nsresult DoDatabaseWork(mozIStorageConnection* aConnection); nsresult GetSuccessResult(nsIWritableVariant* aResult); void ReleaseMainThreadObjects() { mCursor = nsnull; AsyncConnectionHelper::ReleaseMainThreadObjects(); } protected: virtual nsresult BindArgumentsToStatement(mozIStorageStatement* aStatement) = 0; virtual nsresult GatherResultsFromStatement(mozIStorageStatement* aStatement) = 0; protected: nsRefPtr mCursor; Key mKey; Key mObjectKey; nsString mValue; }; class ContinueObjectStoreHelper : public ContinueHelper { public: ContinueObjectStoreHelper(IDBCursor* aCursor) : ContinueHelper(aCursor) { } private: nsresult BindArgumentsToStatement(mozIStorageStatement* aStatement); nsresult GatherResultsFromStatement(mozIStorageStatement* aStatement); }; class ContinueIndexHelper : public ContinueHelper { public: ContinueIndexHelper(IDBCursor* aCursor) : ContinueHelper(aCursor) { } private: nsresult BindArgumentsToStatement(mozIStorageStatement* aStatement); nsresult GatherResultsFromStatement(mozIStorageStatement* aStatement); }; class ContinueIndexObjectHelper : public ContinueIndexHelper { public: ContinueIndexObjectHelper(IDBCursor* aCursor) : ContinueIndexHelper(aCursor) { } private: nsresult GatherResultsFromStatement(mozIStorageStatement* aStatement); }; END_INDEXEDDB_NAMESPACE // static already_AddRefed IDBCursor::Create(IDBRequest* aRequest, IDBTransaction* aTransaction, IDBObjectStore* aObjectStore, PRUint16 aDirection, const Key& aRangeKey, const nsACString& aContinueQuery, const nsACString& aContinueToQuery, const Key& aKey, const nsAString& aValue) { NS_ASSERTION(aObjectStore, "Null pointer!"); NS_ASSERTION(!aKey.IsUnset() && !aKey.IsNull(), "Bad key!"); nsRefPtr cursor = IDBCursor::CreateCommon(aRequest, aTransaction, aObjectStore, aDirection, aRangeKey, aContinueQuery, aContinueToQuery); NS_ASSERTION(cursor, "This shouldn't fail!"); cursor->mObjectStore = aObjectStore; cursor->mType = OBJECTSTORE; cursor->mKey = aKey; cursor->mValue = aValue; return cursor.forget(); } // static already_AddRefed IDBCursor::Create(IDBRequest* aRequest, IDBTransaction* aTransaction, IDBIndex* aIndex, PRUint16 aDirection, const Key& aRangeKey, const nsACString& aContinueQuery, const nsACString& aContinueToQuery, const Key& aKey, const Key& aObjectKey) { NS_ASSERTION(aIndex, "Null pointer!"); NS_ASSERTION(!aKey.IsUnset() && !aKey.IsNull(), "Bad key!"); NS_ASSERTION(!aObjectKey.IsUnset() && !aObjectKey.IsNull(), "Bad key!"); nsRefPtr cursor = IDBCursor::CreateCommon(aRequest, aTransaction, aIndex->ObjectStore(), aDirection, aRangeKey, aContinueQuery, aContinueToQuery); NS_ASSERTION(cursor, "This shouldn't fail!"); cursor->mIndex = aIndex; cursor->mType = INDEX; cursor->mKey = aKey, cursor->mObjectKey = aObjectKey; return cursor.forget(); } // static already_AddRefed IDBCursor::Create(IDBRequest* aRequest, IDBTransaction* aTransaction, IDBIndex* aIndex, PRUint16 aDirection, const Key& aRangeKey, const nsACString& aContinueQuery, const nsACString& aContinueToQuery, const Key& aKey, const Key& aObjectKey, const nsAString& aValue) { NS_ASSERTION(aIndex, "Null pointer!"); NS_ASSERTION(!aKey.IsUnset() && !aKey.IsNull(), "Bad key!"); nsRefPtr cursor = IDBCursor::CreateCommon(aRequest, aTransaction, aIndex->ObjectStore(), aDirection, aRangeKey, aContinueQuery, aContinueToQuery); NS_ASSERTION(cursor, "This shouldn't fail!"); cursor->mObjectStore = aIndex->ObjectStore(); cursor->mIndex = aIndex; cursor->mType = INDEXOBJECT; cursor->mKey = aKey; cursor->mObjectKey = aObjectKey; cursor->mValue = aValue; return cursor.forget(); } // static already_AddRefed IDBCursor::CreateCommon(IDBRequest* aRequest, IDBTransaction* aTransaction, IDBObjectStore* aObjectStore, PRUint16 aDirection, const Key& aRangeKey, const nsACString& aContinueQuery, const nsACString& aContinueToQuery) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(aRequest, "Null pointer!"); NS_ASSERTION(aTransaction, "Null pointer!"); NS_ASSERTION(aObjectStore, "Null pointer!"); NS_ASSERTION(!aContinueQuery.IsEmpty(), "Empty query!"); NS_ASSERTION(!aContinueToQuery.IsEmpty(), "Empty query!"); nsRefPtr cursor = new IDBCursor(); cursor->mRequest = aRequest; cursor->mTransaction = aTransaction; cursor->mObjectStore = aObjectStore; cursor->mScriptContext = aTransaction->Database()->ScriptContext(); cursor->mOwner = aTransaction->Database()->Owner(); cursor->mDirection = aDirection; cursor->mContinueQuery = aContinueQuery; cursor->mContinueToQuery = aContinueToQuery; cursor->mRangeKey = aRangeKey; return cursor.forget(); } IDBCursor::IDBCursor() : mType(OBJECTSTORE), mDirection(nsIIDBCursor::NEXT), mCachedValue(JSVAL_VOID), mHaveCachedValue(false), mValueRooted(false), mContinueCalled(false) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); } IDBCursor::~IDBCursor() { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (mValueRooted) { NS_DROP_JS_OBJECTS(this, IDBCursor); } } NS_IMPL_CYCLE_COLLECTION_CLASS(IDBCursor) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IDBCursor) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mRequest, nsPIDOMEventTarget) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mTransaction, nsPIDOMEventTarget) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mObjectStore) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mIndex) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOwner) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mScriptContext) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_ROOT_BEGIN(IDBCursor) if (tmp->mValueRooted) { NS_DROP_JS_OBJECTS(tmp, IDBCursor); tmp->mCachedValue = JSVAL_VOID; tmp->mHaveCachedValue = false; tmp->mValueRooted = false; } NS_IMPL_CYCLE_COLLECTION_ROOT_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(IDBCursor) if (JSVAL_IS_GCTHING(tmp->mCachedValue)) { void *gcThing = JSVAL_TO_GCTHING(tmp->mCachedValue); NS_IMPL_CYCLE_COLLECTION_TRACE_JS_CALLBACK(gcThing) } NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IDBCursor) // Don't unlink mObjectStore, mIndex, or mTransaction! NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mRequest) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOwner) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mScriptContext) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBCursor) NS_INTERFACE_MAP_ENTRY(nsIIDBCursor) NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(IDBCursor) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(IDBCursor) NS_IMPL_CYCLE_COLLECTING_RELEASE(IDBCursor) DOMCI_DATA(IDBCursor, IDBCursor) NS_IMETHODIMP IDBCursor::GetDirection(PRUint16* aDirection) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); *aDirection = mDirection; return NS_OK; } NS_IMETHODIMP IDBCursor::GetSource(nsISupports** aSource) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); return mType == OBJECTSTORE ? CallQueryInterface(mObjectStore, aSource) : CallQueryInterface(mIndex, aSource); } NS_IMETHODIMP IDBCursor::GetKey(nsIVariant** aKey) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (!mCachedKey) { nsresult rv; nsCOMPtr variant = do_CreateInstance(NS_VARIANT_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); NS_ASSERTION(!mKey.IsUnset() && !mKey.IsNull(), "Bad key!"); if (mKey.IsString()) { rv = variant->SetAsAString(mKey.StringValue()); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } else if (mKey.IsInt()) { rv = variant->SetAsInt64(mKey.IntValue()); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } else { NS_NOTREACHED("Huh?!"); } rv = variant->SetWritable(PR_FALSE); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); nsIWritableVariant* result; variant.forget(&result); mCachedKey = dont_AddRef(static_cast(result)); } nsCOMPtr result(mCachedKey); result.forget(aKey); return NS_OK; } NS_IMETHODIMP IDBCursor::GetValue(JSContext* aCx, jsval* aValue) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); nsresult rv; if (mType == INDEX) { NS_ASSERTION(!mObjectKey.IsUnset() && !mObjectKey.IsNull(), "Bad key!"); rv = IDBObjectStore::GetJSValFromKey(mObjectKey, aCx, aValue); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return NS_OK; } if (!mHaveCachedValue) { JSAutoRequest ar(aCx); nsCOMPtr json(new nsJSON()); rv = json->DecodeToJSVal(mValue, aCx, &mCachedValue); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_SERIAL_ERR); if (!mValueRooted) { NS_HOLD_JS_OBJECTS(this, IDBCursor); mValueRooted = true; } mHaveCachedValue = true; } *aValue = mCachedValue; return NS_OK; } NS_IMETHODIMP IDBCursor::Continue(const jsval &aKey, JSContext* aCx, PRUint8 aOptionalArgCount) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (!mTransaction->TransactionIsOpen() || mContinueCalled) { return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR; } Key key; nsresult rv = IDBObjectStore::GetKeyFromJSVal(aKey, key); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_NON_TRANSIENT_ERR); if (key.IsNull()) { if (aOptionalArgCount) { return NS_ERROR_DOM_INDEXEDDB_NON_TRANSIENT_ERR; } else { key = Key::UNSETKEY; } } if (!key.IsUnset()) { switch (mDirection) { case nsIIDBCursor::NEXT: case nsIIDBCursor::NEXT_NO_DUPLICATE: if (key <= mKey) { return NS_ERROR_DOM_INDEXEDDB_DATA_ERR; } break; case nsIIDBCursor::PREV: case nsIIDBCursor::PREV_NO_DUPLICATE: if (key >= mKey) { return NS_ERROR_DOM_INDEXEDDB_DATA_ERR; } break; default: NS_NOTREACHED("Unknown direction type!"); } } mContinueToKey = key; #ifdef DEBUG { PRUint16 readyState; if (NS_FAILED(mRequest->GetReadyState(&readyState))) { NS_ERROR("This should never fail!"); } NS_ASSERTION(readyState == nsIIDBRequest::DONE, "Should be DONE!"); } #endif mRequest->Reset(); nsRefPtr helper; switch (mType) { case OBJECTSTORE: helper = new ContinueObjectStoreHelper(this); break; case INDEX: helper = new ContinueIndexHelper(this); break; case INDEXOBJECT: helper = new ContinueIndexObjectHelper(this); break; default: NS_NOTREACHED("Unknown cursor type!"); } rv = helper->DispatchToTransactionPool(); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mContinueCalled = true; return NS_OK; } NS_IMETHODIMP IDBCursor::Update(const jsval& aValue, JSContext* aCx, nsIIDBRequest** _retval) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (!mTransaction->TransactionIsOpen() || !mTransaction->IsWriteAllowed()) { return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR; } if (mType != OBJECTSTORE) { NS_WARNING("Update for non-objectStore cursors is not implemented!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } NS_ASSERTION(mObjectStore, "This cannot be null!"); NS_ASSERTION(!mKey.IsUnset() && !mKey.IsNull(), "Bad key!"); JSAutoRequest ar(aCx); js::AutoValueRooter clone(aCx, aValue); nsresult rv; if (!mObjectStore->KeyPath().IsEmpty()) { // Make sure the object given has the correct keyPath value set on it or // we will add it. const nsString& keyPath = mObjectStore->KeyPath(); const jschar* keyPathChars = reinterpret_cast(keyPath.get()); const size_t keyPathLen = keyPath.Length(); js::AutoValueRooter prop(aCx); JSBool ok = JS_GetUCProperty(aCx, JSVAL_TO_OBJECT(clone.jsval_value()), keyPathChars, keyPathLen, prop.jsval_addr()); NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (JSVAL_IS_VOID(prop.jsval_value())) { rv = IDBObjectStore::GetJSValFromKey(mKey, aCx, prop.jsval_addr()); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); ok = JS_StructuredClone(aCx, clone.jsval_value(), clone.jsval_addr()); NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_SERIAL_ERR); ok = JS_DefineUCProperty(aCx, JSVAL_TO_OBJECT(clone.jsval_value()), keyPathChars, keyPathLen, prop.jsval_value(), nsnull, nsnull, JSPROP_ENUMERATE); NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } else { Key newKey; rv = IDBObjectStore::GetKeyFromJSVal(prop.jsval_value(), newKey); NS_ENSURE_SUCCESS(rv, rv); if (newKey.IsUnset() || newKey.IsNull() || newKey != mKey) { return NS_ERROR_DOM_INDEXEDDB_DATA_ERR; } } } ObjectStoreInfo* info; if (!ObjectStoreInfo::Get(mTransaction->Database()->Id(), mObjectStore->Name(), &info)) { NS_ERROR("This should never fail!"); } nsTArray indexUpdateInfo; rv = IDBObjectStore::GetIndexUpdateInfo(info, aCx, clone.jsval_value(), indexUpdateInfo); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); nsCOMPtr json(new nsJSON()); nsString jsonValue; rv = json->EncodeFromJSVal(clone.jsval_addr(), aCx, jsonValue); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_SERIAL_ERR); nsRefPtr request = GenerateRequest(this); NS_ENSURE_TRUE(request, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); nsRefPtr helper = new UpdateHelper(mTransaction, request, mObjectStore->Id(), jsonValue, mKey, mObjectStore->IsAutoIncrement(), indexUpdateInfo); rv = helper->DispatchToTransactionPool(); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); request.forget(_retval); return NS_OK; } NS_IMETHODIMP IDBCursor::Delete(nsIIDBRequest** _retval) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (!mTransaction->TransactionIsOpen() || !mTransaction->IsWriteAllowed()) { return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR; } if (mType != OBJECTSTORE) { NS_WARNING("Delete for non-objectStore cursors is not implemented!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } NS_ASSERTION(mObjectStore, "This cannot be null!"); NS_ASSERTION(!mKey.IsUnset() && !mKey.IsNull(), "Bad key!"); nsRefPtr request = GenerateRequest(this); NS_ENSURE_TRUE(request, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); nsRefPtr helper = new DeleteHelper(mTransaction, request, mObjectStore->Id(), mKey, mObjectStore->IsAutoIncrement()); nsresult rv = helper->DispatchToTransactionPool(); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); request.forget(_retval); return NS_OK; } nsresult UpdateHelper::DoDatabaseWork(mozIStorageConnection* aConnection) { NS_PRECONDITION(aConnection, "Passed a null connection!"); nsresult rv; NS_ASSERTION(!mKey.IsUnset() && !mKey.IsNull(), "Badness!"); nsCOMPtr stmt = mTransaction->AddStatement(false, true, mAutoIncrement); 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"), mOSID); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (mKey.IsInt()) { rv = stmt->BindInt64ByName(keyValue, mKey.IntValue()); } else if (mKey.IsString()) { rv = stmt->BindStringByName(keyValue, mKey.StringValue()); } else { NS_NOTREACHED("Unknown key type!"); } NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = stmt->BindStringByName(NS_LITERAL_CSTRING("data"), mValue); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (NS_FAILED(stmt->Execute())) { return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR; } // Update our indexes if needed. if (!mIndexUpdateInfo.IsEmpty()) { PRInt64 objectDataId = mAutoIncrement ? mKey.IntValue() : LL_MININT; rv = IDBObjectStore::UpdateIndexes(mTransaction, mOSID, mKey, mAutoIncrement, true, 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 UpdateHelper::GetSuccessResult(nsIWritableVariant* aResult) { NS_ASSERTION(!mKey.IsUnset() && !mKey.IsNull(), "Badness!"); if (mKey.IsString()) { aResult->SetAsAString(mKey.StringValue()); } else if (mKey.IsInt()) { aResult->SetAsInt64(mKey.IntValue()); } else { NS_NOTREACHED("Bad key!"); } return NS_OK; } nsresult DeleteHelper::DoDatabaseWork(mozIStorageConnection* aConnection) { NS_PRECONDITION(aConnection, "Passed a null connection!"); nsCOMPtr stmt = mTransaction->DeleteStatement(mAutoIncrement); NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), mOSID); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); NS_ASSERTION(!mKey.IsUnset() && !mKey.IsNull(), "Must have a key here!"); NS_NAMED_LITERAL_CSTRING(key_value, "key_value"); if (mKey.IsInt()) { rv = stmt->BindInt64ByName(key_value, mKey.IntValue()); } else if (mKey.IsString()) { rv = stmt->BindStringByName(key_value, mKey.StringValue()); } else { NS_NOTREACHED("Unknown key type!"); } NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); // Search for it! rv = stmt->Execute(); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return NS_OK; } nsresult DeleteHelper::GetSuccessResult(nsIWritableVariant* aResult) { NS_ASSERTION(!mKey.IsUnset() && !mKey.IsNull(), "Badness!"); if (mKey.IsString()) { aResult->SetAsAString(mKey.StringValue()); } else if (mKey.IsInt()) { aResult->SetAsInt64(mKey.IntValue()); } else { NS_NOTREACHED("Unknown key type!"); } return NS_OK; } nsresult ContinueHelper::DoDatabaseWork(mozIStorageConnection* aConnection) { // We need to pick a query based on whether or not the cursor's mContinueToKey // is set. If it is unset then othing was passed to continue so we'll grab the // next item in the database that is greater than (less than, if we're running // a PREV cursor) the current key. If it is set then a key was passed to // continue so we'll grab the next item in the database that is greater than // (less than, if we're running a PREV cursor) or equal to the key that was // specified. const nsCString& query = mCursor->mContinueToKey.IsUnset() ? mCursor->mContinueQuery : mCursor->mContinueToQuery; NS_ASSERTION(!query.IsEmpty(), "Bad query!"); nsCOMPtr stmt = mTransaction->GetCachedStatement(query); NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); nsresult rv = BindArgumentsToStatement(stmt); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); PRBool hasResult; rv = stmt->ExecuteStep(&hasResult); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (hasResult) { rv = GatherResultsFromStatement(stmt); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } else { mKey = Key::UNSETKEY; } return NS_OK; } nsresult ContinueHelper::GetSuccessResult(nsIWritableVariant* aResult) { nsresult rv; if (mKey.IsUnset()) { rv = aResult->SetAsEmpty(); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } #ifdef DEBUG NS_ASSERTION(!mKey.IsNull(), "Huh?!"); if (mCursor->mType != IDBCursor::OBJECTSTORE) { NS_ASSERTION(!mObjectKey.IsUnset() && !mObjectKey.IsNull(), "Bad key!"); } #endif // Remove cached stuff from last time. mCursor->mCachedKey = nsnull; mCursor->mCachedObjectKey = nsnull; mCursor->mCachedValue = JSVAL_VOID; mCursor->mHaveCachedValue = false; mCursor->mContinueCalled = false; // And set new values. mCursor->mKey = mKey; mCursor->mObjectKey = mObjectKey; mCursor->mValue = mValue; mCursor->mContinueToKey = Key::UNSETKEY; rv = aResult->SetAsISupports(mCursor); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } nsresult ContinueObjectStoreHelper::BindArgumentsToStatement( mozIStorageStatement* aStatement) { // Bind object store id. nsresult rv = aStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), mCursor->mObjectStore->Id()); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); NS_NAMED_LITERAL_CSTRING(currentKeyName, "current_key"); NS_NAMED_LITERAL_CSTRING(rangeKeyName, "range_key"); // Bind current key. const Key& currentKey = mCursor->mContinueToKey.IsUnset() ? mCursor->mKey : mCursor->mContinueToKey; if (currentKey.IsString()) { rv = aStatement->BindStringByName(currentKeyName, currentKey.StringValue()); } else if (currentKey.IsInt()) { rv = aStatement->BindInt64ByName(currentKeyName, currentKey.IntValue()); } else { NS_NOTREACHED("Bad key!"); } NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); // Bind range key if it is specified. const Key& rangeKey = mCursor->mRangeKey; if (!rangeKey.IsUnset()) { if (rangeKey.IsString()) { rv = aStatement->BindStringByName(rangeKeyName, rangeKey.StringValue()); } else if (rangeKey.IsInt()) { rv = aStatement->BindInt64ByName(rangeKeyName, rangeKey.IntValue()); } else { NS_NOTREACHED("Bad key!"); } NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } return NS_OK; } nsresult ContinueObjectStoreHelper::GatherResultsFromStatement( mozIStorageStatement* aStatement) { // Figure out what kind of key we have next. PRInt32 keyType; nsresult rv = aStatement->GetTypeOfIndex(0, &keyType); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (keyType == mozIStorageStatement::VALUE_TYPE_INTEGER) { mKey = aStatement->AsInt64(0); } else if (keyType == mozIStorageStatement::VALUE_TYPE_TEXT) { rv = aStatement->GetString(0, mKey.ToString()); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } else { NS_NOTREACHED("Bad SQLite type!"); } #ifdef DEBUG { PRInt32 valueType; NS_ASSERTION(NS_SUCCEEDED(aStatement->GetTypeOfIndex(1, &valueType)) && valueType == mozIStorageStatement::VALUE_TYPE_TEXT, "Bad value type!"); } #endif rv = aStatement->GetString(1, mValue); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return NS_OK; } nsresult ContinueIndexHelper::BindArgumentsToStatement(mozIStorageStatement* aStatement) { // Bind index id. nsresult rv = aStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), mCursor->mIndex->Id()); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); NS_NAMED_LITERAL_CSTRING(currentKeyName, "current_key"); // Bind current key. const Key& currentKey = mCursor->mContinueToKey.IsUnset() ? mCursor->mKey : mCursor->mContinueToKey; if (currentKey.IsString()) { rv = aStatement->BindStringByName(currentKeyName, currentKey.StringValue()); } else if (currentKey.IsInt()) { rv = aStatement->BindInt64ByName(currentKeyName, currentKey.IntValue()); } else { NS_NOTREACHED("Bad key!"); } NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); // Bind range key if it is specified. if (!mCursor->mRangeKey.IsUnset()) { NS_NAMED_LITERAL_CSTRING(rangeKeyName, "range_key"); if (mCursor->mRangeKey.IsString()) { rv = aStatement->BindStringByName(rangeKeyName, mCursor->mRangeKey.StringValue()); } else if (mCursor->mRangeKey.IsInt()) { rv = aStatement->BindInt64ByName(rangeKeyName, mCursor->mRangeKey.IntValue()); } else { NS_NOTREACHED("Bad key!"); } NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } // Bind object key if duplicates are allowed and we're not continuing to a // specific key. if ((mCursor->mDirection == nsIIDBCursor::NEXT || mCursor->mDirection == nsIIDBCursor::PREV) && mCursor->mContinueToKey.IsUnset()) { NS_ASSERTION(!mCursor->mObjectKey.IsUnset() && !mCursor->mObjectKey.IsNull(), "Bad key!"); NS_NAMED_LITERAL_CSTRING(objectKeyName, "object_key"); if (mCursor->mObjectKey.IsString()) { rv = aStatement->BindStringByName(objectKeyName, mCursor->mObjectKey.StringValue()); } else if (mCursor->mObjectKey.IsInt()) { rv = aStatement->BindInt64ByName(objectKeyName, mCursor->mObjectKey.IntValue()); } else { NS_NOTREACHED("Bad key!"); } NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } return NS_OK; } nsresult ContinueIndexHelper::GatherResultsFromStatement( mozIStorageStatement* aStatement) { PRInt32 keyType; nsresult rv = aStatement->GetTypeOfIndex(0, &keyType); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); NS_ASSERTION(keyType == mozIStorageStatement::VALUE_TYPE_INTEGER || keyType == mozIStorageStatement::VALUE_TYPE_TEXT, "Bad key type!"); if (keyType == mozIStorageStatement::VALUE_TYPE_INTEGER) { mKey = aStatement->AsInt64(0); } else if (keyType == mozIStorageStatement::VALUE_TYPE_TEXT) { rv = aStatement->GetString(0, mKey.ToString()); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } else { NS_NOTREACHED("Bad SQLite type!"); } rv = aStatement->GetTypeOfIndex(1, &keyType); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); NS_ASSERTION(keyType == mozIStorageStatement::VALUE_TYPE_INTEGER || keyType == mozIStorageStatement::VALUE_TYPE_TEXT, "Bad key type!"); if (keyType == mozIStorageStatement::VALUE_TYPE_INTEGER) { mObjectKey = aStatement->AsInt64(1); } else if (keyType == mozIStorageStatement::VALUE_TYPE_TEXT) { rv = aStatement->GetString(1, mObjectKey.ToString()); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } else { NS_NOTREACHED("Bad SQLite type!"); } return NS_OK; } nsresult ContinueIndexObjectHelper::GatherResultsFromStatement( mozIStorageStatement* aStatement) { PRInt32 keyType; nsresult rv = aStatement->GetTypeOfIndex(0, &keyType); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); NS_ASSERTION(keyType == mozIStorageStatement::VALUE_TYPE_INTEGER || keyType == mozIStorageStatement::VALUE_TYPE_TEXT, "Bad key type!"); if (keyType == mozIStorageStatement::VALUE_TYPE_INTEGER) { mKey = aStatement->AsInt64(0); } else if (keyType == mozIStorageStatement::VALUE_TYPE_TEXT) { rv = aStatement->GetString(0, mKey.ToString()); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } else { NS_NOTREACHED("Bad SQLite type!"); } rv = aStatement->GetTypeOfIndex(1, &keyType); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); NS_ASSERTION(keyType == mozIStorageStatement::VALUE_TYPE_INTEGER || keyType == mozIStorageStatement::VALUE_TYPE_TEXT, "Bad key type!"); if (keyType == mozIStorageStatement::VALUE_TYPE_INTEGER) { mObjectKey = aStatement->AsInt64(1); } else if (keyType == mozIStorageStatement::VALUE_TYPE_TEXT) { rv = aStatement->GetString(1, mObjectKey.ToString()); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } else { NS_NOTREACHED("Bad SQLite type!"); } #ifdef DEBUG { PRInt32 valueType; NS_ASSERTION(NS_SUCCEEDED(aStatement->GetTypeOfIndex(2, &valueType)) && valueType == mozIStorageStatement::VALUE_TYPE_TEXT, "Bad value type!"); } #endif rv = aStatement->GetString(2, mValue); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return NS_OK; }