root/content/browser/indexed_db/indexed_db_database.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. transaction_id_
  2. callbacks
  3. ReleaseConnection
  4. version
  5. transaction_id
  6. version_
  7. callbacks
  8. connection
  9. version
  10. PendingDeleteCall
  11. callbacks
  12. Create
  13. factory_
  14. AddObjectStore
  15. RemoveObjectStore
  16. AddIndex
  17. RemoveIndex
  18. OpenInternal
  19. CreateConnection
  20. GetTransaction
  21. ValidateObjectStoreId
  22. ValidateObjectStoreIdAndIndexId
  23. ValidateObjectStoreIdAndOptionalIndexId
  24. ValidateObjectStoreIdAndNewIndexId
  25. CreateObjectStore
  26. CreateObjectStoreOperation
  27. DeleteObjectStore
  28. CreateIndex
  29. CreateIndexOperation
  30. CreateIndexAbortOperation
  31. DeleteIndex
  32. DeleteIndexOperation
  33. DeleteIndexAbortOperation
  34. Commit
  35. Abort
  36. Abort
  37. Get
  38. GetOperation
  39. GenerateKey
  40. UpdateKeyGenerator
  41. Put
  42. PutOperation
  43. SetIndexKeys
  44. SetIndexesReady
  45. SetIndexesReadyOperation
  46. OpenCursor
  47. OpenCursorOperation
  48. Count
  49. CountOperation
  50. DeleteRange
  51. DeleteRangeOperation
  52. Clear
  53. ClearOperation
  54. DeleteObjectStoreOperation
  55. VersionChangeOperation
  56. TransactionFinished
  57. TransactionCommitFailed
  58. ConnectionCount
  59. PendingOpenCount
  60. PendingUpgradeCount
  61. RunningUpgradeCount
  62. PendingDeleteCount
  63. ProcessPendingCalls
  64. CreateTransaction
  65. TransactionCreated
  66. IsOpenConnectionBlocked
  67. OpenConnection
  68. RunVersionChangeTransaction
  69. RunVersionChangeTransactionFinal
  70. DeleteDatabase
  71. IsDeleteDatabaseBlocked
  72. DeleteDatabaseFinal
  73. ForceClose
  74. Close
  75. CreateObjectStoreAbortOperation
  76. DeleteObjectStoreAbortOperation
  77. VersionChangeAbortOperation

// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/indexed_db/indexed_db_database.h"

#include <math.h>
#include <set>

#include "base/auto_reset.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "content/browser/indexed_db/indexed_db_connection.h"
#include "content/browser/indexed_db/indexed_db_context_impl.h"
#include "content/browser/indexed_db/indexed_db_cursor.h"
#include "content/browser/indexed_db/indexed_db_factory.h"
#include "content/browser/indexed_db/indexed_db_index_writer.h"
#include "content/browser/indexed_db/indexed_db_pending_connection.h"
#include "content/browser/indexed_db/indexed_db_tracing.h"
#include "content/browser/indexed_db/indexed_db_transaction.h"
#include "content/browser/indexed_db/indexed_db_value.h"
#include "content/common/indexed_db/indexed_db_key_path.h"
#include "content/common/indexed_db/indexed_db_key_range.h"
#include "third_party/WebKit/public/platform/WebIDBDatabaseException.h"

using base::ASCIIToUTF16;
using base::Int64ToString16;
using blink::WebIDBKeyTypeNumber;

namespace content {

// PendingUpgradeCall has a scoped_ptr<IndexedDBConnection> because it owns the
// in-progress connection.
class IndexedDBDatabase::PendingUpgradeCall {
 public:
  PendingUpgradeCall(scoped_refptr<IndexedDBCallbacks> callbacks,
                     scoped_ptr<IndexedDBConnection> connection,
                     int64 transaction_id,
                     int64 version)
      : callbacks_(callbacks),
        connection_(connection.Pass()),
        version_(version),
        transaction_id_(transaction_id) {}
  scoped_refptr<IndexedDBCallbacks> callbacks() const { return callbacks_; }
  // Takes ownership of the connection object.
  scoped_ptr<IndexedDBConnection> ReleaseConnection() WARN_UNUSED_RESULT {
    return connection_.Pass();
  }
  int64 version() const { return version_; }
  int64 transaction_id() const { return transaction_id_; }

 private:
  scoped_refptr<IndexedDBCallbacks> callbacks_;
  scoped_ptr<IndexedDBConnection> connection_;
  int64 version_;
  const int64 transaction_id_;
};

// PendingSuccessCall has a IndexedDBConnection* because the connection is now
// owned elsewhere, but we need to cancel the success call if that connection
// closes before it is sent.
class IndexedDBDatabase::PendingSuccessCall {
 public:
  PendingSuccessCall(scoped_refptr<IndexedDBCallbacks> callbacks,
                     IndexedDBConnection* connection,
                     int64 version)
      : callbacks_(callbacks), connection_(connection), version_(version) {}
  scoped_refptr<IndexedDBCallbacks> callbacks() const { return callbacks_; }
  IndexedDBConnection* connection() const { return connection_; }
  int64 version() const { return version_; }

 private:
  scoped_refptr<IndexedDBCallbacks> callbacks_;
  IndexedDBConnection* connection_;
  int64 version_;
};

class IndexedDBDatabase::PendingDeleteCall {
 public:
  explicit PendingDeleteCall(scoped_refptr<IndexedDBCallbacks> callbacks)
      : callbacks_(callbacks) {}
  scoped_refptr<IndexedDBCallbacks> callbacks() const { return callbacks_; }

 private:
  scoped_refptr<IndexedDBCallbacks> callbacks_;
};

scoped_refptr<IndexedDBDatabase> IndexedDBDatabase::Create(
    const base::string16& name,
    IndexedDBBackingStore* backing_store,
    IndexedDBFactory* factory,
    const Identifier& unique_identifier) {
  scoped_refptr<IndexedDBDatabase> database =
      new IndexedDBDatabase(name, backing_store, factory, unique_identifier);
  if (!database->OpenInternal().ok())
    return 0;
  return database;
}

namespace {
const base::string16::value_type kNoStringVersion[] = {0};
}

IndexedDBDatabase::IndexedDBDatabase(const base::string16& name,
                                     IndexedDBBackingStore* backing_store,
                                     IndexedDBFactory* factory,
                                     const Identifier& unique_identifier)
    : backing_store_(backing_store),
      metadata_(name,
                kInvalidId,
                kNoStringVersion,
                IndexedDBDatabaseMetadata::NO_INT_VERSION,
                kInvalidId),
      identifier_(unique_identifier),
      factory_(factory) {
}

void IndexedDBDatabase::AddObjectStore(
    const IndexedDBObjectStoreMetadata& object_store,
    int64 new_max_object_store_id) {
  DCHECK(metadata_.object_stores.find(object_store.id) ==
         metadata_.object_stores.end());
  if (new_max_object_store_id != IndexedDBObjectStoreMetadata::kInvalidId) {
    DCHECK_LT(metadata_.max_object_store_id, new_max_object_store_id);
    metadata_.max_object_store_id = new_max_object_store_id;
  }
  metadata_.object_stores[object_store.id] = object_store;
}

void IndexedDBDatabase::RemoveObjectStore(int64 object_store_id) {
  DCHECK(metadata_.object_stores.find(object_store_id) !=
         metadata_.object_stores.end());
  metadata_.object_stores.erase(object_store_id);
}

void IndexedDBDatabase::AddIndex(int64 object_store_id,
                                 const IndexedDBIndexMetadata& index,
                                 int64 new_max_index_id) {
  DCHECK(metadata_.object_stores.find(object_store_id) !=
         metadata_.object_stores.end());
  IndexedDBObjectStoreMetadata object_store =
      metadata_.object_stores[object_store_id];

  DCHECK(object_store.indexes.find(index.id) == object_store.indexes.end());
  object_store.indexes[index.id] = index;
  if (new_max_index_id != IndexedDBIndexMetadata::kInvalidId) {
    DCHECK_LT(object_store.max_index_id, new_max_index_id);
    object_store.max_index_id = new_max_index_id;
  }
  metadata_.object_stores[object_store_id] = object_store;
}

void IndexedDBDatabase::RemoveIndex(int64 object_store_id, int64 index_id) {
  DCHECK(metadata_.object_stores.find(object_store_id) !=
         metadata_.object_stores.end());
  IndexedDBObjectStoreMetadata object_store =
      metadata_.object_stores[object_store_id];

  DCHECK(object_store.indexes.find(index_id) != object_store.indexes.end());
  object_store.indexes.erase(index_id);
  metadata_.object_stores[object_store_id] = object_store;
}

leveldb::Status IndexedDBDatabase::OpenInternal() {
  bool success = false;
  leveldb::Status s = backing_store_->GetIDBDatabaseMetaData(
      metadata_.name, &metadata_, &success);
  DCHECK(success == (metadata_.id != kInvalidId)) << "success = " << success
                                                  << " id = " << metadata_.id;
  if (!s.ok())
    return s;
  if (success)
    return backing_store_->GetObjectStores(metadata_.id,
                                           &metadata_.object_stores);

  return backing_store_->CreateIDBDatabaseMetaData(
      metadata_.name, metadata_.version, metadata_.int_version, &metadata_.id);
}

IndexedDBDatabase::~IndexedDBDatabase() {
  DCHECK(transactions_.empty());
  DCHECK(pending_open_calls_.empty());
  DCHECK(pending_delete_calls_.empty());
}

scoped_ptr<IndexedDBConnection> IndexedDBDatabase::CreateConnection(
    scoped_refptr<IndexedDBDatabaseCallbacks> database_callbacks,
    int child_process_id) {
  scoped_ptr<IndexedDBConnection> connection(
      new IndexedDBConnection(this, database_callbacks));
  connections_.insert(connection.get());
  /* TODO(ericu):  Grant child process permissions here so that the connection
   * can create Blobs.
  */
  return connection.Pass();
}

IndexedDBTransaction* IndexedDBDatabase::GetTransaction(
    int64 transaction_id) const {
  TransactionMap::const_iterator trans_iterator =
      transactions_.find(transaction_id);
  if (trans_iterator == transactions_.end())
    return NULL;
  return trans_iterator->second;
}

bool IndexedDBDatabase::ValidateObjectStoreId(int64 object_store_id) const {
  if (!ContainsKey(metadata_.object_stores, object_store_id)) {
    DLOG(ERROR) << "Invalid object_store_id";
    return false;
  }
  return true;
}

bool IndexedDBDatabase::ValidateObjectStoreIdAndIndexId(int64 object_store_id,
                                                        int64 index_id) const {
  if (!ValidateObjectStoreId(object_store_id))
    return false;
  const IndexedDBObjectStoreMetadata& object_store_metadata =
      metadata_.object_stores.find(object_store_id)->second;
  if (!ContainsKey(object_store_metadata.indexes, index_id)) {
    DLOG(ERROR) << "Invalid index_id";
    return false;
  }
  return true;
}

bool IndexedDBDatabase::ValidateObjectStoreIdAndOptionalIndexId(
    int64 object_store_id,
    int64 index_id) const {
  if (!ValidateObjectStoreId(object_store_id))
    return false;
  const IndexedDBObjectStoreMetadata& object_store_metadata =
      metadata_.object_stores.find(object_store_id)->second;
  if (index_id != IndexedDBIndexMetadata::kInvalidId &&
      !ContainsKey(object_store_metadata.indexes, index_id)) {
    DLOG(ERROR) << "Invalid index_id";
    return false;
  }
  return true;
}

bool IndexedDBDatabase::ValidateObjectStoreIdAndNewIndexId(
    int64 object_store_id,
    int64 index_id) const {
  if (!ValidateObjectStoreId(object_store_id))
    return false;
  const IndexedDBObjectStoreMetadata& object_store_metadata =
      metadata_.object_stores.find(object_store_id)->second;
  if (ContainsKey(object_store_metadata.indexes, index_id)) {
    DLOG(ERROR) << "Invalid index_id";
    return false;
  }
  return true;
}

void IndexedDBDatabase::CreateObjectStore(int64 transaction_id,
                                          int64 object_store_id,
                                          const base::string16& name,
                                          const IndexedDBKeyPath& key_path,
                                          bool auto_increment) {
  IDB_TRACE("IndexedDBDatabase::CreateObjectStore");
  IndexedDBTransaction* transaction = GetTransaction(transaction_id);
  if (!transaction)
    return;
  DCHECK_EQ(transaction->mode(), indexed_db::TRANSACTION_VERSION_CHANGE);

  if (ContainsKey(metadata_.object_stores, object_store_id)) {
    DLOG(ERROR) << "Invalid object_store_id";
    return;
  }

  IndexedDBObjectStoreMetadata object_store_metadata(
      name,
      object_store_id,
      key_path,
      auto_increment,
      IndexedDBDatabase::kMinimumIndexId);

  transaction->ScheduleTask(
      base::Bind(&IndexedDBDatabase::CreateObjectStoreOperation,
                 this,
                 object_store_metadata),
      base::Bind(&IndexedDBDatabase::CreateObjectStoreAbortOperation,
                 this,
                 object_store_id));

  AddObjectStore(object_store_metadata, object_store_id);
}

void IndexedDBDatabase::CreateObjectStoreOperation(
    const IndexedDBObjectStoreMetadata& object_store_metadata,
    IndexedDBTransaction* transaction) {
  IDB_TRACE("IndexedDBDatabase::CreateObjectStoreOperation");
  leveldb::Status s =
      backing_store_->CreateObjectStore(transaction->BackingStoreTransaction(),
                                        transaction->database()->id(),
                                        object_store_metadata.id,
                                        object_store_metadata.name,
                                        object_store_metadata.key_path,
                                        object_store_metadata.auto_increment);
  if (!s.ok()) {
    IndexedDBDatabaseError error(
        blink::WebIDBDatabaseExceptionUnknownError,
        ASCIIToUTF16("Internal error creating object store '") +
            object_store_metadata.name + ASCIIToUTF16("'."));
    transaction->Abort(error);
    if (s.IsCorruption())
      factory_->HandleBackingStoreCorruption(backing_store_->origin_url(),
                                             error);
    return;
  }
}

void IndexedDBDatabase::DeleteObjectStore(int64 transaction_id,
                                          int64 object_store_id) {
  IDB_TRACE("IndexedDBDatabase::DeleteObjectStore");
  IndexedDBTransaction* transaction = GetTransaction(transaction_id);
  if (!transaction)
    return;
  DCHECK_EQ(transaction->mode(), indexed_db::TRANSACTION_VERSION_CHANGE);

  if (!ValidateObjectStoreId(object_store_id))
    return;

  const IndexedDBObjectStoreMetadata& object_store_metadata =
      metadata_.object_stores[object_store_id];

  transaction->ScheduleTask(
      base::Bind(&IndexedDBDatabase::DeleteObjectStoreOperation,
                 this,
                 object_store_metadata),
      base::Bind(&IndexedDBDatabase::DeleteObjectStoreAbortOperation,
                 this,
                 object_store_metadata));
  RemoveObjectStore(object_store_id);
}

void IndexedDBDatabase::CreateIndex(int64 transaction_id,
                                    int64 object_store_id,
                                    int64 index_id,
                                    const base::string16& name,
                                    const IndexedDBKeyPath& key_path,
                                    bool unique,
                                    bool multi_entry) {
  IDB_TRACE("IndexedDBDatabase::CreateIndex");
  IndexedDBTransaction* transaction = GetTransaction(transaction_id);
  if (!transaction)
    return;
  DCHECK_EQ(transaction->mode(), indexed_db::TRANSACTION_VERSION_CHANGE);

  if (!ValidateObjectStoreIdAndNewIndexId(object_store_id, index_id))
    return;
  const IndexedDBIndexMetadata index_metadata(
      name, index_id, key_path, unique, multi_entry);

  transaction->ScheduleTask(
      base::Bind(&IndexedDBDatabase::CreateIndexOperation,
                 this,
                 object_store_id,
                 index_metadata),
      base::Bind(&IndexedDBDatabase::CreateIndexAbortOperation,
                 this,
                 object_store_id,
                 index_id));

  AddIndex(object_store_id, index_metadata, index_id);
}

void IndexedDBDatabase::CreateIndexOperation(
    int64 object_store_id,
    const IndexedDBIndexMetadata& index_metadata,
    IndexedDBTransaction* transaction) {
  IDB_TRACE("IndexedDBDatabase::CreateIndexOperation");
  if (!backing_store_->CreateIndex(transaction->BackingStoreTransaction(),
                                   transaction->database()->id(),
                                   object_store_id,
                                   index_metadata.id,
                                   index_metadata.name,
                                   index_metadata.key_path,
                                   index_metadata.unique,
                                   index_metadata.multi_entry).ok()) {
    base::string16 error_string =
        ASCIIToUTF16("Internal error creating index '") +
        index_metadata.name + ASCIIToUTF16("'.");
    transaction->Abort(IndexedDBDatabaseError(
        blink::WebIDBDatabaseExceptionUnknownError, error_string));
    return;
  }
}

void IndexedDBDatabase::CreateIndexAbortOperation(
    int64 object_store_id,
    int64 index_id,
    IndexedDBTransaction* transaction) {
  IDB_TRACE("IndexedDBDatabase::CreateIndexAbortOperation");
  DCHECK(!transaction);
  RemoveIndex(object_store_id, index_id);
}

void IndexedDBDatabase::DeleteIndex(int64 transaction_id,
                                    int64 object_store_id,
                                    int64 index_id) {
  IDB_TRACE("IndexedDBDatabase::DeleteIndex");
  IndexedDBTransaction* transaction = GetTransaction(transaction_id);
  if (!transaction)
    return;
  DCHECK_EQ(transaction->mode(), indexed_db::TRANSACTION_VERSION_CHANGE);

  if (!ValidateObjectStoreIdAndIndexId(object_store_id, index_id))
    return;
  const IndexedDBIndexMetadata& index_metadata =
      metadata_.object_stores[object_store_id].indexes[index_id];

  transaction->ScheduleTask(
      base::Bind(&IndexedDBDatabase::DeleteIndexOperation,
                 this,
                 object_store_id,
                 index_metadata),
      base::Bind(&IndexedDBDatabase::DeleteIndexAbortOperation,
                 this,
                 object_store_id,
                 index_metadata));

  RemoveIndex(object_store_id, index_id);
}

void IndexedDBDatabase::DeleteIndexOperation(
    int64 object_store_id,
    const IndexedDBIndexMetadata& index_metadata,
    IndexedDBTransaction* transaction) {
  IDB_TRACE("IndexedDBDatabase::DeleteIndexOperation");
  leveldb::Status s =
      backing_store_->DeleteIndex(transaction->BackingStoreTransaction(),
                                  transaction->database()->id(),
                                  object_store_id,
                                  index_metadata.id);
  if (!s.ok()) {
    base::string16 error_string =
        ASCIIToUTF16("Internal error deleting index '") +
        index_metadata.name + ASCIIToUTF16("'.");
    IndexedDBDatabaseError error(blink::WebIDBDatabaseExceptionUnknownError,
                                 error_string);
    transaction->Abort(error);
    if (s.IsCorruption())
      factory_->HandleBackingStoreCorruption(backing_store_->origin_url(),
                                             error);
  }
}

void IndexedDBDatabase::DeleteIndexAbortOperation(
    int64 object_store_id,
    const IndexedDBIndexMetadata& index_metadata,
    IndexedDBTransaction* transaction) {
  IDB_TRACE("IndexedDBDatabase::DeleteIndexAbortOperation");
  DCHECK(!transaction);
  AddIndex(object_store_id, index_metadata, IndexedDBIndexMetadata::kInvalidId);
}

void IndexedDBDatabase::Commit(int64 transaction_id) {
  // The frontend suggests that we commit, but we may have previously initiated
  // an abort, and so have disposed of the transaction. on_abort has already
  // been dispatched to the frontend, so it will find out about that
  // asynchronously.
  IndexedDBTransaction* transaction = GetTransaction(transaction_id);
  if (transaction)
    transaction->Commit();
}

void IndexedDBDatabase::Abort(int64 transaction_id) {
  // If the transaction is unknown, then it has already been aborted by the
  // backend before this call so it is safe to ignore it.
  IndexedDBTransaction* transaction = GetTransaction(transaction_id);
  if (transaction)
    transaction->Abort();
}

void IndexedDBDatabase::Abort(int64 transaction_id,
                              const IndexedDBDatabaseError& error) {
  // If the transaction is unknown, then it has already been aborted by the
  // backend before this call so it is safe to ignore it.
  IndexedDBTransaction* transaction = GetTransaction(transaction_id);
  if (transaction)
    transaction->Abort(error);
}

void IndexedDBDatabase::Get(int64 transaction_id,
                            int64 object_store_id,
                            int64 index_id,
                            scoped_ptr<IndexedDBKeyRange> key_range,
                            bool key_only,
                            scoped_refptr<IndexedDBCallbacks> callbacks) {
  IDB_TRACE("IndexedDBDatabase::Get");
  IndexedDBTransaction* transaction = GetTransaction(transaction_id);
  if (!transaction)
    return;

  if (!ValidateObjectStoreIdAndOptionalIndexId(object_store_id, index_id))
    return;

  transaction->ScheduleTask(base::Bind(
      &IndexedDBDatabase::GetOperation,
      this,
      object_store_id,
      index_id,
      Passed(&key_range),
      key_only ? indexed_db::CURSOR_KEY_ONLY : indexed_db::CURSOR_KEY_AND_VALUE,
      callbacks));
}

void IndexedDBDatabase::GetOperation(
    int64 object_store_id,
    int64 index_id,
    scoped_ptr<IndexedDBKeyRange> key_range,
    indexed_db::CursorType cursor_type,
    scoped_refptr<IndexedDBCallbacks> callbacks,
    IndexedDBTransaction* transaction) {
  IDB_TRACE("IndexedDBDatabase::GetOperation");

  DCHECK(metadata_.object_stores.find(object_store_id) !=
         metadata_.object_stores.end());
  const IndexedDBObjectStoreMetadata& object_store_metadata =
      metadata_.object_stores[object_store_id];

  const IndexedDBKey* key;

  scoped_ptr<IndexedDBBackingStore::Cursor> backing_store_cursor;
  if (key_range->IsOnlyKey()) {
    key = &key_range->lower();
  } else {
    if (index_id == IndexedDBIndexMetadata::kInvalidId) {
      DCHECK_NE(cursor_type, indexed_db::CURSOR_KEY_ONLY);
      // ObjectStore Retrieval Operation
      backing_store_cursor = backing_store_->OpenObjectStoreCursor(
          transaction->BackingStoreTransaction(),
          id(),
          object_store_id,
          *key_range,
          indexed_db::CURSOR_NEXT);
    } else if (cursor_type == indexed_db::CURSOR_KEY_ONLY) {
      // Index Value Retrieval Operation
      backing_store_cursor = backing_store_->OpenIndexKeyCursor(
          transaction->BackingStoreTransaction(),
          id(),
          object_store_id,
          index_id,
          *key_range,
          indexed_db::CURSOR_NEXT);
    } else {
      // Index Referenced Value Retrieval Operation
      backing_store_cursor = backing_store_->OpenIndexCursor(
          transaction->BackingStoreTransaction(),
          id(),
          object_store_id,
          index_id,
          *key_range,
          indexed_db::CURSOR_NEXT);
    }

    if (!backing_store_cursor) {
      callbacks->OnSuccess();
      return;
    }

    key = &backing_store_cursor->key();
  }

  scoped_ptr<IndexedDBKey> primary_key;
  leveldb::Status s;
  if (index_id == IndexedDBIndexMetadata::kInvalidId) {
    // Object Store Retrieval Operation
    IndexedDBValue value;
    s = backing_store_->GetRecord(transaction->BackingStoreTransaction(),
                                  id(),
                                  object_store_id,
                                  *key,
                                  &value);
    if (!s.ok()) {
      IndexedDBDatabaseError error(blink::WebIDBDatabaseExceptionUnknownError,
                                   "Internal error in GetRecord.");
      callbacks->OnError(error);

      if (s.IsCorruption())
        factory_->HandleBackingStoreCorruption(backing_store_->origin_url(),
                                               error);
      return;
    }

    if (value.empty()) {
      callbacks->OnSuccess();
      return;
    }

    if (object_store_metadata.auto_increment &&
        !object_store_metadata.key_path.IsNull()) {
      callbacks->OnSuccess(&value, *key, object_store_metadata.key_path);
      return;
    }

    callbacks->OnSuccess(&value);
    return;
  }

  // From here we are dealing only with indexes.
  s = backing_store_->GetPrimaryKeyViaIndex(
      transaction->BackingStoreTransaction(),
      id(),
      object_store_id,
      index_id,
      *key,
      &primary_key);
  if (!s.ok()) {
    IndexedDBDatabaseError error(blink::WebIDBDatabaseExceptionUnknownError,
                                 "Internal error in GetPrimaryKeyViaIndex.");
    callbacks->OnError(error);
    if (s.IsCorruption())
      factory_->HandleBackingStoreCorruption(backing_store_->origin_url(),
                                             error);
    return;
  }
  if (!primary_key) {
    callbacks->OnSuccess();
    return;
  }
  if (cursor_type == indexed_db::CURSOR_KEY_ONLY) {
    // Index Value Retrieval Operation
    callbacks->OnSuccess(*primary_key);
    return;
  }

  // Index Referenced Value Retrieval Operation
  IndexedDBValue value;
  s = backing_store_->GetRecord(transaction->BackingStoreTransaction(),
                                id(),
                                object_store_id,
                                *primary_key,
                                &value);
  if (!s.ok()) {
    IndexedDBDatabaseError error(blink::WebIDBDatabaseExceptionUnknownError,
                                 "Internal error in GetRecord.");
    callbacks->OnError(error);
    if (s.IsCorruption())
      factory_->HandleBackingStoreCorruption(backing_store_->origin_url(),
                                             error);
    return;
  }

  if (value.empty()) {
    callbacks->OnSuccess();
    return;
  }
  if (object_store_metadata.auto_increment &&
      !object_store_metadata.key_path.IsNull()) {
    callbacks->OnSuccess(&value, *primary_key, object_store_metadata.key_path);
    return;
  }
  callbacks->OnSuccess(&value);
}

static scoped_ptr<IndexedDBKey> GenerateKey(
    IndexedDBBackingStore* backing_store,
    IndexedDBTransaction* transaction,
    int64 database_id,
    int64 object_store_id) {
  const int64 max_generator_value =
      9007199254740992LL;  // Maximum integer storable as ECMAScript number.
  int64 current_number;
  leveldb::Status s = backing_store->GetKeyGeneratorCurrentNumber(
      transaction->BackingStoreTransaction(),
      database_id,
      object_store_id,
      &current_number);
  if (!s.ok()) {
    LOG(ERROR) << "Failed to GetKeyGeneratorCurrentNumber";
    return make_scoped_ptr(new IndexedDBKey());
  }
  if (current_number < 0 || current_number > max_generator_value)
    return make_scoped_ptr(new IndexedDBKey());

  return make_scoped_ptr(new IndexedDBKey(current_number, WebIDBKeyTypeNumber));
}

static leveldb::Status UpdateKeyGenerator(IndexedDBBackingStore* backing_store,
                                          IndexedDBTransaction* transaction,
                                          int64 database_id,
                                          int64 object_store_id,
                                          const IndexedDBKey& key,
                                          bool check_current) {
  DCHECK_EQ(WebIDBKeyTypeNumber, key.type());
  return backing_store->MaybeUpdateKeyGeneratorCurrentNumber(
      transaction->BackingStoreTransaction(),
      database_id,
      object_store_id,
      static_cast<int64>(floor(key.number())) + 1,
      check_current);
}

struct IndexedDBDatabase::PutOperationParams {
  PutOperationParams() {}
  int64 object_store_id;
  IndexedDBValue value;
  scoped_ptr<IndexedDBKey> key;
  IndexedDBDatabase::PutMode put_mode;
  scoped_refptr<IndexedDBCallbacks> callbacks;
  std::vector<IndexKeys> index_keys;

 private:
  DISALLOW_COPY_AND_ASSIGN(PutOperationParams);
};

void IndexedDBDatabase::Put(int64 transaction_id,
                            int64 object_store_id,
                            IndexedDBValue* value,
                            scoped_ptr<IndexedDBKey> key,
                            PutMode put_mode,
                            scoped_refptr<IndexedDBCallbacks> callbacks,
                            const std::vector<IndexKeys>& index_keys) {
  IDB_TRACE("IndexedDBDatabase::Put");
  IndexedDBTransaction* transaction = GetTransaction(transaction_id);
  if (!transaction)
    return;
  DCHECK_NE(transaction->mode(), indexed_db::TRANSACTION_READ_ONLY);

  if (!ValidateObjectStoreId(object_store_id))
    return;

  DCHECK(key);
  scoped_ptr<PutOperationParams> params(new PutOperationParams());
  params->object_store_id = object_store_id;
  params->value.swap(*value);
  params->key = key.Pass();
  params->put_mode = put_mode;
  params->callbacks = callbacks;
  params->index_keys = index_keys;
  transaction->ScheduleTask(base::Bind(
      &IndexedDBDatabase::PutOperation, this, base::Passed(&params)));
}

void IndexedDBDatabase::PutOperation(scoped_ptr<PutOperationParams> params,
                                     IndexedDBTransaction* transaction) {
  IDB_TRACE("IndexedDBDatabase::PutOperation");
  DCHECK_NE(transaction->mode(), indexed_db::TRANSACTION_READ_ONLY);
  bool key_was_generated = false;

  DCHECK(metadata_.object_stores.find(params->object_store_id) !=
         metadata_.object_stores.end());
  const IndexedDBObjectStoreMetadata& object_store =
      metadata_.object_stores[params->object_store_id];
  DCHECK(object_store.auto_increment || params->key->IsValid());

  scoped_ptr<IndexedDBKey> key;
  if (params->put_mode != IndexedDBDatabase::CURSOR_UPDATE &&
      object_store.auto_increment && !params->key->IsValid()) {
    scoped_ptr<IndexedDBKey> auto_inc_key = GenerateKey(
        backing_store_.get(), transaction, id(), params->object_store_id);
    key_was_generated = true;
    if (!auto_inc_key->IsValid()) {
      params->callbacks->OnError(
          IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionConstraintError,
                                 "Maximum key generator value reached."));
      return;
    }
    key = auto_inc_key.Pass();
  } else {
    key = params->key.Pass();
  }

  DCHECK(key->IsValid());

  IndexedDBBackingStore::RecordIdentifier record_identifier;
  if (params->put_mode == IndexedDBDatabase::ADD_ONLY) {
    bool found = false;
    leveldb::Status s = backing_store_->KeyExistsInObjectStore(
        transaction->BackingStoreTransaction(),
        id(),
        params->object_store_id,
        *key,
        &record_identifier,
        &found);
    if (!s.ok()) {
      IndexedDBDatabaseError error(blink::WebIDBDatabaseExceptionUnknownError,
                                   "Internal error checking key existence.");
      params->callbacks->OnError(error);
      if (s.IsCorruption())
        factory_->HandleBackingStoreCorruption(backing_store_->origin_url(),
                                               error);
      return;
    }
    if (found) {
      params->callbacks->OnError(
          IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionConstraintError,
                                 "Key already exists in the object store."));
      return;
    }
  }

  ScopedVector<IndexWriter> index_writers;
  base::string16 error_message;
  bool obeys_constraints = false;
  bool backing_store_success = MakeIndexWriters(transaction,
                                                backing_store_.get(),
                                                id(),
                                                object_store,
                                                *key,
                                                key_was_generated,
                                                params->index_keys,
                                                &index_writers,
                                                &error_message,
                                                &obeys_constraints);
  if (!backing_store_success) {
    params->callbacks->OnError(IndexedDBDatabaseError(
        blink::WebIDBDatabaseExceptionUnknownError,
        "Internal error: backing store error updating index keys."));
    return;
  }
  if (!obeys_constraints) {
    params->callbacks->OnError(IndexedDBDatabaseError(
        blink::WebIDBDatabaseExceptionConstraintError, error_message));
    return;
  }

  // Before this point, don't do any mutation. After this point, rollback the
  // transaction in case of error.
  leveldb::Status s =
      backing_store_->PutRecord(transaction->BackingStoreTransaction(),
                                id(),
                                params->object_store_id,
                                *key,
                                params->value,
                                &record_identifier);
  if (!s.ok()) {
    IndexedDBDatabaseError error(
        blink::WebIDBDatabaseExceptionUnknownError,
        "Internal error: backing store error performing put/add.");
    params->callbacks->OnError(error);
    if (s.IsCorruption())
      factory_->HandleBackingStoreCorruption(backing_store_->origin_url(),
                                             error);
    return;
  }

  for (size_t i = 0; i < index_writers.size(); ++i) {
    IndexWriter* index_writer = index_writers[i];
    index_writer->WriteIndexKeys(record_identifier,
                                 backing_store_.get(),
                                 transaction->BackingStoreTransaction(),
                                 id(),
                                 params->object_store_id);
  }

  if (object_store.auto_increment &&
      params->put_mode != IndexedDBDatabase::CURSOR_UPDATE &&
      key->type() == WebIDBKeyTypeNumber) {
    leveldb::Status s = UpdateKeyGenerator(backing_store_.get(),
                                           transaction,
                                           id(),
                                           params->object_store_id,
                                           *key,
                                           !key_was_generated);
    if (!s.ok()) {
      IndexedDBDatabaseError error(blink::WebIDBDatabaseExceptionUnknownError,
                                   "Internal error updating key generator.");
      params->callbacks->OnError(error);
      if (s.IsCorruption())
        factory_->HandleBackingStoreCorruption(backing_store_->origin_url(),
                                               error);
      return;
    }
  }

  params->callbacks->OnSuccess(*key);
}

void IndexedDBDatabase::SetIndexKeys(int64 transaction_id,
                                     int64 object_store_id,
                                     scoped_ptr<IndexedDBKey> primary_key,
                                     const std::vector<IndexKeys>& index_keys) {
  IDB_TRACE("IndexedDBDatabase::SetIndexKeys");
  IndexedDBTransaction* transaction = GetTransaction(transaction_id);
  if (!transaction)
    return;
  DCHECK_EQ(transaction->mode(), indexed_db::TRANSACTION_VERSION_CHANGE);

  // TODO(alecflett): This method could be asynchronous, but we need to
  // evaluate if it's worth the extra complexity.
  IndexedDBBackingStore::RecordIdentifier record_identifier;
  bool found = false;
  leveldb::Status s = backing_store_->KeyExistsInObjectStore(
      transaction->BackingStoreTransaction(),
      metadata_.id,
      object_store_id,
      *primary_key,
      &record_identifier,
      &found);
  if (!s.ok()) {
    IndexedDBDatabaseError error(blink::WebIDBDatabaseExceptionUnknownError,
                                 "Internal error setting index keys.");
    transaction->Abort(error);
    if (s.IsCorruption())
      factory_->HandleBackingStoreCorruption(backing_store_->origin_url(),
                                             error);
    return;
  }
  if (!found) {
    transaction->Abort(IndexedDBDatabaseError(
        blink::WebIDBDatabaseExceptionUnknownError,
        "Internal error setting index keys for object store."));
    return;
  }

  ScopedVector<IndexWriter> index_writers;
  base::string16 error_message;
  bool obeys_constraints = false;
  DCHECK(metadata_.object_stores.find(object_store_id) !=
         metadata_.object_stores.end());
  const IndexedDBObjectStoreMetadata& object_store_metadata =
      metadata_.object_stores[object_store_id];
  bool backing_store_success = MakeIndexWriters(transaction,
                                                backing_store_,
                                                id(),
                                                object_store_metadata,
                                                *primary_key,
                                                false,
                                                index_keys,
                                                &index_writers,
                                                &error_message,
                                                &obeys_constraints);
  if (!backing_store_success) {
    transaction->Abort(IndexedDBDatabaseError(
        blink::WebIDBDatabaseExceptionUnknownError,
        "Internal error: backing store error updating index keys."));
    return;
  }
  if (!obeys_constraints) {
    transaction->Abort(IndexedDBDatabaseError(
        blink::WebIDBDatabaseExceptionConstraintError, error_message));
    return;
  }

  for (size_t i = 0; i < index_writers.size(); ++i) {
    IndexWriter* index_writer = index_writers[i];
    index_writer->WriteIndexKeys(record_identifier,
                                 backing_store_,
                                 transaction->BackingStoreTransaction(),
                                 id(),
                                 object_store_id);
  }
}

void IndexedDBDatabase::SetIndexesReady(int64 transaction_id,
                                        int64,
                                        const std::vector<int64>& index_ids) {
  IDB_TRACE("IndexedDBDatabase::SetIndexesReady");
  IndexedDBTransaction* transaction = GetTransaction(transaction_id);
  if (!transaction)
    return;
  DCHECK_EQ(transaction->mode(), indexed_db::TRANSACTION_VERSION_CHANGE);

  transaction->ScheduleTask(
      IndexedDBDatabase::PREEMPTIVE_TASK,
      base::Bind(&IndexedDBDatabase::SetIndexesReadyOperation,
                 this,
                 index_ids.size()));
}

void IndexedDBDatabase::SetIndexesReadyOperation(
    size_t index_count,
    IndexedDBTransaction* transaction) {
  IDB_TRACE("IndexedDBDatabase::SetIndexesReadyOperation");
  for (size_t i = 0; i < index_count; ++i)
    transaction->DidCompletePreemptiveEvent();
}

struct IndexedDBDatabase::OpenCursorOperationParams {
  OpenCursorOperationParams() {}
  int64 object_store_id;
  int64 index_id;
  scoped_ptr<IndexedDBKeyRange> key_range;
  indexed_db::CursorDirection direction;
  indexed_db::CursorType cursor_type;
  IndexedDBDatabase::TaskType task_type;
  scoped_refptr<IndexedDBCallbacks> callbacks;

 private:
  DISALLOW_COPY_AND_ASSIGN(OpenCursorOperationParams);
};

void IndexedDBDatabase::OpenCursor(
    int64 transaction_id,
    int64 object_store_id,
    int64 index_id,
    scoped_ptr<IndexedDBKeyRange> key_range,
    indexed_db::CursorDirection direction,
    bool key_only,
    TaskType task_type,
    scoped_refptr<IndexedDBCallbacks> callbacks) {
  IDB_TRACE("IndexedDBDatabase::OpenCursor");
  IndexedDBTransaction* transaction = GetTransaction(transaction_id);
  if (!transaction)
    return;

  if (!ValidateObjectStoreIdAndOptionalIndexId(object_store_id, index_id))
    return;

  scoped_ptr<OpenCursorOperationParams> params(new OpenCursorOperationParams());
  params->object_store_id = object_store_id;
  params->index_id = index_id;
  params->key_range = key_range.Pass();
  params->direction = direction;
  params->cursor_type =
      key_only ? indexed_db::CURSOR_KEY_ONLY : indexed_db::CURSOR_KEY_AND_VALUE;
  params->task_type = task_type;
  params->callbacks = callbacks;
  transaction->ScheduleTask(base::Bind(
      &IndexedDBDatabase::OpenCursorOperation, this, base::Passed(&params)));
}

void IndexedDBDatabase::OpenCursorOperation(
    scoped_ptr<OpenCursorOperationParams> params,
    IndexedDBTransaction* transaction) {
  IDB_TRACE("IndexedDBDatabase::OpenCursorOperation");

  // The frontend has begun indexing, so this pauses the transaction
  // until the indexing is complete. This can't happen any earlier
  // because we don't want to switch to early mode in case multiple
  // indexes are being created in a row, with Put()'s in between.
  if (params->task_type == IndexedDBDatabase::PREEMPTIVE_TASK)
    transaction->AddPreemptiveEvent();

  scoped_ptr<IndexedDBBackingStore::Cursor> backing_store_cursor;
  if (params->index_id == IndexedDBIndexMetadata::kInvalidId) {
    if (params->cursor_type == indexed_db::CURSOR_KEY_ONLY) {
      DCHECK_EQ(params->task_type, IndexedDBDatabase::NORMAL_TASK);
      backing_store_cursor = backing_store_->OpenObjectStoreKeyCursor(
          transaction->BackingStoreTransaction(),
          id(),
          params->object_store_id,
          *params->key_range,
          params->direction);
    } else {
      backing_store_cursor = backing_store_->OpenObjectStoreCursor(
          transaction->BackingStoreTransaction(),
          id(),
          params->object_store_id,
          *params->key_range,
        params->direction);
    }
  } else {
    DCHECK_EQ(params->task_type, IndexedDBDatabase::NORMAL_TASK);
    if (params->cursor_type == indexed_db::CURSOR_KEY_ONLY) {
      backing_store_cursor = backing_store_->OpenIndexKeyCursor(
          transaction->BackingStoreTransaction(),
          id(),
          params->object_store_id,
          params->index_id,
          *params->key_range,
          params->direction);
    } else {
      backing_store_cursor = backing_store_->OpenIndexCursor(
          transaction->BackingStoreTransaction(),
          id(),
          params->object_store_id,
          params->index_id,
          *params->key_range,
          params->direction);
    }
  }

  if (!backing_store_cursor) {
    params->callbacks->OnSuccess(static_cast<IndexedDBValue*>(NULL));
    return;
  }

  scoped_refptr<IndexedDBCursor> cursor =
      new IndexedDBCursor(backing_store_cursor.Pass(),
                          params->cursor_type,
                          params->task_type,
                          transaction);
  params->callbacks->OnSuccess(
      cursor, cursor->key(), cursor->primary_key(), cursor->Value());
}

void IndexedDBDatabase::Count(int64 transaction_id,
                              int64 object_store_id,
                              int64 index_id,
                              scoped_ptr<IndexedDBKeyRange> key_range,
                              scoped_refptr<IndexedDBCallbacks> callbacks) {
  IDB_TRACE("IndexedDBDatabase::Count");
  IndexedDBTransaction* transaction = GetTransaction(transaction_id);
  if (!transaction)
    return;

  if (!ValidateObjectStoreIdAndOptionalIndexId(object_store_id, index_id))
    return;

  transaction->ScheduleTask(base::Bind(&IndexedDBDatabase::CountOperation,
                                       this,
                                       object_store_id,
                                       index_id,
                                       base::Passed(&key_range),
                                       callbacks));
}

void IndexedDBDatabase::CountOperation(
    int64 object_store_id,
    int64 index_id,
    scoped_ptr<IndexedDBKeyRange> key_range,
    scoped_refptr<IndexedDBCallbacks> callbacks,
    IndexedDBTransaction* transaction) {
  IDB_TRACE("IndexedDBDatabase::CountOperation");
  uint32 count = 0;
  scoped_ptr<IndexedDBBackingStore::Cursor> backing_store_cursor;

  if (index_id == IndexedDBIndexMetadata::kInvalidId) {
    backing_store_cursor = backing_store_->OpenObjectStoreKeyCursor(
        transaction->BackingStoreTransaction(),
        id(),
        object_store_id,
        *key_range,
        indexed_db::CURSOR_NEXT);
  } else {
    backing_store_cursor = backing_store_->OpenIndexKeyCursor(
        transaction->BackingStoreTransaction(),
        id(),
        object_store_id,
        index_id,
        *key_range,
        indexed_db::CURSOR_NEXT);
  }
  if (!backing_store_cursor) {
    callbacks->OnSuccess(count);
    return;
  }

  do {
    ++count;
  } while (backing_store_cursor->Continue());

  callbacks->OnSuccess(count);
}

void IndexedDBDatabase::DeleteRange(
    int64 transaction_id,
    int64 object_store_id,
    scoped_ptr<IndexedDBKeyRange> key_range,
    scoped_refptr<IndexedDBCallbacks> callbacks) {
  IDB_TRACE("IndexedDBDatabase::DeleteRange");
  IndexedDBTransaction* transaction = GetTransaction(transaction_id);
  if (!transaction)
    return;
  DCHECK_NE(transaction->mode(), indexed_db::TRANSACTION_READ_ONLY);

  if (!ValidateObjectStoreId(object_store_id))
    return;

  transaction->ScheduleTask(base::Bind(&IndexedDBDatabase::DeleteRangeOperation,
                                       this,
                                       object_store_id,
                                       base::Passed(&key_range),
                                       callbacks));
}

void IndexedDBDatabase::DeleteRangeOperation(
    int64 object_store_id,
    scoped_ptr<IndexedDBKeyRange> key_range,
    scoped_refptr<IndexedDBCallbacks> callbacks,
    IndexedDBTransaction* transaction) {
  IDB_TRACE("IndexedDBDatabase::DeleteRangeOperation");
  scoped_ptr<IndexedDBBackingStore::Cursor> backing_store_cursor =
      backing_store_->OpenObjectStoreCursor(
          transaction->BackingStoreTransaction(),
          id(),
          object_store_id,
          *key_range,
          indexed_db::CURSOR_NEXT);
  if (backing_store_cursor) {
    do {
      if (!backing_store_->DeleteRecord(
                               transaction->BackingStoreTransaction(),
                               id(),
                               object_store_id,
                               backing_store_cursor->record_identifier())
               .ok()) {
        callbacks->OnError(
            IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError,
                                   "Internal error deleting data in range"));
        return;
      }
    } while (backing_store_cursor->Continue());
  }

  callbacks->OnSuccess();
}

void IndexedDBDatabase::Clear(int64 transaction_id,
                              int64 object_store_id,
                              scoped_refptr<IndexedDBCallbacks> callbacks) {
  IDB_TRACE("IndexedDBDatabase::Clear");
  IndexedDBTransaction* transaction = GetTransaction(transaction_id);
  if (!transaction)
    return;
  DCHECK_NE(transaction->mode(), indexed_db::TRANSACTION_READ_ONLY);

  if (!ValidateObjectStoreId(object_store_id))
    return;

  transaction->ScheduleTask(base::Bind(
      &IndexedDBDatabase::ClearOperation, this, object_store_id, callbacks));
}

void IndexedDBDatabase::ClearOperation(
    int64 object_store_id,
    scoped_refptr<IndexedDBCallbacks> callbacks,
    IndexedDBTransaction* transaction) {
  IDB_TRACE("IndexedDBDatabase::ObjectStoreClearOperation");
  if (!backing_store_->ClearObjectStore(transaction->BackingStoreTransaction(),
                                        id(),
                                        object_store_id).ok()) {
    callbacks->OnError(
        IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError,
                               "Internal error clearing object store"));
    return;
  }
  callbacks->OnSuccess();
}

void IndexedDBDatabase::DeleteObjectStoreOperation(
    const IndexedDBObjectStoreMetadata& object_store_metadata,
    IndexedDBTransaction* transaction) {
  IDB_TRACE("IndexedDBDatabase::DeleteObjectStoreOperation");
  leveldb::Status s =
      backing_store_->DeleteObjectStore(transaction->BackingStoreTransaction(),
                                        transaction->database()->id(),
                                        object_store_metadata.id);
  if (!s.ok()) {
    base::string16 error_string =
        ASCIIToUTF16("Internal error deleting object store '") +
        object_store_metadata.name + ASCIIToUTF16("'.");
    IndexedDBDatabaseError error(blink::WebIDBDatabaseExceptionUnknownError,
                                 error_string);
    transaction->Abort(error);
    if (s.IsCorruption())
      factory_->HandleBackingStoreCorruption(backing_store_->origin_url(),
                                             error);
  }
}

void IndexedDBDatabase::VersionChangeOperation(
    int64 version,
    scoped_refptr<IndexedDBCallbacks> callbacks,
    scoped_ptr<IndexedDBConnection> connection,
    IndexedDBTransaction* transaction) {
  IDB_TRACE("IndexedDBDatabase::VersionChangeOperation");
  int64 old_version = metadata_.int_version;
  DCHECK_GT(version, old_version);
  metadata_.int_version = version;
  if (!backing_store_->UpdateIDBDatabaseIntVersion(
          transaction->BackingStoreTransaction(),
          id(),
          metadata_.int_version)) {
    IndexedDBDatabaseError error(
        blink::WebIDBDatabaseExceptionUnknownError,
        ASCIIToUTF16(
            "Internal error writing data to stable storage when "
            "updating version."));
    callbacks->OnError(error);
    transaction->Abort(error);
    return;
  }
  DCHECK(!pending_second_half_open_);
  pending_second_half_open_.reset(
      new PendingSuccessCall(callbacks, connection.get(), version));
  callbacks->OnUpgradeNeeded(old_version, connection.Pass(), metadata());
}

void IndexedDBDatabase::TransactionFinished(IndexedDBTransaction* transaction,
                                            bool committed) {
  DCHECK(transactions_.find(transaction->id()) != transactions_.end());
  DCHECK_EQ(transactions_[transaction->id()], transaction);
  transactions_.erase(transaction->id());

  if (transaction->mode() == indexed_db::TRANSACTION_VERSION_CHANGE) {
    if (pending_second_half_open_) {
      if (committed) {
        DCHECK_EQ(pending_second_half_open_->version(), metadata_.int_version);
        DCHECK(metadata_.id != kInvalidId);

        // Connection was already minted for OnUpgradeNeeded callback.
        scoped_ptr<IndexedDBConnection> connection;
        pending_second_half_open_->callbacks()->OnSuccess(connection.Pass(),
                                                          this->metadata());
      } else {
        pending_second_half_open_->callbacks()->OnError(
            IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionAbortError,
                                   "Version change transaction was aborted in "
                                   "upgradeneeded event handler."));
      }
      pending_second_half_open_.reset();
    }

    // Connection queue is now unblocked.
    ProcessPendingCalls();
  }
}

void IndexedDBDatabase::TransactionCommitFailed() {
  // Factory may be null in unit tests.
  if (!factory_)
    return;
  factory_->HandleBackingStoreFailure(backing_store_->origin_url());
}

size_t IndexedDBDatabase::ConnectionCount() const {
  // This does not include pending open calls, as those should not block version
  // changes and deletes.
  return connections_.size();
}

size_t IndexedDBDatabase::PendingOpenCount() const {
  return pending_open_calls_.size();
}

size_t IndexedDBDatabase::PendingUpgradeCount() const {
  return pending_run_version_change_transaction_call_ ? 1 : 0;
}

size_t IndexedDBDatabase::RunningUpgradeCount() const {
  return pending_second_half_open_ ? 1 : 0;
}

size_t IndexedDBDatabase::PendingDeleteCount() const {
  return pending_delete_calls_.size();
}

void IndexedDBDatabase::ProcessPendingCalls() {
  if (pending_run_version_change_transaction_call_ && ConnectionCount() == 1) {
    DCHECK(pending_run_version_change_transaction_call_->version() >
           metadata_.int_version);
    scoped_ptr<PendingUpgradeCall> pending_call =
        pending_run_version_change_transaction_call_.Pass();
    RunVersionChangeTransactionFinal(pending_call->callbacks(),
                                     pending_call->ReleaseConnection(),
                                     pending_call->transaction_id(),
                                     pending_call->version());
    DCHECK_EQ(1u, ConnectionCount());
    // Fall through would be a no-op, since transaction must complete
    // asynchronously.
    DCHECK(IsDeleteDatabaseBlocked());
    DCHECK(IsOpenConnectionBlocked());
    return;
  }

  if (!IsDeleteDatabaseBlocked()) {
    PendingDeleteCallList pending_delete_calls;
    pending_delete_calls_.swap(pending_delete_calls);
    while (!pending_delete_calls.empty()) {
      // Only the first delete call will delete the database, but each must fire
      // callbacks.
      scoped_ptr<PendingDeleteCall> pending_delete_call(
          pending_delete_calls.front());
      pending_delete_calls.pop_front();
      DeleteDatabaseFinal(pending_delete_call->callbacks());
    }
    // delete_database_final should never re-queue calls.
    DCHECK(pending_delete_calls_.empty());
    // Fall through when complete, as pending opens may be unblocked.
  }

  if (!IsOpenConnectionBlocked()) {
    PendingOpenCallList pending_open_calls;
    pending_open_calls_.swap(pending_open_calls);
    while (!pending_open_calls.empty()) {
      OpenConnection(pending_open_calls.front());
      pending_open_calls.pop_front();
    }
  }
}

void IndexedDBDatabase::CreateTransaction(
    int64 transaction_id,
    IndexedDBConnection* connection,
    const std::vector<int64>& object_store_ids,
    uint16 mode) {

  DCHECK(connections_.count(connection));
  DCHECK(transactions_.find(transaction_id) == transactions_.end());
  if (transactions_.find(transaction_id) != transactions_.end())
    return;

  // The transaction will add itself to this database's coordinator, which
  // manages the lifetime of the object.
  TransactionCreated(new IndexedDBTransaction(
      transaction_id,
      connection->callbacks(),
      std::set<int64>(object_store_ids.begin(), object_store_ids.end()),
      static_cast<indexed_db::TransactionMode>(mode),
      this,
      new IndexedDBBackingStore::Transaction(backing_store_)));
}

void IndexedDBDatabase::TransactionCreated(IndexedDBTransaction* transaction) {
  transactions_[transaction->id()] = transaction;
}

bool IndexedDBDatabase::IsOpenConnectionBlocked() const {
  return !pending_delete_calls_.empty() ||
         transaction_coordinator_.IsRunningVersionChangeTransaction() ||
         pending_run_version_change_transaction_call_;
}

void IndexedDBDatabase::OpenConnection(
    const IndexedDBPendingConnection& connection) {
  DCHECK(backing_store_);

  // TODO(jsbell): Should have a priority queue so that higher version
  // requests are processed first. http://crbug.com/225850
  if (IsOpenConnectionBlocked()) {
    // The backing store only detects data loss when it is first opened. The
    // presence of existing connections means we didn't even check for data loss
    // so there'd better not be any.
    DCHECK_NE(blink::WebIDBDataLossTotal, connection.callbacks->data_loss());
    pending_open_calls_.push_back(connection);
    return;
  }

  if (metadata_.id == kInvalidId) {
    // The database was deleted then immediately re-opened; OpenInternal()
    // recreates it in the backing store.
    if (OpenInternal().ok()) {
      DCHECK_EQ(IndexedDBDatabaseMetadata::NO_INT_VERSION,
                metadata_.int_version);
    } else {
      base::string16 message;
      if (connection.version == IndexedDBDatabaseMetadata::NO_INT_VERSION) {
        message = ASCIIToUTF16(
            "Internal error opening database with no version specified.");
      } else {
        message =
            ASCIIToUTF16("Internal error opening database with version ") +
            Int64ToString16(connection.version);
      }
      connection.callbacks->OnError(IndexedDBDatabaseError(
          blink::WebIDBDatabaseExceptionUnknownError, message));
      return;
    }
  }

  // We infer that the database didn't exist from its lack of either type of
  // version.
  bool is_new_database =
      metadata_.version == kNoStringVersion &&
      metadata_.int_version == IndexedDBDatabaseMetadata::NO_INT_VERSION;

  if (connection.version == IndexedDBDatabaseMetadata::DEFAULT_INT_VERSION) {
    // For unit tests only - skip upgrade steps. Calling from script with
    // DEFAULT_INT_VERSION throws exception.
    // TODO(jsbell): DCHECK that not in unit tests.
    DCHECK(is_new_database);
    connection.callbacks->OnSuccess(
        CreateConnection(connection.database_callbacks,
                         connection.child_process_id),
        this->metadata());
    return;
  }

  // We may need to change the version.
  int64 local_version = connection.version;
  if (local_version == IndexedDBDatabaseMetadata::NO_INT_VERSION) {
    if (!is_new_database) {
      connection.callbacks->OnSuccess(
          CreateConnection(connection.database_callbacks,
                           connection.child_process_id),
          this->metadata());
      return;
    }
    // Spec says: If no version is specified and no database exists, set
    // database version to 1.
    local_version = 1;
  }

  if (local_version > metadata_.int_version) {
    RunVersionChangeTransaction(connection.callbacks,
                                CreateConnection(connection.database_callbacks,
                                                 connection.child_process_id),
                                connection.transaction_id,
                                local_version);
    return;
  }
  if (local_version < metadata_.int_version) {
    connection.callbacks->OnError(IndexedDBDatabaseError(
        blink::WebIDBDatabaseExceptionVersionError,
        ASCIIToUTF16("The requested version (") +
            Int64ToString16(local_version) +
            ASCIIToUTF16(") is less than the existing version (") +
            Int64ToString16(metadata_.int_version) + ASCIIToUTF16(").")));
    return;
  }
  DCHECK_EQ(local_version, metadata_.int_version);
  connection.callbacks->OnSuccess(
      CreateConnection(connection.database_callbacks,
                       connection.child_process_id),
      this->metadata());
}

void IndexedDBDatabase::RunVersionChangeTransaction(
    scoped_refptr<IndexedDBCallbacks> callbacks,
    scoped_ptr<IndexedDBConnection> connection,
    int64 transaction_id,
    int64 requested_version) {

  DCHECK(callbacks);
  DCHECK(connections_.count(connection.get()));
  if (ConnectionCount() > 1) {
    DCHECK_NE(blink::WebIDBDataLossTotal, callbacks->data_loss());
    // Front end ensures the event is not fired at connections that have
    // close_pending set.
    for (ConnectionSet::const_iterator it = connections_.begin();
         it != connections_.end();
         ++it) {
      if (*it != connection.get()) {
        (*it)->callbacks()->OnVersionChange(metadata_.int_version,
                                            requested_version);
      }
    }
    // TODO(jsbell): Remove the call to OnBlocked and instead wait
    // until the frontend tells us that all the "versionchange" events
    // have been delivered.  http://crbug.com/100123
    callbacks->OnBlocked(metadata_.int_version);

    DCHECK(!pending_run_version_change_transaction_call_);
    pending_run_version_change_transaction_call_.reset(new PendingUpgradeCall(
        callbacks, connection.Pass(), transaction_id, requested_version));
    return;
  }
  RunVersionChangeTransactionFinal(
      callbacks, connection.Pass(), transaction_id, requested_version);
}

void IndexedDBDatabase::RunVersionChangeTransactionFinal(
    scoped_refptr<IndexedDBCallbacks> callbacks,
    scoped_ptr<IndexedDBConnection> connection,
    int64 transaction_id,
    int64 requested_version) {

  std::vector<int64> object_store_ids;
  CreateTransaction(transaction_id,
                    connection.get(),
                    object_store_ids,
                    indexed_db::TRANSACTION_VERSION_CHANGE);

  transactions_[transaction_id]
      ->ScheduleTask(base::Bind(&IndexedDBDatabase::VersionChangeOperation,
                                this,
                                requested_version,
                                callbacks,
                                base::Passed(&connection)),
                     base::Bind(&IndexedDBDatabase::VersionChangeAbortOperation,
                                this,
                                metadata_.version,
                                metadata_.int_version));

  DCHECK(!pending_second_half_open_);
}

void IndexedDBDatabase::DeleteDatabase(
    scoped_refptr<IndexedDBCallbacks> callbacks) {

  if (IsDeleteDatabaseBlocked()) {
    for (ConnectionSet::const_iterator it = connections_.begin();
         it != connections_.end();
         ++it) {
      // Front end ensures the event is not fired at connections that have
      // close_pending set.
      (*it)->callbacks()->OnVersionChange(
          metadata_.int_version, IndexedDBDatabaseMetadata::NO_INT_VERSION);
    }
    // TODO(jsbell): Only fire OnBlocked if there are open
    // connections after the VersionChangeEvents are received, not
    // just set up to fire.  http://crbug.com/100123
    callbacks->OnBlocked(metadata_.int_version);
    pending_delete_calls_.push_back(new PendingDeleteCall(callbacks));
    return;
  }
  DeleteDatabaseFinal(callbacks);
}

bool IndexedDBDatabase::IsDeleteDatabaseBlocked() const {
  return !!ConnectionCount();
}

void IndexedDBDatabase::DeleteDatabaseFinal(
    scoped_refptr<IndexedDBCallbacks> callbacks) {
  DCHECK(!IsDeleteDatabaseBlocked());
  DCHECK(backing_store_);
  if (!backing_store_->DeleteDatabase(metadata_.name).ok()) {
    callbacks->OnError(
        IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError,
                               "Internal error deleting database."));
    return;
  }
  metadata_.version = kNoStringVersion;
  metadata_.id = kInvalidId;
  metadata_.int_version = IndexedDBDatabaseMetadata::NO_INT_VERSION;
  metadata_.object_stores.clear();
  callbacks->OnSuccess();
  if (factory_)
    factory_->DatabaseDeleted(identifier_);
}

void IndexedDBDatabase::ForceClose() {
  // IndexedDBConnection::ForceClose() may delete this database, so hold ref.
  scoped_refptr<IndexedDBDatabase> protect(this);
  ConnectionSet::const_iterator it = connections_.begin();
  while (it != connections_.end()) {
    IndexedDBConnection* connection = *it++;
    connection->ForceClose();
  }
  DCHECK(connections_.empty());
}

void IndexedDBDatabase::Close(IndexedDBConnection* connection, bool forced) {
  DCHECK(connections_.count(connection));
  DCHECK(connection->IsConnected());
  DCHECK(connection->database() == this);

  // Abort outstanding transactions from the closing connection. This
  // can not happen if the close is requested by the connection itself
  // as the front-end defers the close until all transactions are
  // complete, but can occur on process termination or forced close.
  {
    TransactionMap transactions(transactions_);
    for (TransactionMap::const_iterator it = transactions.begin(),
                                        end = transactions.end();
         it != end;
         ++it) {
      if (it->second->connection() == connection->callbacks())
        it->second->Abort(
            IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError,
                                   "Connection is closing."));
    }
  }

  connections_.erase(connection);
  if (pending_second_half_open_ &&
      pending_second_half_open_->connection() == connection) {
    pending_second_half_open_->callbacks()->OnError(
        IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionAbortError,
                               "The connection was closed."));
    pending_second_half_open_.reset();
  }

  ProcessPendingCalls();

  // TODO(jsbell): Add a test for the pending_open_calls_ cases below.
  if (!ConnectionCount() && !pending_open_calls_.size() &&
      !pending_delete_calls_.size()) {
    DCHECK(transactions_.empty());

    const GURL origin_url = backing_store_->origin_url();
    backing_store_ = NULL;

    // factory_ should only be null in unit tests.
    // TODO(jsbell): DCHECK(factory_ || !in_unit_tests) - somehow.
    if (factory_) {
      factory_->ReleaseDatabase(identifier_, forced);
      factory_ = NULL;
    }
  }
}

void IndexedDBDatabase::CreateObjectStoreAbortOperation(
    int64 object_store_id,
    IndexedDBTransaction* transaction) {
  IDB_TRACE("IndexedDBDatabase::CreateObjectStoreAbortOperation");
  DCHECK(!transaction);
  RemoveObjectStore(object_store_id);
}

void IndexedDBDatabase::DeleteObjectStoreAbortOperation(
    const IndexedDBObjectStoreMetadata& object_store_metadata,
    IndexedDBTransaction* transaction) {
  IDB_TRACE("IndexedDBDatabase::DeleteObjectStoreAbortOperation");
  DCHECK(!transaction);
  AddObjectStore(object_store_metadata,
                 IndexedDBObjectStoreMetadata::kInvalidId);
}

void IndexedDBDatabase::VersionChangeAbortOperation(
    const base::string16& previous_version,
    int64 previous_int_version,
    IndexedDBTransaction* transaction) {
  IDB_TRACE("IndexedDBDatabase::VersionChangeAbortOperation");
  DCHECK(!transaction);
  metadata_.version = previous_version;
  metadata_.int_version = previous_int_version;
}

}  // namespace content

/* [<][>][^][v][top][bottom][index][help] */