Bug 692629 - 'IndexedDB: Support IDBObjectStore/IDBIndex.count'. r=sicking.
authorBen Turner <bent.mozilla@gmail.com>
Thu, 03 Nov 2011 08:57:42 -0700
changeset 79689 776dc60cee13872d318d2aaf34b4cb2e78114432
parent 79688 ad1f423c7755a2da364024f9b4665bf54e43f66c
child 79690 f8bbd27f41ce74c8183b26b27e42a58058383efa
push id21419
push userbmo@edmorley.co.uk
push dateThu, 03 Nov 2011 19:51:58 +0000
treeherdermozilla-central@f8d66a792ddc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssicking
bugs692629
milestone10.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 692629 - 'IndexedDB: Support IDBObjectStore/IDBIndex.count'. r=sicking.
dom/indexedDB/IDBIndex.cpp
dom/indexedDB/IDBObjectStore.cpp
dom/indexedDB/nsIIDBIndex.idl
dom/indexedDB/nsIIDBObjectStore.idl
dom/indexedDB/test/Makefile.in
dom/indexedDB/test/test_count.html
--- a/dom/indexedDB/IDBIndex.cpp
+++ b/dom/indexedDB/IDBIndex.cpp
@@ -237,16 +237,44 @@ private:
   Key mKey;
   Key mObjectKey;
   JSAutoStructuredCloneBuffer mCloneBuffer;
   nsCString mContinueQuery;
   nsCString mContinueToQuery;
   Key mRangeKey;
 };
 
+class CountHelper : public AsyncConnectionHelper
+{
+public:
+  CountHelper(IDBTransaction* aTransaction,
+              IDBRequest* aRequest,
+              IDBIndex* aIndex,
+              IDBKeyRange* aKeyRange)
+  : AsyncConnectionHelper(aTransaction, aRequest), mIndex(aIndex),
+    mKeyRange(aKeyRange), mCount(0)
+  { }
+
+  nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
+  nsresult GetSuccessResult(JSContext* aCx,
+                            jsval* aVal);
+
+  void ReleaseMainThreadObjects()
+  {
+    mIndex = nsnull;
+    mKeyRange = nsnull;
+    AsyncConnectionHelper::ReleaseMainThreadObjects();
+  }
+
+private:
+  nsRefPtr<IDBIndex> mIndex;
+  nsRefPtr<IDBKeyRange> mKeyRange;
+  PRUint64 mCount;
+};
+
 inline
 already_AddRefed<IDBRequest>
 GenerateRequest(IDBIndex* aIndex)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   IDBTransaction* transaction = aIndex->ObjectStore()->Transaction();
   IDBDatabase* database = transaction->Database();
   return IDBRequest::Create(aIndex, database->ScriptContext(),
@@ -588,31 +616,46 @@ IDBIndex::OpenKeyCursor(const jsval& aKe
 
   rv = helper->DispatchToTransactionPool();
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   request.forget(_retval);
   return NS_OK;
 }
 
-/*
 NS_IMETHODIMP
 IDBIndex::Count(const jsval& aKey,
                 JSContext* aCx,
                 PRUint8 aOptionalArgCount,
                 nsIIDBRequest** _retval)
 {
   IDBTransaction* transaction = mObjectStore->Transaction();
   if (!transaction->IsOpen()) {
     return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR;
   }
 
-  return NS_ERROR_NOT_IMPLEMENTED;
+  nsresult rv;
+
+  nsRefPtr<IDBKeyRange> keyRange;
+  if (aOptionalArgCount) {
+    rv = IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  nsRefPtr<IDBRequest> request = GenerateRequest(this);
+  NS_ENSURE_TRUE(request, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  nsRefPtr<CountHelper> helper =
+    new CountHelper(transaction, request, this, keyRange);
+  rv = helper->DispatchToTransactionPool();
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  request.forget(_retval);
+  return NS_OK;
 }
-*/
 
 nsresult
 GetKeyHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
 {
   NS_ASSERTION(aConnection, "Passed a null connection!");
 
   nsCOMPtr<mozIStorageStatement> stmt =
     mTransaction->IndexGetStatement(mIndex->IsUnique(),
@@ -1366,8 +1409,87 @@ OpenCursorHelper::GetSuccessResult(JSCon
                       mContinueQuery, mContinueToQuery, mKey, mObjectKey,
                       mCloneBuffer);
   NS_ENSURE_TRUE(cursor, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   NS_ASSERTION(!mCloneBuffer.data(), "Should have swapped!");
 
   return WrapNative(aCx, cursor, aVal);
 }
+
+nsresult
+CountHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
+{
+  nsCString table;
+
+  if (mIndex->IsAutoIncrement()) {
+    if (mIndex->IsUnique()) {
+      table.AssignLiteral("ai_unique_index_data");
+    }
+    else {
+      table.AssignLiteral("ai_index_data");
+    }
+  }
+  else {
+    if (mIndex->IsUnique()) {
+      table.AssignLiteral("unique_index_data");
+    }
+    else {
+      table.AssignLiteral("index_data");
+    }
+  }
+
+  NS_NAMED_LITERAL_CSTRING(lowerKeyName, "lower_key");
+  NS_NAMED_LITERAL_CSTRING(upperKeyName, "upper_key");
+  NS_NAMED_LITERAL_CSTRING(value, "value");
+
+  nsCAutoString keyRangeClause;
+  if (mKeyRange) {
+    if (!mKeyRange->Lower().IsUnset()) {
+      AppendConditionClause(value, lowerKeyName, false,
+                            !mKeyRange->IsLowerOpen(), keyRangeClause);
+    }
+    if (!mKeyRange->Upper().IsUnset()) {
+      AppendConditionClause(value, upperKeyName, true,
+                            !mKeyRange->IsUpperOpen(), keyRangeClause);
+    }
+  }
+
+  NS_NAMED_LITERAL_CSTRING(id, "id");
+
+  nsCString query = NS_LITERAL_CSTRING("SELECT count(*) FROM ") + table +
+                    NS_LITERAL_CSTRING(" WHERE index_id = :") + id +
+                    keyRangeClause;
+
+  nsCOMPtr<mozIStorageStatement> stmt = mTransaction->GetCachedStatement(query);
+  NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  mozStorageStatementScoper scoper(stmt);
+
+  nsresult rv = stmt->BindInt64ByName(id, mIndex->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<jsdouble>(mCount), aVal);
+}
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -337,16 +337,46 @@ protected:
   nsRefPtr<IDBKeyRange> mKeyRange;
   const PRUint32 mLimit;
 
 private:
   // Out-params.
   nsTArray<JSAutoStructuredCloneBuffer> mCloneBuffers;
 };
 
+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<IDBObjectStore> mObjectStore;
+  nsRefPtr<IDBKeyRange> mKeyRange;
+
+private:
+  PRUint64 mCount;
+};
+
 NS_STACK_CLASS
 class AutoRemoveIndex
 {
 public:
   AutoRemoveIndex(nsIAtom* aDatabaseId,
                   const nsAString& aObjectStoreName,
                   const nsAString& aIndexName)
   : mDatabaseId(aDatabaseId), mObjectStoreName(aObjectStoreName),
@@ -1455,30 +1485,45 @@ IDBObjectStore::DeleteIndex(const nsAStr
 
   nsresult rv = helper->DispatchToTransactionPool();
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   info->indexes.RemoveElementAt(index);
   return NS_OK;
 }
 
-/*
 NS_IMETHODIMP
-IDBObjectStore::Count(jsval aKey,
+IDBObjectStore::Count(const jsval& aKey,
                       JSContext* aCx,
                       PRUint8 aOptionalArgCount,
                       nsIIDBRequest** _retval)
 {
   if (!mTransaction->IsOpen()) {
     return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR;
   }
 
-  return NS_ERROR_NOT_IMPLEMENTED;
+  nsresult rv;
+
+  nsRefPtr<IDBKeyRange> keyRange;
+  if (aOptionalArgCount) {
+    rv = IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  nsRefPtr<IDBRequest> request = GenerateRequest(this);
+  NS_ENSURE_TRUE(request, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  nsRefPtr<CountHelper> 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;
 }
-*/
 
 nsresult
 AddHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
 {
   NS_PRECONDITION(aConnection, "Passed a null connection!");
 
   nsresult rv;
   bool mayOverwrite = mOverwrite;
@@ -2306,8 +2351,91 @@ GetAllHelper::GetSuccessResult(JSContext
 
   for (PRUint32 index = 0; index < mCloneBuffers.Length(); index++) {
     mCloneBuffers[index].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<mozIStorageStatement> 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<jsdouble>(mCount), aVal);
+}
--- a/dom/indexedDB/nsIIDBIndex.idl
+++ b/dom/indexedDB/nsIIDBIndex.idl
@@ -83,15 +83,13 @@ interface nsIIDBIndex : nsISupports
   openCursor([optional /* null */] in jsval key,
              [optional /* nsIIDBCursor::NEXT */] in unsigned short direction);
 
   [implicit_jscontext, optional_argc]
   nsIIDBRequest
   openKeyCursor([optional /* null */] in jsval key,
                 [optional /* nsIIDBCursor::NEXT */] in unsigned short direction);
 
-  /*
   // Accepts null, a key value, or a nsIIDBKeyRange object.
   [implicit_jscontext, optional_argc]
   nsIIDBRequest
   count([optional] in jsval key);
-  */
 };
--- a/dom/indexedDB/nsIIDBObjectStore.idl
+++ b/dom/indexedDB/nsIIDBObjectStore.idl
@@ -114,15 +114,13 @@ interface nsIIDBObjectStore : nsISupport
 
   // Returns object immediately
   nsIIDBIndex
   index(in AString name);
 
   void
   deleteIndex(in AString name);
 
-  /*
   // Accepts null, a key value, or a nsIIDBKeyRange object.
   [implicit_jscontext, optional_argc]
   nsIIDBRequest
   count([optional] in jsval key);
-  */
 };
--- a/dom/indexedDB/test/Makefile.in
+++ b/dom/indexedDB/test/Makefile.in
@@ -52,16 +52,17 @@ TEST_FILES = \
   exceptions_in_success_events_iframe.html \
   helpers.js \
   leaving_page_iframe.html \
   test_add_twice_failure.html \
   test_autoIncrement_indexes.html \
   test_bad_keypath.html \
   test_bfcache.html \
   test_clear.html \
+  test_count.html \
   test_create_index.html \
   test_create_index_with_integer_keys.html \
   test_create_objectStore.html \
   test_cursors.html \
   test_cursor_mutation.html \
   test_cursor_update_updates_indexes.html \
   test_error_events_abort_transactions.html \
   test_event_propagation.html \
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_count.html
@@ -0,0 +1,366 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database Property Test</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7">
+    function testSteps()
+    {
+      const name = window.location.pathname;
+      const objectStoreName = "People";
+
+      const objectStoreData = [
+        { key: "237-23-7732", value: { name: "Bob", height: 60, weight: 120 } },
+        { key: "237-23-7733", value: { name: "Ann", height: 52, weight: 110 } },
+        { key: "237-23-7734", value: { name: "Ron", height: 73, weight: 180 } },
+        { key: "237-23-7735", value: { name: "Sue", height: 58, weight: 130 } },
+        { key: "237-23-7736", value: { name: "Joe", height: 65, weight: 150 } },
+        { key: "237-23-7737", value: { name: "Pat", height: 65 } },
+        { key: "237-23-7738", value: { name: "Mel", height: 66, weight: {} } },
+        { key: "237-23-7739", value: { name: "Tom", height: 62, weight: 130 } }
+      ];
+
+      const indexData = {
+        name: "weight",
+        keyPath: "weight",
+        options: { unique: false }
+      };
+
+      const weightSort = [1, 0, 3, 7, 4, 2];
+
+      let request = mozIndexedDB.open(name, 1);
+      request.onerror = errorHandler;
+      request.onupgradeneeded = grabEventAndContinueHandler;
+      request.onsuccess = grabEventAndContinueHandler;
+      let event = yield;
+
+      is(event.type, "upgradeneeded", "Got correct event type");
+
+      let db = event.target.result;
+      db.onerror = errorHandler;
+
+      let objectStore = db.createObjectStore(objectStoreName, { });
+      objectStore.createIndex(indexData.name, indexData.keyPath,
+                              indexData.options);
+
+      for each (let data in objectStoreData) {
+        objectStore.add(data.value, data.key);
+      }
+
+      event = yield;
+
+      is(event.type, "success", "Got correct event type");
+
+      objectStore = db.transaction(db.objectStoreNames)
+                      .objectStore(objectStoreName);
+
+      objectStore.count().onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, objectStoreData.length,
+         "Correct number of object store entries for all keys");
+
+      objectStore.count(null).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, objectStoreData.length,
+         "Correct number of object store entries for null key");
+
+      objectStore.count(objectStoreData[2].key).onsuccess =
+        grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, 1,
+         "Correct number of object store entries for single existing key");
+
+      objectStore.count("foo").onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, 0,
+         "Correct number of object store entries for single non-existing key");
+
+      let keyRange = IDBKeyRange.only(objectStoreData[2].key);
+      objectStore.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, 1,
+         "Correct number of object store entries for existing only keyRange");
+
+      keyRange = IDBKeyRange.only("foo");
+      objectStore.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, 0,
+         "Correct number of object store entries for non-existing only keyRange");
+
+      keyRange = IDBKeyRange.lowerBound(objectStoreData[2].key);
+      objectStore.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, objectStoreData.length - 2,
+         "Correct number of object store entries for lowerBound keyRange");
+
+      keyRange = IDBKeyRange.lowerBound(objectStoreData[2].key, true);
+      objectStore.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, objectStoreData.length - 3,
+         "Correct number of object store entries for lowerBound keyRange");
+
+      keyRange = IDBKeyRange.lowerBound("foo");
+      objectStore.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, 0,
+         "Correct number of object store entries for lowerBound keyRange");
+
+      keyRange = IDBKeyRange.upperBound(objectStoreData[2].key, false);
+      objectStore.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, 3,
+         "Correct number of object store entries for upperBound keyRange");
+
+      keyRange = IDBKeyRange.upperBound(objectStoreData[2].key, true);
+      objectStore.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, 2,
+         "Correct number of object store entries for upperBound keyRange");
+
+      keyRange = IDBKeyRange.upperBound("foo", true);
+      objectStore.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, objectStoreData.length,
+         "Correct number of object store entries for upperBound keyRange");
+
+      keyRange = IDBKeyRange.bound(objectStoreData[0].key,
+                                   objectStoreData[objectStoreData.length - 1].key);
+      objectStore.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, objectStoreData.length,
+         "Correct number of object store entries for bound keyRange");
+
+      keyRange = IDBKeyRange.bound(objectStoreData[0].key,
+                                   objectStoreData[objectStoreData.length - 1].key,
+                                   true);
+      objectStore.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, objectStoreData.length - 1,
+         "Correct number of object store entries for bound keyRange");
+
+      keyRange = IDBKeyRange.bound(objectStoreData[0].key,
+                                   objectStoreData[objectStoreData.length - 1].key,
+                                   true, true);
+      objectStore.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, objectStoreData.length - 2,
+         "Correct number of object store entries for bound keyRange");
+
+      keyRange = IDBKeyRange.bound("foo", "foopy", true, true);
+      objectStore.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, 0,
+         "Correct number of object store entries for bound keyRange");
+
+      keyRange = IDBKeyRange.bound(objectStoreData[0].key, "foo", true, true);
+      objectStore.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, objectStoreData.length - 1,
+         "Correct number of object store entries for bound keyRange");
+
+      let index = objectStore.index(indexData.name);
+
+      index.count().onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, weightSort.length,
+         "Correct number of index entries for no key");
+
+      index.count(objectStoreData[7].value.weight).onsuccess =
+        grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, 2,
+         "Correct number of index entries for duplicate key");
+
+      index.count(objectStoreData[0].value.weight).onsuccess =
+        grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, 1,
+         "Correct number of index entries for single key");
+
+      keyRange = IDBKeyRange.only(objectStoreData[0].value.weight);
+      index.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, 1,
+         "Correct number of index entries for only existing keyRange");
+
+      keyRange = IDBKeyRange.only("foo");
+      index.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, 0,
+         "Correct number of index entries for only non-existing keyRange");
+
+      keyRange = IDBKeyRange.only(objectStoreData[7].value.weight);
+      index.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, 2,
+         "Correct number of index entries for only duplicate keyRange");
+
+      keyRange = IDBKeyRange.lowerBound(objectStoreData[weightSort[0]].value.weight);
+      index.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, weightSort.length,
+         "Correct number of index entries for lowerBound keyRange");
+
+      keyRange = IDBKeyRange.lowerBound(objectStoreData[weightSort[1]].value.weight);
+      index.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, weightSort.length - 1,
+         "Correct number of index entries for lowerBound keyRange");
+
+      keyRange = IDBKeyRange.lowerBound(objectStoreData[weightSort[0]].value.weight - 1);
+      index.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, weightSort.length,
+         "Correct number of index entries for lowerBound keyRange");
+
+      keyRange = IDBKeyRange.lowerBound(objectStoreData[weightSort[0]].value.weight,
+                                        true);
+      index.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, weightSort.length - 1,
+         "Correct number of index entries for lowerBound keyRange");
+
+      keyRange = IDBKeyRange.lowerBound(objectStoreData[weightSort[weightSort.length - 1]].value.weight);
+      index.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, 1,
+         "Correct number of index entries for lowerBound keyRange");
+
+      keyRange = IDBKeyRange.lowerBound(objectStoreData[weightSort[weightSort.length - 1]].value.weight,
+                                        true);
+      index.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, 0,
+         "Correct number of index entries for lowerBound keyRange");
+
+      keyRange = IDBKeyRange.lowerBound(objectStoreData[weightSort[weightSort.length - 1]].value.weight + 1,
+                                        true);
+      index.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, 0,
+         "Correct number of index entries for lowerBound keyRange");
+
+      keyRange = IDBKeyRange.upperBound(objectStoreData[weightSort[0]].value.weight);
+      index.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, 1,
+         "Correct number of index entries for upperBound keyRange");
+
+      keyRange = IDBKeyRange.upperBound(objectStoreData[weightSort[0]].value.weight,
+                                        true);
+      index.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, 0,
+         "Correct number of index entries for upperBound keyRange");
+
+      keyRange = IDBKeyRange.upperBound(objectStoreData[weightSort[weightSort.length - 1]].value.weight);
+      index.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, weightSort.length,
+         "Correct number of index entries for upperBound keyRange");
+
+      keyRange = IDBKeyRange.upperBound(objectStoreData[weightSort[weightSort.length - 1]].value.weight,
+                                        true);
+      index.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, weightSort.length - 1,
+         "Correct number of index entries for upperBound keyRange");
+
+      keyRange = IDBKeyRange.upperBound(objectStoreData[weightSort[weightSort.length - 1]].value.weight,
+                                        true);
+      index.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, weightSort.length - 1,
+         "Correct number of index entries for upperBound keyRange");
+
+      keyRange = IDBKeyRange.upperBound("foo");
+      index.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, weightSort.length,
+         "Correct number of index entries for upperBound keyRange");
+
+      keyRange = IDBKeyRange.bound("foo", "foopy");
+      index.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, 0,
+         "Correct number of index entries for bound keyRange");
+
+      keyRange = IDBKeyRange.bound(objectStoreData[weightSort[0]].value.weight,
+                                   objectStoreData[weightSort[weightSort.length - 1]].value.weight);
+      index.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, weightSort.length,
+         "Correct number of index entries for bound keyRange");
+
+      keyRange = IDBKeyRange.bound(objectStoreData[weightSort[0]].value.weight,
+                                   objectStoreData[weightSort[weightSort.length - 1]].value.weight,
+                                   true);
+      index.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, weightSort.length - 1,
+         "Correct number of index entries for bound keyRange");
+
+      keyRange = IDBKeyRange.bound(objectStoreData[weightSort[0]].value.weight,
+                                   objectStoreData[weightSort[weightSort.length - 1]].value.weight,
+                                   true, true);
+      index.count(keyRange).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      is(event.target.result, weightSort.length - 2,
+         "Correct number of index entries for bound keyRange");
+
+      finishTest();
+      yield;
+    }
+  </script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+</head>
+
+<body onload="runTest();"></body>
+
+</html>