예제 #1
0
// static
already_AddRefed<IDBCursor>
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(), "Bad key!");
    NS_ASSERTION(!aObjectKey.IsUnset(), "Bad key!");

    nsRefPtr<IDBCursor> cursor =
        IDBCursor::CreateCommon(aRequest, aTransaction, aIndex->ObjectStore(),
                                aDirection, aRangeKey, aContinueQuery,
                                aContinueToQuery);
    NS_ASSERTION(cursor, "This shouldn't fail!");

    cursor->mIndex = aIndex;
    cursor->mType = INDEXKEY;
    cursor->mKey = aKey,
            cursor->mObjectKey = aObjectKey;

    return cursor.forget();
}
예제 #2
0
// static
already_AddRefed<IDBCursor>
IDBCursor::Create(IDBIndex* aIndex,
                  BackgroundCursorChild* aBackgroundActor,
                  Direction aDirection,
                  const Key& aKey,
                  const Key& aPrimaryKey)
{
  MOZ_ASSERT(aIndex);
  aIndex->AssertIsOnOwningThread();
  MOZ_ASSERT(aBackgroundActor);
  MOZ_ASSERT(!aKey.IsUnset());
  MOZ_ASSERT(!aPrimaryKey.IsUnset());

  nsRefPtr<IDBCursor> cursor =
    new IDBCursor(Type_IndexKey,
                  nullptr,
                  aIndex,
                  aIndex->ObjectStore()->Transaction(),
                  aBackgroundActor,
                  aDirection,
                  aKey);

  cursor->mPrimaryKey = Move(aPrimaryKey);

  return cursor.forget();
}
예제 #3
0
nsresult
KeyPath::ExtractOrCreateKey(JSContext* aCx, const JS::Value& aValue,
                            Key& aKey, ExtractOrCreateKeyCallback aCallback,
                            void* aClosure) const
{
    NS_ASSERTION(IsString(), "This doesn't make sense!");

    JS::Rooted<JS::Value> value(aCx);

    aKey.Unset();

    nsresult rv = GetJSValFromKeyPathString(aCx, aValue, mStrings[0],
                                            value.address(),
                                            CreateProperties, aCallback,
                                            aClosure);
    if (NS_FAILED(rv)) {
        return rv;
    }

    if (NS_FAILED(aKey.AppendItem(aCx, false, value))) {
        NS_ASSERTION(aKey.IsUnset(), "Should be unset");
        return value.isUndefined() ? NS_OK : NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
    }

    aKey.FinishArray();

    return NS_OK;
}
예제 #4
0
nsresult
KeyPath::ExtractKey(JSContext* aCx, const JS::Value& aValue, Key& aKey) const
{
    uint32_t len = mStrings.Length();
    JS::Rooted<JS::Value> value(aCx);

    aKey.Unset();

    for (uint32_t i = 0; i < len; ++i) {
        nsresult rv = GetJSValFromKeyPathString(aCx, aValue, mStrings[i],
                                                value.address(),
                                                DoNotCreateProperties, nullptr,
                                                nullptr);
        if (NS_FAILED(rv)) {
            return rv;
        }

        if (NS_FAILED(aKey.AppendItem(aCx, IsArray() && i == 0, value))) {
            NS_ASSERTION(aKey.IsUnset(), "Encoding error should unset");
            return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
        }
    }

    aKey.FinishArray();

    return NS_OK;
}
예제 #5
0
NS_IMETHODIMP
IDBIndex::GetKey(nsIVariant* aKey,
                 nsIIDBRequest** _retval)
{
  NS_PRECONDITION(NS_IsMainThread(), "Wrong thread!");

  IDBTransaction* transaction = mObjectStore->Transaction();
  if (!transaction->IsOpen()) {
    return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR;
  }

  Key key;
  nsresult rv = IDBObjectStore::GetKeyFromVariant(aKey, key);
  if (NS_FAILED(rv)) {
    return rv;
  }

  if (key.IsUnset()) {
    return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
  }

  nsRefPtr<IDBRequest> request = GenerateRequest(this);
  NS_ENSURE_TRUE(request, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);

  nsRefPtr<GetKeyHelper> helper =
    new GetKeyHelper(transaction, request, this, key);

  rv = helper->DispatchToTransactionPool();
  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);

  request.forget(_retval);
  return NS_OK;
}
예제 #6
0
END_INDEXEDDB_NAMESPACE

// static
already_AddRefed<IDBCursor>
IDBCursor::Create(IDBRequest* aRequest,
                  IDBTransaction* aTransaction,
                  IDBObjectStore* aObjectStore,
                  PRUint16 aDirection,
                  const Key& aRangeKey,
                  const nsACString& aContinueQuery,
                  const nsACString& aContinueToQuery,
                  const Key& aKey,
                  StructuredCloneReadInfo& aCloneReadInfo)
{
    NS_ASSERTION(aObjectStore, "Null pointer!");
    NS_ASSERTION(!aKey.IsUnset(), "Bad key!");

    nsRefPtr<IDBCursor> 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->mCloneReadInfo.Swap(aCloneReadInfo);

    return cursor.forget();
}
예제 #7
0
// static
already_AddRefed<IDBCursor>
IDBCursor::Create(BackgroundCursorChild* aBackgroundActor,
                  const Key& aKey,
                  const Key& aPrimaryKey)
{
  MOZ_ASSERT(aBackgroundActor);
  aBackgroundActor->AssertIsOnOwningThread();
  MOZ_ASSERT(aBackgroundActor->GetIndex());
  MOZ_ASSERT(!aBackgroundActor->GetObjectStore());
  MOZ_ASSERT(!aKey.IsUnset());
  MOZ_ASSERT(!aPrimaryKey.IsUnset());

  nsRefPtr<IDBCursor> cursor =
    new IDBCursor(Type_IndexKey, aBackgroundActor, aKey);

  cursor->mPrimaryKey = Move(aPrimaryKey);

  return cursor.forget();
}
예제 #8
0
// static
already_AddRefed<IDBCursor>
IDBCursor::Create(BackgroundCursorChild* aBackgroundActor,
                  const Key& aKey,
                  const Key& aSortKey,
                  const Key& aPrimaryKey,
                  StructuredCloneReadInfo&& aCloneInfo)
{
  MOZ_ASSERT(aBackgroundActor);
  aBackgroundActor->AssertIsOnOwningThread();
  MOZ_ASSERT(aBackgroundActor->GetIndex());
  MOZ_ASSERT(!aBackgroundActor->GetObjectStore());
  MOZ_ASSERT(!aKey.IsUnset());
  MOZ_ASSERT(!aPrimaryKey.IsUnset());

  RefPtr<IDBCursor> cursor =
    new IDBCursor(Type_Index, aBackgroundActor, aKey);

  cursor->mSortKey = std::move(aSortKey);
  cursor->mPrimaryKey = std::move(aPrimaryKey);
  cursor->mCloneInfo = std::move(aCloneInfo);

  return cursor.forget();
}
예제 #9
0
void
IDBCursor::Continue(JSContext* aCx,
                    JS::Handle<JS::Value> aKey,
                    ErrorResult &aRv)
{
  AssertIsOnOwningThread();

  if (!mTransaction->IsOpen()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
    return;
  }

  if (!mHaveValue || mContinueCalled) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
    return;
  }

  Key key;
  aRv = key.SetFromJSVal(aCx, aKey);
  if (aRv.Failed()) {
    return;
  }

  if (!key.IsUnset()) {
    switch (mDirection) {
      case NEXT:
      case NEXT_UNIQUE:
        if (key <= mKey) {
          aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
          return;
        }
        break;

      case PREV:
      case PREV_UNIQUE:
        if (key >= mKey) {
          aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
          return;
        }
        break;

      default:
        MOZ_CRASH("Unknown direction type!");
    }
  }

  const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();
  mRequest->SetLoggingSerialNumber(requestSerialNumber);

  if (mType == Type_ObjectStore || mType == Type_ObjectStoreKey) {
    IDB_LOG_MARK("IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
                   "database(%s).transaction(%s).objectStore(%s)."
                   "cursor(%s).continue(%s)",
                 "IndexedDB %s: C T[%lld] R[%llu]: IDBCursor.continue()",
                 IDB_LOG_ID_STRING(),
                 mTransaction->LoggingSerialNumber(),
                 requestSerialNumber,
                 IDB_LOG_STRINGIFY(mTransaction->Database()),
                 IDB_LOG_STRINGIFY(mTransaction),
                 IDB_LOG_STRINGIFY(mSourceObjectStore),
                 IDB_LOG_STRINGIFY(mDirection),
                 IDB_LOG_STRINGIFY(key));
  } else {
    IDB_LOG_MARK("IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
                   "database(%s).transaction(%s).objectStore(%s)."
                   "index(%s).cursor(%s).continue(%s)",
                 "IndexedDB %s: C T[%lld] R[%llu]: IDBCursor.continue()",
                 IDB_LOG_ID_STRING(),
                 mTransaction->LoggingSerialNumber(),
                 requestSerialNumber,
                 IDB_LOG_STRINGIFY(mTransaction->Database()),
                 IDB_LOG_STRINGIFY(mTransaction),
                 IDB_LOG_STRINGIFY(mSourceIndex->ObjectStore()),
                 IDB_LOG_STRINGIFY(mSourceIndex),
                 IDB_LOG_STRINGIFY(mDirection),
                 IDB_LOG_STRINGIFY(key));
  }

  mBackgroundActor->SendContinueInternal(ContinueParams(key));

  mContinueCalled = true;
}
예제 #10
0
void
IDBCursor::ContinuePrimaryKey(JSContext* aCx,
                             JS::Handle<JS::Value> aKey,
                             JS::Handle<JS::Value> aPrimaryKey,
                             ErrorResult &aRv)
{
  AssertIsOnOwningThread();

  if (!mTransaction->IsOpen()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
    return;
  }

  if (IsSourceDeleted()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
    return;
  }

  if ((mType != Type_Index && mType != Type_IndexKey) ||
      (mDirection != NEXT && mDirection != PREV)) {
    aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
    return;
  }

  if (!mHaveValue || mContinueCalled) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
    return;
  }

  Key key;
  aRv = key.SetFromJSVal(aCx, aKey);
  if (aRv.Failed()) {
    return;
  }

  if (IsLocaleAware() && !key.IsUnset()) {
    Key tmp;
    aRv = key.ToLocaleBasedKey(tmp, mSourceIndex->Locale());
    if (aRv.Failed()) {
      return;
    }
    key = tmp;
  }

  const Key& sortKey = IsLocaleAware() ? mSortKey : mKey;

  if (key.IsUnset()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
    return;
  }

  Key primaryKey;
  aRv = primaryKey.SetFromJSVal(aCx, aPrimaryKey);
  if (aRv.Failed()) {
    return;
  }

  if (primaryKey.IsUnset()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
    return;
  }

  switch (mDirection) {
    case NEXT:
      if (key < sortKey ||
          (key == sortKey && primaryKey <= mPrimaryKey)) {
        aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
        return;
      }
      break;

    case PREV:
      if (key > sortKey ||
          (key == sortKey && primaryKey >= mPrimaryKey)) {
        aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
        return;
      }
      break;

    default:
      MOZ_CRASH("Unknown direction type!");
  }

  const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();
  mRequest->SetLoggingSerialNumber(requestSerialNumber);

  IDB_LOG_MARK("IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
                 "database(%s).transaction(%s).objectStore(%s)."
                 "index(%s).cursor(%s).continuePrimaryKey(%s, %s)",
               "IndexedDB %s: C T[%lld] R[%llu]: IDBCursor.continuePrimaryKey()",
               IDB_LOG_ID_STRING(),
               mTransaction->LoggingSerialNumber(),
               requestSerialNumber,
               IDB_LOG_STRINGIFY(mTransaction->Database()),
               IDB_LOG_STRINGIFY(mTransaction),
               IDB_LOG_STRINGIFY(mSourceIndex->ObjectStore()),
               IDB_LOG_STRINGIFY(mSourceIndex),
               IDB_LOG_STRINGIFY(mDirection),
               IDB_LOG_STRINGIFY(key),
               IDB_LOG_STRINGIFY(primaryKey));

  mBackgroundActor->SendContinueInternal(ContinuePrimaryKeyParams(key, primaryKey));

  mContinueCalled = true;
}
예제 #11
0
NS_IMETHODIMP
ContinueRunnable::Run()
{
  nsresult rv;

  if (!NS_IsMainThread()) {
    rv = NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
    NS_ENSURE_SUCCESS(rv, rv);

    return NS_OK;
  }

  // Remove cached stuff from last time.
  mCursor->mCachedKey = nsnull;
  mCursor->mCachedValue = JSVAL_VOID;
  mCursor->mHaveCachedValue = false;
  mCursor->mContinueCalled = false;

  if (mCursor->mType == IDBCursor::INDEX) {
    mCursor->mKeyData.RemoveElementAt(mCursor->mDataIndex);
  }
  else {
    mCursor->mData.RemoveElementAt(mCursor->mDataIndex);
  }
  if (mCursor->mDataIndex) {
    mCursor->mDataIndex--;
  }

  nsCOMPtr<nsIWritableVariant> variant =
    do_CreateInstance(NS_VARIANT_CONTRACTID);
  if (!variant) {
    NS_ERROR("Couldn't create variant!");
    return NS_ERROR_FAILURE;
  }

  PRBool empty = mCursor->mType == IDBCursor::INDEX ?
                 mCursor->mKeyData.IsEmpty() :
                 mCursor->mData.IsEmpty();

  if (empty) {
    rv = variant->SetAsEmpty();
    NS_ENSURE_SUCCESS(rv, rv);
  }
  else {
    if (!mKey.IsUnset()) {
      NS_ASSERTION(!mKey.IsNull(), "Huh?!");

      NS_WARNING("Using a slow O(n) search for continue(key), do something "
                 "smarter!");

      // Skip ahead to our next key match.
      PRInt32 index = PRInt32(mCursor->mDataIndex);

      if (mCursor->mType == IDBCursor::INDEX) {
        while (index >= 0) {
          const Key& key = mCursor->mKeyData[index].key;
          if (mKey == key) {
            break;
          }
          if (key < mKey) {
            index--;
            continue;
          }
          index = -1;
          break;
        }

        if (index >= 0) {
          mCursor->mDataIndex = PRUint32(index);
          mCursor->mKeyData.RemoveElementsAt(index + 1,
                                             mCursor->mKeyData.Length() - index
                                             - 1);
        }
      }
      else {
        while (index >= 0) {
          const Key& key = mCursor->mData[index].key;
          if (mKey == key) {
            break;
          }
          if (key < mKey) {
            index--;
            continue;
          }
          index = -1;
          break;
        }

        if (index >= 0) {
          mCursor->mDataIndex = PRUint32(index);
          mCursor->mData.RemoveElementsAt(index + 1,
                                          mCursor->mData.Length() - index - 1);
        }
      }
    }

    rv = variant->SetAsISupports(static_cast<IDBRequest::Generator*>(mCursor));
    NS_ENSURE_SUCCESS(rv, rv);
  }

  rv = variant->SetWritable(PR_FALSE);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIDOMEvent> event =
    IDBSuccessEvent::Create(mCursor->mRequest, variant, mCursor->mTransaction);
  if (!event) {
    NS_ERROR("Failed to create event!");
    return NS_ERROR_FAILURE;
  }

  PRBool dummy;
  mCursor->mRequest->DispatchEvent(event, &dummy);

  mCursor->mTransaction->OnRequestFinished();

  mCursor = nsnull;

  return NS_OK;
}
예제 #12
0
NS_IMETHODIMP
IDBCursor::Update(const jsval &aValue,
                  JSContext* aCx,
                  nsIIDBRequest** _retval)
{
  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");

  if (mType != OBJECTSTORE) {
    NS_NOTYETIMPLEMENTED("Implement me!");
    return NS_ERROR_NOT_IMPLEMENTED;
  }

  if (!mObjectStore->TransactionIsOpen()) {
    return NS_ERROR_UNEXPECTED;
  }

  if (!mObjectStore->IsWriteAllowed()) {
    return NS_ERROR_OBJECT_IS_IMMUTABLE;
  }

  const Key& key = mData[mDataIndex].key;
  NS_ASSERTION(!key.IsUnset() && !key.IsNull(), "Bad key!");

  JSAutoRequest ar(aCx);

  js::AutoValueRooter clone(aCx);
  nsresult rv = nsContentUtils::CreateStructuredClone(aCx, aValue,
                                                      clone.jsval_addr());
  if (NS_FAILED(rv)) {
    return 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<const jschar*>(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_FAILURE);

    if (JSVAL_IS_VOID(prop.jsval_value())) {
      rv = IDBObjectStore::GetJSValFromKey(key, aCx, prop.jsval_addr());
      NS_ENSURE_SUCCESS(rv, rv);

      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_FAILURE);
    }
    else {
      Key newKey;
      rv = IDBObjectStore::GetKeyFromJSVal(prop.jsval_value(), newKey);
      NS_ENSURE_SUCCESS(rv, rv);

      if (newKey.IsUnset() || newKey.IsNull() || newKey != key) {
        return NS_ERROR_INVALID_ARG;
      }
    }
  }

  nsTArray<IndexUpdateInfo> indexUpdateInfo;
  rv = IDBObjectStore::GetIndexUpdateInfo(mObjectStore->GetObjectStoreInfo(),
                                          aCx, clone.jsval_value(), indexUpdateInfo);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIJSON> json(new nsJSON());

  nsString jsonValue;
  rv = json->EncodeFromJSVal(clone.jsval_addr(), aCx, jsonValue);
  NS_ENSURE_SUCCESS(rv, rv);

  nsRefPtr<IDBRequest> request =
    GenerateWriteRequest(mTransaction->ScriptContext(), mTransaction->Owner());
  NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);

  nsRefPtr<UpdateHelper> helper =
    new UpdateHelper(mTransaction, request, mObjectStore->Id(), jsonValue, key,
                     mObjectStore->IsAutoIncrement(), indexUpdateInfo);
  rv = helper->DispatchToTransactionPool();
  NS_ENSURE_SUCCESS(rv, rv);

  request.forget(_retval);
  return NS_OK;
}
예제 #13
0
void
IDBCursor::Continue(JSContext* aCx,
                    JS::Handle<JS::Value> aKey,
                    ErrorResult &aRv)
{
  AssertIsOnOwningThread();

  if (!mTransaction->IsOpen()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
    return;
  }

  if (!mHaveValue || mContinueCalled) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
    return;
  }

  Key key;
  aRv = key.SetFromJSVal(aCx, aKey);
  if (aRv.Failed()) {
    return;
  }

  if (!key.IsUnset()) {
    switch (mDirection) {
      case NEXT:
      case NEXT_UNIQUE:
        if (key <= mKey) {
          aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
          return;
        }
        break;

      case PREV:
      case PREV_UNIQUE:
        if (key >= mKey) {
          aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
          return;
        }
        break;

      default:
        MOZ_CRASH("Unknown direction type!");
    }
  }

  mBackgroundActor->SendContinueInternal(ContinueParams(key));

  mContinueCalled = true;

#ifdef IDB_PROFILER_USE_MARKS
  if (mType == Type_ObjectStore || mType == Type_ObjectStoreKey) {
    IDB_PROFILER_MARK("IndexedDB Request %llu: "
                      "database(%s).transaction(%s).objectStore(%s).cursor(%s)."
                      "continue(%s)",
                      "IDBRequest[%llu] MT IDBCursor.continue()",
                      Request()->GetSerialNumber(),
                      IDB_PROFILER_STRING(Transaction()->Database()),
                      IDB_PROFILER_STRING(Transaction()),
                      IDB_PROFILER_STRING(mSourceObjectStore),
                      IDB_PROFILER_STRING(mDirection),
                      key.IsUnset() ? "" : IDB_PROFILER_STRING(key));
  } else {
    IDB_PROFILER_MARK("IndexedDB Request %llu: "
                      "database(%s).transaction(%s).objectStore(%s).index(%s)."
                      "cursor(%s).continue(%s)",
                      "IDBRequest[%llu] MT IDBCursor.continue()",
                      Request()->GetSerialNumber(),
                      IDB_PROFILER_STRING(Transaction()->Database()),
                      IDB_PROFILER_STRING(Transaction()),
                      IDB_PROFILER_STRING(mSourceIndex->ObjectStore()),
                      IDB_PROFILER_STRING(mSourceIndex),
                      IDB_PROFILER_STRING(mDirection),
                      key.IsUnset() ? "" : IDB_PROFILER_STRING(key));
  }
#endif
}