diff options
Diffstat (limited to 'content/browser/indexed_db/indexed_db_database_impl.cc')
-rw-r--r-- | content/browser/indexed_db/indexed_db_database_impl.cc | 1823 |
1 files changed, 1823 insertions, 0 deletions
diff --git a/content/browser/indexed_db/indexed_db_database_impl.cc b/content/browser/indexed_db/indexed_db_database_impl.cc new file mode 100644 index 0000000..dcde2d0 --- /dev/null +++ b/content/browser/indexed_db/indexed_db_database_impl.cc @@ -0,0 +1,1823 @@ +// 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_impl.h" + +#include <math.h> +#include <vector> + +#include "base/auto_reset.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "content/browser/indexed_db/indexed_db_backing_store.h" +#include "content/browser/indexed_db/indexed_db_cursor_impl.h" +#include "content/browser/indexed_db/indexed_db_factory_impl.h" +#include "content/browser/indexed_db/indexed_db_index_writer.h" +#include "content/browser/indexed_db/indexed_db_tracing.h" +#include "content/browser/indexed_db/indexed_db_transaction.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/Source/Platform/chromium/public/WebIDBDatabaseException.h" + +using base::Int64ToString16; +using WebKit::WebIDBKey; + +namespace content { + +class CreateObjectStoreOperation : public IndexedDBTransaction::Operation { + public: + CreateObjectStoreOperation( + scoped_refptr<IndexedDBBackingStore> backing_store, + const IndexedDBObjectStoreMetadata& object_store_metadata) + : backing_store_(backing_store), + object_store_metadata_(object_store_metadata) {} + virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE; + + private: + const scoped_refptr<IndexedDBBackingStore> backing_store_; + const IndexedDBObjectStoreMetadata object_store_metadata_; +}; + +class DeleteObjectStoreOperation : public IndexedDBTransaction::Operation { + public: + DeleteObjectStoreOperation( + scoped_refptr<IndexedDBBackingStore> backing_store, + const IndexedDBObjectStoreMetadata& object_store_metadata) + : backing_store_(backing_store), + object_store_metadata_(object_store_metadata) {} + virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE; + + private: + const scoped_refptr<IndexedDBBackingStore> backing_store_; + const IndexedDBObjectStoreMetadata object_store_metadata_; +}; + +class IndexedDBDatabaseImpl::VersionChangeOperation + : public IndexedDBTransaction::Operation { + public: + VersionChangeOperation( + scoped_refptr<IndexedDBDatabaseImpl> database, + int64 transaction_id, + int64 version, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks, + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> database_callbacks) + : database_(database), + transaction_id_(transaction_id), + version_(version), + callbacks_(callbacks), + database_callbacks_(database_callbacks) {} + virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE; + + private: + scoped_refptr<IndexedDBDatabaseImpl> database_; + int64 transaction_id_; + int64 version_; + scoped_refptr<IndexedDBCallbacksWrapper> callbacks_; + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> database_callbacks_; +}; + +class CreateObjectStoreAbortOperation : public IndexedDBTransaction::Operation { + public: + CreateObjectStoreAbortOperation(scoped_refptr<IndexedDBDatabaseImpl> database, + int64 object_store_id) + : database_(database), object_store_id_(object_store_id) {} + virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE; + + private: + const scoped_refptr<IndexedDBDatabaseImpl> database_; + const int64 object_store_id_; +}; + +class DeleteObjectStoreAbortOperation : public IndexedDBTransaction::Operation { + public: + DeleteObjectStoreAbortOperation( + scoped_refptr<IndexedDBDatabaseImpl> database, + const IndexedDBObjectStoreMetadata& object_store_metadata) + : database_(database), object_store_metadata_(object_store_metadata) {} + virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE; + + private: + scoped_refptr<IndexedDBDatabaseImpl> database_; + IndexedDBObjectStoreMetadata object_store_metadata_; +}; + +class IndexedDBDatabaseImpl::VersionChangeAbortOperation + : public IndexedDBTransaction::Operation { + public: + VersionChangeAbortOperation(scoped_refptr<IndexedDBDatabaseImpl> database, + const string16& previous_version, + int64 previous_int_version) + : database_(database), + previous_version_(previous_version), + previous_int_version_(previous_int_version) {} + virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE; + + private: + scoped_refptr<IndexedDBDatabaseImpl> database_; + string16 previous_version_; + int64 previous_int_version_; +}; + +class CreateIndexOperation : public IndexedDBTransaction::Operation { + public: + CreateIndexOperation(scoped_refptr<IndexedDBBackingStore> backing_store, + int64 object_store_id, + const IndexedDBIndexMetadata& index_metadata) + : backing_store_(backing_store), + object_store_id_(object_store_id), + index_metadata_(index_metadata) {} + virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE; + + private: + const scoped_refptr<IndexedDBBackingStore> backing_store_; + const int64 object_store_id_; + const IndexedDBIndexMetadata index_metadata_; +}; + +class DeleteIndexOperation : public IndexedDBTransaction::Operation { + public: + DeleteIndexOperation(scoped_refptr<IndexedDBBackingStore> backing_store, + int64 object_store_id, + const IndexedDBIndexMetadata& index_metadata) + : backing_store_(backing_store), + object_store_id_(object_store_id), + index_metadata_(index_metadata) {} + virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE; + + private: + const scoped_refptr<IndexedDBBackingStore> backing_store_; + const int64 object_store_id_; + const IndexedDBIndexMetadata index_metadata_; +}; + +class CreateIndexAbortOperation : public IndexedDBTransaction::Operation { + public: + CreateIndexAbortOperation(scoped_refptr<IndexedDBDatabaseImpl> database, + int64 object_store_id, + int64 index_id) + : database_(database), + object_store_id_(object_store_id), + index_id_(index_id) {} + virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE; + + private: + const scoped_refptr<IndexedDBDatabaseImpl> database_; + const int64 object_store_id_; + const int64 index_id_; +}; + +class DeleteIndexAbortOperation : public IndexedDBTransaction::Operation { + public: + DeleteIndexAbortOperation(scoped_refptr<IndexedDBDatabaseImpl> database, + int64 object_store_id, + const IndexedDBIndexMetadata& index_metadata) + : database_(database), + object_store_id_(object_store_id), + index_metadata_(index_metadata) {} + virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE; + + private: + const scoped_refptr<IndexedDBDatabaseImpl> database_; + const int64 object_store_id_; + const IndexedDBIndexMetadata index_metadata_; +}; + +class GetOperation : public IndexedDBTransaction::Operation { + public: + GetOperation(scoped_refptr<IndexedDBBackingStore> backing_store, + const IndexedDBDatabaseMetadata& metadata, + int64 object_store_id, + int64 index_id, + scoped_ptr<IndexedDBKeyRange> key_range, + indexed_db::CursorType cursor_type, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) + : backing_store_(backing_store), + database_id_(metadata.id), + object_store_id_(object_store_id), + index_id_(index_id), + key_path_(metadata.object_stores.find(object_store_id) + ->second.key_path), + auto_increment_(metadata.object_stores.find(object_store_id) + ->second.auto_increment), + key_range_(key_range.Pass()), + cursor_type_(cursor_type), + callbacks_(callbacks) { + DCHECK(metadata.object_stores.find(object_store_id) != + metadata.object_stores.end()); + DCHECK(metadata.object_stores.find(object_store_id)->second.id == + object_store_id); + } + virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE; + + private: + const scoped_refptr<IndexedDBBackingStore> backing_store_; + const int64 database_id_; + const int64 object_store_id_; + const int64 index_id_; + const IndexedDBKeyPath key_path_; + const bool auto_increment_; + const scoped_ptr<IndexedDBKeyRange> key_range_; + const indexed_db::CursorType cursor_type_; + const scoped_refptr<IndexedDBCallbacksWrapper> callbacks_; +}; + +class PutOperation : public IndexedDBTransaction::Operation { + public: + PutOperation(scoped_refptr<IndexedDBBackingStore> backing_store, + int64 database_id, + const IndexedDBObjectStoreMetadata& object_store, + std::vector<char>* value, + scoped_ptr<IndexedDBKey> key, + IndexedDBDatabase::PutMode put_mode, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks, + const std::vector<int64>& index_ids, + const std::vector<IndexedDBDatabase::IndexKeys>& index_keys) + : backing_store_(backing_store), + database_id_(database_id), + object_store_(object_store), + key_(key.Pass()), + put_mode_(put_mode), + callbacks_(callbacks), + index_ids_(index_ids), + index_keys_(index_keys) { + value_.swap(*value); + } + virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE; + + private: + const scoped_refptr<IndexedDBBackingStore> backing_store_; + const int64 database_id_; + const IndexedDBObjectStoreMetadata object_store_; + std::vector<char> value_; + scoped_ptr<IndexedDBKey> key_; + const IndexedDBDatabase::PutMode put_mode_; + const scoped_refptr<IndexedDBCallbacksWrapper> callbacks_; + const std::vector<int64> index_ids_; + const std::vector<IndexedDBDatabase::IndexKeys> index_keys_; +}; + +class SetIndexesReadyOperation : public IndexedDBTransaction::Operation { + public: + explicit SetIndexesReadyOperation(size_t index_count) + : index_count_(index_count) {} + virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE; + + private: + const size_t index_count_; +}; + +class OpenCursorOperation : public IndexedDBTransaction::Operation { + public: + OpenCursorOperation(scoped_refptr<IndexedDBBackingStore> backing_store, + int64 database_id, + 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<IndexedDBCallbacksWrapper> callbacks) + : backing_store_(backing_store), + database_id_(database_id), + object_store_id_(object_store_id), + index_id_(index_id), + key_range_(key_range.Pass()), + direction_(direction), + cursor_type_(cursor_type), + task_type_(task_type), + callbacks_(callbacks) {} + virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE; + + private: + const scoped_refptr<IndexedDBBackingStore> backing_store_; + const int64 database_id_; + const int64 object_store_id_; + const int64 index_id_; + const scoped_ptr<IndexedDBKeyRange> key_range_; + const indexed_db::CursorDirection direction_; + const indexed_db::CursorType cursor_type_; + const IndexedDBDatabase::TaskType task_type_; + const scoped_refptr<IndexedDBCallbacksWrapper> callbacks_; +}; + +class CountOperation : public IndexedDBTransaction::Operation { + public: + CountOperation(scoped_refptr<IndexedDBBackingStore> backing_store, + int64 database_id, + int64 object_store_id, + int64 index_id, + scoped_ptr<IndexedDBKeyRange> key_range, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) + : backing_store_(backing_store), + database_id_(database_id), + object_store_id_(object_store_id), + index_id_(index_id), + key_range_(key_range.Pass()), + callbacks_(callbacks) {} + virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE; + + private: + const scoped_refptr<IndexedDBBackingStore> backing_store_; + const int64 database_id_; + const int64 object_store_id_; + const int64 index_id_; + const scoped_ptr<IndexedDBKeyRange> key_range_; + const scoped_refptr<IndexedDBCallbacksWrapper> callbacks_; +}; + +class DeleteRangeOperation : public IndexedDBTransaction::Operation { + public: + DeleteRangeOperation(scoped_refptr<IndexedDBBackingStore> backing_store, + int64 database_id, + int64 object_store_id, + scoped_ptr<IndexedDBKeyRange> key_range, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) + : backing_store_(backing_store), + database_id_(database_id), + object_store_id_(object_store_id), + key_range_(key_range.Pass()), + callbacks_(callbacks) {} + virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE; + + private: + const scoped_refptr<IndexedDBBackingStore> backing_store_; + const int64 database_id_; + const int64 object_store_id_; + const scoped_ptr<IndexedDBKeyRange> key_range_; + const scoped_refptr<IndexedDBCallbacksWrapper> callbacks_; +}; + +class ClearOperation : public IndexedDBTransaction::Operation { + public: + ClearOperation(scoped_refptr<IndexedDBBackingStore> backing_store, + int64 database_id, + int64 object_store_id, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) + : backing_store_(backing_store), + database_id_(database_id), + object_store_id_(object_store_id), + callbacks_(callbacks) {} + virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE; + + private: + const scoped_refptr<IndexedDBBackingStore> backing_store_; + const int64 database_id_; + const int64 object_store_id_; + const scoped_refptr<IndexedDBCallbacksWrapper> callbacks_; +}; + +class IndexedDBDatabaseImpl::PendingOpenCall { + public: + PendingOpenCall( + scoped_refptr<IndexedDBCallbacksWrapper> callbacks, + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> database_callbacks, + int64 transaction_id, + int64 version) + : callbacks_(callbacks), + database_callbacks_(database_callbacks), + version_(version), + transaction_id_(transaction_id) {} + scoped_refptr<IndexedDBCallbacksWrapper> Callbacks() { return callbacks_; } + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> DatabaseCallbacks() { + return database_callbacks_; + } + int64 Version() { return version_; } + int64 TransactionId() const { return transaction_id_; } + + private: + scoped_refptr<IndexedDBCallbacksWrapper> callbacks_; + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> database_callbacks_; + int64 version_; + const int64 transaction_id_; +}; + +class IndexedDBDatabaseImpl::PendingDeleteCall { + public: + explicit PendingDeleteCall(scoped_refptr<IndexedDBCallbacksWrapper> callbacks) + : callbacks_(callbacks) {} + scoped_refptr<IndexedDBCallbacksWrapper> Callbacks() { return callbacks_; } + + private: + scoped_refptr<IndexedDBCallbacksWrapper> callbacks_; +}; + +scoped_refptr<IndexedDBDatabaseImpl> IndexedDBDatabaseImpl::Create( + const string16& name, + IndexedDBBackingStore* database, + IndexedDBFactoryImpl* factory, + const string16& unique_identifier) { + scoped_refptr<IndexedDBDatabaseImpl> backend = + new IndexedDBDatabaseImpl(name, database, factory, unique_identifier); + if (!backend->OpenInternal()) + return 0; + return backend; +} + +namespace { +const base::string16::value_type kNoStringVersion[] = {0}; +} + +IndexedDBDatabaseImpl::IndexedDBDatabaseImpl( + const string16& name, + IndexedDBBackingStore* backing_store, + IndexedDBFactoryImpl* factory, + const string16& unique_identifier) + : backing_store_(backing_store), + metadata_(name, + kInvalidId, + kNoStringVersion, + IndexedDBDatabaseMetadata::NO_INT_VERSION, + kInvalidId), + identifier_(unique_identifier), + factory_(factory), + running_version_change_transaction_(NULL), + closing_connection_(false) { + DCHECK(!metadata_.name.empty()); +} + +void IndexedDBDatabaseImpl::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 IndexedDBDatabaseImpl::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 IndexedDBDatabaseImpl::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 IndexedDBDatabaseImpl::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; +} + +bool IndexedDBDatabaseImpl::OpenInternal() { + bool success = false; + bool ok = backing_store_->GetIDBDatabaseMetaData( + metadata_.name, &metadata_, success); + DCHECK(success == (metadata_.id != kInvalidId)) << "success = " << success + << " id_ = " << metadata_.id; + if (!ok) + return false; + if (success) + return backing_store_->GetObjectStores(metadata_.id, + &metadata_.object_stores); + + return backing_store_->CreateIDBDatabaseMetaData( + metadata_.name, metadata_.version, metadata_.int_version, metadata_.id); +} + +IndexedDBDatabaseImpl::~IndexedDBDatabaseImpl() { + DCHECK(transactions_.empty()); + DCHECK(pending_open_calls_.empty()); + DCHECK(pending_delete_calls_.empty()); +} + +scoped_refptr<IndexedDBBackingStore> IndexedDBDatabaseImpl::BackingStore() + const { + return backing_store_; +} + +void IndexedDBDatabaseImpl::CreateObjectStore(int64 transaction_id, + int64 object_store_id, + const string16& name, + const IndexedDBKeyPath& key_path, + bool auto_increment) { + IDB_TRACE("IndexedDBDatabaseImpl::create_object_store"); + TransactionMap::const_iterator trans_iterator = + transactions_.find(transaction_id); + if (trans_iterator == transactions_.end()) + return; + IndexedDBTransaction* transaction = trans_iterator->second; + DCHECK_EQ(transaction->mode(), indexed_db::TRANSACTION_VERSION_CHANGE); + + DCHECK(metadata_.object_stores.find(object_store_id) == + metadata_.object_stores.end()); + IndexedDBObjectStoreMetadata object_store_metadata( + name, + object_store_id, + key_path, + auto_increment, + IndexedDBDatabase::kMinimumIndexId); + + transaction->ScheduleTask( + new CreateObjectStoreOperation(backing_store_, object_store_metadata), + new CreateObjectStoreAbortOperation(this, object_store_id)); + + AddObjectStore(object_store_metadata, object_store_id); +} + +void CreateObjectStoreOperation::Perform(IndexedDBTransaction* transaction) { + IDB_TRACE("CreateObjectStoreOperation"); + if (!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)) { + string16 error_string = + ASCIIToUTF16("Internal error creating object store '") + + object_store_metadata_.name + ASCIIToUTF16("'."); + + scoped_refptr<IndexedDBDatabaseError> error = + IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, error_string); + transaction->Abort(error); + return; + } +} + +void IndexedDBDatabaseImpl::DeleteObjectStore(int64 transaction_id, + int64 object_store_id) { + IDB_TRACE("IndexedDBDatabaseImpl::delete_object_store"); + TransactionMap::const_iterator trans_iterator = + transactions_.find(transaction_id); + if (trans_iterator == transactions_.end()) + return; + IndexedDBTransaction* transaction = trans_iterator->second; + DCHECK_EQ(transaction->mode(), indexed_db::TRANSACTION_VERSION_CHANGE); + + DCHECK(metadata_.object_stores.find(object_store_id) != + metadata_.object_stores.end()); + const IndexedDBObjectStoreMetadata& object_store_metadata = + metadata_.object_stores[object_store_id]; + + transaction->ScheduleTask( + new DeleteObjectStoreOperation(backing_store_, object_store_metadata), + new DeleteObjectStoreAbortOperation(this, object_store_metadata)); + RemoveObjectStore(object_store_id); +} + +void IndexedDBDatabaseImpl::CreateIndex(int64 transaction_id, + int64 object_store_id, + int64 index_id, + const string16& name, + const IndexedDBKeyPath& key_path, + bool unique, + bool multi_entry) { + IDB_TRACE("IndexedDBDatabaseImpl::create_index"); + TransactionMap::const_iterator trans_iterator = + transactions_.find(transaction_id); + if (trans_iterator == transactions_.end()) + return; + IndexedDBTransaction* transaction = trans_iterator->second; + DCHECK_EQ(transaction->mode(), indexed_db::TRANSACTION_VERSION_CHANGE); + + DCHECK(metadata_.object_stores.find(object_store_id) != + metadata_.object_stores.end()); + const IndexedDBObjectStoreMetadata object_store = + metadata_.object_stores[object_store_id]; + + DCHECK(object_store.indexes.find(index_id) == object_store.indexes.end()); + const IndexedDBIndexMetadata index_metadata( + name, index_id, key_path, unique, multi_entry); + + transaction->ScheduleTask( + new CreateIndexOperation(backing_store_, object_store_id, index_metadata), + new CreateIndexAbortOperation(this, object_store_id, index_id)); + + AddIndex(object_store_id, index_metadata, index_id); +} + +void CreateIndexOperation::Perform(IndexedDBTransaction* transaction) { + IDB_TRACE("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)) { + string16 error_string = ASCIIToUTF16("Internal error creating index '") + + index_metadata_.name + ASCIIToUTF16("'."); + transaction->Abort(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, error_string)); + return; + } +} + +void CreateIndexAbortOperation::Perform(IndexedDBTransaction* transaction) { + IDB_TRACE("CreateIndexAbortOperation"); + DCHECK(!transaction); + database_->RemoveIndex(object_store_id_, index_id_); +} + +void IndexedDBDatabaseImpl::DeleteIndex(int64 transaction_id, + int64 object_store_id, + int64 index_id) { + IDB_TRACE("IndexedDBDatabaseImpl::delete_index"); + TransactionMap::const_iterator trans_iterator = + transactions_.find(transaction_id); + if (trans_iterator == transactions_.end()) + return; + IndexedDBTransaction* transaction = trans_iterator->second; + DCHECK_EQ(transaction->mode(), indexed_db::TRANSACTION_VERSION_CHANGE); + + 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()); + const IndexedDBIndexMetadata& index_metadata = object_store.indexes[index_id]; + + transaction->ScheduleTask( + new DeleteIndexOperation(backing_store_, object_store_id, index_metadata), + new DeleteIndexAbortOperation(this, object_store_id, index_metadata)); + + RemoveIndex(object_store_id, index_id); +} + +void DeleteIndexOperation::Perform(IndexedDBTransaction* transaction) { + IDB_TRACE("DeleteIndexOperation"); + bool ok = backing_store_->DeleteIndex(transaction->BackingStoreTransaction(), + transaction->database()->id(), + object_store_id_, + index_metadata_.id); + if (!ok) { + string16 error_string = ASCIIToUTF16("Internal error deleting index '") + + index_metadata_.name + ASCIIToUTF16("'."); + scoped_refptr<IndexedDBDatabaseError> error = + IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, error_string); + transaction->Abort(error); + } +} + +void DeleteIndexAbortOperation::Perform(IndexedDBTransaction* transaction) { + IDB_TRACE("DeleteIndexAbortOperation"); + DCHECK(!transaction); + database_->AddIndex( + object_store_id_, index_metadata_, IndexedDBIndexMetadata::kInvalidId); +} + +void IndexedDBDatabaseImpl::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. + if (transactions_.find(transaction_id) != transactions_.end()) + transactions_[transaction_id]->Commit(); +} + +void IndexedDBDatabaseImpl::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. + if (transactions_.find(transaction_id) != transactions_.end()) + transactions_[transaction_id]->Abort(); +} + +void IndexedDBDatabaseImpl::Abort(int64 transaction_id, + scoped_refptr<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. + if (transactions_.find(transaction_id) != transactions_.end()) + transactions_[transaction_id]->Abort(error); +} + +void IndexedDBDatabaseImpl::Get( + int64 transaction_id, + int64 object_store_id, + int64 index_id, + scoped_ptr<IndexedDBKeyRange> key_range, + bool key_only, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) { + IDB_TRACE("IndexedDBDatabaseImpl::get"); + TransactionMap::const_iterator trans_iterator = + transactions_.find(transaction_id); + if (trans_iterator == transactions_.end()) + return; + IndexedDBTransaction* transaction = trans_iterator->second; + + transaction->ScheduleTask(new GetOperation( + backing_store_, + metadata_, + object_store_id, + index_id, + key_range.Pass(), + key_only ? indexed_db::CURSOR_KEY_ONLY : indexed_db::CURSOR_KEY_AND_VALUE, + callbacks)); +} + +void GetOperation::Perform(IndexedDBTransaction* transaction) { + IDB_TRACE("GetOperation"); + + 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(), + database_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(), + database_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(), + database_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; + bool ok; + if (index_id_ == IndexedDBIndexMetadata::kInvalidId) { + // Object Store Retrieval Operation + std::vector<char> value; + ok = backing_store_->GetRecord(transaction->BackingStoreTransaction(), + database_id_, + object_store_id_, + *key, + value); + if (!ok) { + callbacks_->OnError(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, + ASCIIToUTF16("Internal error in get_record."))); + return; + } + + if (value.empty()) { + callbacks_->OnSuccess(); + return; + } + + if (auto_increment_ && !key_path_.IsNull()) { + callbacks_->OnSuccess(&value, *key, key_path_); + return; + } + + callbacks_->OnSuccess(&value); + return; + } + + // From here we are dealing only with indexes. + ok = backing_store_->GetPrimaryKeyViaIndex( + transaction->BackingStoreTransaction(), + database_id_, + object_store_id_, + index_id_, + *key, + &primary_key); + if (!ok) { + callbacks_->OnError(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, + ASCIIToUTF16("Internal error in get_primary_key_via_index."))); + 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 + std::vector<char> value; + ok = backing_store_->GetRecord(transaction->BackingStoreTransaction(), + database_id_, + object_store_id_, + *primary_key, + value); + if (!ok) { + callbacks_->OnError(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, + ASCIIToUTF16("Internal error in get_record."))); + return; + } + + if (value.empty()) { + callbacks_->OnSuccess(); + return; + } + if (auto_increment_ && !key_path_.IsNull()) { + callbacks_->OnSuccess(&value, *primary_key, key_path_); + return; + } + callbacks_->OnSuccess(&value); +} + +static scoped_ptr<IndexedDBKey> GenerateKey( + scoped_refptr<IndexedDBBackingStore> backing_store, + scoped_refptr<IndexedDBTransaction> transaction, + int64 database_id, + int64 object_store_id) { + const int64 max_generator_value = + 9007199254740992LL; // Maximum integer storable as ECMAScript number. + int64 current_number; + bool ok = backing_store->GetKeyGeneratorCurrentNumber( + transaction->BackingStoreTransaction(), + database_id, + object_store_id, + current_number); + if (!ok) { + LOG(ERROR) << "Failed to get_key_generator_current_number"; + 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, WebIDBKey::NumberType)); +} + +static bool UpdateKeyGenerator( + scoped_refptr<IndexedDBBackingStore> backing_store, + scoped_refptr<IndexedDBTransaction> transaction, + int64 database_id, + int64 object_store_id, + const IndexedDBKey* key, + bool check_current) { + DCHECK(key && key->type() == WebIDBKey::NumberType); + return backing_store->MaybeUpdateKeyGeneratorCurrentNumber( + transaction->BackingStoreTransaction(), + database_id, + object_store_id, + static_cast<int64>(floor(key->number())) + 1, + check_current); +} + +void IndexedDBDatabaseImpl::Put( + int64 transaction_id, + int64 object_store_id, + std::vector<char>* value, + scoped_ptr<IndexedDBKey> key, + PutMode put_mode, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks, + const std::vector<int64>& index_ids, + const std::vector<IndexKeys>& index_keys) { + IDB_TRACE("IndexedDBDatabaseImpl::put"); + TransactionMap::const_iterator trans_iterator = + transactions_.find(transaction_id); + if (trans_iterator == transactions_.end()) + return; + IndexedDBTransaction* transaction = trans_iterator->second; + DCHECK_NE(transaction->mode(), indexed_db::TRANSACTION_READ_ONLY); + + const IndexedDBObjectStoreMetadata object_store_metadata = + metadata_.object_stores[object_store_id]; + + DCHECK(key); + DCHECK(object_store_metadata.auto_increment || key->IsValid()); + transaction->ScheduleTask(new PutOperation(backing_store_, + id(), + object_store_metadata, + value, + key.Pass(), + put_mode, + callbacks, + index_ids, + index_keys)); +} + +void PutOperation::Perform(IndexedDBTransaction* transaction) { + IDB_TRACE("PutOperation"); + DCHECK_NE(transaction->mode(), indexed_db::TRANSACTION_READ_ONLY); + DCHECK_EQ(index_ids_.size(), index_keys_.size()); + bool key_was_generated = false; + + scoped_ptr<IndexedDBKey> key; + if (put_mode_ != IndexedDBDatabase::CURSOR_UPDATE && + object_store_.auto_increment && !key_->IsValid()) { + scoped_ptr<IndexedDBKey> auto_inc_key = GenerateKey( + backing_store_, transaction, database_id_, object_store_.id); + key_was_generated = true; + if (!auto_inc_key->IsValid()) { + callbacks_->OnError(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionConstraintError, + ASCIIToUTF16("Maximum key generator value reached."))); + return; + } + key = auto_inc_key.Pass(); + } else { + key = key_.Pass(); + } + + DCHECK(key->IsValid()); + + IndexedDBBackingStore::RecordIdentifier record_identifier; + if (put_mode_ == IndexedDBDatabase::ADD_ONLY) { + bool found = false; + bool ok = backing_store_->KeyExistsInObjectStore( + transaction->BackingStoreTransaction(), + database_id_, + object_store_.id, + *key.get(), + &record_identifier, + found); + if (!ok) { + callbacks_->OnError(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, + ASCIIToUTF16("Internal error checking key existence."))); + return; + } + if (found) { + callbacks_->OnError(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionConstraintError, + ASCIIToUTF16("Key already exists in the object store."))); + return; + } + } + + ScopedVector<IndexedDBObjectStoreImpl::IndexWriter> index_writers; + string16 error_message; + bool obeys_constraints = false; + bool backing_store_success = + IndexedDBObjectStoreImpl::MakeIndexWriters(transaction, + backing_store_.get(), + database_id_, + object_store_, + *key, + key_was_generated, + index_ids_, + index_keys_, + &index_writers, + &error_message, + &obeys_constraints); + if (!backing_store_success) { + callbacks_->OnError(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, + ASCIIToUTF16( + "Internal error: backing store error updating index keys."))); + return; + } + if (!obeys_constraints) { + callbacks_->OnError(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionConstraintError, error_message)); + return; + } + + // Before this point, don't do any mutation. After this point, rollback the + // transaction in case of error. + backing_store_success = + backing_store_->PutRecord(transaction->BackingStoreTransaction(), + database_id_, + object_store_.id, + *key.get(), + value_, + &record_identifier); + if (!backing_store_success) { + callbacks_->OnError(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, + ASCIIToUTF16( + "Internal error: backing store error performing put/add."))); + return; + } + + for (size_t i = 0; i < index_writers.size(); ++i) { + IndexedDBObjectStoreImpl::IndexWriter* index_writer = index_writers[i]; + index_writer->WriteIndexKeys(record_identifier, + backing_store_, + transaction->BackingStoreTransaction(), + database_id_, + object_store_.id); + } + + if (object_store_.auto_increment && + put_mode_ != IndexedDBDatabase::CURSOR_UPDATE && + key->type() == WebIDBKey::NumberType) { + bool ok = UpdateKeyGenerator(backing_store_, + transaction, + database_id_, + object_store_.id, + key.get(), + !key_was_generated); + if (!ok) { + callbacks_->OnError(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, + ASCIIToUTF16("Internal error updating key generator."))); + return; + } + } + + callbacks_->OnSuccess(*key); +} + +void IndexedDBDatabaseImpl::SetIndexKeys( + int64 transaction_id, + int64 object_store_id, + scoped_ptr<IndexedDBKey> primary_key, + const std::vector<int64>& index_ids, + const std::vector<IndexKeys>& index_keys) { + IDB_TRACE("IndexedDBDatabaseImpl::set_index_keys"); + TransactionMap::const_iterator trans_iterator = + transactions_.find(transaction_id); + if (trans_iterator == transactions_.end()) + return; + IndexedDBTransaction* transaction = trans_iterator->second; + DCHECK_EQ(transaction->mode(), indexed_db::TRANSACTION_VERSION_CHANGE); + + scoped_refptr<IndexedDBBackingStore> store = BackingStore(); + // TODO(jsbell): This method could be asynchronous, but we need to + // evaluate if it's worth the extra complexity. + IndexedDBBackingStore::RecordIdentifier record_identifier; + bool found = false; + bool ok = + store->KeyExistsInObjectStore(transaction->BackingStoreTransaction(), + metadata_.id, + object_store_id, + *primary_key, + &record_identifier, + found); + if (!ok) { + transaction->Abort(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, + ASCIIToUTF16("Internal error setting index keys."))); + return; + } + if (!found) { + scoped_refptr<IndexedDBDatabaseError> error = + IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, + ASCIIToUTF16( + "Internal error setting index keys for object store.")); + transaction->Abort(error); + return; + } + + ScopedVector<IndexedDBObjectStoreImpl::IndexWriter> index_writers; + 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 = + IndexedDBObjectStoreImpl::MakeIndexWriters(transaction, + store.get(), + id(), + object_store_metadata, + *primary_key, + false, + index_ids, + index_keys, + &index_writers, + &error_message, + &obeys_constraints); + if (!backing_store_success) { + transaction->Abort(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, + ASCIIToUTF16( + "Internal error: backing store error updating index keys."))); + return; + } + if (!obeys_constraints) { + transaction->Abort(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionConstraintError, error_message)); + return; + } + + for (size_t i = 0; i < index_writers.size(); ++i) { + IndexedDBObjectStoreImpl::IndexWriter* index_writer = index_writers[i]; + index_writer->WriteIndexKeys(record_identifier, + store.get(), + transaction->BackingStoreTransaction(), + id(), + object_store_id); + } +} + +void IndexedDBDatabaseImpl::SetIndexesReady( + int64 transaction_id, + int64, + const std::vector<int64>& index_ids) { + IDB_TRACE("IndexedDBObjectStoreImpl::set_indexes_ready"); + + TransactionMap::const_iterator trans_iterator = + transactions_.find(transaction_id); + if (trans_iterator == transactions_.end()) + return; + IndexedDBTransaction* transaction = trans_iterator->second; + + transaction->ScheduleTask(IndexedDBDatabase::PREEMPTIVE_TASK, + new SetIndexesReadyOperation(index_ids.size())); +} + +void SetIndexesReadyOperation::Perform(IndexedDBTransaction* transaction) { + IDB_TRACE("SetIndexesReadyOperation"); + for (size_t i = 0; i < index_count_; ++i) + transaction->DidCompletePreemptiveEvent(); +} + +void IndexedDBDatabaseImpl::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<IndexedDBCallbacksWrapper> callbacks) { + IDB_TRACE("IndexedDBDatabaseImpl::open_cursor"); + TransactionMap::const_iterator trans_iterator = + transactions_.find(transaction_id); + if (trans_iterator == transactions_.end()) + return; + IndexedDBTransaction* transaction = trans_iterator->second; + + transaction->ScheduleTask(new OpenCursorOperation( + backing_store_, + id(), + object_store_id, + index_id, + key_range.Pass(), + direction, + key_only ? indexed_db::CURSOR_KEY_ONLY : indexed_db::CURSOR_KEY_AND_VALUE, + task_type, + callbacks)); +} + +void OpenCursorOperation::Perform(IndexedDBTransaction* transaction) { + IDB_TRACE("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 (task_type_ == IndexedDBDatabase::PREEMPTIVE_TASK) + transaction->AddPreemptiveEvent(); + + scoped_ptr<IndexedDBBackingStore::Cursor> backing_store_cursor; + if (index_id_ == IndexedDBIndexMetadata::kInvalidId) { + DCHECK_NE(cursor_type_, indexed_db::CURSOR_KEY_ONLY); + backing_store_cursor = backing_store_->OpenObjectStoreCursor( + transaction->BackingStoreTransaction(), + database_id_, + object_store_id_, + *key_range_, + direction_); + } else { + DCHECK_EQ(task_type_, IndexedDBDatabase::NORMAL_TASK); + if (cursor_type_ == indexed_db::CURSOR_KEY_ONLY) { + backing_store_cursor = backing_store_->OpenIndexKeyCursor( + transaction->BackingStoreTransaction(), + database_id_, + object_store_id_, + index_id_, + *key_range_, + direction_); + } else { + backing_store_cursor = backing_store_->OpenIndexCursor( + transaction->BackingStoreTransaction(), + database_id_, + object_store_id_, + index_id_, + *key_range_, + direction_); + } + } + + if (!backing_store_cursor) { + callbacks_->OnSuccess(static_cast<std::vector<char>*>(NULL)); + return; + } + + IndexedDBDatabase::TaskType task_type( + static_cast<IndexedDBDatabase::TaskType>(task_type_)); + scoped_refptr<IndexedDBCursorImpl> cursor = + IndexedDBCursorImpl::Create(backing_store_cursor.Pass(), + cursor_type_, + task_type, + transaction, + object_store_id_); + callbacks_->OnSuccess( + cursor, cursor->key(), cursor->primary_key(), cursor->Value()); +} + +void IndexedDBDatabaseImpl::Count( + int64 transaction_id, + int64 object_store_id, + int64 index_id, + scoped_ptr<IndexedDBKeyRange> key_range, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) { + IDB_TRACE("IndexedDBDatabaseImpl::count"); + TransactionMap::const_iterator trans_iterator = + transactions_.find(transaction_id); + if (trans_iterator == transactions_.end()) + return; + IndexedDBTransaction* transaction = trans_iterator->second; + + DCHECK(metadata_.object_stores.find(object_store_id) != + metadata_.object_stores.end()); + transaction->ScheduleTask(new CountOperation(backing_store_, + id(), + object_store_id, + index_id, + key_range.Pass(), + callbacks)); +} + +void CountOperation::Perform(IndexedDBTransaction* transaction) { + IDB_TRACE("CountOperation"); + uint32 count = 0; + scoped_ptr<IndexedDBBackingStore::Cursor> backing_store_cursor; + + if (index_id_ == IndexedDBIndexMetadata::kInvalidId) { + backing_store_cursor = backing_store_->OpenObjectStoreKeyCursor( + transaction->BackingStoreTransaction(), + database_id_, + object_store_id_, + *key_range_, + indexed_db::CURSOR_NEXT); + } else { + backing_store_cursor = backing_store_->OpenIndexKeyCursor( + transaction->BackingStoreTransaction(), + database_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->ContinueFunction(0)); + + callbacks_->OnSuccess(count); +} + +void IndexedDBDatabaseImpl::DeleteRange( + int64 transaction_id, + int64 object_store_id, + scoped_ptr<IndexedDBKeyRange> key_range, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) { + IDB_TRACE("IndexedDBDatabaseImpl::delete_range"); + TransactionMap::const_iterator trans_iterator = + transactions_.find(transaction_id); + if (trans_iterator == transactions_.end()) + return; + IndexedDBTransaction* transaction = trans_iterator->second; + + transaction->ScheduleTask(new DeleteRangeOperation( + backing_store_, id(), object_store_id, key_range.Pass(), callbacks)); +} + +void DeleteRangeOperation::Perform(IndexedDBTransaction* transaction) { + IDB_TRACE("DeleteRangeOperation"); + scoped_ptr<IndexedDBBackingStore::Cursor> backing_store_cursor = + backing_store_->OpenObjectStoreCursor( + transaction->BackingStoreTransaction(), + database_id_, + object_store_id_, + *key_range_, + indexed_db::CURSOR_NEXT); + if (backing_store_cursor) { + do { + if (!backing_store_->DeleteRecord( + transaction->BackingStoreTransaction(), + database_id_, + object_store_id_, + backing_store_cursor->record_identifier())) { + callbacks_->OnError(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, + ASCIIToUTF16("Internal error deleting data in range"))); + return; + } + } while (backing_store_cursor->ContinueFunction(0)); + } + + callbacks_->OnSuccess(); +} + +void IndexedDBDatabaseImpl::Clear( + int64 transaction_id, + int64 object_store_id, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) { + IDB_TRACE("IndexedDBDatabaseImpl::clear"); + TransactionMap::const_iterator trans_iterator = + transactions_.find(transaction_id); + if (trans_iterator == transactions_.end()) + return; + IndexedDBTransaction* transaction = trans_iterator->second; + DCHECK_NE(transaction->mode(), indexed_db::TRANSACTION_READ_ONLY); + + transaction->ScheduleTask( + new ClearOperation(backing_store_, id(), object_store_id, callbacks)); +} + +void ClearOperation::Perform(IndexedDBTransaction* transaction) { + IDB_TRACE("ObjectStoreClearOperation"); + if (!backing_store_->ClearObjectStore(transaction->BackingStoreTransaction(), + database_id_, + object_store_id_)) { + callbacks_->OnError(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, + ASCIIToUTF16("Internal error clearing object store"))); + return; + } + callbacks_->OnSuccess(); +} + +void DeleteObjectStoreOperation::Perform(IndexedDBTransaction* transaction) { + IDB_TRACE("DeleteObjectStoreOperation"); + bool ok = + backing_store_->DeleteObjectStore(transaction->BackingStoreTransaction(), + transaction->database()->id(), + object_store_metadata_.id); + if (!ok) { + string16 error_string = + ASCIIToUTF16("Internal error deleting object store '") + + object_store_metadata_.name + ASCIIToUTF16("'."); + scoped_refptr<IndexedDBDatabaseError> error = + IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, error_string); + transaction->Abort(error); + } +} + +void IndexedDBDatabaseImpl::VersionChangeOperation::Perform( + IndexedDBTransaction* transaction) { + IDB_TRACE("VersionChangeOperation"); + int64 database_id = database_->id(); + int64 old_version = database_->metadata_.int_version; + DCHECK_GT(version_, old_version); + database_->metadata_.int_version = version_; + if (!database_->backing_store_->UpdateIDBDatabaseIntVersion( + transaction->BackingStoreTransaction(), + database_id, + database_->metadata_.int_version)) { + scoped_refptr<IndexedDBDatabaseError> error = + IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, + ASCIIToUTF16("Internal error writing data to stable storage when " + "updating version.")); + callbacks_->OnError(error); + transaction->Abort(error); + return; + } + DCHECK(!database_->pending_second_half_open_); + database_->pending_second_half_open_.reset(new PendingOpenCall( + callbacks_, database_callbacks_, transaction_id_, version_)); + callbacks_->OnUpgradeNeeded(old_version, database_, database_->metadata()); +} + +void IndexedDBDatabaseImpl::TransactionStarted( + IndexedDBTransaction* transaction) { + + if (transaction->mode() == indexed_db::TRANSACTION_VERSION_CHANGE) { + DCHECK(!running_version_change_transaction_); + running_version_change_transaction_ = transaction; + } +} + +void IndexedDBDatabaseImpl::TransactionFinished( + IndexedDBTransaction* transaction) { + + 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) { + DCHECK_EQ(transaction, running_version_change_transaction_); + running_version_change_transaction_ = NULL; + } +} + +void IndexedDBDatabaseImpl::TransactionFinishedAndAbortFired( + IndexedDBTransaction* transaction) { + if (transaction->mode() == indexed_db::TRANSACTION_VERSION_CHANGE) { + if (pending_second_half_open_) { + pending_second_half_open_->Callbacks() + ->OnError(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionAbortError, + ASCIIToUTF16("Version change transaction was aborted in " + "upgradeneeded event handler."))); + pending_second_half_open_.reset(); + } + ProcessPendingCalls(); + } +} + +void IndexedDBDatabaseImpl::TransactionFinishedAndCompleteFired( + IndexedDBTransaction* transaction) { + if (transaction->mode() == indexed_db::TRANSACTION_VERSION_CHANGE) { + DCHECK(pending_second_half_open_); + if (pending_second_half_open_) { + DCHECK_EQ(pending_second_half_open_->Version(), metadata_.int_version); + DCHECK(metadata_.id != kInvalidId); + pending_second_half_open_->Callbacks()->OnSuccess(this, this->metadata()); + pending_second_half_open_.reset(); + } + ProcessPendingCalls(); + } +} + +size_t IndexedDBDatabaseImpl::ConnectionCount() const { + // This does not include pending open calls, as those should not block version + // changes and deletes. + return database_callbacks_set_.size(); +} + +void IndexedDBDatabaseImpl::ProcessPendingCalls() { + if (pending_second_half_open_) { + DCHECK_EQ(pending_second_half_open_->Version(), metadata_.int_version); + DCHECK(metadata_.id != kInvalidId); + scoped_ptr<PendingOpenCall> pending_call = pending_second_half_open_.Pass(); + pending_call->Callbacks()->OnSuccess(this, this->metadata()); + // Fall through when complete, as pending opens may be unblocked. + } + + if (pending_run_version_change_transaction_call_ && ConnectionCount() == 1) { + DCHECK(pending_run_version_change_transaction_call_->Version() > + metadata_.int_version); + scoped_ptr<PendingOpenCall> pending_call = + pending_run_version_change_transaction_call_.Pass(); + RunVersionChangeTransactionFinal(pending_call->Callbacks(), + pending_call->DatabaseCallbacks(), + pending_call->TransactionId(), + pending_call->Version()); + DCHECK_EQ(static_cast<size_t>(1), 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()) { + scoped_ptr<PendingOpenCall> pending_open_call(pending_open_calls.front()); + pending_open_calls.pop_front(); + OpenConnection(pending_open_call->Callbacks(), + pending_open_call->DatabaseCallbacks(), + pending_open_call->TransactionId(), + pending_open_call->Version()); + } + } +} + +void IndexedDBDatabaseImpl::CreateTransaction( + int64 transaction_id, + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> callbacks, + const std::vector<int64>& object_store_ids, + uint16 mode) { + + DCHECK(database_callbacks_set_.has(callbacks)); + + scoped_refptr<IndexedDBTransaction> transaction = + IndexedDBTransaction::Create( + transaction_id, + callbacks, + object_store_ids, + static_cast<indexed_db::TransactionMode>(mode), + this); + DCHECK(transactions_.find(transaction_id) == transactions_.end()); + transactions_[transaction_id] = transaction; +} + +bool IndexedDBDatabaseImpl::IsOpenConnectionBlocked() const { + return !pending_delete_calls_.empty() || + running_version_change_transaction_ || + pending_run_version_change_transaction_call_; +} + +void IndexedDBDatabaseImpl::OpenConnection( + scoped_refptr<IndexedDBCallbacksWrapper> callbacks, + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> database_callbacks, + int64 transaction_id, + int64 version) { + DCHECK(backing_store_.get()); + + // TODO(jsbell): Should have a priority queue so that higher version + // requests are processed first. http://crbug.com/225850 + if (IsOpenConnectionBlocked()) { + pending_open_calls_.push_back(new PendingOpenCall( + callbacks, database_callbacks, transaction_id, version)); + return; + } + + if (metadata_.id == kInvalidId) { + // The database was deleted then immediately re-opened; OpenInternal() + // recreates it in the backing store. + if (OpenInternal()) { + DCHECK_EQ(metadata_.int_version, + IndexedDBDatabaseMetadata::NO_INT_VERSION); + } else { + string16 message; + scoped_refptr<IndexedDBDatabaseError> error; + if (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(version); + callbacks->OnError(IndexedDBDatabaseError::Create( + WebKit::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 (version == IndexedDBDatabaseMetadata::DEFAULT_INT_VERSION) { + // For unit tests only - skip upgrade steps. Calling from script with + // DEFAULT_INT_VERSION throws exception. + // TODO(jsbell): Assert that we're executing a unit test. + DCHECK(is_new_database); + database_callbacks_set_.insert(database_callbacks); + callbacks->OnSuccess(this, this->metadata()); + return; + } + + if (version == IndexedDBDatabaseMetadata::NO_INT_VERSION) { + if (!is_new_database) { + database_callbacks_set_.insert(database_callbacks); + callbacks->OnSuccess(this, this->metadata()); + return; + } + // Spec says: If no version is specified and no database exists, set + // database version to 1. + version = 1; + } + + if (version > metadata_.int_version) { + database_callbacks_set_.insert(database_callbacks); + RunVersionChangeTransaction( + callbacks, database_callbacks, transaction_id, version); + return; + } + if (version < metadata_.int_version) { + callbacks->OnError(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionVersionError, + ASCIIToUTF16("The requested version (") + Int64ToString16(version) + + ASCIIToUTF16(") is less than the existing version (") + + Int64ToString16(metadata_.int_version) + ASCIIToUTF16(")."))); + return; + } + DCHECK_EQ(version, metadata_.int_version); + database_callbacks_set_.insert(database_callbacks); + callbacks->OnSuccess(this, this->metadata()); +} + +void IndexedDBDatabaseImpl::RunVersionChangeTransaction( + scoped_refptr<IndexedDBCallbacksWrapper> callbacks, + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> database_callbacks, + int64 transaction_id, + int64 requested_version) { + + DCHECK(callbacks); + DCHECK(database_callbacks_set_.has(database_callbacks)); + if (ConnectionCount() > 1) { + // Front end ensures the event is not fired at connections that have + // close_pending set. + for (DatabaseCallbacksSet::const_iterator it = + database_callbacks_set_.begin(); + it != database_callbacks_set_.end(); + ++it) { + if (*it != database_callbacks.get()) + (*it)->OnVersionChange(metadata_.int_version, requested_version); + } + // TODO(jsbell): Remove the call to on_blocked 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 PendingOpenCall( + callbacks, database_callbacks, transaction_id, requested_version)); + return; + } + RunVersionChangeTransactionFinal( + callbacks, database_callbacks, transaction_id, requested_version); +} + +void IndexedDBDatabaseImpl::RunVersionChangeTransactionFinal( + scoped_refptr<IndexedDBCallbacksWrapper> callbacks, + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> database_callbacks, + int64 transaction_id, + int64 requested_version) { + + std::vector<int64> object_store_ids; + CreateTransaction(transaction_id, + database_callbacks, + object_store_ids, + indexed_db::TRANSACTION_VERSION_CHANGE); + scoped_refptr<IndexedDBTransaction> transaction = + transactions_[transaction_id]; + + transaction->ScheduleTask( + new VersionChangeOperation(this, + transaction_id, + requested_version, + callbacks, + database_callbacks), + new VersionChangeAbortOperation( + this, metadata_.version, metadata_.int_version)); + + DCHECK(!pending_second_half_open_); +} + +void IndexedDBDatabaseImpl::DeleteDatabase( + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) { + + if (IsDeleteDatabaseBlocked()) { + for (DatabaseCallbacksSet::const_iterator it = + database_callbacks_set_.begin(); + it != database_callbacks_set_.end(); + ++it) { + // Front end ensures the event is not fired at connections that have + // close_pending set. + (*it)->OnVersionChange(metadata_.int_version, + IndexedDBDatabaseMetadata::NO_INT_VERSION); + } + // TODO(jsbell): Only fire on_blocked 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 IndexedDBDatabaseImpl::IsDeleteDatabaseBlocked() const { + return !!ConnectionCount(); +} + +void IndexedDBDatabaseImpl::DeleteDatabaseFinal( + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) { + DCHECK(!IsDeleteDatabaseBlocked()); + DCHECK(backing_store_); + if (!backing_store_->DeleteDatabase(metadata_.name)) { + callbacks->OnError(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, + ASCIIToUTF16("Internal error deleting database."))); + return; + } + metadata_.version = kNoStringVersion; + metadata_.id = kInvalidId; + metadata_.int_version = IndexedDBDatabaseMetadata::NO_INT_VERSION; + metadata_.object_stores.clear(); + callbacks->OnSuccess(); +} + +void IndexedDBDatabaseImpl::Close( + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> callbacks) { + DCHECK(callbacks); + DCHECK(database_callbacks_set_.has(callbacks)); + + // Close 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, so something unusual has happened e.g. unexpected + // process termination. + { + TransactionMap transactions(transactions_); + for (TransactionMap::const_iterator it = transactions.begin(), + end = transactions.end(); + it != end; + ++it) { + if (it->second->connection() == callbacks) + it->second->Abort(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, + ASCIIToUTF16("Connection is closing."))); + } + } + + database_callbacks_set_.erase(callbacks); + if (pending_second_half_open_ && + pending_second_half_open_->DatabaseCallbacks() == callbacks) { + pending_second_half_open_->Callbacks() + ->OnError(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionAbortError, + ASCIIToUTF16("The connection was closed."))); + pending_second_half_open_.reset(); + } + + // process_pending_calls allows the inspector to process a pending open call + // and call close, reentering IndexedDBDatabaseImpl::close. Then the + // backend would be removed both by the inspector closing its connection, and + // by the connection that first called close. + // To avoid that situation, don't proceed in case of reentrancy. + if (closing_connection_) + return; + base::AutoReset<bool> ClosingConnection(&closing_connection_, true); + 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()); + + backing_store_ = NULL; + + // This check should only be false in unit tests. + // TODO(jsbell): Assert factory_ || we're executing a unit test. + if (factory_) + factory_->RemoveIDBDatabaseBackend(identifier_); + } +} + +void CreateObjectStoreAbortOperation::Perform( + IndexedDBTransaction* transaction) { + IDB_TRACE("CreateObjectStoreAbortOperation"); + DCHECK(!transaction); + database_->RemoveObjectStore(object_store_id_); +} + +void DeleteObjectStoreAbortOperation::Perform( + IndexedDBTransaction* transaction) { + IDB_TRACE("DeleteObjectStoreAbortOperation"); + DCHECK(!transaction); + database_->AddObjectStore(object_store_metadata_, + IndexedDBObjectStoreMetadata::kInvalidId); +} + +void IndexedDBDatabaseImpl::VersionChangeAbortOperation::Perform( + IndexedDBTransaction* transaction) { + IDB_TRACE("VersionChangeAbortOperation"); + DCHECK(!transaction); + database_->metadata_.version = previous_version_; + database_->metadata_.int_version = previous_int_version_; +} + +} // namespace content |