From 627c72a72df9ef9bc8d6d4fb17a018b27118e246 Mon Sep 17 00:00:00 2001 From: Ben Turner Date: Thu, 17 Jun 2010 11:57:22 -0700 Subject: [PATCH] Implement GetAll on objectStores --- dom/indexedDB/IDBEvents.cpp | 95 +++++++++- dom/indexedDB/IDBEvents.h | 20 ++ dom/indexedDB/IDBObjectStoreRequest.cpp | 207 ++++++++++++++++++++- dom/indexedDB/nsIIDBObjectStoreRequest.idl | 2 +- dom/indexedDB/test/Makefile.in | 1 + dom/indexedDB/test/test_getAll.html | 170 +++++++++++++++++ 6 files changed, 490 insertions(+), 5 deletions(-) create mode 100644 dom/indexedDB/test/test_getAll.html diff --git a/dom/indexedDB/IDBEvents.cpp b/dom/indexedDB/IDBEvents.cpp index 583e1438562..ae52fb226fa 100644 --- a/dom/indexedDB/IDBEvents.cpp +++ b/dom/indexedDB/IDBEvents.cpp @@ -42,6 +42,7 @@ #include "nsIIDBDatabaseException.h" #include "nsIPrivateDOMEvent.h" +#include "jscntxt.h" #include "nsContentUtils.h" #include "nsDOMClassInfo.h" #include "nsJSON.h" @@ -379,7 +380,7 @@ GetSuccessEvent::GetResult(nsIVariant** /* aResult */) { // This is the slow path, need to do this better once XPIDL can pass raw // jsvals. - NS_WARNING("Using a slow path for GetObject! Fix this now!"); + NS_WARNING("Using a slow path for GetResult! Fix this now!"); nsIXPConnect* xpc = nsContentUtils::XPConnect(); NS_ENSURE_TRUE(xpc, NS_ERROR_UNEXPECTED); @@ -399,6 +400,9 @@ GetSuccessEvent::GetResult(nsIVariant** /* aResult */) } if (!mJSRuntime) { + nsString jsonValue = mValue; + mValue.Truncate(); + JSContext* cx; rv = cc->GetJSContext(&cx); NS_ENSURE_SUCCESS(rv, rv); @@ -414,7 +418,7 @@ GetSuccessEvent::GetResult(nsIVariant** /* aResult */) mJSRuntime = rt; nsCOMPtr json(new nsJSON()); - rv = json->DecodeToJSVal(mValue, cx, &mCachedValue); + rv = json->DecodeToJSVal(jsonValue, cx, &mCachedValue); if (NS_FAILED(rv)) { mCachedValue = JSVAL_VOID; @@ -427,3 +431,90 @@ GetSuccessEvent::GetResult(nsIVariant** /* aResult */) cc->SetReturnValueWasSet(PR_TRUE); return NS_OK; } + +NS_IMETHODIMP +GetAllSuccessEvent::GetResult(nsIVariant** /* aResult */) +{ + // This is the slow path, need to do this better once XPIDL can pass raw + // jsvals. + NS_WARNING("Using a slow path for GetResult! Fix this now!"); + + nsIXPConnect* xpc = nsContentUtils::XPConnect(); + NS_ENSURE_TRUE(xpc, NS_ERROR_UNEXPECTED); + + nsAXPCNativeCallContext* cc; + nsresult rv = xpc->GetCurrentNativeCallContext(&cc); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(cc, NS_ERROR_UNEXPECTED); + + jsval* retval; + rv = cc->GetRetValPtr(&retval); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mJSRuntime) { + JSContext* cx; + rv = cc->GetJSContext(&cx); + NS_ENSURE_SUCCESS(rv, rv); + + JSAutoRequest ar(cx); + + JSRuntime* rt = JS_GetRuntime(cx); + + JSBool ok = JS_AddNamedRootRT(rt, &mCachedValue, + "GetSuccessEvent::mCachedValue"); + NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); + + mJSRuntime = rt; + + // Swap into a stack array so that we don't hang on to the strings if + // something fails. + nsTArray values; + if (!mValues.SwapElements(values)) { + NS_ERROR("Failed to swap elements!"); + return NS_ERROR_FAILURE; + } + + JSObject* array = JS_NewArrayObject(cx, 0, NULL); + if (!array) { + NS_ERROR("Failed to make array!"); + return NS_ERROR_FAILURE; + } + + mCachedValue = OBJECT_TO_JSVAL(array); + + if (!values.IsEmpty()) { + if (!JS_SetArrayLength(cx, array, jsuint(values.Length()))) { + mCachedValue = JSVAL_VOID; + NS_ERROR("Failed to set array length!"); + return NS_ERROR_FAILURE; + } + + nsCOMPtr json(new nsJSON()); + js::AutoValueRooter value(cx); + + jsint count = jsint(values.Length()); + + for (jsint index = 0; index < count; index++) { + nsString jsonValue = values[index]; + values[index].Truncate(); + + rv = json->DecodeToJSVal(jsonValue, cx, value.addr()); + if (NS_FAILED(rv)) { + mCachedValue = JSVAL_VOID; + NS_ERROR("Failed to decode!"); + return rv; + } + + if (!JS_SetElement(cx, array, index, value.addr())) { + mCachedValue = JSVAL_VOID; + NS_ERROR("Failed to set array element!"); + return NS_ERROR_FAILURE; + } + } + } + } + + *retval = mCachedValue; + cc->SetReturnValueWasSet(PR_TRUE); + return NS_OK; +} diff --git a/dom/indexedDB/IDBEvents.h b/dom/indexedDB/IDBEvents.h index 6fc4442c2c1..3f2d08c9b7a 100644 --- a/dom/indexedDB/IDBEvents.h +++ b/dom/indexedDB/IDBEvents.h @@ -81,6 +81,7 @@ public: protected: IDBEvent() : nsDOMEvent(nsnull, nsnull) { } + virtual ~IDBEvent() { } nsCOMPtr mSource; }; @@ -159,10 +160,29 @@ public: private: nsString mValue; + +protected: jsval mCachedValue; JSRuntime* mJSRuntime; }; +class GetAllSuccessEvent : public GetSuccessEvent +{ +public: + GetAllSuccessEvent(nsTArray& aValues) + : GetSuccessEvent(EmptyString()) + { + if (!mValues.SwapElements(aValues)) { + NS_ERROR("Failed to swap elements!"); + } + } + + NS_IMETHOD GetResult(nsIVariant** aResult); + +private: + nsTArray mValues; +}; + END_INDEXEDDB_NAMESPACE #endif // mozilla_dom_indexeddb_idbevents_h__ diff --git a/dom/indexedDB/IDBObjectStoreRequest.cpp b/dom/indexedDB/IDBObjectStoreRequest.cpp index 80ee8f81e6a..2269f58d38d 100644 --- a/dom/indexedDB/IDBObjectStoreRequest.cpp +++ b/dom/indexedDB/IDBObjectStoreRequest.cpp @@ -244,6 +244,39 @@ private: nsRefPtr mObjectStore; }; +class GetAllHelper : public AsyncConnectionHelper +{ +public: + GetAllHelper(IDBTransactionRequest* aTransaction, + IDBRequest* aRequest, + PRInt64 aObjectStoreID, + const Key& aLeftKey, + const Key& aRightKey, + const PRUint16 aKeyRangeFlags, + const PRUint32 aLimit, + bool aAutoIncrement) + : AsyncConnectionHelper(aTransaction, aRequest), mOSID(aObjectStoreID), + mLeftKey(aLeftKey), mRightKey(aRightKey), mKeyRangeFlags(aKeyRangeFlags), + mLimit(aLimit), mAutoIncrement(aAutoIncrement) + { } + + PRUint16 DoDatabaseWork(mozIStorageConnection* aConnection); + PRUint16 OnSuccess(nsIDOMEventTarget* aTarget); + +protected: + // In-params. + const PRInt64 mOSID; + const Key mLeftKey; + const Key mRightKey; + const PRUint16 mKeyRangeFlags; + const PRUint32 mLimit; + const bool mAutoIncrement; + +private: + // Out-params. + nsTArray mValues; +}; + inline nsresult GetKeyFromObject(JSContext* aCx, @@ -738,8 +771,44 @@ IDBObjectStoreRequest::GetAll(nsIIDBKeyRange* aKeyRange, return NS_ERROR_UNEXPECTED; } - NS_NOTYETIMPLEMENTED("Implement me!"); - return NS_ERROR_NOT_IMPLEMENTED; + if (aOptionalArgCount < 2) { + aLimit = PR_UINT32_MAX; + } + + Key leftKey, rightKey; + PRUint16 keyRangeFlags = 0; + + nsresult rv; + if (aKeyRange) { + rv = aKeyRange->GetFlags(&keyRangeFlags); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr variant; + rv = aKeyRange->GetLeft(getter_AddRefs(variant)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GetKeyFromVariant(variant, leftKey); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aKeyRange->GetRight(getter_AddRefs(variant)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GetKeyFromVariant(variant, rightKey); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsRefPtr request = GenerateRequest(); + NS_ENSURE_TRUE(request, NS_ERROR_FAILURE); + + nsRefPtr helper = + new GetAllHelper(mTransaction, request, mId, leftKey, rightKey, + keyRangeFlags, aLimit, mAutoIncrement); + + rv = helper->DispatchToTransactionPool(); + NS_ENSURE_SUCCESS(rv, rv); + + request.forget(_retval); + return NS_OK; } NS_IMETHODIMP @@ -2000,3 +2069,137 @@ RemoveIndexHelper::GetSuccessResult(nsIWritableVariant* /* aResult */) return OK; } + +PRUint16 +GetAllHelper::DoDatabaseWork(mozIStorageConnection* aConnection) +{ + nsCString table; + nsCString keyColumn; + + if (mAutoIncrement) { + 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(leftKeyName, "left_key"); + NS_NAMED_LITERAL_CSTRING(rightKeyName, "right_key"); + + nsCAutoString keyRangeClause; + if (!mLeftKey.IsUnset()) { + keyRangeClause.AppendLiteral(" AND "); + keyRangeClause.Append(keyColumn); + if (mKeyRangeFlags & nsIIDBKeyRange::LEFT_OPEN) { + keyRangeClause.AppendLiteral(" > :"); + } + else { + NS_ASSERTION(mKeyRangeFlags & nsIIDBKeyRange::LEFT_BOUND, "Bad flags!"); + keyRangeClause.AppendLiteral(" >= :"); + } + keyRangeClause.Append(leftKeyName); + } + + if (!mRightKey.IsUnset()) { + keyRangeClause.AppendLiteral(" AND "); + keyRangeClause.Append(keyColumn); + if (mKeyRangeFlags & nsIIDBKeyRange::RIGHT_OPEN) { + keyRangeClause.AppendLiteral(" < :"); + } + else { + NS_ASSERTION(mKeyRangeFlags & nsIIDBKeyRange::RIGHT_BOUND, "Bad flags!"); + keyRangeClause.AppendLiteral(" <= :"); + } + keyRangeClause.Append(rightKeyName); + } + + nsCAutoString query("SELECT data FROM "); + query.Append(table); + query.AppendLiteral(" WHERE object_store_id = :"); + query.Append(osid); + query.Append(keyRangeClause); + query.AppendLiteral(" ORDER BY "); + query.Append(keyColumn); + query.AppendLiteral(" ASC"); + + if (!mValues.SetCapacity(50)) { + NS_ERROR("Out of memory!"); + return nsIIDBDatabaseException::UNKNOWN_ERR; + } + + nsCOMPtr stmt = mTransaction->GetCachedStatement(query); + NS_ENSURE_TRUE(stmt, nsIIDBDatabaseException::UNKNOWN_ERR); + + mozStorageStatementScoper scoper(stmt); + + nsresult rv = stmt->BindInt64ByName(osid, mOSID); + NS_ENSURE_SUCCESS(rv, nsIIDBDatabaseException::UNKNOWN_ERR); + + if (!mLeftKey.IsUnset()) { + if (mLeftKey.IsString()) { + rv = stmt->BindStringByName(leftKeyName, mLeftKey.StringValue()); + } + else if (mLeftKey.IsInt()) { + rv = stmt->BindInt64ByName(leftKeyName, mLeftKey.IntValue()); + } + else { + NS_NOTREACHED("Bad key!"); + } + NS_ENSURE_SUCCESS(rv, nsIIDBDatabaseException::UNKNOWN_ERR); + } + + if (!mRightKey.IsUnset()) { + if (mRightKey.IsString()) { + rv = stmt->BindStringByName(rightKeyName, mRightKey.StringValue()); + } + else if (mRightKey.IsInt()) { + rv = stmt->BindInt64ByName(rightKeyName, mRightKey.IntValue()); + } + else { + NS_NOTREACHED("Bad key!"); + } + NS_ENSURE_SUCCESS(rv, nsIIDBDatabaseException::UNKNOWN_ERR); + } + + PRUint32 resultCount = 0; + + PRBool hasResult; + while (resultCount++ < mLimit && + NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) { + if (mValues.Capacity() == mValues.Length()) { + if (!mValues.SetCapacity(mValues.Capacity() * 2)) { + NS_ERROR("Out of memory!"); + return nsIIDBDatabaseException::UNKNOWN_ERR; + } + } + + nsString* value = mValues.AppendElement(); + NS_ASSERTION(value, "Shouldn't fail if SetCapacity succeeded!"); + + rv = stmt->GetString(0, *value); + NS_ENSURE_SUCCESS(rv, nsIIDBDatabaseException::UNKNOWN_ERR); + } + NS_ENSURE_SUCCESS(rv, nsIIDBDatabaseException::UNKNOWN_ERR); + + return OK; +} + +PRUint16 +GetAllHelper::OnSuccess(nsIDOMEventTarget* aTarget) +{ + NS_ASSERTION(mValues.Length() <= mLimit, "Too many results!"); + + nsRefPtr event(new GetAllSuccessEvent(mValues)); + + NS_ASSERTION(mValues.IsEmpty(), "Should have swapped!"); + + nsresult rv = event->Init(mRequest, mTransaction); + NS_ENSURE_SUCCESS(rv, nsIIDBDatabaseException::UNKNOWN_ERR); + + PRBool dummy; + aTarget->DispatchEvent(static_cast(event), &dummy); + return OK; +} diff --git a/dom/indexedDB/nsIIDBObjectStoreRequest.idl b/dom/indexedDB/nsIIDBObjectStoreRequest.idl index 39b5e77fddd..3cf5bd26c88 100644 --- a/dom/indexedDB/nsIIDBObjectStoreRequest.idl +++ b/dom/indexedDB/nsIIDBObjectStoreRequest.idl @@ -59,7 +59,7 @@ interface nsIIDBObjectStoreRequest : nsIIDBObjectStore // Success fires IDBTransactionEvent, result == array of values for given keys [optional_argc] nsIIDBRequest - getAll(in nsIIDBKeyRange key, + getAll([optional /* null */] in nsIIDBKeyRange key, [optional /* unlimited */] in unsigned long limit); // Success fires IDBTransactionEvent, result == key diff --git a/dom/indexedDB/test/Makefile.in b/dom/indexedDB/test/Makefile.in index 0eb3a53eb53..0959fa3c753 100644 --- a/dom/indexedDB/test/Makefile.in +++ b/dom/indexedDB/test/Makefile.in @@ -52,6 +52,7 @@ _TEST_FILES = \ test_create_objectStore.html \ test_cursors.html \ test_event_source.html \ + test_getAll.html \ test_global_data.html \ test_indexes.html \ test_indexes_bad_values.html \ diff --git a/dom/indexedDB/test/test_getAll.html b/dom/indexedDB/test/test_getAll.html new file mode 100644 index 00000000000..fdc1cd4ae1e --- /dev/null +++ b/dom/indexedDB/test/test_getAll.html @@ -0,0 +1,170 @@ + + + + Indexed Database Property Test + + + + + + + + + + + + +