diff options
Diffstat (limited to 'content/browser')
63 files changed, 14652 insertions, 410 deletions
diff --git a/content/browser/in_process_webkit/indexed_db_database_callbacks.cc b/content/browser/in_process_webkit/indexed_db_database_callbacks.cc index 9a7cc10..a57193d 100644 --- a/content/browser/in_process_webkit/indexed_db_database_callbacks.cc +++ b/content/browser/in_process_webkit/indexed_db_database_callbacks.cc @@ -4,6 +4,7 @@ #include "content/browser/in_process_webkit/indexed_db_database_callbacks.h" +#include "base/memory/scoped_vector.h" #include "content/browser/in_process_webkit/indexed_db_dispatcher_host.h" #include "content/common/indexed_db/indexed_db_messages.h" @@ -15,46 +16,39 @@ IndexedDBDatabaseCallbacks::IndexedDBDatabaseCallbacks( int ipc_database_callbacks_id) : dispatcher_host_(dispatcher_host), ipc_thread_id_(ipc_thread_id), - ipc_database_callbacks_id_(ipc_database_callbacks_id) { -} + ipc_database_callbacks_id_(ipc_database_callbacks_id) {} -IndexedDBDatabaseCallbacks::~IndexedDBDatabaseCallbacks() { -} +IndexedDBDatabaseCallbacks::~IndexedDBDatabaseCallbacks() {} void IndexedDBDatabaseCallbacks::onForcedClose() { - dispatcher_host_->Send( - new IndexedDBMsg_DatabaseCallbacksForcedClose( - ipc_thread_id_, - ipc_database_callbacks_id_)); + dispatcher_host_->Send(new IndexedDBMsg_DatabaseCallbacksForcedClose( + ipc_thread_id_, ipc_database_callbacks_id_)); } void IndexedDBDatabaseCallbacks::onVersionChange(long long old_version, long long new_version) { - dispatcher_host_->Send( - new IndexedDBMsg_DatabaseCallbacksIntVersionChange( - ipc_thread_id_, - ipc_database_callbacks_id_, - old_version, - new_version)); + dispatcher_host_->Send(new IndexedDBMsg_DatabaseCallbacksIntVersionChange( + ipc_thread_id_, ipc_database_callbacks_id_, old_version, new_version)); } void IndexedDBDatabaseCallbacks::onAbort( long long host_transaction_id, const WebKit::WebIDBDatabaseError& error) { - dispatcher_host_->FinishTransaction(host_transaction_id, false); - dispatcher_host_->Send( - new IndexedDBMsg_DatabaseCallbacksAbort( - ipc_thread_id_, ipc_database_callbacks_id_, - dispatcher_host_->RendererTransactionId(host_transaction_id), - error.code(), error.message())); + dispatcher_host_->FinishTransaction(host_transaction_id, false); + dispatcher_host_->Send(new IndexedDBMsg_DatabaseCallbacksAbort( + ipc_thread_id_, + ipc_database_callbacks_id_, + dispatcher_host_->RendererTransactionId(host_transaction_id), + error.code(), + error.message())); } void IndexedDBDatabaseCallbacks::onComplete(long long host_transaction_id) { - dispatcher_host_->FinishTransaction(host_transaction_id, true); - dispatcher_host_->Send( - new IndexedDBMsg_DatabaseCallbacksComplete( - ipc_thread_id_, ipc_database_callbacks_id_, - dispatcher_host_->RendererTransactionId(host_transaction_id))); + dispatcher_host_->FinishTransaction(host_transaction_id, true); + dispatcher_host_->Send(new IndexedDBMsg_DatabaseCallbacksComplete( + ipc_thread_id_, + ipc_database_callbacks_id_, + dispatcher_host_->RendererTransactionId(host_transaction_id))); } } // namespace content diff --git a/content/browser/in_process_webkit/indexed_db_dispatcher_host.cc b/content/browser/in_process_webkit/indexed_db_dispatcher_host.cc index 8532d21..6d640c5 100644 --- a/content/browser/in_process_webkit/indexed_db_dispatcher_host.cc +++ b/content/browser/in_process_webkit/indexed_db_dispatcher_host.cc @@ -41,26 +41,24 @@ using WebKit::WebIDBCallbacks; using WebKit::WebIDBCursor; using WebKit::WebIDBDatabase; using WebKit::WebIDBDatabaseError; -using WebKit::WebIDBIndex; using WebKit::WebIDBKey; using WebKit::WebIDBMetadata; -using WebKit::WebIDBObjectStore; using WebKit::WebString; using WebKit::WebVector; namespace content { namespace { -template <class T> -void DeleteOnWebKitThread(T* obj) { - if (!BrowserThread::DeleteSoon(BrowserThread::WEBKIT_DEPRECATED, - FROM_HERE, obj)) +template <class T> void DeleteOnWebKitThread(T* obj) { + if (!BrowserThread::DeleteSoon( + BrowserThread::WEBKIT_DEPRECATED, FROM_HERE, obj)) delete obj; } } IndexedDBDispatcherHost::IndexedDBDispatcherHost( - int ipc_process_id, IndexedDBContextImpl* indexed_db_context) + int ipc_process_id, + IndexedDBContextImpl* indexed_db_context) : indexed_db_context_(indexed_db_context), database_dispatcher_host_(new DatabaseDispatcherHost(this)), cursor_dispatcher_host_(new CursorDispatcherHost(this)), @@ -68,14 +66,14 @@ IndexedDBDispatcherHost::IndexedDBDispatcherHost( DCHECK(indexed_db_context_.get()); } -IndexedDBDispatcherHost::~IndexedDBDispatcherHost() { -} +IndexedDBDispatcherHost::~IndexedDBDispatcherHost() {} void IndexedDBDispatcherHost::OnChannelClosing() { BrowserMessageFilter::OnChannelClosing(); bool success = BrowserThread::PostTask( - BrowserThread::WEBKIT_DEPRECATED, FROM_HERE, + BrowserThread::WEBKIT_DEPRECATED, + FROM_HERE, base::Bind(&IndexedDBDispatcherHost::ResetDispatcherHosts, this)); if (!success) @@ -119,12 +117,12 @@ bool IndexedDBDispatcherHost::OnMessageReceived(const IPC::Message& message, if (!handled) { handled = true; IPC_BEGIN_MESSAGE_MAP_EX(IndexedDBDispatcherHost, message, *message_was_ok) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_FactoryGetDatabaseNames, - OnIDBFactoryGetDatabaseNames) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_FactoryOpen, OnIDBFactoryOpen) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_FactoryDeleteDatabase, - OnIDBFactoryDeleteDatabase) - IPC_MESSAGE_UNHANDLED(handled = false) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_FactoryGetDatabaseNames, + OnIDBFactoryGetDatabaseNames) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_FactoryOpen, OnIDBFactoryOpen) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_FactoryDeleteDatabase, + OnIDBFactoryDeleteDatabase) + IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() } return handled; @@ -151,10 +149,8 @@ int32 IndexedDBDispatcherHost::Add(WebIDBDatabase* idb_database, return ipc_database_id; } -void IndexedDBDispatcherHost::RegisterTransactionId( - int64 host_transaction_id, - const GURL& url) -{ +void IndexedDBDispatcherHost::RegisterTransactionId(int64 host_transaction_id, + const GURL& url) { if (!database_dispatcher_host_) return; database_dispatcher_host_->transaction_url_map_[host_transaction_id] = url; @@ -175,8 +171,8 @@ int64 IndexedDBDispatcherHost::HostTransactionId(int64 transaction_id) { int64 IndexedDBDispatcherHost::RendererTransactionId( int64 host_transaction_id) { - DCHECK(host_transaction_id >> 32 == base::GetProcId(peer_handle())) << - "Invalid renderer target for transaction id"; + DCHECK(host_transaction_id >> 32 == base::GetProcId(peer_handle())) + << "Invalid renderer target for transaction id"; return host_transaction_id & 0xffffffff; } @@ -186,8 +182,7 @@ WebIDBCursor* IndexedDBDispatcherHost::GetCursorFromId(int32 ipc_cursor_id) { } IndexedDBDatabaseMetadata IndexedDBDispatcherHost::ConvertMetadata( - const WebIDBMetadata& web_metadata) -{ + const WebIDBMetadata& web_metadata) { IndexedDBDatabaseMetadata metadata; metadata.id = web_metadata.id; metadata.name = web_metadata.name; @@ -227,8 +222,9 @@ void IndexedDBDispatcherHost::OnIDBFactoryGetDatabaseNames( base::FilePath indexed_db_path = indexed_db_context_->data_path(); Context()->GetIDBFactory()->getDatabaseNames( - new IndexedDBCallbacks<WebVector<WebString> >(this, params.ipc_thread_id, - params.ipc_callbacks_id), params.database_identifier, + new IndexedDBCallbacks<WebVector<WebString> >( + this, params.ipc_thread_id, params.ipc_callbacks_id), + params.database_identifier, webkit_base::FilePathToWebString(indexed_db_path)); } @@ -246,19 +242,20 @@ void IndexedDBDispatcherHost::OnIDBFactoryOpen( // TODO(dgrogan): Don't let a non-existing database be opened (and therefore // created) if this origin is already over quota. - Context()->GetIDBFactory()->open( - params.name, - params.version, - host_transaction_id, - new IndexedDBCallbacksDatabase(this, params.ipc_thread_id, - params.ipc_callbacks_id, - params.ipc_database_callbacks_id, - host_transaction_id, - origin_url), - new IndexedDBDatabaseCallbacks(this, params.ipc_thread_id, - params.ipc_database_callbacks_id), - params.database_identifier, - webkit_base::FilePathToWebString(indexed_db_path)); + Context()->GetIDBFactory() + ->open(params.name, + params.version, + host_transaction_id, + new IndexedDBCallbacksDatabase(this, + params.ipc_thread_id, + params.ipc_callbacks_id, + params.ipc_database_callbacks_id, + host_transaction_id, + origin_url), + new IndexedDBDatabaseCallbacks( + this, params.ipc_thread_id, params.ipc_database_callbacks_id), + params.database_identifier, + webkit_base::FilePathToWebString(indexed_db_path)); } void IndexedDBDispatcherHost::OnIDBFactoryDeleteDatabase( @@ -266,23 +263,22 @@ void IndexedDBDispatcherHost::OnIDBFactoryDeleteDatabase( base::FilePath indexed_db_path = indexed_db_context_->data_path(); DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); - Context()->GetIDBFactory()->deleteDatabase( - params.name, - new IndexedDBCallbacks<WebData>(this, - params.ipc_thread_id, - params.ipc_callbacks_id), - params.database_identifier, - webkit_base::FilePathToWebString(indexed_db_path)); + Context()->GetIDBFactory() + ->deleteDatabase(params.name, + new IndexedDBCallbacks<WebData>( + this, params.ipc_thread_id, params.ipc_callbacks_id), + params.database_identifier, + webkit_base::FilePathToWebString(indexed_db_path)); } -void IndexedDBDispatcherHost::FinishTransaction( - int64 host_transaction_id, bool committed) { +void IndexedDBDispatcherHost::FinishTransaction(int64 host_transaction_id, + bool committed) { TransactionIDToURLMap& transaction_url_map = - database_dispatcher_host_->transaction_url_map_; + database_dispatcher_host_->transaction_url_map_; TransactionIDToSizeMap& transaction_size_map = - database_dispatcher_host_->transaction_size_map_; + database_dispatcher_host_->transaction_size_map_; TransactionIDToDatabaseIDMap& transaction_database_map = - database_dispatcher_host_->transaction_database_map_; + database_dispatcher_host_->transaction_database_map_; if (committed) Context()->TransactionComplete(transaction_url_map[host_transaction_id]); // It's unclear if std::map::erase(key) has defined behavior if the @@ -305,7 +301,8 @@ void IndexedDBDispatcherHost::FinishTransaction( template <typename ObjectType> ObjectType* IndexedDBDispatcherHost::GetOrTerminateProcess( - IDMap<ObjectType, IDMapOwnPointer>* map, int32 ipc_return_object_id) { + IDMap<ObjectType, IDMapOwnPointer>* map, + int32 ipc_return_object_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); ObjectType* return_object = map->Lookup(ipc_return_object_id); if (!return_object) { @@ -319,12 +316,12 @@ ObjectType* IndexedDBDispatcherHost::GetOrTerminateProcess( template <typename ObjectType> void IndexedDBDispatcherHost::DestroyObject( - IDMap<ObjectType, IDMapOwnPointer>* map, int32 ipc_object_id) { + IDMap<ObjectType, IDMapOwnPointer>* map, + int32 ipc_object_id) { GetOrTerminateProcess(map, ipc_object_id); map->Remove(ipc_object_id); } - ////////////////////////////////////////////////////////////////////// // IndexedDBDispatcherHost::DatabaseDispatcherHost // @@ -353,14 +350,16 @@ void IndexedDBDispatcherHost::DatabaseDispatcherHost::CloseAll() { ++iter; WebIDBDatabase* database = map_.Lookup(ipc_database_id); if (database) { - database->abort(transaction_id, WebIDBDatabaseError( - WebKit::WebIDBDatabaseExceptionUnknownError)); + database->abort( + transaction_id, + WebIDBDatabaseError(WebKit::WebIDBDatabaseExceptionUnknownError)); } } DCHECK(transaction_database_map_.empty()); for (WebIDBObjectIDToURLMap::iterator iter = database_url_map_.begin(); - iter != database_url_map_.end(); iter++) { + iter != database_url_map_.end(); + iter++) { WebIDBDatabase* database = map_.Lookup(iter->first); if (database) { database->close(); @@ -370,37 +369,33 @@ void IndexedDBDispatcherHost::DatabaseDispatcherHost::CloseAll() { } bool IndexedDBDispatcherHost::DatabaseDispatcherHost::OnMessageReceived( - const IPC::Message& message, bool* msg_is_ok) { + const IPC::Message& message, + bool* msg_is_ok) { bool handled = true; - IPC_BEGIN_MESSAGE_MAP_EX(IndexedDBDispatcherHost::DatabaseDispatcherHost, - message, *msg_is_ok) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseCreateObjectStore, - OnCreateObjectStore) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseDeleteObjectStore, - OnDeleteObjectStore) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseCreateTransaction, - OnCreateTransaction) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseClose, OnClose) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseDestroyed, OnDestroyed) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseGet, OnGet) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabasePut, OnPut) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseSetIndexKeys, - OnSetIndexKeys) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseSetIndexesReady, - OnSetIndexesReady) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseOpenCursor, OnOpenCursor) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseCount, OnCount) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseDeleteRange, OnDeleteRange) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseClear, OnClear) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseCreateIndex, - OnCreateIndex) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseDeleteIndex, - OnDeleteIndex) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseAbort, - OnAbort) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseCommit, - OnCommit) - IPC_MESSAGE_UNHANDLED(handled = false) + IPC_BEGIN_MESSAGE_MAP_EX( + IndexedDBDispatcherHost::DatabaseDispatcherHost, message, *msg_is_ok) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseCreateObjectStore, + OnCreateObjectStore) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseDeleteObjectStore, + OnDeleteObjectStore) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseCreateTransaction, + OnCreateTransaction) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseClose, OnClose) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseDestroyed, OnDestroyed) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseGet, OnGet) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabasePut, OnPut) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseSetIndexKeys, OnSetIndexKeys) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseSetIndexesReady, + OnSetIndexesReady) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseOpenCursor, OnOpenCursor) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseCount, OnCount) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseDeleteRange, OnDeleteRange) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseClear, OnClear) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseCreateIndex, OnCreateIndex) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseDeleteIndex, OnDeleteIndex) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseAbort, OnAbort) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseCommit, OnCommit) + IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; } @@ -413,20 +408,22 @@ void IndexedDBDispatcherHost::DatabaseDispatcherHost::Send( void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnCreateObjectStore( const IndexedDBHostMsg_DatabaseCreateObjectStore_Params& params) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); - WebIDBDatabase* database = parent_->GetOrTerminateProcess( - &map_, params.ipc_database_id); + WebIDBDatabase* database = + parent_->GetOrTerminateProcess(&map_, params.ipc_database_id); if (!database) return; int64 host_transaction_id = parent_->HostTransactionId(params.transaction_id); - database->createObjectStore( - host_transaction_id, - params.object_store_id, - params.name, params.key_path, params.auto_increment); + database->createObjectStore(host_transaction_id, + params.object_store_id, + params.name, + params.key_path, + params.auto_increment); if (parent_->Context()->IsOverQuota( - database_url_map_[params.ipc_database_id])) { - database->abort(host_transaction_id, WebIDBDatabaseError( - WebKit::WebIDBDatabaseExceptionQuotaError)); + database_url_map_[params.ipc_database_id])) { + database->abort( + host_transaction_id, + WebIDBDatabaseError(WebKit::WebIDBDatabaseExceptionQuotaError)); } } @@ -435,8 +432,8 @@ void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnDeleteObjectStore( int64 transaction_id, int64 object_store_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); - WebIDBDatabase* database = parent_->GetOrTerminateProcess( - &map_, ipc_database_id); + WebIDBDatabase* database = + parent_->GetOrTerminateProcess(&map_, ipc_database_id); if (!database) return; @@ -446,22 +443,23 @@ void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnDeleteObjectStore( void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnCreateTransaction( const IndexedDBHostMsg_DatabaseCreateTransaction_Params& params) { - WebIDBDatabase* database = parent_->GetOrTerminateProcess( - &map_, params.ipc_database_id); + WebIDBDatabase* database = + parent_->GetOrTerminateProcess(&map_, params.ipc_database_id); if (!database) - return; + return; WebVector<long long> object_stores(params.object_store_ids.size()); for (size_t i = 0; i < params.object_store_ids.size(); ++i) - object_stores[i] = params.object_store_ids[i]; + object_stores[i] = params.object_store_ids[i]; int64 host_transaction_id = parent_->HostTransactionId(params.transaction_id); database->createTransaction( host_transaction_id, - new IndexedDBDatabaseCallbacks(parent_, params.ipc_thread_id, - params.ipc_database_callbacks_id), - object_stores, params.mode); + new IndexedDBDatabaseCallbacks( + parent_, params.ipc_thread_id, params.ipc_database_callbacks_id), + object_stores, + params.mode); transaction_database_map_[host_transaction_id] = params.ipc_database_id; parent_->RegisterTransactionId(host_transaction_id, database_url_map_[params.ipc_database_id]); @@ -469,8 +467,8 @@ void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnCreateTransaction( void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnClose( int32 ipc_database_id) { - WebIDBDatabase* database = parent_->GetOrTerminateProcess( - &map_, ipc_database_id); + WebIDBDatabase* database = + parent_->GetOrTerminateProcess(&map_, ipc_database_id); if (!database) return; database->close(); @@ -479,8 +477,8 @@ void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnClose( void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnDestroyed( int32 ipc_object_id) { WebIDBDatabase* database = map_.Lookup(ipc_object_id); - parent_->Context()->ConnectionClosed(database_url_map_[ipc_object_id], - database); + parent_->Context() + ->ConnectionClosed(database_url_map_[ipc_object_id], database); database_url_map_.erase(ipc_object_id); parent_->DestroyObject(&map_, ipc_object_id); } @@ -488,41 +486,42 @@ void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnDestroyed( void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnGet( const IndexedDBHostMsg_DatabaseGet_Params& params) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); - WebIDBDatabase* database = parent_->GetOrTerminateProcess( - &map_, params.ipc_database_id); + WebIDBDatabase* database = + parent_->GetOrTerminateProcess(&map_, params.ipc_database_id); if (!database) return; - scoped_ptr<WebIDBCallbacks> callbacks( - new IndexedDBCallbacks<WebData>( - parent_, params.ipc_thread_id, - params.ipc_callbacks_id)); + scoped_ptr<WebIDBCallbacks> callbacks(new IndexedDBCallbacks<WebData>( + parent_, params.ipc_thread_id, params.ipc_callbacks_id)); database->get(parent_->HostTransactionId(params.transaction_id), params.object_store_id, params.index_id, - params.key_range, params.key_only, callbacks.release()); + params.key_range, + params.key_only, + callbacks.release()); } void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnPut( const IndexedDBHostMsg_DatabasePut_Params& params) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); - WebIDBDatabase* database = parent_->GetOrTerminateProcess( - &map_, params.ipc_database_id); + WebIDBDatabase* database = + parent_->GetOrTerminateProcess(&map_, params.ipc_database_id); if (!database) return; - scoped_ptr<WebIDBCallbacks> callbacks( - new IndexedDBCallbacks<WebIDBKey>(parent_, params.ipc_thread_id, - params.ipc_callbacks_id)); + scoped_ptr<WebIDBCallbacks> callbacks(new IndexedDBCallbacks<WebIDBKey>( + parent_, params.ipc_thread_id, params.ipc_callbacks_id)); // Be careful with empty vectors. WebData value; if (params.value.size()) - value.assign(¶ms.value.front(), params.value.size()); + value.assign(¶ms.value.front(), params.value.size()); int64 host_transaction_id = parent_->HostTransactionId(params.transaction_id); database->put(host_transaction_id, params.object_store_id, - value, params.key, - params.put_mode, callbacks.release(), + value, + params.key, + params.put_mode, + callbacks.release(), params.index_ids, params.index_keys); TransactionIDToSizeMap* map = @@ -535,22 +534,25 @@ void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnPut( void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnSetIndexKeys( const IndexedDBHostMsg_DatabaseSetIndexKeys_Params& params) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); - WebIDBDatabase* database = parent_->GetOrTerminateProcess( - &map_, params.ipc_database_id); + WebIDBDatabase* database = + parent_->GetOrTerminateProcess(&map_, params.ipc_database_id); if (!database) return; int64 host_transaction_id = parent_->HostTransactionId(params.transaction_id); if (params.index_ids.size() != params.index_keys.size()) { - database->abort(host_transaction_id, WebIDBDatabaseError( - WebKit::WebIDBDatabaseExceptionUnknownError, - "Malformed IPC message: index_ids.size() != index_keys.size()")); + database->abort( + host_transaction_id, + WebIDBDatabaseError( + WebKit::WebIDBDatabaseExceptionUnknownError, + "Malformed IPC message: index_ids.size() != index_keys.size()")); return; } database->setIndexKeys(host_transaction_id, params.object_store_id, - params.primary_key, params.index_ids, + params.primary_key, + params.index_ids, params.index_keys); } @@ -560,8 +562,8 @@ void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnSetIndexesReady( int64 object_store_id, const std::vector<int64>& index_ids) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); - WebIDBDatabase* database = parent_->GetOrTerminateProcess( - &map_, ipc_database_id); + WebIDBDatabase* database = + parent_->GetOrTerminateProcess(&map_, ipc_database_id); if (!database) return; @@ -573,54 +575,54 @@ void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnSetIndexesReady( void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnOpenCursor( const IndexedDBHostMsg_DatabaseOpenCursor_Params& params) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); - WebIDBDatabase* database = parent_->GetOrTerminateProcess( - &map_, params.ipc_database_id); + WebIDBDatabase* database = + parent_->GetOrTerminateProcess(&map_, params.ipc_database_id); if (!database) return; - scoped_ptr<WebIDBCallbacks> callbacks( - new IndexedDBCallbacks<WebIDBCursor>(parent_, params.ipc_thread_id, - params.ipc_callbacks_id, -1)); - database->openCursor( - parent_->HostTransactionId(params.transaction_id), - params.object_store_id, params.index_id, - params.key_range, params.direction, params.key_only, params.task_type, - callbacks.release()); + scoped_ptr<WebIDBCallbacks> callbacks(new IndexedDBCallbacks<WebIDBCursor>( + parent_, params.ipc_thread_id, params.ipc_callbacks_id, -1)); + database->openCursor(parent_->HostTransactionId(params.transaction_id), + params.object_store_id, + params.index_id, + params.key_range, + params.direction, + params.key_only, + params.task_type, + callbacks.release()); } void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnCount( const IndexedDBHostMsg_DatabaseCount_Params& params) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); - WebIDBDatabase* database = parent_->GetOrTerminateProcess( - &map_, params.ipc_database_id); + WebIDBDatabase* database = + parent_->GetOrTerminateProcess(&map_, params.ipc_database_id); if (!database) return; - scoped_ptr<WebIDBCallbacks> callbacks( - new IndexedDBCallbacks<WebData>( - parent_, params.ipc_thread_id, - params.ipc_callbacks_id)); - database->count( - parent_->HostTransactionId(params.transaction_id), - params.object_store_id, params.index_id, - params.key_range, callbacks.release()); + scoped_ptr<WebIDBCallbacks> callbacks(new IndexedDBCallbacks<WebData>( + parent_, params.ipc_thread_id, params.ipc_callbacks_id)); + database->count(parent_->HostTransactionId(params.transaction_id), + params.object_store_id, + params.index_id, + params.key_range, + callbacks.release()); } void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnDeleteRange( const IndexedDBHostMsg_DatabaseDeleteRange_Params& params) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); - WebIDBDatabase* database = parent_->GetOrTerminateProcess( - &map_, params.ipc_database_id); + WebIDBDatabase* database = + parent_->GetOrTerminateProcess(&map_, params.ipc_database_id); if (!database) return; - scoped_ptr<WebIDBCallbacks> callbacks( - new IndexedDBCallbacks<WebData>( - parent_, params.ipc_thread_id, - params.ipc_callbacks_id)); + scoped_ptr<WebIDBCallbacks> callbacks(new IndexedDBCallbacks<WebData>( + parent_, params.ipc_thread_id, params.ipc_callbacks_id)); database->deleteRange(parent_->HostTransactionId(params.transaction_id), params.object_store_id, - params.key_range, callbacks.release()); + params.key_range, + callbacks.release()); } void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnClear( @@ -630,26 +632,25 @@ void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnClear( int64 transaction_id, int64 object_store_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); - WebIDBDatabase* database = parent_->GetOrTerminateProcess( - &map_, ipc_database_id); + WebIDBDatabase* database = + parent_->GetOrTerminateProcess(&map_, ipc_database_id); if (!database) return; - scoped_ptr<WebIDBCallbacks> callbacks( - new IndexedDBCallbacks<WebData>( - parent_, ipc_thread_id, - ipc_callbacks_id)); + scoped_ptr<WebIDBCallbacks> callbacks(new IndexedDBCallbacks<WebData>( + parent_, ipc_thread_id, ipc_callbacks_id)); database->clear(parent_->HostTransactionId(transaction_id), - object_store_id, callbacks.release()); + object_store_id, + callbacks.release()); } void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnAbort( int32 ipc_database_id, int64 transaction_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); - WebIDBDatabase* database = parent_->GetOrTerminateProcess( - &map_, ipc_database_id); + WebIDBDatabase* database = + parent_->GetOrTerminateProcess(&map_, ipc_database_id); if (!database) return; @@ -660,17 +661,19 @@ void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnCommit( int32 ipc_database_id, int64 transaction_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); - WebIDBDatabase* database = parent_->GetOrTerminateProcess( - &map_, ipc_database_id); + WebIDBDatabase* database = + parent_->GetOrTerminateProcess(&map_, ipc_database_id); if (!database) return; int64 host_transaction_id = parent_->HostTransactionId(transaction_id); int64 transaction_size = transaction_size_map_[host_transaction_id]; - if (transaction_size && parent_->Context()->WouldBeOverQuota( - transaction_url_map_[host_transaction_id], transaction_size)) { - database->abort(host_transaction_id, WebIDBDatabaseError( - WebKit::WebIDBDatabaseExceptionQuotaError)); + if (transaction_size && + parent_->Context()->WouldBeOverQuota( + transaction_url_map_[host_transaction_id], transaction_size)) { + database->abort( + host_transaction_id, + WebIDBDatabaseError(WebKit::WebIDBDatabaseExceptionQuotaError)); return; } @@ -680,24 +683,24 @@ void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnCommit( void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnCreateIndex( const IndexedDBHostMsg_DatabaseCreateIndex_Params& params) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); - WebIDBDatabase* database = parent_->GetOrTerminateProcess( - &map_, params.ipc_database_id); + WebIDBDatabase* database = + parent_->GetOrTerminateProcess(&map_, params.ipc_database_id); if (!database) return; int64 host_transaction_id = parent_->HostTransactionId(params.transaction_id); - database->createIndex( - host_transaction_id, - params.object_store_id, - params.index_id, - params.name, - params.key_path, - params.unique, - params.multi_entry); + database->createIndex(host_transaction_id, + params.object_store_id, + params.index_id, + params.name, + params.key_path, + params.unique, + params.multi_entry); if (parent_->Context()->IsOverQuota( - database_url_map_[params.ipc_database_id])) { - database->abort(host_transaction_id, WebIDBDatabaseError( - WebKit::WebIDBDatabaseExceptionQuotaError)); + database_url_map_[params.ipc_database_id])) { + database->abort( + host_transaction_id, + WebIDBDatabaseError(WebKit::WebIDBDatabaseExceptionQuotaError)); } } @@ -707,13 +710,13 @@ void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnDeleteIndex( int64 object_store_id, int64 index_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); - WebIDBDatabase* database = parent_->GetOrTerminateProcess( - &map_, ipc_database_id); + WebIDBDatabase* database = + parent_->GetOrTerminateProcess(&map_, ipc_database_id); if (!database) return; - database->deleteIndex(parent_->HostTransactionId(transaction_id), - object_store_id, index_id); + database->deleteIndex( + parent_->HostTransactionId(transaction_id), object_store_id, index_id); } ////////////////////////////////////////////////////////////////////// @@ -726,48 +729,45 @@ IndexedDBDispatcherHost::CursorDispatcherHost::CursorDispatcherHost( map_.set_check_on_null_data(true); } -IndexedDBDispatcherHost::CursorDispatcherHost::~CursorDispatcherHost() { -} +IndexedDBDispatcherHost::CursorDispatcherHost::~CursorDispatcherHost() {} bool IndexedDBDispatcherHost::CursorDispatcherHost::OnMessageReceived( - const IPC::Message& message, bool* msg_is_ok) { + const IPC::Message& message, + bool* msg_is_ok) { bool handled = true; - IPC_BEGIN_MESSAGE_MAP_EX(IndexedDBDispatcherHost::CursorDispatcherHost, - message, *msg_is_ok) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorAdvance, OnAdvance) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorContinue, OnContinue) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorPrefetch, OnPrefetch) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorPrefetchReset, OnPrefetchReset) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorDelete, OnDelete) - IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorDestroyed, OnDestroyed) - IPC_MESSAGE_UNHANDLED(handled = false) + IPC_BEGIN_MESSAGE_MAP_EX( + IndexedDBDispatcherHost::CursorDispatcherHost, message, *msg_is_ok) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorAdvance, OnAdvance) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorContinue, OnContinue) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorPrefetch, OnPrefetch) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorPrefetchReset, OnPrefetchReset) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorDelete, OnDelete) + IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorDestroyed, OnDestroyed) + IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; } - void IndexedDBDispatcherHost::CursorDispatcherHost::Send( IPC::Message* message) { parent_->Send(message); } - void IndexedDBDispatcherHost::CursorDispatcherHost::OnAdvance( int32 ipc_cursor_id, int32 ipc_thread_id, int32 ipc_callbacks_id, unsigned long count) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); - WebIDBCursor* idb_cursor = parent_->GetOrTerminateProcess( - &map_, ipc_cursor_id); + WebIDBCursor* idb_cursor = + parent_->GetOrTerminateProcess(&map_, ipc_cursor_id); if (!idb_cursor) return; - idb_cursor->advance(count, - new IndexedDBCallbacks<WebIDBCursor>(parent_, - ipc_thread_id, - ipc_callbacks_id, - ipc_cursor_id)); + idb_cursor->advance( + count, + new IndexedDBCallbacks<WebIDBCursor>( + parent_, ipc_thread_id, ipc_callbacks_id, ipc_cursor_id)); } void IndexedDBDispatcherHost::CursorDispatcherHost::OnContinue( @@ -776,15 +776,15 @@ void IndexedDBDispatcherHost::CursorDispatcherHost::OnContinue( int32 ipc_callbacks_id, const IndexedDBKey& key) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); - WebIDBCursor* idb_cursor = parent_->GetOrTerminateProcess(&map_, - ipc_cursor_id); + WebIDBCursor* idb_cursor = + parent_->GetOrTerminateProcess(&map_, ipc_cursor_id); if (!idb_cursor) return; idb_cursor->continueFunction( - key, new IndexedDBCallbacks<WebIDBCursor>(parent_, ipc_thread_id, - ipc_callbacks_id, - ipc_cursor_id)); + key, + new IndexedDBCallbacks<WebIDBCursor>( + parent_, ipc_thread_id, ipc_callbacks_id, ipc_cursor_id)); } void IndexedDBDispatcherHost::CursorDispatcherHost::OnPrefetch( @@ -793,22 +793,24 @@ void IndexedDBDispatcherHost::CursorDispatcherHost::OnPrefetch( int32 ipc_callbacks_id, int n) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); - WebIDBCursor* idb_cursor = parent_->GetOrTerminateProcess(&map_, - ipc_cursor_id); + WebIDBCursor* idb_cursor = + parent_->GetOrTerminateProcess(&map_, ipc_cursor_id); if (!idb_cursor) return; idb_cursor->prefetchContinue( - n, new IndexedDBCallbacks<WebIDBCursor>(parent_, ipc_thread_id, - ipc_callbacks_id, - ipc_cursor_id)); + n, + new IndexedDBCallbacks<WebIDBCursor>( + parent_, ipc_thread_id, ipc_callbacks_id, ipc_cursor_id)); } void IndexedDBDispatcherHost::CursorDispatcherHost::OnPrefetchReset( - int32 ipc_cursor_id, int used_prefetches, int unused_prefetches) { + int32 ipc_cursor_id, + int used_prefetches, + int unused_prefetches) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); - WebIDBCursor* idb_cursor = parent_->GetOrTerminateProcess(&map_, - ipc_cursor_id); + WebIDBCursor* idb_cursor = + parent_->GetOrTerminateProcess(&map_, ipc_cursor_id); if (!idb_cursor) return; @@ -820,14 +822,13 @@ void IndexedDBDispatcherHost::CursorDispatcherHost::OnDelete( int32 ipc_thread_id, int32 ipc_callbacks_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); - WebIDBCursor* idb_cursor = parent_->GetOrTerminateProcess(&map_, - ipc_cursor_id); + WebIDBCursor* idb_cursor = + parent_->GetOrTerminateProcess(&map_, ipc_cursor_id); if (!idb_cursor) return; - idb_cursor->deleteFunction( - new IndexedDBCallbacks<WebData>(parent_, ipc_thread_id, - ipc_callbacks_id)); + idb_cursor->deleteFunction(new IndexedDBCallbacks<WebData>( + parent_, ipc_thread_id, ipc_callbacks_id)); } void IndexedDBDispatcherHost::CursorDispatcherHost::OnDestroyed( diff --git a/content/browser/in_process_webkit/indexed_db_dispatcher_host.h b/content/browser/in_process_webkit/indexed_db_dispatcher_host.h index 48a75d7..9908e31 100644 --- a/content/browser/in_process_webkit/indexed_db_dispatcher_host.h +++ b/content/browser/in_process_webkit/indexed_db_dispatcher_host.h @@ -30,8 +30,6 @@ struct IndexedDBHostMsg_FactoryOpen_Params; namespace WebKit { class WebIDBCursor; class WebIDBDatabase; -class WebIDBIndex; -class WebIDBObjectStore; struct WebIDBMetadata; } @@ -93,8 +91,8 @@ class IndexedDBDispatcherHost : public BrowserMessageFilter { // Helper templates. template <class ReturnType> - ReturnType* GetOrTerminateProcess( - IDMap<ReturnType, IDMapOwnPointer>* map, int32 ipc_return_object_id); + ReturnType* GetOrTerminateProcess(IDMap<ReturnType, IDMapOwnPointer>* map, + int32 ipc_return_object_id); template <typename ObjectType> void DestroyObject(IDMap<ObjectType, IDMapOwnPointer>* map, @@ -113,7 +111,7 @@ class IndexedDBDispatcherHost : public BrowserMessageFilter { ~DatabaseDispatcherHost(); void CloseAll(); - bool OnMessageReceived(const IPC::Message& message, bool *msg_is_ok); + bool OnMessageReceived(const IPC::Message& message, bool* msg_is_ok); void Send(IPC::Message* message); void OnCreateObjectStore( @@ -123,7 +121,8 @@ class IndexedDBDispatcherHost : public BrowserMessageFilter { int64 object_store_id); void OnCreateTransaction( const IndexedDBHostMsg_DatabaseCreateTransaction_Params&); - void OnOpen(int32 ipc_database_id, int32 ipc_thread_id, + void OnOpen(int32 ipc_database_id, + int32 ipc_thread_id, int32 ipc_callbacks_id); void OnClose(int32 ipc_database_id); void OnDestroyed(int32 ipc_database_id); @@ -132,13 +131,11 @@ class IndexedDBDispatcherHost : public BrowserMessageFilter { void OnPut(const IndexedDBHostMsg_DatabasePut_Params& params); void OnSetIndexKeys( const IndexedDBHostMsg_DatabaseSetIndexKeys_Params& params); - void OnSetIndexesReady( - int32 ipc_database_id, - int64 transaction_id, - int64 object_store_id, - const std::vector<int64>& ids); - void OnOpenCursor( - const IndexedDBHostMsg_DatabaseOpenCursor_Params& params); + void OnSetIndexesReady(int32 ipc_database_id, + int64 transaction_id, + int64 object_store_id, + const std::vector<int64>& ids); + void OnOpenCursor(const IndexedDBHostMsg_DatabaseOpenCursor_Params& params); void OnCount(const IndexedDBHostMsg_DatabaseCount_Params& params); void OnDeleteRange( const IndexedDBHostMsg_DatabaseDeleteRange_Params& params); @@ -169,7 +166,7 @@ class IndexedDBDispatcherHost : public BrowserMessageFilter { explicit CursorDispatcherHost(IndexedDBDispatcherHost* parent); ~CursorDispatcherHost(); - bool OnMessageReceived(const IPC::Message& message, bool *msg_is_ok); + bool OnMessageReceived(const IPC::Message& message, bool* msg_is_ok); void Send(IPC::Message* message); void OnAdvance(int32 ipc_object_store_id, @@ -184,7 +181,8 @@ class IndexedDBDispatcherHost : public BrowserMessageFilter { int32 ipc_thread_id, int32 ipc_callbacks_id, int n); - void OnPrefetchReset(int32 ipc_cursor_id, int used_prefetches, + void OnPrefetchReset(int32 ipc_cursor_id, + int used_prefetches, int unused_prefetches); void OnDelete(int32 ipc_object_store_id, int32 ipc_thread_id, diff --git a/content/browser/indexed_db/DEPS b/content/browser/indexed_db/DEPS new file mode 100644 index 0000000..743a2f3 --- /dev/null +++ b/content/browser/indexed_db/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+third_party/leveldatabase", +] diff --git a/content/browser/indexed_db/indexed_db.h b/content/browser/indexed_db/indexed_db.h new file mode 100644 index 0000000..3714d40 --- /dev/null +++ b/content/browser/indexed_db/indexed_db.h @@ -0,0 +1,34 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_H_ +#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_H_ + +namespace content { + +namespace indexed_db { + +enum TransactionMode { + TRANSACTION_READ_ONLY = 0, + TRANSACTION_READ_WRITE = 1, + TRANSACTION_VERSION_CHANGE = 2 +}; + +enum CursorDirection { + CURSOR_NEXT = 0, + CURSOR_NEXT_NO_DUPLICATE = 1, + CURSOR_PREV = 2, + CURSOR_PREV_NO_DUPLICATE = 3, +}; + +enum CursorType { + CURSOR_KEY_AND_VALUE = 0, + CURSOR_KEY_ONLY +}; + +} // namespace indexed_db + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_H_ diff --git a/content/browser/indexed_db/indexed_db_backing_store.cc b/content/browser/indexed_db/indexed_db_backing_store.cc new file mode 100644 index 0000000..1596aef --- /dev/null +++ b/content/browser/indexed_db/indexed_db_backing_store.cc @@ -0,0 +1,2543 @@ +// 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_backing_store.h" + +#include <string> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/utf_string_conversions.h" +#include "content/browser/indexed_db/indexed_db_leveldb_coding.h" +#include "content/browser/indexed_db/indexed_db_metadata.h" +#include "content/browser/indexed_db/indexed_db_tracing.h" +#include "content/browser/indexed_db/leveldb/leveldb_comparator.h" +#include "content/browser/indexed_db/leveldb/leveldb_database.h" +#include "content/browser/indexed_db/leveldb/leveldb_iterator.h" +#include "content/browser/indexed_db/leveldb/leveldb_slice.h" +#include "content/browser/indexed_db/leveldb/leveldb_transaction.h" +#include "content/common/indexed_db/indexed_db_key.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/WebIDBKey.h" +#include "third_party/WebKit/public/platform/WebIDBKeyPath.h" + +// TODO(jsbell): Make blink push the version during the open() call. +static const uint32 kWireVersion = 2; + +namespace content { + +static const int64 kKeyGeneratorInitialNumber = + 1; // From the IndexedDB specification. + +enum IndexedDBBackingStoreErrorSource { + // 0 - 2 are no longer used. + FIND_KEY_IN_INDEX = 3, + GET_IDBDATABASE_METADATA, + GET_INDEXES, + GET_KEY_GENERATOR_CURRENT_NUMBER, + GET_OBJECT_STORES, + GET_RECORD, + KEY_EXISTS_IN_OBJECT_STORE, + LOAD_CURRENT_ROW, + SET_UP_METADATA, + GET_PRIMARY_KEY_VIA_INDEX, + KEY_EXISTS_IN_INDEX, + VERSION_EXISTS, + DELETE_OBJECT_STORE, + SET_MAX_OBJECT_STORE_ID, + SET_MAX_INDEX_ID, + GET_NEW_DATABASE_ID, + GET_NEW_VERSION_NUMBER, + CREATE_IDBDATABASE_METADATA, + DELETE_DATABASE, + TRANSACTION_COMMIT_METHOD, // TRANSACTION_COMMIT is a WinNT.h macro + INTERNAL_ERROR_MAX, +}; + +static void RecordInternalError(const char* type, + IndexedDBBackingStoreErrorSource location) { + string16 name = ASCIIToUTF16("WebCore.IndexedDB.BackingStore.") + + UTF8ToUTF16(type) + ASCIIToUTF16("Error"); + base::Histogram::FactoryGet(UTF16ToUTF8(name), + 1, + INTERNAL_ERROR_MAX, + INTERNAL_ERROR_MAX + 1, + base::HistogramBase::kUmaTargetedHistogramFlag) + ->Add(location); +} + +// Use to signal conditions that usually indicate developer error, but +// could be caused by data corruption. A macro is used instead of an +// inline function so that the assert and log report the line number. +#define REPORT_ERROR(type, location) \ + do { \ + LOG(ERROR) << "IndexedDB " type " Error: " #location; \ + NOTREACHED(); \ + RecordInternalError(type, location); \ + } while (0) + +#define INTERNAL_READ_ERROR(location) REPORT_ERROR("Read", location) +#define INTERNAL_CONSISTENCY_ERROR(location) \ + REPORT_ERROR("Consistency", location) +#define INTERNAL_WRITE_ERROR(location) REPORT_ERROR("Write", location) + +static void PutBool(LevelDBTransaction* transaction, + const LevelDBSlice& key, + bool value) { + transaction->Put(key, EncodeBool(value)); +} + +template <typename DBOrTransaction> +static bool GetInt(DBOrTransaction* db, + const LevelDBSlice& key, + int64& found_int, + bool& found) { + std::vector<char> result; + bool ok = db->Get(key, result, found); + if (!ok) + return false; + if (!found) + return true; + + found_int = DecodeInt(result.begin(), result.end()); + return true; +} + +static void PutInt(LevelDBTransaction* transaction, + const LevelDBSlice& key, + int64 value) { + DCHECK_GE(value, 0); + transaction->Put(key, EncodeInt(value)); +} + +template <typename DBOrTransaction> +WARN_UNUSED_RESULT static bool GetVarInt(DBOrTransaction* db, + const LevelDBSlice& key, + int64& found_int, + bool& found) { + std::vector<char> result; + bool ok = db->Get(key, result, found); + if (!ok) + return false; + if (!found) + return true; + if (!result.size()) + return false; + + found = DecodeVarInt(&*result.begin(), &*result.rbegin() + 1, found_int) == + &*result.rbegin() + 1; + return true; +} + +static void PutVarInt(LevelDBTransaction* transaction, + const LevelDBSlice& key, + int64 value) { + transaction->Put(key, EncodeVarInt(value)); +} + +template <typename DBOrTransaction> +WARN_UNUSED_RESULT static bool GetString(DBOrTransaction* db, + const LevelDBSlice& key, + string16& found_string, + bool& found) { + std::vector<char> result; + found = false; + bool ok = db->Get(key, result, found); + if (!ok) + return false; + if (!found) + return true; + if (!result.size()) { + found_string.clear(); + return true; + } + + found_string = DecodeString(&*result.begin(), &*result.rbegin() + 1); + return true; +} + +static void PutString(LevelDBTransaction* transaction, + const LevelDBSlice& key, + const string16& value) { + transaction->Put(key, EncodeString(value)); +} + +static void PutIDBKeyPath(LevelDBTransaction* transaction, + const LevelDBSlice& key, + const IndexedDBKeyPath& value) { + transaction->Put(key, EncodeIDBKeyPath(value)); +} + +static int CompareKeys(const LevelDBSlice& a, const LevelDBSlice& b) { + return Compare(a, b); +} + +static int CompareIndexKeys(const LevelDBSlice& a, const LevelDBSlice& b) { + return Compare(a, b, true); +} + +class Comparator : public LevelDBComparator { + public: + virtual int Compare(const LevelDBSlice& a, const LevelDBSlice& b) const + OVERRIDE { + return content::Compare(a, b); + } + virtual const char* Name() const OVERRIDE { return "idb_cmp1"; } +}; + +// 0 - Initial version. +// 1 - Adds UserIntVersion to DatabaseMetaData. +// 2 - Adds DataVersion to to global metadata. +static const int64 kLatestKnownSchemaVersion = 2; +WARN_UNUSED_RESULT static bool IsSchemaKnown(LevelDBDatabase* db, bool* known) { + int64 db_schema_version = 0; + bool found = false; + bool ok = GetInt( + db, LevelDBSlice(SchemaVersionKey::Encode()), db_schema_version, found); + if (!ok) + return false; + if (!found) { + *known = true; + return true; + } + if (db_schema_version > kLatestKnownSchemaVersion) { + *known = false; + return true; + } + + const uint32 latest_known_data_version = kWireVersion; + int64 db_data_version = 0; + ok = GetInt( + db, LevelDBSlice(DataVersionKey::Encode()), db_data_version, found); + if (!ok) + return false; + if (!found) { + *known = true; + return true; + } + + if (db_data_version > latest_known_data_version) { + *known = false; + return true; + } + + *known = true; + return true; +} + +WARN_UNUSED_RESULT static bool SetUpMetadata(LevelDBDatabase* db, + const string16& origin) { + const uint32 latest_known_data_version = kWireVersion; + const std::vector<char> schema_version_key = SchemaVersionKey::Encode(); + const std::vector<char> data_version_key = DataVersionKey::Encode(); + + scoped_refptr<LevelDBTransaction> transaction = + LevelDBTransaction::Create(db); + + int64 db_schema_version = 0; + int64 db_data_version = 0; + bool found = false; + bool ok = GetInt(transaction.get(), + LevelDBSlice(schema_version_key), + db_schema_version, + found); + if (!ok) { + INTERNAL_READ_ERROR(SET_UP_METADATA); + return false; + } + if (!found) { + // Initialize new backing store. + db_schema_version = kLatestKnownSchemaVersion; + PutInt( + transaction.get(), LevelDBSlice(schema_version_key), db_schema_version); + db_data_version = latest_known_data_version; + PutInt(transaction.get(), LevelDBSlice(data_version_key), db_data_version); + } else { + // Upgrade old backing store. + DCHECK_LE(db_schema_version, kLatestKnownSchemaVersion); + if (db_schema_version < 1) { + db_schema_version = 1; + PutInt(transaction.get(), + LevelDBSlice(schema_version_key), + db_schema_version); + const std::vector<char> start_key = + DatabaseNameKey::EncodeMinKeyForOrigin(origin); + const std::vector<char> stop_key = + DatabaseNameKey::EncodeStopKeyForOrigin(origin); + scoped_ptr<LevelDBIterator> it = db->CreateIterator(); + for (it->Seek(LevelDBSlice(start_key)); + it->IsValid() && CompareKeys(it->Key(), LevelDBSlice(stop_key)) < 0; + it->Next()) { + int64 database_id = 0; + found = false; + bool ok = GetInt(transaction.get(), it->Key(), database_id, found); + if (!ok) { + INTERNAL_READ_ERROR(SET_UP_METADATA); + return false; + } + if (!found) { + INTERNAL_CONSISTENCY_ERROR(SET_UP_METADATA); + return false; + } + std::vector<char> int_version_key = DatabaseMetaDataKey::Encode( + database_id, DatabaseMetaDataKey::USER_INT_VERSION); + PutVarInt(transaction.get(), + LevelDBSlice(int_version_key), + IndexedDBDatabaseMetadata::DEFAULT_INT_VERSION); + } + } + if (db_schema_version < 2) { + db_schema_version = 2; + PutInt(transaction.get(), + LevelDBSlice(schema_version_key), + db_schema_version); + db_data_version = kWireVersion; + PutInt( + transaction.get(), LevelDBSlice(data_version_key), db_data_version); + } + } + + // All new values will be written using this serialization version. + found = false; + ok = GetInt(transaction.get(), + LevelDBSlice(data_version_key), + db_data_version, + found); + if (!ok) { + INTERNAL_READ_ERROR(SET_UP_METADATA); + return false; + } + if (!found) { + INTERNAL_CONSISTENCY_ERROR(SET_UP_METADATA); + return false; + } + if (db_data_version < latest_known_data_version) { + db_data_version = latest_known_data_version; + PutInt(transaction.get(), LevelDBSlice(data_version_key), db_data_version); + } + + DCHECK_EQ(db_schema_version, kLatestKnownSchemaVersion); + DCHECK_EQ(db_data_version, latest_known_data_version); + + if (!transaction->Commit()) { + INTERNAL_WRITE_ERROR(SET_UP_METADATA); + return false; + } + return true; +} + +template <typename DBOrTransaction> +WARN_UNUSED_RESULT static bool GetMaxObjectStoreId(DBOrTransaction* db, + int64 database_id, + int64& max_object_store_id) { + const std::vector<char> max_object_store_id_key = DatabaseMetaDataKey::Encode( + database_id, DatabaseMetaDataKey::MAX_OBJECT_STORE_ID); + bool ok = + GetMaxObjectStoreId(db, max_object_store_id_key, max_object_store_id); + return ok; +} + +template <typename DBOrTransaction> +WARN_UNUSED_RESULT static bool GetMaxObjectStoreId( + DBOrTransaction* db, + const std::vector<char>& max_object_store_id_key, + int64& max_object_store_id) { + max_object_store_id = -1; + bool found = false; + bool ok = GetInt( + db, LevelDBSlice(max_object_store_id_key), max_object_store_id, found); + if (!ok) + return false; + if (!found) + max_object_store_id = 0; + + DCHECK_GE(max_object_store_id, 0); + return true; +} + +class DefaultLevelDBFactory : public LevelDBFactory { + public: + virtual scoped_ptr<LevelDBDatabase> OpenLevelDB( + const base::FilePath& file_name, + const LevelDBComparator* comparator) OVERRIDE { + return LevelDBDatabase::Open(file_name, comparator); + } + virtual bool DestroyLevelDB(const base::FilePath& file_name) OVERRIDE { + return LevelDBDatabase::Destroy(file_name); + } +}; + +IndexedDBBackingStore::IndexedDBBackingStore( + const string16& identifier, + scoped_ptr<LevelDBDatabase> db, + scoped_ptr<LevelDBComparator> comparator) + : identifier_(identifier), + db_(db.Pass()), + comparator_(comparator.Pass()), + weak_factory_(this) {} + +IndexedDBBackingStore::~IndexedDBBackingStore() { + // db_'s destructor uses comparator_. The order of destruction is important. + db_.reset(); + comparator_.reset(); +} + +IndexedDBBackingStore::RecordIdentifier::RecordIdentifier( + const std::vector<char>& primary_key, + int64 version) + : primary_key_(primary_key), version_(version) { + DCHECK(!primary_key.empty()); +} +IndexedDBBackingStore::RecordIdentifier::RecordIdentifier() + : primary_key_(), version_(-1) {} +IndexedDBBackingStore::RecordIdentifier::~RecordIdentifier() {} + +IndexedDBBackingStore::Cursor::CursorOptions::CursorOptions() {} +IndexedDBBackingStore::Cursor::CursorOptions::~CursorOptions() {} + +enum IndexedDBLevelDBBackingStoreOpenResult { + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MEMORY_SUCCESS, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_SUCCESS, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_FAILED_DIRECTORY, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_FAILED_UNKNOWN_SCHEMA, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_CLEANUP_DESTROY_FAILED, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_CLEANUP_REOPEN_FAILED, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_CLEANUP_REOPEN_SUCCESS, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_FAILED_IO_ERROR_CHECKING_SCHEMA, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_FAILED_UNKNOWN_ERR, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MEMORY_FAILED, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_ATTEMPT_NON_ASCII, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX, +}; + +scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::Open( + const string16& database_identifier, + const string16& path_base_arg, + const string16& file_identifier) { + DefaultLevelDBFactory leveldb_factory; + return IndexedDBBackingStore::Open( + database_identifier, path_base_arg, file_identifier, &leveldb_factory); +} + +scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::Open( + const string16& database_identifier, + const string16& path_base_arg, + const string16& file_identifier, + LevelDBFactory* leveldb_factory) { + IDB_TRACE("IndexedDBBackingStore::open"); + DCHECK(!path_base_arg.empty()); + string16 path_base = path_base_arg; + + scoped_ptr<LevelDBComparator> comparator(new Comparator()); + scoped_ptr<LevelDBDatabase> db; + + if (!IsStringASCII(path_base)) { + base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus", + 1, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1, + base::HistogramBase::kUmaTargetedHistogramFlag) + ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_ATTEMPT_NON_ASCII); + } + base::FilePath file_path_base = + base::FilePath::FromUTF8Unsafe(UTF16ToUTF8(path_base)); + if (!file_util::CreateDirectory(file_path_base)) { + LOG(ERROR) << "Unable to create IndexedDB database path " << path_base; + base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus", + 1, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1, + base::HistogramBase::kUmaTargetedHistogramFlag) + ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_FAILED_DIRECTORY); + return scoped_refptr<IndexedDBBackingStore>(); + } + + // TODO(jsbell): Rework to use FilePath throughout. + base::FilePath identifier_path = + base::FilePath::FromUTF8Unsafe(UTF16ToUTF8(database_identifier)); + base::FilePath file_path = + file_path_base.Append(identifier_path).AppendASCII(".indexeddb.leveldb"); + + db = leveldb_factory->OpenLevelDB(file_path, comparator.get()); + + if (db) { + bool known = false; + bool ok = IsSchemaKnown(db.get(), &known); + if (!ok) { + LOG(ERROR) << "IndexedDB had IO error checking schema, treating it as " + "failure to open"; + base::Histogram::FactoryGet( + "WebCore.IndexedDB.BackingStore.OpenStatus", + 1, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1, + base::HistogramBase::kUmaTargetedHistogramFlag) + ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_FAILED_IO_ERROR_CHECKING_SCHEMA); + db.reset(); + } else if (!known) { + LOG(ERROR) << "IndexedDB backing store had unknown schema, treating it " + "as failure to open"; + base::Histogram::FactoryGet( + "WebCore.IndexedDB.BackingStore.OpenStatus", + 1, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1, + base::HistogramBase::kUmaTargetedHistogramFlag) + ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_FAILED_UNKNOWN_SCHEMA); + db.reset(); + } + } + + if (db) { + base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus", + 1, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1, + base::HistogramBase::kUmaTargetedHistogramFlag) + ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_SUCCESS); + } else { + LOG(ERROR) << "IndexedDB backing store open failed, attempting cleanup"; + bool success = leveldb_factory->DestroyLevelDB(file_path); + if (!success) { + LOG(ERROR) << "IndexedDB backing store cleanup failed"; + base::Histogram::FactoryGet( + "WebCore.IndexedDB.BackingStore.OpenStatus", + 1, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1, + base::HistogramBase::kUmaTargetedHistogramFlag) + ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_CLEANUP_DESTROY_FAILED); + return scoped_refptr<IndexedDBBackingStore>(); + } + + LOG(ERROR) << "IndexedDB backing store cleanup succeeded, reopening"; + db = leveldb_factory->OpenLevelDB(file_path, comparator.get()); + if (!db) { + LOG(ERROR) << "IndexedDB backing store reopen after recovery failed"; + base::Histogram::FactoryGet( + "WebCore.IndexedDB.BackingStore.OpenStatus", + 1, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1, + base::HistogramBase::kUmaTargetedHistogramFlag) + ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_CLEANUP_REOPEN_FAILED); + return scoped_refptr<IndexedDBBackingStore>(); + } + base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus", + 1, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1, + base::HistogramBase::kUmaTargetedHistogramFlag) + ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_CLEANUP_REOPEN_SUCCESS); + } + + if (!db) { + NOTREACHED(); + base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus", + 1, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1, + base::HistogramBase::kUmaTargetedHistogramFlag) + ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_FAILED_UNKNOWN_ERR); + return scoped_refptr<IndexedDBBackingStore>(); + } + + return Create(file_identifier, db.Pass(), comparator.Pass()); +} + +scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::OpenInMemory( + const string16& identifier) { + DefaultLevelDBFactory leveldb_factory; + return IndexedDBBackingStore::OpenInMemory(identifier, &leveldb_factory); +} + +scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::OpenInMemory( + const string16& identifier, + LevelDBFactory* leveldb_factory) { + IDB_TRACE("IndexedDBBackingStore::open_in_memory"); + + scoped_ptr<LevelDBComparator> comparator(new Comparator()); + scoped_ptr<LevelDBDatabase> db = + LevelDBDatabase::OpenInMemory(comparator.get()); + if (!db) { + LOG(ERROR) << "LevelDBDatabase::open_in_memory failed."; + base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus", + 1, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1, + base::HistogramBase::kUmaTargetedHistogramFlag) + ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MEMORY_FAILED); + return scoped_refptr<IndexedDBBackingStore>(); + } + base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus", + 1, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX, + INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1, + base::HistogramBase::kUmaTargetedHistogramFlag) + ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MEMORY_SUCCESS); + + return Create(identifier, db.Pass(), comparator.Pass()); +} + +scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::Create( + const string16& identifier, + scoped_ptr<LevelDBDatabase> db, + scoped_ptr<LevelDBComparator> comparator) { + // TODO(jsbell): Handle comparator name changes. + scoped_refptr<IndexedDBBackingStore> backing_store( + new IndexedDBBackingStore(identifier, db.Pass(), comparator.Pass())); + + if (!SetUpMetadata(backing_store->db_.get(), identifier)) + return scoped_refptr<IndexedDBBackingStore>(); + + return backing_store; +} + +std::vector<string16> IndexedDBBackingStore::GetDatabaseNames() { + std::vector<string16> found_names; + const std::vector<char> start_key = + DatabaseNameKey::EncodeMinKeyForOrigin(identifier_); + const std::vector<char> stop_key = + DatabaseNameKey::EncodeStopKeyForOrigin(identifier_); + + DCHECK(found_names.empty()); + + scoped_ptr<LevelDBIterator> it = db_->CreateIterator(); + for (it->Seek(LevelDBSlice(start_key)); + it->IsValid() && CompareKeys(it->Key(), LevelDBSlice(stop_key)) < 0; + it->Next()) { + const char* p = it->Key().begin(); + const char* limit = it->Key().end(); + + DatabaseNameKey database_name_key; + p = DatabaseNameKey::Decode(p, limit, &database_name_key); + DCHECK(p); + + found_names.push_back(database_name_key.database_name()); + } + return found_names; +} + +bool IndexedDBBackingStore::GetIDBDatabaseMetaData( + const string16& name, + IndexedDBDatabaseMetadata* metadata, + bool& found) { + const std::vector<char> key = DatabaseNameKey::Encode(identifier_, name); + found = false; + + bool ok = GetInt(db_.get(), LevelDBSlice(key), metadata->id, found); + if (!ok) { + INTERNAL_READ_ERROR(GET_IDBDATABASE_METADATA); + return false; + } + if (!found) + return true; + + ok = GetString(db_.get(), + LevelDBSlice(DatabaseMetaDataKey::Encode( + metadata->id, DatabaseMetaDataKey::USER_VERSION)), + metadata->version, + found); + if (!ok) { + INTERNAL_READ_ERROR(GET_IDBDATABASE_METADATA); + return false; + } + if (!found) { + INTERNAL_CONSISTENCY_ERROR(GET_IDBDATABASE_METADATA); + return false; + } + + ok = GetVarInt(db_.get(), + LevelDBSlice(DatabaseMetaDataKey::Encode( + metadata->id, DatabaseMetaDataKey::USER_INT_VERSION)), + metadata->int_version, + found); + if (!ok) { + INTERNAL_READ_ERROR(GET_IDBDATABASE_METADATA); + return false; + } + if (!found) { + INTERNAL_CONSISTENCY_ERROR(GET_IDBDATABASE_METADATA); + return false; + } + + if (metadata->int_version == IndexedDBDatabaseMetadata::DEFAULT_INT_VERSION) + metadata->int_version = IndexedDBDatabaseMetadata::NO_INT_VERSION; + + ok = GetMaxObjectStoreId( + db_.get(), metadata->id, metadata->max_object_store_id); + if (!ok) { + INTERNAL_READ_ERROR(GET_IDBDATABASE_METADATA); + return false; + } + + return true; +} + +WARN_UNUSED_RESULT static bool GetNewDatabaseId(LevelDBDatabase* db, + int64& new_id) { + scoped_refptr<LevelDBTransaction> transaction = + LevelDBTransaction::Create(db); + + new_id = -1; + int64 max_database_id = -1; + bool found = false; + bool ok = GetInt(transaction.get(), + LevelDBSlice(MaxDatabaseIdKey::Encode()), + max_database_id, + found); + if (!ok) { + INTERNAL_READ_ERROR(GET_NEW_DATABASE_ID); + return false; + } + if (!found) + max_database_id = 0; + + DCHECK_GE(max_database_id, 0); + + int64 database_id = max_database_id + 1; + PutInt( + transaction.get(), LevelDBSlice(MaxDatabaseIdKey::Encode()), database_id); + if (!transaction->Commit()) { + INTERNAL_WRITE_ERROR(GET_NEW_DATABASE_ID); + return false; + } + new_id = database_id; + return true; +} + +bool IndexedDBBackingStore::CreateIDBDatabaseMetaData(const string16& name, + const string16& version, + int64 int_version, + int64& row_id) { + bool ok = GetNewDatabaseId(db_.get(), row_id); + if (!ok) + return false; + DCHECK_GE(row_id, 0); + + if (int_version == IndexedDBDatabaseMetadata::NO_INT_VERSION) + int_version = IndexedDBDatabaseMetadata::DEFAULT_INT_VERSION; + + scoped_refptr<LevelDBTransaction> transaction = + LevelDBTransaction::Create(db_.get()); + PutInt(transaction.get(), + LevelDBSlice(DatabaseNameKey::Encode(identifier_, name)), + row_id); + PutString(transaction.get(), + LevelDBSlice(DatabaseMetaDataKey::Encode( + row_id, DatabaseMetaDataKey::USER_VERSION)), + version); + PutVarInt(transaction.get(), + LevelDBSlice(DatabaseMetaDataKey::Encode( + row_id, DatabaseMetaDataKey::USER_INT_VERSION)), + int_version); + if (!transaction->Commit()) { + INTERNAL_WRITE_ERROR(CREATE_IDBDATABASE_METADATA); + return false; + } + return true; +} + +bool IndexedDBBackingStore::UpdateIDBDatabaseIntVersion( + IndexedDBBackingStore::Transaction* transaction, + int64 row_id, + int64 int_version) { + if (int_version == IndexedDBDatabaseMetadata::NO_INT_VERSION) + int_version = IndexedDBDatabaseMetadata::DEFAULT_INT_VERSION; + DCHECK_GE(int_version, 0) << "int_version was " << int_version; + PutVarInt(Transaction::LevelDBTransactionFrom(transaction), + LevelDBSlice(DatabaseMetaDataKey::Encode( + row_id, DatabaseMetaDataKey::USER_INT_VERSION)), + int_version); + return true; +} + +bool IndexedDBBackingStore::UpdateIDBDatabaseMetaData( + IndexedDBBackingStore::Transaction* transaction, + int64 row_id, + const string16& version) { + PutString(Transaction::LevelDBTransactionFrom(transaction), + LevelDBSlice(DatabaseMetaDataKey::Encode( + row_id, DatabaseMetaDataKey::USER_VERSION)), + version); + return true; +} + +static void DeleteRange(LevelDBTransaction* transaction, + const std::vector<char>& begin, + const std::vector<char>& end) { + scoped_ptr<LevelDBIterator> it = transaction->CreateIterator(); + for (it->Seek(LevelDBSlice(begin)); + it->IsValid() && CompareKeys(it->Key(), LevelDBSlice(end)) < 0; + it->Next()) + transaction->Remove(it->Key()); +} + +bool IndexedDBBackingStore::DeleteDatabase(const string16& name) { + IDB_TRACE("IndexedDBBackingStore::delete_database"); + scoped_ptr<LevelDBWriteOnlyTransaction> transaction = + LevelDBWriteOnlyTransaction::Create(db_.get()); + + IndexedDBDatabaseMetadata metadata; + bool success = false; + bool ok = GetIDBDatabaseMetaData(name, &metadata, success); + if (!ok) + return false; + if (!success) + return true; + + const std::vector<char> start_key = DatabaseMetaDataKey::Encode( + metadata.id, DatabaseMetaDataKey::ORIGIN_NAME); + const std::vector<char> stop_key = DatabaseMetaDataKey::Encode( + metadata.id + 1, DatabaseMetaDataKey::ORIGIN_NAME); + scoped_ptr<LevelDBIterator> it = db_->CreateIterator(); + for (it->Seek(LevelDBSlice(start_key)); + it->IsValid() && CompareKeys(it->Key(), LevelDBSlice(stop_key)) < 0; + it->Next()) + transaction->Remove(it->Key()); + + const std::vector<char> key = DatabaseNameKey::Encode(identifier_, name); + transaction->Remove(LevelDBSlice(key)); + + if (!transaction->Commit()) { + INTERNAL_WRITE_ERROR(DELETE_DATABASE); + return false; + } + return true; +} + +static bool CheckObjectStoreAndMetaDataType(const LevelDBIterator* it, + const std::vector<char>& stop_key, + int64 object_store_id, + int64 meta_data_type) { + if (!it->IsValid() || CompareKeys(it->Key(), LevelDBSlice(stop_key)) >= 0) + return false; + + ObjectStoreMetaDataKey meta_data_key; + const char* p = ObjectStoreMetaDataKey::Decode( + it->Key().begin(), it->Key().end(), &meta_data_key); + DCHECK(p); + if (meta_data_key.ObjectStoreId() != object_store_id) + return false; + if (meta_data_key.MetaDataType() != meta_data_type) + return false; + return true; +} + +// TODO(jsbell): This should do some error handling rather than +// plowing ahead when bad data is encountered. +bool IndexedDBBackingStore::GetObjectStores( + int64 database_id, + IndexedDBDatabaseMetadata::ObjectStoreMap* object_stores) { + IDB_TRACE("IndexedDBBackingStore::get_object_stores"); + if (!KeyPrefix::IsValidDatabaseId(database_id)) + return false; + const std::vector<char> start_key = + ObjectStoreMetaDataKey::Encode(database_id, 1, 0); + const std::vector<char> stop_key = + ObjectStoreMetaDataKey::EncodeMaxKey(database_id); + + DCHECK(object_stores->empty()); + + scoped_ptr<LevelDBIterator> it = db_->CreateIterator(); + it->Seek(LevelDBSlice(start_key)); + while (it->IsValid() && CompareKeys(it->Key(), LevelDBSlice(stop_key)) < 0) { + const char* p = it->Key().begin(); + const char* limit = it->Key().end(); + + ObjectStoreMetaDataKey meta_data_key; + p = ObjectStoreMetaDataKey::Decode(p, limit, &meta_data_key); + DCHECK(p); + if (meta_data_key.MetaDataType() != ObjectStoreMetaDataKey::NAME) { + INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES); + // Possible stale metadata, but don't fail the load. + it->Next(); + continue; + } + + int64 object_store_id = meta_data_key.ObjectStoreId(); + + // TODO(jsbell): Do this by direct key lookup rather than iteration, to + // simplify. + string16 object_store_name = + DecodeString(it->Value().begin(), it->Value().end()); + + it->Next(); + if (!CheckObjectStoreAndMetaDataType(it.get(), + stop_key, + object_store_id, + ObjectStoreMetaDataKey::KEY_PATH)) { + INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES); + break; + } + IndexedDBKeyPath key_path = + DecodeIDBKeyPath(it->Value().begin(), it->Value().end()); + + it->Next(); + if (!CheckObjectStoreAndMetaDataType( + it.get(), + stop_key, + object_store_id, + ObjectStoreMetaDataKey::AUTO_INCREMENT)) { + INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES); + break; + } + bool auto_increment = DecodeBool(it->Value().begin(), it->Value().end()); + + it->Next(); // Is evicatble. + if (!CheckObjectStoreAndMetaDataType(it.get(), + stop_key, + object_store_id, + ObjectStoreMetaDataKey::EVICTABLE)) { + INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES); + break; + } + + it->Next(); // Last version. + if (!CheckObjectStoreAndMetaDataType( + it.get(), + stop_key, + object_store_id, + ObjectStoreMetaDataKey::LAST_VERSION)) { + INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES); + break; + } + + it->Next(); // Maximum index id allocated. + if (!CheckObjectStoreAndMetaDataType( + it.get(), + stop_key, + object_store_id, + ObjectStoreMetaDataKey::MAX_INDEX_ID)) { + INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES); + break; + } + int64 max_index_id = DecodeInt(it->Value().begin(), it->Value().end()); + + it->Next(); // [optional] has key path (is not null) + if (CheckObjectStoreAndMetaDataType(it.get(), + stop_key, + object_store_id, + ObjectStoreMetaDataKey::HAS_KEY_PATH)) { + bool has_key_path = DecodeBool(it->Value().begin(), it->Value().end()); + // This check accounts for two layers of legacy coding: + // (1) Initially, has_key_path was added to distinguish null vs. string. + // (2) Later, null vs. string vs. array was stored in the key_path itself. + // So this check is only relevant for string-type key_paths. + if (!has_key_path && + (key_path.type() == WebKit::WebIDBKeyPath::StringType && + !key_path.string().empty())) { + INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES); + break; + } + if (!has_key_path) + key_path = IndexedDBKeyPath(); + it->Next(); + } + + int64 key_generator_current_number = -1; + if (CheckObjectStoreAndMetaDataType( + it.get(), + stop_key, + object_store_id, + ObjectStoreMetaDataKey::KEY_GENERATOR_CURRENT_NUMBER)) { + key_generator_current_number = + DecodeInt(it->Value().begin(), it->Value().end()); + // TODO(jsbell): Return key_generator_current_number, cache in + // object store, and write lazily to backing store. For now, + // just assert that if it was written it was valid. + DCHECK_GE(key_generator_current_number, kKeyGeneratorInitialNumber); + it->Next(); + } + + IndexedDBObjectStoreMetadata metadata(object_store_name, + object_store_id, + key_path, + auto_increment, + max_index_id); + if (!GetIndexes(database_id, object_store_id, &metadata.indexes)) + return false; + (*object_stores)[object_store_id] = metadata; + } + return true; +} + +WARN_UNUSED_RESULT static bool SetMaxObjectStoreId( + LevelDBTransaction* transaction, + int64 database_id, + int64 object_store_id) { + const std::vector<char> max_object_store_id_key = DatabaseMetaDataKey::Encode( + database_id, DatabaseMetaDataKey::MAX_OBJECT_STORE_ID); + int64 max_object_store_id = -1; + bool ok = GetMaxObjectStoreId( + transaction, max_object_store_id_key, max_object_store_id); + if (!ok) { + INTERNAL_READ_ERROR(SET_MAX_OBJECT_STORE_ID); + return false; + } + + if (object_store_id <= max_object_store_id) { + INTERNAL_CONSISTENCY_ERROR(SET_MAX_OBJECT_STORE_ID); + return false; + } + PutInt(transaction, LevelDBSlice(max_object_store_id_key), object_store_id); + return true; +} + +bool IndexedDBBackingStore::CreateObjectStore( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + const string16& name, + const IndexedDBKeyPath& key_path, + bool auto_increment) { + IDB_TRACE("IndexedDBBackingStore::create_object_store"); + if (!KeyPrefix::ValidIds(database_id, object_store_id)) + return false; + LevelDBTransaction* leveldb_transaction = + IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction); + if (!SetMaxObjectStoreId(leveldb_transaction, database_id, object_store_id)) + return false; + + const std::vector<char> name_key = ObjectStoreMetaDataKey::Encode( + database_id, object_store_id, ObjectStoreMetaDataKey::NAME); + const std::vector<char> key_path_key = ObjectStoreMetaDataKey::Encode( + database_id, object_store_id, ObjectStoreMetaDataKey::KEY_PATH); + const std::vector<char> auto_increment_key = ObjectStoreMetaDataKey::Encode( + database_id, object_store_id, ObjectStoreMetaDataKey::AUTO_INCREMENT); + const std::vector<char> evictable_key = ObjectStoreMetaDataKey::Encode( + database_id, object_store_id, ObjectStoreMetaDataKey::EVICTABLE); + const std::vector<char> last_version_key = ObjectStoreMetaDataKey::Encode( + database_id, object_store_id, ObjectStoreMetaDataKey::LAST_VERSION); + const std::vector<char> max_index_id_key = ObjectStoreMetaDataKey::Encode( + database_id, object_store_id, ObjectStoreMetaDataKey::MAX_INDEX_ID); + const std::vector<char> has_key_path_key = ObjectStoreMetaDataKey::Encode( + database_id, object_store_id, ObjectStoreMetaDataKey::HAS_KEY_PATH); + const std::vector<char> key_generator_current_number_key = + ObjectStoreMetaDataKey::Encode( + database_id, + object_store_id, + ObjectStoreMetaDataKey::KEY_GENERATOR_CURRENT_NUMBER); + const std::vector<char> names_key = + ObjectStoreNamesKey::Encode(database_id, name); + + PutString(leveldb_transaction, LevelDBSlice(name_key), name); + PutIDBKeyPath(leveldb_transaction, LevelDBSlice(key_path_key), key_path); + PutInt(leveldb_transaction, LevelDBSlice(auto_increment_key), auto_increment); + PutInt(leveldb_transaction, LevelDBSlice(evictable_key), false); + PutInt(leveldb_transaction, LevelDBSlice(last_version_key), 1); + PutInt(leveldb_transaction, LevelDBSlice(max_index_id_key), kMinimumIndexId); + PutBool( + leveldb_transaction, LevelDBSlice(has_key_path_key), !key_path.IsNull()); + PutInt(leveldb_transaction, + LevelDBSlice(key_generator_current_number_key), + kKeyGeneratorInitialNumber); + PutInt(leveldb_transaction, LevelDBSlice(names_key), object_store_id); + return true; +} + +bool IndexedDBBackingStore::DeleteObjectStore( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id) { + IDB_TRACE("IndexedDBBackingStore::delete_object_store"); + if (!KeyPrefix::ValidIds(database_id, object_store_id)) + return false; + LevelDBTransaction* leveldb_transaction = + IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction); + + string16 object_store_name; + bool found = false; + bool ok = GetString( + leveldb_transaction, + LevelDBSlice(ObjectStoreMetaDataKey::Encode( + database_id, object_store_id, ObjectStoreMetaDataKey::NAME)), + object_store_name, + found); + if (!ok) { + INTERNAL_READ_ERROR(DELETE_OBJECT_STORE); + return false; + } + if (!found) { + INTERNAL_CONSISTENCY_ERROR(DELETE_OBJECT_STORE); + return false; + } + + DeleteRange( + leveldb_transaction, + ObjectStoreMetaDataKey::Encode(database_id, object_store_id, 0), + ObjectStoreMetaDataKey::EncodeMaxKey(database_id, object_store_id)); + + leveldb_transaction->Remove(LevelDBSlice( + ObjectStoreNamesKey::Encode(database_id, object_store_name))); + + DeleteRange(leveldb_transaction, + IndexFreeListKey::Encode(database_id, object_store_id, 0), + IndexFreeListKey::EncodeMaxKey(database_id, object_store_id)); + DeleteRange(leveldb_transaction, + IndexMetaDataKey::Encode(database_id, object_store_id, 0, 0), + IndexMetaDataKey::EncodeMaxKey(database_id, object_store_id)); + + return ClearObjectStore(transaction, database_id, object_store_id); +} + +bool IndexedDBBackingStore::GetRecord( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + const IndexedDBKey& key, + std::vector<char>& record) { + IDB_TRACE("IndexedDBBackingStore::get_record"); + if (!KeyPrefix::ValidIds(database_id, object_store_id)) + return false; + LevelDBTransaction* leveldb_transaction = + IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction); + + const std::vector<char> leveldb_key = + ObjectStoreDataKey::Encode(database_id, object_store_id, key); + std::vector<char> data; + + record.clear(); + + bool found = false; + bool ok = leveldb_transaction->Get(LevelDBSlice(leveldb_key), data, found); + if (!ok) { + INTERNAL_READ_ERROR(GET_RECORD); + return false; + } + if (!found) + return true; + if (!data.size()) { + INTERNAL_READ_ERROR(GET_RECORD); + return false; + } + + int64 version; + const char* p = DecodeVarInt(&*data.begin(), &*data.rbegin() + 1, version); + if (!p) { + INTERNAL_READ_ERROR(GET_RECORD); + return false; + } + + record.insert(record.end(), p, static_cast<const char*>(&*data.rbegin()) + 1); + return true; +} + +WARN_UNUSED_RESULT static bool GetNewVersionNumber( + LevelDBTransaction* transaction, + int64 database_id, + int64 object_store_id, + int64& new_version_number) { + const std::vector<char> last_version_key = ObjectStoreMetaDataKey::Encode( + database_id, object_store_id, ObjectStoreMetaDataKey::LAST_VERSION); + + new_version_number = -1; + int64 last_version = -1; + bool found = false; + bool ok = + GetInt(transaction, LevelDBSlice(last_version_key), last_version, found); + if (!ok) { + INTERNAL_READ_ERROR(GET_NEW_VERSION_NUMBER); + return false; + } + if (!found) + last_version = 0; + + DCHECK_GE(last_version, 0); + + int64 version = last_version + 1; + PutInt(transaction, LevelDBSlice(last_version_key), version); + + DCHECK(version > + last_version); // TODO(jsbell): Think about how we want to handle + // the overflow scenario. + + new_version_number = version; + return true; +} + +bool IndexedDBBackingStore::PutRecord( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + const IndexedDBKey& key, + const std::vector<char>& value, + RecordIdentifier* record_identifier) { + IDB_TRACE("IndexedDBBackingStore::put_record"); + if (!KeyPrefix::ValidIds(database_id, object_store_id)) + return false; + DCHECK(key.IsValid()); + + LevelDBTransaction* leveldb_transaction = + IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction); + int64 version = -1; + bool ok = GetNewVersionNumber( + leveldb_transaction, database_id, object_store_id, version); + if (!ok) + return false; + DCHECK_GE(version, 0); + const std::vector<char> object_storedata_key = + ObjectStoreDataKey::Encode(database_id, object_store_id, key); + + std::vector<char> v(EncodeVarInt(version)); + + v.insert(v.end(), value.begin(), value.end()); + + leveldb_transaction->Put(LevelDBSlice(object_storedata_key), v); + + const std::vector<char> exists_entry_key = + ExistsEntryKey::Encode(database_id, object_store_id, key); + leveldb_transaction->Put(LevelDBSlice(exists_entry_key), EncodeInt(version)); + + record_identifier->Reset(EncodeIDBKey(key), version); + return true; +} + +bool IndexedDBBackingStore::ClearObjectStore( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id) { + IDB_TRACE("IndexedDBBackingStore::clear_object_store"); + if (!KeyPrefix::ValidIds(database_id, object_store_id)) + return false; + LevelDBTransaction* leveldb_transaction = + IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction); + const std::vector<char> start_key = + KeyPrefix(database_id, object_store_id).Encode(); + const std::vector<char> stop_key = + KeyPrefix(database_id, object_store_id + 1).Encode(); + + DeleteRange(leveldb_transaction, start_key, stop_key); + return true; +} + +bool IndexedDBBackingStore::DeleteRecord( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + const RecordIdentifier& record_identifier) { + IDB_TRACE("IndexedDBBackingStore::delete_record"); + if (!KeyPrefix::ValidIds(database_id, object_store_id)) + return false; + LevelDBTransaction* leveldb_transaction = + IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction); + + const std::vector<char> object_store_data_key = ObjectStoreDataKey::Encode( + database_id, object_store_id, record_identifier.primary_key()); + leveldb_transaction->Remove(LevelDBSlice(object_store_data_key)); + + const std::vector<char> exists_entry_key = ExistsEntryKey::Encode( + database_id, object_store_id, record_identifier.primary_key()); + leveldb_transaction->Remove(LevelDBSlice(exists_entry_key)); + return true; +} + +bool IndexedDBBackingStore::GetKeyGeneratorCurrentNumber( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + int64& key_generator_current_number) { + if (!KeyPrefix::ValidIds(database_id, object_store_id)) + return false; + LevelDBTransaction* leveldb_transaction = + IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction); + + const std::vector<char> key_generator_current_number_key = + ObjectStoreMetaDataKey::Encode( + database_id, + object_store_id, + ObjectStoreMetaDataKey::KEY_GENERATOR_CURRENT_NUMBER); + + key_generator_current_number = -1; + std::vector<char> data; + + bool found = false; + bool ok = leveldb_transaction->Get( + LevelDBSlice(key_generator_current_number_key), data, found); + if (!ok) { + INTERNAL_READ_ERROR(GET_KEY_GENERATOR_CURRENT_NUMBER); + return false; + } + if (found) { + key_generator_current_number = DecodeInt(data.begin(), data.end()); + } else { + // Previously, the key generator state was not stored explicitly + // but derived from the maximum numeric key present in existing + // data. This violates the spec as the data may be cleared but the + // key generator state must be preserved. + // TODO(jsbell): Fix this for all stores on database open? + const std::vector<char> start_key = + ObjectStoreDataKey::Encode(database_id, object_store_id, MinIDBKey()); + const std::vector<char> stop_key = + ObjectStoreDataKey::Encode(database_id, object_store_id, MaxIDBKey()); + + scoped_ptr<LevelDBIterator> it = leveldb_transaction->CreateIterator(); + int64 max_numeric_key = 0; + + for (it->Seek(LevelDBSlice(start_key)); + it->IsValid() && CompareKeys(it->Key(), LevelDBSlice(stop_key)) < 0; + it->Next()) { + const char* p = it->Key().begin(); + const char* limit = it->Key().end(); + + ObjectStoreDataKey data_key; + p = ObjectStoreDataKey::Decode(p, limit, &data_key); + DCHECK(p); + + scoped_ptr<IndexedDBKey> user_key = data_key.user_key(); + if (user_key->type() == WebKit::WebIDBKey::NumberType) { + int64 n = static_cast<int64>(user_key->number()); + if (n > max_numeric_key) + max_numeric_key = n; + } + } + + key_generator_current_number = max_numeric_key + 1; + } + + return true; +} + +bool IndexedDBBackingStore::MaybeUpdateKeyGeneratorCurrentNumber( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + int64 new_number, + bool check_current) { + if (!KeyPrefix::ValidIds(database_id, object_store_id)) + return false; + LevelDBTransaction* leveldb_transaction = + IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction); + + if (check_current) { + int64 current_number; + bool ok = GetKeyGeneratorCurrentNumber( + transaction, database_id, object_store_id, current_number); + if (!ok) + return false; + if (new_number <= current_number) + return true; + } + + const std::vector<char> key_generator_current_number_key = + ObjectStoreMetaDataKey::Encode( + database_id, + object_store_id, + ObjectStoreMetaDataKey::KEY_GENERATOR_CURRENT_NUMBER); + PutInt(leveldb_transaction, + LevelDBSlice(key_generator_current_number_key), + new_number); + return true; +} + +bool IndexedDBBackingStore::KeyExistsInObjectStore( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + const IndexedDBKey& key, + RecordIdentifier* found_record_identifier, + bool& found) { + IDB_TRACE("IndexedDBBackingStore::key_exists_in_object_store"); + if (!KeyPrefix::ValidIds(database_id, object_store_id)) + return false; + found = false; + LevelDBTransaction* leveldb_transaction = + IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction); + const std::vector<char> leveldb_key = + ObjectStoreDataKey::Encode(database_id, object_store_id, key); + std::vector<char> data; + + bool ok = leveldb_transaction->Get(LevelDBSlice(leveldb_key), data, found); + if (!ok) { + INTERNAL_READ_ERROR(KEY_EXISTS_IN_OBJECT_STORE); + return false; + } + if (!found) + return true; + if (!data.size()) { + INTERNAL_READ_ERROR(KEY_EXISTS_IN_OBJECT_STORE); + return false; + } + + int64 version; + if (DecodeVarInt(&*data.begin(), &*data.rbegin() + 1, version) == 0) + return false; + + found_record_identifier->Reset(EncodeIDBKey(key), version); + return true; +} + +static bool CheckIndexAndMetaDataKey(const LevelDBIterator* it, + const std::vector<char>& stop_key, + int64 index_id, + unsigned char meta_data_type) { + if (!it->IsValid() || CompareKeys(it->Key(), LevelDBSlice(stop_key)) >= 0) + return false; + + IndexMetaDataKey meta_data_key; + const char* p = IndexMetaDataKey::Decode( + it->Key().begin(), it->Key().end(), &meta_data_key); + DCHECK(p); + if (meta_data_key.IndexId() != index_id) + return false; + if (meta_data_key.meta_data_type() != meta_data_type) + return false; + return true; +} + +// TODO(jsbell): This should do some error handling rather than plowing ahead +// when bad +// data is encountered. +bool IndexedDBBackingStore::GetIndexes( + int64 database_id, + int64 object_store_id, + IndexedDBObjectStoreMetadata::IndexMap* indexes) { + IDB_TRACE("IndexedDBBackingStore::get_indexes"); + if (!KeyPrefix::ValidIds(database_id, object_store_id)) + return false; + const std::vector<char> start_key = + IndexMetaDataKey::Encode(database_id, object_store_id, 0, 0); + const std::vector<char> stop_key = + IndexMetaDataKey::Encode(database_id, object_store_id + 1, 0, 0); + + DCHECK(indexes->empty()); + + scoped_ptr<LevelDBIterator> it = db_->CreateIterator(); + it->Seek(LevelDBSlice(start_key)); + while (it->IsValid() && + CompareKeys(LevelDBSlice(it->Key()), LevelDBSlice(stop_key)) < 0) { + const char* p = it->Key().begin(); + const char* limit = it->Key().end(); + + IndexMetaDataKey meta_data_key; + p = IndexMetaDataKey::Decode(p, limit, &meta_data_key); + DCHECK(p); + if (meta_data_key.meta_data_type() != IndexMetaDataKey::NAME) { + INTERNAL_CONSISTENCY_ERROR(GET_INDEXES); + // Possible stale metadata due to http://webkit.org/b/85557 but don't fail + // the load. + it->Next(); + continue; + } + + // TODO(jsbell): Do this by direct key lookup rather than iteration, to + // simplify. + int64 index_id = meta_data_key.IndexId(); + string16 index_name = DecodeString(it->Value().begin(), it->Value().end()); + + it->Next(); // unique flag + if (!CheckIndexAndMetaDataKey( + it.get(), stop_key, index_id, IndexMetaDataKey::UNIQUE)) { + INTERNAL_CONSISTENCY_ERROR(GET_INDEXES); + break; + } + bool index_unique = DecodeBool(it->Value().begin(), it->Value().end()); + + it->Next(); // key_path + if (!CheckIndexAndMetaDataKey( + it.get(), stop_key, index_id, IndexMetaDataKey::KEY_PATH)) { + INTERNAL_CONSISTENCY_ERROR(GET_INDEXES); + break; + } + IndexedDBKeyPath key_path = + DecodeIDBKeyPath(it->Value().begin(), it->Value().end()); + + it->Next(); // [optional] multi_entry flag + bool index_multi_entry = false; + if (CheckIndexAndMetaDataKey( + it.get(), stop_key, index_id, IndexMetaDataKey::MULTI_ENTRY)) { + index_multi_entry = DecodeBool(it->Value().begin(), it->Value().end()); + it->Next(); + } + + (*indexes)[index_id] = IndexedDBIndexMetadata( + index_name, index_id, key_path, index_unique, index_multi_entry); + } + return true; +} + +WARN_UNUSED_RESULT static bool SetMaxIndexId(LevelDBTransaction* transaction, + int64 database_id, + int64 object_store_id, + int64 index_id) { + int64 max_index_id = -1; + const std::vector<char> max_index_id_key = ObjectStoreMetaDataKey::Encode( + database_id, object_store_id, ObjectStoreMetaDataKey::MAX_INDEX_ID); + bool found = false; + bool ok = + GetInt(transaction, LevelDBSlice(max_index_id_key), max_index_id, found); + if (!ok) { + INTERNAL_READ_ERROR(SET_MAX_INDEX_ID); + return false; + } + if (!found) + max_index_id = kMinimumIndexId; + + if (index_id <= max_index_id) { + INTERNAL_CONSISTENCY_ERROR(SET_MAX_INDEX_ID); + return false; + } + + PutInt(transaction, LevelDBSlice(max_index_id_key), index_id); + return true; +} + +bool IndexedDBBackingStore::CreateIndex( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + int64 index_id, + const string16& name, + const IndexedDBKeyPath& key_path, + bool is_unique, + bool is_multi_entry) { + IDB_TRACE("IndexedDBBackingStore::create_index"); + if (!KeyPrefix::ValidIds(database_id, object_store_id, index_id)) + return false; + LevelDBTransaction* leveldb_transaction = + IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction); + if (!SetMaxIndexId( + leveldb_transaction, database_id, object_store_id, index_id)) + return false; + + const std::vector<char> name_key = IndexMetaDataKey::Encode( + database_id, object_store_id, index_id, IndexMetaDataKey::NAME); + const std::vector<char> unique_key = IndexMetaDataKey::Encode( + database_id, object_store_id, index_id, IndexMetaDataKey::UNIQUE); + const std::vector<char> key_path_key = IndexMetaDataKey::Encode( + database_id, object_store_id, index_id, IndexMetaDataKey::KEY_PATH); + const std::vector<char> multi_entry_key = IndexMetaDataKey::Encode( + database_id, object_store_id, index_id, IndexMetaDataKey::MULTI_ENTRY); + + PutString(leveldb_transaction, LevelDBSlice(name_key), name); + PutBool(leveldb_transaction, LevelDBSlice(unique_key), is_unique); + PutIDBKeyPath(leveldb_transaction, LevelDBSlice(key_path_key), key_path); + PutBool(leveldb_transaction, LevelDBSlice(multi_entry_key), is_multi_entry); + return true; +} + +bool IndexedDBBackingStore::DeleteIndex( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + int64 index_id) { + IDB_TRACE("IndexedDBBackingStore::delete_index"); + if (!KeyPrefix::ValidIds(database_id, object_store_id, index_id)) + return false; + LevelDBTransaction* leveldb_transaction = + IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction); + + const std::vector<char> index_meta_data_start = + IndexMetaDataKey::Encode(database_id, object_store_id, index_id, 0); + const std::vector<char> index_meta_data_end = + IndexMetaDataKey::EncodeMaxKey(database_id, object_store_id, index_id); + DeleteRange(leveldb_transaction, index_meta_data_start, index_meta_data_end); + + const std::vector<char> index_data_start = + IndexDataKey::EncodeMinKey(database_id, object_store_id, index_id); + const std::vector<char> index_data_end = + IndexDataKey::EncodeMaxKey(database_id, object_store_id, index_id); + DeleteRange(leveldb_transaction, index_data_start, index_data_end); + return true; +} + +bool IndexedDBBackingStore::PutIndexDataForRecord( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + int64 index_id, + const IndexedDBKey& key, + const RecordIdentifier& record_identifier) { + IDB_TRACE("IndexedDBBackingStore::put_index_data_for_record"); + DCHECK(key.IsValid()); + if (!KeyPrefix::ValidIds(database_id, object_store_id, index_id)) + return false; + + LevelDBTransaction* leveldb_transaction = + IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction); + const std::vector<char> index_data_key = + IndexDataKey::Encode(database_id, + object_store_id, + index_id, + EncodeIDBKey(key), + record_identifier.primary_key()); + + std::vector<char> data(EncodeVarInt(record_identifier.version())); + const std::vector<char>& primary_key = record_identifier.primary_key(); + data.insert(data.end(), primary_key.begin(), primary_key.end()); + + leveldb_transaction->Put(LevelDBSlice(index_data_key), data); + return true; +} + +static bool FindGreatestKeyLessThanOrEqual(LevelDBTransaction* transaction, + const std::vector<char>& target, + std::vector<char>& found_key) { + scoped_ptr<LevelDBIterator> it = transaction->CreateIterator(); + it->Seek(LevelDBSlice(target)); + + if (!it->IsValid()) { + it->SeekToLast(); + if (!it->IsValid()) + return false; + } + + while (CompareIndexKeys(LevelDBSlice(it->Key()), LevelDBSlice(target)) > 0) { + it->Prev(); + if (!it->IsValid()) + return false; + } + + do { + found_key.assign(it->Key().begin(), it->Key().end()); + + // There can be several index keys that compare equal. We want the last one. + it->Next(); + } while (it->IsValid() && !CompareIndexKeys(it->Key(), LevelDBSlice(target))); + + return true; +} + +static bool VersionExists(LevelDBTransaction* transaction, + int64 database_id, + int64 object_store_id, + int64 version, + const std::vector<char>& encoded_primary_key, + bool& exists) { + const std::vector<char> key = + ExistsEntryKey::Encode(database_id, object_store_id, encoded_primary_key); + std::vector<char> data; + + bool ok = transaction->Get(LevelDBSlice(key), data, exists); + if (!ok) { + INTERNAL_READ_ERROR(VERSION_EXISTS); + return false; + } + if (!exists) + return true; + + exists = (DecodeInt(data.begin(), data.end()) == version); + return true; +} + +bool IndexedDBBackingStore::FindKeyInIndex( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + int64 index_id, + const IndexedDBKey& key, + std::vector<char>& found_encoded_primary_key, + bool& found) { + IDB_TRACE("IndexedDBBackingStore::find_key_in_index"); + DCHECK(KeyPrefix::ValidIds(database_id, object_store_id, index_id)); + + DCHECK(found_encoded_primary_key.empty()); + found = false; + + LevelDBTransaction* leveldb_transaction = + IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction); + const std::vector<char> leveldb_key = + IndexDataKey::Encode(database_id, object_store_id, index_id, key); + scoped_ptr<LevelDBIterator> it = leveldb_transaction->CreateIterator(); + it->Seek(LevelDBSlice(leveldb_key)); + + for (;;) { + if (!it->IsValid()) + return true; + if (CompareIndexKeys(it->Key(), LevelDBSlice(leveldb_key)) > 0) + return true; + + int64 version; + const char* p = + DecodeVarInt(it->Value().begin(), it->Value().end(), version); + if (!p) { + INTERNAL_READ_ERROR(FIND_KEY_IN_INDEX); + return false; + } + found_encoded_primary_key.insert( + found_encoded_primary_key.end(), p, it->Value().end()); + + bool exists = false; + bool ok = VersionExists(leveldb_transaction, + database_id, + object_store_id, + version, + found_encoded_primary_key, + exists); + if (!ok) + return false; + if (!exists) { + // Delete stale index data entry and continue. + leveldb_transaction->Remove(it->Key()); + it->Next(); + continue; + } + found = true; + return true; + } +} + +bool IndexedDBBackingStore::GetPrimaryKeyViaIndex( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + int64 index_id, + const IndexedDBKey& key, + scoped_ptr<IndexedDBKey>* primary_key) { + IDB_TRACE("IndexedDBBackingStore::get_primary_key_via_index"); + if (!KeyPrefix::ValidIds(database_id, object_store_id, index_id)) + return false; + + bool found = false; + std::vector<char> found_encoded_primary_key; + bool ok = FindKeyInIndex(transaction, + database_id, + object_store_id, + index_id, + key, + found_encoded_primary_key, + found); + if (!ok) { + INTERNAL_READ_ERROR(GET_PRIMARY_KEY_VIA_INDEX); + return false; + } + if (!found) + return true; + if (!found_encoded_primary_key.size()) { + INTERNAL_READ_ERROR(GET_PRIMARY_KEY_VIA_INDEX); + return false; + } + + DecodeIDBKey(&*found_encoded_primary_key.begin(), + &*found_encoded_primary_key.rbegin() + 1, + primary_key); + return true; +} + +bool IndexedDBBackingStore::KeyExistsInIndex( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + int64 index_id, + const IndexedDBKey& index_key, + scoped_ptr<IndexedDBKey>* found_primary_key, + bool& exists) { + IDB_TRACE("IndexedDBBackingStore::key_exists_in_index"); + if (!KeyPrefix::ValidIds(database_id, object_store_id, index_id)) + return false; + + exists = false; + std::vector<char> found_encoded_primary_key; + bool ok = FindKeyInIndex(transaction, + database_id, + object_store_id, + index_id, + index_key, + found_encoded_primary_key, + exists); + if (!ok) { + INTERNAL_READ_ERROR(KEY_EXISTS_IN_INDEX); + return false; + } + if (!exists) + return true; + if (!found_encoded_primary_key.size()) { + INTERNAL_READ_ERROR(KEY_EXISTS_IN_INDEX); + return false; + } + + DecodeIDBKey(&*found_encoded_primary_key.begin(), + &*found_encoded_primary_key.rbegin() + 1, + found_primary_key); + return true; +} + +IndexedDBBackingStore::Cursor::Cursor( + const IndexedDBBackingStore::Cursor* other) + : transaction_(other->transaction_), + cursor_options_(other->cursor_options_), + current_key_(new IndexedDBKey(*other->current_key_)) { + if (other->iterator_) { + iterator_ = transaction_->CreateIterator(); + + if (other->iterator_->IsValid()) { + iterator_->Seek(other->iterator_->Key()); + DCHECK(iterator_->IsValid()); + } + } +} + +IndexedDBBackingStore::Cursor::Cursor(LevelDBTransaction* transaction, + const CursorOptions& cursor_options) + : transaction_(transaction), cursor_options_(cursor_options) {} +IndexedDBBackingStore::Cursor::~Cursor() {} + +bool IndexedDBBackingStore::Cursor::FirstSeek() { + iterator_ = transaction_->CreateIterator(); + if (cursor_options_.forward) + iterator_->Seek(LevelDBSlice(cursor_options_.low_key)); + else + iterator_->Seek(LevelDBSlice(cursor_options_.high_key)); + + return ContinueFunction(0, READY); +} + +bool IndexedDBBackingStore::Cursor::Advance(unsigned long count) { + while (count--) { + if (!ContinueFunction()) + return false; + } + return true; +} + +bool IndexedDBBackingStore::Cursor::ContinueFunction(const IndexedDBKey* key, + IteratorState next_state) { + // TODO(alecflett): avoid a copy here? + IndexedDBKey previous_key = current_key_ ? *current_key_ : IndexedDBKey(); + + bool first_iteration = true; + + // When iterating with PrevNoDuplicate, spec requires that the + // value we yield for each key is the first duplicate in forwards + // order. + IndexedDBKey last_duplicate_key; + + bool forward = cursor_options_.forward; + + for (;;) { + if (next_state == SEEK) { + // TODO(jsbell): Optimize seeking for reverse cursors as well. + if (first_iteration && key && key->IsValid() && forward) { + iterator_->Seek(LevelDBSlice(EncodeKey(*key))); + first_iteration = false; + } else if (forward) { + iterator_->Next(); + } else { + iterator_->Prev(); + } + } else { + next_state = SEEK; // for subsequent iterations + } + + if (!iterator_->IsValid()) { + if (!forward && last_duplicate_key.IsValid()) { + // We need to walk forward because we hit the end of + // the data. + forward = true; + continue; + } + + return false; + } + + if (IsPastBounds()) { + if (!forward && last_duplicate_key.IsValid()) { + // We need to walk forward because now we're beyond the + // bounds defined by the cursor. + forward = true; + continue; + } + + return false; + } + + if (!HaveEnteredRange()) + continue; + + // The row may not load because there's a stale entry in the + // index. This is not fatal. + if (!LoadCurrentRow()) + continue; + + if (key && key->IsValid()) { + if (forward) { + if (current_key_->IsLessThan(*key)) + continue; + } else { + if (key->IsLessThan(*current_key_)) + continue; + } + } + + if (cursor_options_.unique) { + if (previous_key.IsValid() && current_key_->IsEqual(previous_key)) { + // We should never be able to walk forward all the way + // to the previous key. + DCHECK(!last_duplicate_key.IsValid()); + continue; + } + + if (!forward) { + if (!last_duplicate_key.IsValid()) { + last_duplicate_key = *current_key_; + continue; + } + + // We need to walk forward because we hit the boundary + // between key ranges. + if (!last_duplicate_key.IsEqual(*current_key_)) { + forward = true; + continue; + } + + continue; + } + } + break; + } + + DCHECK(!last_duplicate_key.IsValid() || + (forward && last_duplicate_key.IsEqual(*current_key_))); + return true; +} + +bool IndexedDBBackingStore::Cursor::HaveEnteredRange() const { + if (cursor_options_.forward) { + int compare = CompareIndexKeys(iterator_->Key(), + LevelDBSlice(cursor_options_.low_key)); + if (cursor_options_.low_open) { + return compare > 0; + } + return compare >= 0; + } + int compare = CompareIndexKeys(iterator_->Key(), + LevelDBSlice(cursor_options_.high_key)); + if (cursor_options_.high_open) { + return compare < 0; + } + return compare <= 0; +} + +bool IndexedDBBackingStore::Cursor::IsPastBounds() const { + if (cursor_options_.forward) { + int compare = CompareIndexKeys(iterator_->Key(), + LevelDBSlice(cursor_options_.high_key)); + if (cursor_options_.high_open) { + return compare >= 0; + } + return compare > 0; + } + int compare = + CompareIndexKeys(iterator_->Key(), LevelDBSlice(cursor_options_.low_key)); + if (cursor_options_.low_open) { + return compare <= 0; + } + return compare < 0; +} + +const IndexedDBKey& IndexedDBBackingStore::Cursor::primary_key() const { + return *current_key_; +} + +const IndexedDBBackingStore::RecordIdentifier& +IndexedDBBackingStore::Cursor::record_identifier() const { + return record_identifier_; +} + +class ObjectStoreKeyCursorImpl : public IndexedDBBackingStore::Cursor { + public: + ObjectStoreKeyCursorImpl( + LevelDBTransaction* transaction, + const IndexedDBBackingStore::Cursor::CursorOptions& cursor_options) + : IndexedDBBackingStore::Cursor(transaction, cursor_options) {} + + virtual Cursor* Clone() OVERRIDE { + return new ObjectStoreKeyCursorImpl(this); + } + + // IndexedDBBackingStore::Cursor + virtual std::vector<char>* Value() OVERRIDE { + NOTREACHED(); + return NULL; + } + virtual bool LoadCurrentRow() OVERRIDE; + + protected: + virtual std::vector<char> EncodeKey(const IndexedDBKey& key) OVERRIDE { + return ObjectStoreDataKey::Encode( + cursor_options_.database_id, cursor_options_.object_store_id, key); + } + + private: + explicit ObjectStoreKeyCursorImpl(const ObjectStoreKeyCursorImpl* other) + : IndexedDBBackingStore::Cursor(other) {} +}; + +bool ObjectStoreKeyCursorImpl::LoadCurrentRow() { + const char* key_position = iterator_->Key().begin(); + const char* key_limit = iterator_->Key().end(); + + ObjectStoreDataKey object_store_data_key; + key_position = ObjectStoreDataKey::Decode( + key_position, key_limit, &object_store_data_key); + if (!key_position) { + INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + return false; + } + + current_key_ = object_store_data_key.user_key(); + + int64 version; + const char* value_position = DecodeVarInt( + iterator_->Value().begin(), iterator_->Value().end(), version); + if (!value_position) { + INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + return false; + } + + // TODO(jsbell): This re-encodes what was just decoded; try and optimize. + record_identifier_.Reset(EncodeIDBKey(*current_key_), version); + + return true; +} + +class ObjectStoreCursorImpl : public IndexedDBBackingStore::Cursor { + public: + ObjectStoreCursorImpl( + LevelDBTransaction* transaction, + const IndexedDBBackingStore::Cursor::CursorOptions& cursor_options) + : IndexedDBBackingStore::Cursor(transaction, cursor_options) {} + + virtual Cursor* Clone() OVERRIDE { return new ObjectStoreCursorImpl(this); } + + // IndexedDBBackingStore::Cursor + virtual std::vector<char>* Value() OVERRIDE { return ¤t_value_; } + virtual bool LoadCurrentRow() OVERRIDE; + + protected: + virtual std::vector<char> EncodeKey(const IndexedDBKey& key) OVERRIDE { + return ObjectStoreDataKey::Encode( + cursor_options_.database_id, cursor_options_.object_store_id, key); + } + + private: + explicit ObjectStoreCursorImpl(const ObjectStoreCursorImpl* other) + : IndexedDBBackingStore::Cursor(other), + current_value_(other->current_value_) {} + + std::vector<char> current_value_; +}; + +bool ObjectStoreCursorImpl::LoadCurrentRow() { + const char* key_position = iterator_->Key().begin(); + const char* key_limit = iterator_->Key().end(); + + ObjectStoreDataKey object_store_data_key; + key_position = ObjectStoreDataKey::Decode( + key_position, key_limit, &object_store_data_key); + if (!key_position) { + INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + return false; + } + + current_key_ = object_store_data_key.user_key(); + + int64 version; + const char* value_position = DecodeVarInt( + iterator_->Value().begin(), iterator_->Value().end(), version); + if (!value_position) { + INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + return false; + } + + // TODO(jsbell): This re-encodes what was just decoded; try and optimize. + record_identifier_.Reset(EncodeIDBKey(*current_key_), version); + + std::vector<char> value; + value.insert(value.end(), value_position, iterator_->Value().end()); + current_value_.swap(value); + return true; +} + +class IndexKeyCursorImpl : public IndexedDBBackingStore::Cursor { + public: + IndexKeyCursorImpl( + LevelDBTransaction* transaction, + const IndexedDBBackingStore::Cursor::CursorOptions& cursor_options) + : IndexedDBBackingStore::Cursor(transaction, cursor_options) {} + + virtual Cursor* Clone() OVERRIDE { return new IndexKeyCursorImpl(this); } + + // IndexedDBBackingStore::Cursor + virtual std::vector<char>* Value() OVERRIDE { + NOTREACHED(); + return NULL; + } + virtual const IndexedDBKey& primary_key() const OVERRIDE { + return *primary_key_; + } + virtual const IndexedDBBackingStore::RecordIdentifier& RecordIdentifier() + const { + NOTREACHED(); + return record_identifier_; + } + virtual bool LoadCurrentRow() OVERRIDE; + + protected: + virtual std::vector<char> EncodeKey(const IndexedDBKey& key) OVERRIDE { + return IndexDataKey::Encode(cursor_options_.database_id, + cursor_options_.object_store_id, + cursor_options_.index_id, + key); + } + + private: + explicit IndexKeyCursorImpl(const IndexKeyCursorImpl* other) + : IndexedDBBackingStore::Cursor(other), + primary_key_(new IndexedDBKey(*other->primary_key_)) {} + + scoped_ptr<IndexedDBKey> primary_key_; +}; + +bool IndexKeyCursorImpl::LoadCurrentRow() { + const char* key_position = iterator_->Key().begin(); + const char* key_limit = iterator_->Key().end(); + + IndexDataKey index_data_key; + key_position = IndexDataKey::Decode(key_position, key_limit, &index_data_key); + + current_key_ = index_data_key.user_key(); + DCHECK(current_key_); + + int64 index_data_version; + const char* value_position = DecodeVarInt( + iterator_->Value().begin(), iterator_->Value().end(), index_data_version); + if (!value_position) { + INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + return false; + } + + value_position = + DecodeIDBKey(value_position, iterator_->Value().end(), &primary_key_); + if (!value_position) { + INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + return false; + } + + std::vector<char> primary_leveldb_key = + ObjectStoreDataKey::Encode(index_data_key.DatabaseId(), + index_data_key.ObjectStoreId(), + *primary_key_); + + std::vector<char> result; + bool found = false; + bool ok = transaction_->Get(LevelDBSlice(primary_leveldb_key), result, found); + if (!ok) { + INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + return false; + } + if (!found) { + transaction_->Remove(iterator_->Key()); + return false; + } + if (!result.size()) { + INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + return false; + } + + int64 object_store_data_version; + const char* t = DecodeVarInt( + &*result.begin(), &*result.rbegin() + 1, object_store_data_version); + if (!t) { + INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + return false; + } + + if (object_store_data_version != index_data_version) { + transaction_->Remove(iterator_->Key()); + return false; + } + + return true; +} + +class IndexCursorImpl : public IndexedDBBackingStore::Cursor { + public: + IndexCursorImpl( + LevelDBTransaction* transaction, + const IndexedDBBackingStore::Cursor::CursorOptions& cursor_options) + : IndexedDBBackingStore::Cursor(transaction, cursor_options) {} + + virtual Cursor* Clone() OVERRIDE { return new IndexCursorImpl(this); } + + // IndexedDBBackingStore::Cursor + virtual std::vector<char>* Value() OVERRIDE { return ¤t_value_; } + virtual const IndexedDBKey& primary_key() const OVERRIDE { + return *primary_key_; + } + virtual const IndexedDBBackingStore::RecordIdentifier& RecordIdentifier() + const { + NOTREACHED(); + return record_identifier_; + } + virtual bool LoadCurrentRow() OVERRIDE; + + protected: + virtual std::vector<char> EncodeKey(const IndexedDBKey& key) OVERRIDE { + return IndexDataKey::Encode(cursor_options_.database_id, + cursor_options_.object_store_id, + cursor_options_.index_id, + key); + } + + private: + explicit IndexCursorImpl(const IndexCursorImpl* other) + : IndexedDBBackingStore::Cursor(other), + primary_key_(new IndexedDBKey(*other->primary_key_)), + current_value_(other->current_value_), + primary_leveldb_key_(other->primary_leveldb_key_) {} + + scoped_ptr<IndexedDBKey> primary_key_; + std::vector<char> current_value_; + std::vector<char> primary_leveldb_key_; +}; + +bool IndexCursorImpl::LoadCurrentRow() { + const char* key_position = iterator_->Key().begin(); + const char* key_limit = iterator_->Key().end(); + + IndexDataKey index_data_key; + key_position = IndexDataKey::Decode(key_position, key_limit, &index_data_key); + + current_key_ = index_data_key.user_key(); + DCHECK(current_key_); + + const char* value_position = iterator_->Value().begin(); + const char* value_limit = iterator_->Value().end(); + + int64 index_data_version; + value_position = + DecodeVarInt(value_position, value_limit, index_data_version); + if (!value_position) { + INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + return false; + } + value_position = DecodeIDBKey(value_position, value_limit, &primary_key_); + if (!value_position) { + INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + return false; + } + + primary_leveldb_key_ = + ObjectStoreDataKey::Encode(index_data_key.DatabaseId(), + index_data_key.ObjectStoreId(), + *primary_key_); + + std::vector<char> result; + bool found = false; + bool ok = + transaction_->Get(LevelDBSlice(primary_leveldb_key_), result, found); + if (!ok) { + INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + return false; + } + if (!found) { + transaction_->Remove(iterator_->Key()); + return false; + } + if (!result.size()) { + INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + return false; + } + + int64 object_store_data_version; + value_position = DecodeVarInt( + &*result.begin(), &*result.rbegin() + 1, object_store_data_version); + if (!value_position) { + INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + return false; + } + + if (object_store_data_version != index_data_version) { + transaction_->Remove(iterator_->Key()); + return false; + } + + // TODO(jsbell): Make value_position an iterator. + std::vector<char> value; + value.insert(value.end(), + value_position, + static_cast<const char*>(&*result.rbegin()) + 1); + current_value_.swap(value); + return true; +} + +bool ObjectStoreCursorOptions( + LevelDBTransaction* transaction, + int64 database_id, + int64 object_store_id, + const IndexedDBKeyRange& range, + indexed_db::CursorDirection direction, + IndexedDBBackingStore::Cursor::CursorOptions& cursor_options) { + cursor_options.database_id = database_id; + cursor_options.object_store_id = object_store_id; + + bool lower_bound = range.lower().IsValid(); + bool upper_bound = range.upper().IsValid(); + cursor_options.forward = (direction == indexed_db::CURSOR_NEXT_NO_DUPLICATE || + direction == indexed_db::CURSOR_NEXT); + cursor_options.unique = (direction == indexed_db::CURSOR_NEXT_NO_DUPLICATE || + direction == indexed_db::CURSOR_PREV_NO_DUPLICATE); + + if (!lower_bound) { + cursor_options.low_key = + ObjectStoreDataKey::Encode(database_id, object_store_id, MinIDBKey()); + cursor_options.low_open = true; // Not included. + } else { + cursor_options.low_key = + ObjectStoreDataKey::Encode(database_id, object_store_id, range.lower()); + cursor_options.low_open = range.lowerOpen(); + } + + if (!upper_bound) { + cursor_options.high_key = + ObjectStoreDataKey::Encode(database_id, object_store_id, MaxIDBKey()); + + if (cursor_options.forward) { + cursor_options.high_open = true; // Not included. + } else { + // We need a key that exists. + if (!FindGreatestKeyLessThanOrEqual( + transaction, cursor_options.high_key, cursor_options.high_key)) + return false; + cursor_options.high_open = false; + } + } else { + cursor_options.high_key = + ObjectStoreDataKey::Encode(database_id, object_store_id, range.upper()); + cursor_options.high_open = range.upperOpen(); + + if (!cursor_options.forward) { + // For reverse cursors, we need a key that exists. + std::vector<char> found_high_key; + if (!FindGreatestKeyLessThanOrEqual( + transaction, cursor_options.high_key, found_high_key)) + return false; + + // If the target key should not be included, but we end up with a smaller + // key, we should include that. + if (cursor_options.high_open && + CompareIndexKeys(LevelDBSlice(found_high_key), + LevelDBSlice(cursor_options.high_key)) < + 0) + cursor_options.high_open = false; + + cursor_options.high_key = found_high_key; + } + } + + return true; +} + +bool IndexCursorOptions( + LevelDBTransaction* transaction, + int64 database_id, + int64 object_store_id, + int64 index_id, + const IndexedDBKeyRange& range, + indexed_db::CursorDirection direction, + IndexedDBBackingStore::Cursor::CursorOptions& cursor_options) { + DCHECK(transaction); + if (!KeyPrefix::ValidIds(database_id, object_store_id, index_id)) + return false; + + cursor_options.database_id = database_id; + cursor_options.object_store_id = object_store_id; + cursor_options.index_id = index_id; + + bool lower_bound = range.lower().IsValid(); + bool upper_bound = range.upper().IsValid(); + cursor_options.forward = (direction == indexed_db::CURSOR_NEXT_NO_DUPLICATE || + direction == indexed_db::CURSOR_NEXT); + cursor_options.unique = (direction == indexed_db::CURSOR_NEXT_NO_DUPLICATE || + direction == indexed_db::CURSOR_PREV_NO_DUPLICATE); + + if (!lower_bound) { + cursor_options.low_key = + IndexDataKey::EncodeMinKey(database_id, object_store_id, index_id); + cursor_options.low_open = false; // Included. + } else { + cursor_options.low_key = IndexDataKey::Encode( + database_id, object_store_id, index_id, range.lower()); + cursor_options.low_open = range.lowerOpen(); + } + + if (!upper_bound) { + cursor_options.high_key = + IndexDataKey::EncodeMaxKey(database_id, object_store_id, index_id); + cursor_options.high_open = false; // Included. + + if (!cursor_options.forward) { // We need a key that exists. + if (!FindGreatestKeyLessThanOrEqual( + transaction, cursor_options.high_key, cursor_options.high_key)) + return false; + cursor_options.high_open = false; + } + } else { + cursor_options.high_key = IndexDataKey::Encode( + database_id, object_store_id, index_id, range.upper()); + cursor_options.high_open = range.upperOpen(); + + std::vector<char> found_high_key; + if (!FindGreatestKeyLessThanOrEqual( + transaction, + cursor_options.high_key, + found_high_key)) // Seek to the *last* key in the set of non-unique + // keys. + return false; + + // If the target key should not be included, but we end up with a smaller + // key, we should include that. + if (cursor_options.high_open && + CompareIndexKeys(LevelDBSlice(found_high_key), + LevelDBSlice(cursor_options.high_key)) < + 0) + cursor_options.high_open = false; + + cursor_options.high_key = found_high_key; + } + + return true; +} + +scoped_ptr<IndexedDBBackingStore::Cursor> +IndexedDBBackingStore::OpenObjectStoreCursor( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + const IndexedDBKeyRange& range, + indexed_db::CursorDirection direction) { + IDB_TRACE("IndexedDBBackingStore::open_object_store_cursor"); + LevelDBTransaction* leveldb_transaction = + IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction); + IndexedDBBackingStore::Cursor::CursorOptions cursor_options; + if (!ObjectStoreCursorOptions(leveldb_transaction, + database_id, + object_store_id, + range, + direction, + cursor_options)) + return scoped_ptr<IndexedDBBackingStore::Cursor>(); + scoped_ptr<ObjectStoreCursorImpl> cursor( + new ObjectStoreCursorImpl(leveldb_transaction, cursor_options)); + if (!cursor->FirstSeek()) + return scoped_ptr<IndexedDBBackingStore::Cursor>(); + + return cursor.PassAs<IndexedDBBackingStore::Cursor>(); +} + +scoped_ptr<IndexedDBBackingStore::Cursor> +IndexedDBBackingStore::OpenObjectStoreKeyCursor( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + const IndexedDBKeyRange& range, + indexed_db::CursorDirection direction) { + IDB_TRACE("IndexedDBBackingStore::open_object_store_key_cursor"); + LevelDBTransaction* leveldb_transaction = + IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction); + IndexedDBBackingStore::Cursor::CursorOptions cursor_options; + if (!ObjectStoreCursorOptions(leveldb_transaction, + database_id, + object_store_id, + range, + direction, + cursor_options)) + return scoped_ptr<IndexedDBBackingStore::Cursor>(); + scoped_ptr<ObjectStoreKeyCursorImpl> cursor( + new ObjectStoreKeyCursorImpl(leveldb_transaction, cursor_options)); + if (!cursor->FirstSeek()) + return scoped_ptr<IndexedDBBackingStore::Cursor>(); + + return cursor.PassAs<IndexedDBBackingStore::Cursor>(); +} + +scoped_ptr<IndexedDBBackingStore::Cursor> +IndexedDBBackingStore::OpenIndexKeyCursor( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + int64 index_id, + const IndexedDBKeyRange& range, + indexed_db::CursorDirection direction) { + IDB_TRACE("IndexedDBBackingStore::open_index_key_cursor"); + LevelDBTransaction* leveldb_transaction = + IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction); + IndexedDBBackingStore::Cursor::CursorOptions cursor_options; + if (!IndexCursorOptions(leveldb_transaction, + database_id, + object_store_id, + index_id, + range, + direction, + cursor_options)) + return scoped_ptr<IndexedDBBackingStore::Cursor>(); + scoped_ptr<IndexKeyCursorImpl> cursor( + new IndexKeyCursorImpl(leveldb_transaction, cursor_options)); + if (!cursor->FirstSeek()) + return scoped_ptr<IndexedDBBackingStore::Cursor>(); + + return cursor.PassAs<IndexedDBBackingStore::Cursor>(); +} + +scoped_ptr<IndexedDBBackingStore::Cursor> +IndexedDBBackingStore::OpenIndexCursor( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + int64 index_id, + const IndexedDBKeyRange& range, + indexed_db::CursorDirection direction) { + IDB_TRACE("IndexedDBBackingStore::open_index_cursor"); + LevelDBTransaction* leveldb_transaction = + IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction); + IndexedDBBackingStore::Cursor::CursorOptions cursor_options; + if (!IndexCursorOptions(leveldb_transaction, + database_id, + object_store_id, + index_id, + range, + direction, + cursor_options)) + return scoped_ptr<IndexedDBBackingStore::Cursor>(); + scoped_ptr<IndexCursorImpl> cursor( + new IndexCursorImpl(leveldb_transaction, cursor_options)); + if (!cursor->FirstSeek()) + return scoped_ptr<IndexedDBBackingStore::Cursor>(); + + return cursor.PassAs<IndexedDBBackingStore::Cursor>(); +} + +IndexedDBBackingStore::Transaction::Transaction( + IndexedDBBackingStore* backing_store) + : backing_store_(backing_store) {} + +IndexedDBBackingStore::Transaction::~Transaction() {} + +void IndexedDBBackingStore::Transaction::begin() { + IDB_TRACE("IndexedDBBackingStore::Transaction::begin"); + DCHECK(!transaction_); + transaction_ = LevelDBTransaction::Create(backing_store_->db_.get()); +} + +bool IndexedDBBackingStore::Transaction::Commit() { + IDB_TRACE("IndexedDBBackingStore::Transaction::commit"); + DCHECK(transaction_); + bool result = transaction_->Commit(); + transaction_ = NULL; + if (!result) + INTERNAL_WRITE_ERROR(TRANSACTION_COMMIT_METHOD); + return result; +} + +void IndexedDBBackingStore::Transaction::Rollback() { + IDB_TRACE("IndexedDBBackingStore::Transaction::rollback"); + DCHECK(transaction_); + transaction_->Rollback(); + transaction_ = NULL; +} + +} // namespace content diff --git a/content/browser/indexed_db/indexed_db_backing_store.h b/content/browser/indexed_db/indexed_db_backing_store.h new file mode 100644 index 0000000..6862d91 --- /dev/null +++ b/content/browser/indexed_db/indexed_db_backing_store.h @@ -0,0 +1,319 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_BACKING_STORE_H_ +#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_BACKING_STORE_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "content/browser/indexed_db/indexed_db.h" +#include "content/browser/indexed_db/indexed_db_metadata.h" +#include "content/browser/indexed_db/leveldb/leveldb_iterator.h" +#include "content/browser/indexed_db/leveldb/leveldb_transaction.h" +#include "content/common/content_export.h" +#include "content/common/indexed_db/indexed_db_key.h" +#include "content/common/indexed_db/indexed_db_key_path.h" +#include "content/common/indexed_db/indexed_db_key_range.h" + +namespace content { + +class LevelDBComparator; +class LevelDBDatabase; + +class LevelDBFactory { + public: + virtual scoped_ptr<LevelDBDatabase> OpenLevelDB( + const base::FilePath& file_name, + const LevelDBComparator* comparator) = 0; + virtual bool DestroyLevelDB(const base::FilePath& file_name) = 0; +}; + +class CONTENT_EXPORT IndexedDBBackingStore + : public base::RefCounted<IndexedDBBackingStore> { + public: + class CONTENT_EXPORT Transaction; + + static scoped_refptr<IndexedDBBackingStore> Open( + const string16& database_identifier, + const string16& path_base, + const string16& file_identifier); + static scoped_refptr<IndexedDBBackingStore> Open( + const string16& database_identifier, + const string16& path_base, + const string16& file_identifier, + LevelDBFactory* factory); + static scoped_refptr<IndexedDBBackingStore> OpenInMemory( + const string16& identifier); + static scoped_refptr<IndexedDBBackingStore> OpenInMemory( + const string16& identifier, + LevelDBFactory* factory); + base::WeakPtr<IndexedDBBackingStore> GetWeakPtr() { + return weak_factory_.GetWeakPtr(); + } + + virtual std::vector<string16> GetDatabaseNames(); + virtual bool GetIDBDatabaseMetaData(const string16& name, + IndexedDBDatabaseMetadata* metadata, + bool& success) WARN_UNUSED_RESULT; + virtual bool CreateIDBDatabaseMetaData(const string16& name, + const string16& version, + int64 int_version, + int64& row_id); + virtual bool UpdateIDBDatabaseMetaData( + IndexedDBBackingStore::Transaction* transaction, + int64 row_id, + const string16& version); + virtual bool UpdateIDBDatabaseIntVersion( + IndexedDBBackingStore::Transaction* transaction, + int64 row_id, + int64 int_version); + virtual bool DeleteDatabase(const string16& name); + + bool GetObjectStores(int64 database_id, + IndexedDBDatabaseMetadata::ObjectStoreMap* map) + WARN_UNUSED_RESULT; + virtual bool CreateObjectStore( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + const string16& name, + const IndexedDBKeyPath& key_path, + bool auto_increment); + virtual bool DeleteObjectStore( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id) WARN_UNUSED_RESULT; + + class CONTENT_EXPORT RecordIdentifier { + public: + RecordIdentifier(const std::vector<char>& primary_key, int64 version); + RecordIdentifier(); + ~RecordIdentifier(); + + const std::vector<char>& primary_key() const { return primary_key_; } + int64 version() const { return version_; } + void Reset(const std::vector<char>& primary_key, int64 version) { + primary_key_ = primary_key; + version_ = version; + } + + private: + std::vector<char> + primary_key_; // TODO(jsbell): Make it more clear that this is + // the *encoded* version of the key. + int64 version_; + DISALLOW_COPY_AND_ASSIGN(RecordIdentifier); + }; + + virtual bool GetRecord(IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + const IndexedDBKey& key, + std::vector<char>& record) WARN_UNUSED_RESULT; + virtual bool PutRecord(IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + const IndexedDBKey& key, + const std::vector<char>& value, + RecordIdentifier* record) WARN_UNUSED_RESULT; + virtual bool ClearObjectStore(IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id) WARN_UNUSED_RESULT; + virtual bool DeleteRecord(IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + const RecordIdentifier& record) WARN_UNUSED_RESULT; + virtual bool GetKeyGeneratorCurrentNumber( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + int64& current_number) WARN_UNUSED_RESULT; + virtual bool MaybeUpdateKeyGeneratorCurrentNumber( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + int64 new_state, + bool check_current) WARN_UNUSED_RESULT; + virtual bool KeyExistsInObjectStore( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + const IndexedDBKey& key, + RecordIdentifier* found_record_identifier, + bool& found) WARN_UNUSED_RESULT; + + virtual bool CreateIndex(IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + int64 index_id, + const string16& name, + const IndexedDBKeyPath& key_path, + bool is_unique, + bool is_multi_entry) WARN_UNUSED_RESULT; + virtual bool DeleteIndex(IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + int64 index_id) WARN_UNUSED_RESULT; + virtual bool PutIndexDataForRecord( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + int64 index_id, + const IndexedDBKey& key, + const RecordIdentifier& record) WARN_UNUSED_RESULT; + virtual bool GetPrimaryKeyViaIndex( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + int64 index_id, + const IndexedDBKey& key, + scoped_ptr<IndexedDBKey>* primary_key) WARN_UNUSED_RESULT; + virtual bool KeyExistsInIndex(IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + int64 index_id, + const IndexedDBKey& key, + scoped_ptr<IndexedDBKey>* found_primary_key, + bool& exists) WARN_UNUSED_RESULT; + + class Cursor { + public: + virtual ~Cursor(); + + enum IteratorState { + READY = 0, + SEEK + }; + + struct CursorOptions { + CursorOptions(); + ~CursorOptions(); + int64 database_id; + int64 object_store_id; + int64 index_id; + std::vector<char> low_key; + bool low_open; + std::vector<char> high_key; + bool high_open; + bool forward; + bool unique; + }; + + const IndexedDBKey& key() const { return *current_key_; } + bool ContinueFunction(const IndexedDBKey* = 0, IteratorState = SEEK); + bool Advance(unsigned long); + bool FirstSeek(); + + virtual Cursor* Clone() = 0; + virtual const IndexedDBKey& primary_key() const; + virtual std::vector<char>* Value() = 0; + virtual const RecordIdentifier& record_identifier() const; + virtual bool LoadCurrentRow() = 0; + + protected: + Cursor(LevelDBTransaction* transaction, + const CursorOptions& cursor_options); + explicit Cursor(const IndexedDBBackingStore::Cursor* other); + + virtual std::vector<char> EncodeKey(const IndexedDBKey& key) = 0; + + bool IsPastBounds() const; + bool HaveEnteredRange() const; + + LevelDBTransaction* transaction_; + const CursorOptions cursor_options_; + scoped_ptr<LevelDBIterator> iterator_; + scoped_ptr<IndexedDBKey> current_key_; + IndexedDBBackingStore::RecordIdentifier record_identifier_; + }; + + virtual scoped_ptr<Cursor> OpenObjectStoreKeyCursor( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + const IndexedDBKeyRange& key_range, + indexed_db::CursorDirection); + virtual scoped_ptr<Cursor> OpenObjectStoreCursor( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + const IndexedDBKeyRange& key_range, + indexed_db::CursorDirection); + virtual scoped_ptr<Cursor> OpenIndexKeyCursor( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + int64 index_id, + const IndexedDBKeyRange& key_range, + indexed_db::CursorDirection); + virtual scoped_ptr<Cursor> OpenIndexCursor( + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + int64 index_id, + const IndexedDBKeyRange& key_range, + indexed_db::CursorDirection); + + class Transaction { + public: + explicit Transaction(IndexedDBBackingStore* backing_store); + ~Transaction(); + void begin(); + bool Commit(); + void Rollback(); + void Reset() { + backing_store_ = NULL; + transaction_ = NULL; + } + + static LevelDBTransaction* LevelDBTransactionFrom( + Transaction* transaction) { + return transaction->transaction_.get(); + } + + private: + IndexedDBBackingStore* backing_store_; + scoped_refptr<LevelDBTransaction> transaction_; + }; + + protected: + IndexedDBBackingStore(const string16& identifier, + scoped_ptr<LevelDBDatabase> db, + scoped_ptr<LevelDBComparator> comparator); + virtual ~IndexedDBBackingStore(); + friend class base::RefCounted<IndexedDBBackingStore>; + + private: + static scoped_refptr<IndexedDBBackingStore> Create( + const string16& identifier, + scoped_ptr<LevelDBDatabase> db, + scoped_ptr<LevelDBComparator> comparator); + + bool FindKeyInIndex(IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + int64 index_id, + const IndexedDBKey& key, + std::vector<char>& found_encoded_primary_key, + bool& found); + bool GetIndexes(int64 database_id, + int64 object_store_id, + IndexedDBObjectStoreMetadata::IndexMap* map) + WARN_UNUSED_RESULT; + + string16 identifier_; + + scoped_ptr<LevelDBDatabase> db_; + scoped_ptr<LevelDBComparator> comparator_; + base::WeakPtrFactory<IndexedDBBackingStore> weak_factory_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_BACKING_STORE_H_ diff --git a/content/browser/indexed_db/indexed_db_backing_store_unittest.cc b/content/browser/indexed_db/indexed_db_backing_store_unittest.cc new file mode 100644 index 0000000..0e60d7b --- /dev/null +++ b/content/browser/indexed_db/indexed_db_backing_store_unittest.cc @@ -0,0 +1,403 @@ +// 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_backing_store.h" + +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "base/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "content/browser/indexed_db/indexed_db_factory_impl.h" +#include "content/browser/indexed_db/indexed_db_leveldb_coding.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityOrigin.h" +#include "third_party/WebKit/public/platform/WebData.h" + +using WebKit::WebSecurityOrigin; +using WebKit::WebIDBKey; + +namespace content { + +namespace { + +class IndexedDBBackingStoreTest : public testing::Test { + public: + IndexedDBBackingStoreTest() {} + virtual void SetUp() { + string16 file_identifier; + backing_store_ = IndexedDBBackingStore::OpenInMemory(file_identifier); + + // useful keys and values during tests + const char raw_value1[] = "value1"; + + const char raw_value2[] = "value2"; + const char raw_value3[] = "value3"; + m_value1.insert( + m_value1.end(), &raw_value1[0], &raw_value1[0] + sizeof(raw_value1)); + m_value2.insert( + m_value2.end(), &raw_value2[0], &raw_value2[0] + sizeof(raw_value2)); + m_value3.insert( + m_value3.end(), &raw_value3[0], &raw_value3[0] + sizeof(raw_value3)); + m_key1 = IndexedDBKey(99, WebIDBKey::NumberType); + m_key2 = IndexedDBKey(ASCIIToUTF16("key2")); + m_key3 = IndexedDBKey(ASCIIToUTF16("key3")); + } + + protected: + scoped_refptr<IndexedDBBackingStore> backing_store_; + + // Sample keys and values that are consistent. + IndexedDBKey m_key1; + IndexedDBKey m_key2; + IndexedDBKey m_key3; + std::vector<char> m_value1; + std::vector<char> m_value2; + std::vector<char> m_value3; +}; + +TEST_F(IndexedDBBackingStoreTest, PutGetConsistency) { + { + IndexedDBBackingStore::Transaction transaction1(backing_store_.get()); + transaction1.begin(); + IndexedDBBackingStore::RecordIdentifier record; + bool ok = backing_store_->PutRecord( + &transaction1, 1, 1, m_key1, m_value1, &record); + EXPECT_TRUE(ok); + transaction1.Commit(); + } + + { + IndexedDBBackingStore::Transaction transaction2(backing_store_.get()); + transaction2.begin(); + std::vector<char> result_value; + bool ok = + backing_store_->GetRecord(&transaction2, 1, 1, m_key1, result_value); + transaction2.Commit(); + EXPECT_TRUE(ok); + EXPECT_EQ(m_value1, result_value); + } +} + +// Make sure that using very high ( more than 32 bit ) values for database_id +// and object_store_id still work. +TEST_F(IndexedDBBackingStoreTest, HighIds) { + const int64 high_database_id = 1ULL << 35; + const int64 high_object_store_id = 1ULL << 39; + // index_ids are capped at 32 bits for storage purposes. + const int64 high_index_id = 1ULL << 29; + + const int64 invalid_high_index_id = 1ULL << 37; + + const IndexedDBKey& index_key = m_key2; + std::vector<char> index_key_raw = EncodeIDBKey(index_key); + { + IndexedDBBackingStore::Transaction transaction1(backing_store_.get()); + transaction1.begin(); + IndexedDBBackingStore::RecordIdentifier record; + bool ok = backing_store_->PutRecord(&transaction1, + high_database_id, + high_object_store_id, + m_key1, + m_value1, + &record); + EXPECT_TRUE(ok); + + ok = backing_store_->PutIndexDataForRecord(&transaction1, + high_database_id, + high_object_store_id, + invalid_high_index_id, + index_key, + record); + EXPECT_FALSE(ok); + + ok = backing_store_->PutIndexDataForRecord(&transaction1, + high_database_id, + high_object_store_id, + high_index_id, + index_key, + record); + EXPECT_TRUE(ok); + + ok = transaction1.Commit(); + EXPECT_TRUE(ok); + } + + { + IndexedDBBackingStore::Transaction transaction2(backing_store_.get()); + transaction2.begin(); + std::vector<char> result_value; + bool ok = backing_store_->GetRecord(&transaction2, + high_database_id, + high_object_store_id, + m_key1, + result_value); + EXPECT_TRUE(ok); + EXPECT_EQ(m_value1, result_value); + + scoped_ptr<IndexedDBKey> new_primary_key; + ok = backing_store_->GetPrimaryKeyViaIndex(&transaction2, + high_database_id, + high_object_store_id, + invalid_high_index_id, + index_key, + &new_primary_key); + EXPECT_FALSE(ok); + + ok = backing_store_->GetPrimaryKeyViaIndex(&transaction2, + high_database_id, + high_object_store_id, + high_index_id, + index_key, + &new_primary_key); + EXPECT_TRUE(ok); + EXPECT_TRUE(new_primary_key->IsEqual(m_key1)); + + ok = transaction2.Commit(); + EXPECT_TRUE(ok); + } +} + +// Make sure that other invalid ids do not crash. +TEST_F(IndexedDBBackingStoreTest, InvalidIds) { + // valid ids for use when testing invalid ids + const int64 database_id = 1; + const int64 object_store_id = 1; + const int64 index_id = kMinimumIndexId; + const int64 invalid_low_index_id = 19; // index_ids must be > kMinimumIndexId + + std::vector<char> result_value; + + IndexedDBBackingStore::Transaction transaction1(backing_store_.get()); + transaction1.begin(); + + IndexedDBBackingStore::RecordIdentifier record; + bool ok = backing_store_->PutRecord(&transaction1, + database_id, + KeyPrefix::kInvalidId, + m_key1, + m_value1, + &record); + EXPECT_FALSE(ok); + ok = backing_store_->PutRecord( + &transaction1, database_id, 0, m_key1, m_value1, &record); + EXPECT_FALSE(ok); + ok = backing_store_->PutRecord(&transaction1, + KeyPrefix::kInvalidId, + object_store_id, + m_key1, + m_value1, + &record); + EXPECT_FALSE(ok); + ok = backing_store_->PutRecord( + &transaction1, 0, object_store_id, m_key1, m_value1, &record); + EXPECT_FALSE(ok); + + ok = backing_store_->GetRecord( + &transaction1, database_id, KeyPrefix::kInvalidId, m_key1, result_value); + EXPECT_FALSE(ok); + ok = backing_store_->GetRecord( + &transaction1, database_id, 0, m_key1, result_value); + EXPECT_FALSE(ok); + ok = backing_store_->GetRecord(&transaction1, + KeyPrefix::kInvalidId, + object_store_id, + m_key1, + result_value); + EXPECT_FALSE(ok); + ok = backing_store_->GetRecord( + &transaction1, 0, object_store_id, m_key1, result_value); + EXPECT_FALSE(ok); + + scoped_ptr<IndexedDBKey> new_primary_key; + ok = backing_store_->GetPrimaryKeyViaIndex(&transaction1, + database_id, + object_store_id, + KeyPrefix::kInvalidId, + m_key1, + &new_primary_key); + EXPECT_FALSE(ok); + ok = backing_store_->GetPrimaryKeyViaIndex(&transaction1, + database_id, + object_store_id, + invalid_low_index_id, + m_key1, + &new_primary_key); + EXPECT_FALSE(ok); + ok = backing_store_->GetPrimaryKeyViaIndex( + &transaction1, database_id, object_store_id, 0, m_key1, &new_primary_key); + EXPECT_FALSE(ok); + + ok = backing_store_->GetPrimaryKeyViaIndex(&transaction1, + KeyPrefix::kInvalidId, + object_store_id, + index_id, + m_key1, + &new_primary_key); + EXPECT_FALSE(ok); + ok = backing_store_->GetPrimaryKeyViaIndex(&transaction1, + database_id, + KeyPrefix::kInvalidId, + index_id, + m_key1, + &new_primary_key); + EXPECT_FALSE(ok); +} + +TEST_F(IndexedDBBackingStoreTest, CreateDatabase) { + const string16 database_name(ASCIIToUTF16("db1")); + int64 database_id; + const string16 version(ASCIIToUTF16("old_string_version")); + const int64 int_version = 9; + + const int64 object_store_id = 99; + const string16 object_store_name(ASCIIToUTF16("object_store1")); + const bool auto_increment = true; + const IndexedDBKeyPath object_store_key_path( + ASCIIToUTF16("object_store_key")); + + const int64 index_id = 999; + const string16 index_name(ASCIIToUTF16("index1")); + const bool unique = true; + const bool multi_entry = true; + const IndexedDBKeyPath index_key_path(ASCIIToUTF16("index_key")); + + { + bool ok = backing_store_->CreateIDBDatabaseMetaData( + database_name, version, int_version, database_id); + EXPECT_TRUE(ok); + EXPECT_GT(database_id, 0); + + IndexedDBBackingStore::Transaction transaction(backing_store_.get()); + transaction.begin(); + + ok = backing_store_->CreateObjectStore(&transaction, + database_id, + object_store_id, + object_store_name, + object_store_key_path, + auto_increment); + EXPECT_TRUE(ok); + + ok = backing_store_->CreateIndex(&transaction, + database_id, + object_store_id, + index_id, + index_name, + index_key_path, + unique, + multi_entry); + EXPECT_TRUE(ok); + + ok = transaction.Commit(); + EXPECT_TRUE(ok); + } + + { + IndexedDBDatabaseMetadata database; + bool found; + bool ok = + backing_store_->GetIDBDatabaseMetaData(database_name, &database, found); + EXPECT_TRUE(ok); + EXPECT_TRUE(found); + + // database.name is not filled in by the implementation. + EXPECT_EQ(version, database.version); + EXPECT_EQ(int_version, database.int_version); + EXPECT_EQ(database_id, database.id); + + ok = backing_store_->GetObjectStores(database.id, &database.object_stores); + EXPECT_TRUE(ok); + + EXPECT_EQ(1UL, database.object_stores.size()); + IndexedDBObjectStoreMetadata object_store = + database.object_stores[object_store_id]; + EXPECT_EQ(object_store_name, object_store.name); + EXPECT_EQ(object_store_key_path, object_store.key_path); + EXPECT_EQ(auto_increment, object_store.auto_increment); + + EXPECT_EQ(1UL, object_store.indexes.size()); + IndexedDBIndexMetadata index = object_store.indexes[index_id]; + EXPECT_EQ(index_name, index.name); + EXPECT_EQ(index_key_path, index.key_path); + EXPECT_EQ(unique, index.unique); + EXPECT_EQ(multi_entry, index.multi_entry); + } +} + +class MockIDBFactory : public IndexedDBFactoryImpl { + public: + static scoped_refptr<MockIDBFactory> Create() { + return make_scoped_refptr(new MockIDBFactory()); + } + + scoped_refptr<IndexedDBBackingStore> TestOpenBackingStore( + const WebSecurityOrigin& origin, + const base::FilePath& data_directory) { + string16 path = UTF8ToUTF16(data_directory.AsUTF8Unsafe()); + return OpenBackingStore(origin.databaseIdentifier(), path); + } + + private: + virtual ~MockIDBFactory() {} +}; + +TEST(IndexedDBFactoryTest, BackingStoreLifetime) { + WebSecurityOrigin origin1 = + WebSecurityOrigin::createFromString("http://localhost:81"); + WebSecurityOrigin origin2 = + WebSecurityOrigin::createFromString("http://localhost:82"); + + scoped_refptr<MockIDBFactory> factory = MockIDBFactory::Create(); + + base::ScopedTempDir temp_directory; + ASSERT_TRUE(temp_directory.CreateUniqueTempDir()); + scoped_refptr<IndexedDBBackingStore> disk_store1 = + factory->TestOpenBackingStore(origin1, temp_directory.path()); + EXPECT_TRUE(disk_store1->HasOneRef()); + + scoped_refptr<IndexedDBBackingStore> disk_store2 = + factory->TestOpenBackingStore(origin1, temp_directory.path()); + EXPECT_EQ(disk_store1.get(), disk_store2.get()); + // TODO(alecflett): Verify refcount indirectly. + // EXPECT_EQ(2, disk_store2->RefCount()); + + scoped_refptr<IndexedDBBackingStore> disk_store3 = + factory->TestOpenBackingStore(origin2, temp_directory.path()); + EXPECT_TRUE(disk_store3->HasOneRef()); + // TODO(alecflett): Verify refcount indirectly. + // EXPECT_EQ(2, disk_store1->RefCount()); +} + +TEST(IndexedDBFactoryTest, MemoryBackingStoreLifetime) { + WebSecurityOrigin origin1 = + WebSecurityOrigin::createFromString("http://localhost:81"); + WebSecurityOrigin origin2 = + WebSecurityOrigin::createFromString("http://localhost:82"); + + scoped_refptr<MockIDBFactory> factory = MockIDBFactory::Create(); + scoped_refptr<IndexedDBBackingStore> mem_store1 = + factory->TestOpenBackingStore(origin1, base::FilePath()); + // TODO(alecflett): Verify refcount indirectly. + // EXPECT_EQ(2, mem_store1->RefCount()); + scoped_refptr<IndexedDBBackingStore> mem_store2 = + factory->TestOpenBackingStore(origin1, base::FilePath()); + EXPECT_EQ(mem_store1.get(), mem_store2.get()); + // TODO(alecflett): Verify refcount indirectly. + // EXPECT_EQ(3, mem_store2->RefCount()); + + scoped_refptr<IndexedDBBackingStore> mem_store3 = + factory->TestOpenBackingStore(origin2, base::FilePath()); + // TODO(alecflett): Verify refcount indirectly. + // EXPECT_EQ(2, mem_store3->RefCount()); + // EXPECT_EQ(3, mem_store1->RefCount()); + + factory = NULL; + // TODO(alecflett): Verify refcount indirectly. + // EXPECT_EQ(2, mem_store1->RefCount()); + // EXPECT_EQ(1, mem_store3->RefCount()); +} + +} // namespace + +} // namespace content diff --git a/content/browser/indexed_db/indexed_db_callbacks_wrapper.cc b/content/browser/indexed_db/indexed_db_callbacks_wrapper.cc new file mode 100644 index 0000000..1c34878 --- /dev/null +++ b/content/browser/indexed_db/indexed_db_callbacks_wrapper.cc @@ -0,0 +1,216 @@ +// 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_callbacks_wrapper.h" + +#include "content/browser/indexed_db/indexed_db_cursor.h" +#include "content/browser/indexed_db/indexed_db_metadata.h" +#include "content/browser/indexed_db/webidbcursor_impl.h" +#include "content/browser/indexed_db/webidbdatabase_impl.h" +#include "third_party/WebKit/public/platform/WebData.h" +#include "third_party/WebKit/public/platform/WebIDBDatabaseError.h" +#include "third_party/WebKit/public/platform/WebIDBKey.h" +#include "third_party/WebKit/public/platform/WebIDBMetadata.h" +#include "third_party/WebKit/public/platform/WebString.h" +#include "third_party/WebKit/public/platform/WebVector.h" + +namespace content { + +using WebKit::WebData; +using WebKit::WebIDBKey; +using WebKit::WebIDBMetadata; +using WebKit::WebString; +using WebKit::WebVector; + +IndexedDBCallbacksWrapper::IndexedDBCallbacksWrapper( + WebKit::WebIDBCallbacks* callbacks) + : callbacks_(callbacks), did_complete_(false), did_create_proxy_(false) {} + +IndexedDBCallbacksWrapper::~IndexedDBCallbacksWrapper() {} + +void IndexedDBCallbacksWrapper::OnError( + scoped_refptr<IndexedDBDatabaseError> error) { + DCHECK(callbacks_); + callbacks_->onError( + WebKit::WebIDBDatabaseError(error->code(), error->message())); + callbacks_.reset(); +} + +void IndexedDBCallbacksWrapper::OnSuccess(const std::vector<string16>& value) { + DCHECK(callbacks_); + callbacks_->onSuccess(WebVector<WebString>(value)); + callbacks_.reset(); +} + +void IndexedDBCallbacksWrapper::OnSuccess(scoped_refptr<IndexedDBCursor> cursor, + const IndexedDBKey& key, + const IndexedDBKey& primary_key, + std::vector<char>* value) { + DCHECK(callbacks_); + WebData web_value; + if (value && value->size()) + web_value.assign(&value->front(), value->size()); + callbacks_->onSuccess( + new WebIDBCursorImpl(cursor), WebIDBKey(key), primary_key, web_value); + callbacks_.reset(); +} + +void IndexedDBCallbacksWrapper::OnSuccess(const IndexedDBKey& key) { + DCHECK(callbacks_); + callbacks_->onSuccess(WebIDBKey(key)); + callbacks_.reset(); +} + +void IndexedDBCallbacksWrapper::OnSuccess(std::vector<char>* value) { + WebData web_value; + if (value && value->size()) + web_value.assign(&value->front(), value->size()); + + DCHECK(callbacks_); + callbacks_->onSuccess(web_value); + callbacks_.reset(); +} + +void IndexedDBCallbacksWrapper::OnSuccess(std::vector<char>* value, + const IndexedDBKey& key, + const IndexedDBKeyPath& key_path) { + WebData web_value; + if (value && value->size()) + web_value.assign(&value->front(), value->size()); + DCHECK(callbacks_); + callbacks_->onSuccess(web_value, WebIDBKey(key), key_path); + callbacks_.reset(); +} + +void IndexedDBCallbacksWrapper::OnSuccess(int64 value) { + DCHECK(callbacks_); + callbacks_->onSuccess(value); + callbacks_.reset(); +} + +void IndexedDBCallbacksWrapper::OnSuccess() { + DCHECK(callbacks_); + callbacks_->onSuccess(); + callbacks_.reset(); +} + +void IndexedDBCallbacksWrapper::OnSuccess(const IndexedDBKey& key, + const IndexedDBKey& primary_key, + std::vector<char>* value) { + WebData web_value; + if (value && value->size()) + web_value.assign(&value->front(), value->size()); + DCHECK(callbacks_); + callbacks_->onSuccess(key, primary_key, web_value); + callbacks_.reset(); +} + +void IndexedDBCallbacksWrapper::OnSuccessWithPrefetch( + const std::vector<IndexedDBKey>& keys, + const std::vector<IndexedDBKey>& primary_keys, + const std::vector<std::vector<char> >& values) { + DCHECK_EQ(keys.size(), primary_keys.size()); + DCHECK_EQ(keys.size(), values.size()); + + std::vector<WebIDBKey> web_keys(keys.size()); + std::vector<WebIDBKey> web_primary_keys(primary_keys.size()); + std::vector<WebData> web_values(values.size()); + for (size_t i = 0; i < keys.size(); ++i) { + web_keys[i] = keys[i]; + web_primary_keys[i] = primary_keys[i]; + if (values[i].size()) + web_values[i].assign(&values[i].front(), values[i].size()); + } + + DCHECK(callbacks_); + callbacks_->onSuccessWithPrefetch(web_keys, web_primary_keys, web_values); + callbacks_.reset(); +} + +void IndexedDBCallbacksWrapper::OnBlocked(int64 existing_version) { + DCHECK(callbacks_); + callbacks_->onBlocked(existing_version); +} + +WebIDBMetadata ConvertMetadata(const IndexedDBDatabaseMetadata& idb_metadata) { + WebIDBMetadata web_metadata; + web_metadata.id = idb_metadata.id; + web_metadata.name = idb_metadata.name; + web_metadata.version = idb_metadata.version; + web_metadata.intVersion = idb_metadata.int_version; + web_metadata.maxObjectStoreId = idb_metadata.max_object_store_id; + web_metadata.objectStores = + WebVector<WebIDBMetadata::ObjectStore>(idb_metadata.object_stores.size()); + + size_t i = 0; + for (IndexedDBDatabaseMetadata::ObjectStoreMap::const_iterator + it = idb_metadata.object_stores.begin(); + it != idb_metadata.object_stores.end(); + ++it, ++i) { + const IndexedDBObjectStoreMetadata& idb_store_metadata = it->second; + WebIDBMetadata::ObjectStore& web_store_metadata = + web_metadata.objectStores[i]; + + web_store_metadata.id = idb_store_metadata.id; + web_store_metadata.name = idb_store_metadata.name; + web_store_metadata.keyPath = idb_store_metadata.key_path; + web_store_metadata.autoIncrement = idb_store_metadata.auto_increment; + web_store_metadata.maxIndexId = idb_store_metadata.max_index_id; + web_store_metadata.indexes = + WebVector<WebIDBMetadata::Index>(idb_store_metadata.indexes.size()); + + size_t j = 0; + for (IndexedDBObjectStoreMetadata::IndexMap::const_iterator + it2 = idb_store_metadata.indexes.begin(); + it2 != idb_store_metadata.indexes.end(); + ++it2, ++j) { + const IndexedDBIndexMetadata& idb_index_metadata = it2->second; + WebIDBMetadata::Index& web_index_metadata = web_store_metadata.indexes[j]; + + web_index_metadata.id = idb_index_metadata.id; + web_index_metadata.name = idb_index_metadata.name; + web_index_metadata.keyPath = idb_index_metadata.key_path; + web_index_metadata.unique = idb_index_metadata.unique; + web_index_metadata.multiEntry = idb_index_metadata.multi_entry; + } + } + + return web_metadata; +} + +void IndexedDBCallbacksWrapper::OnUpgradeNeeded( + int64 old_version, + scoped_refptr<IndexedDBDatabase> database, + const IndexedDBDatabaseMetadata& metadata) { + DCHECK(callbacks_); + WebIDBMetadata web_metadata = ConvertMetadata(metadata); + did_create_proxy_ = true; + callbacks_->onUpgradeNeeded( + old_version, + new WebIDBDatabaseImpl(database, database_callbacks_), + web_metadata); + database_callbacks_ = NULL; +} + +void IndexedDBCallbacksWrapper::OnSuccess( + scoped_refptr<IndexedDBDatabase> database, + const IndexedDBDatabaseMetadata& metadata) { + DCHECK(callbacks_); + WebIDBMetadata web_metadata = ConvertMetadata(metadata); + scoped_refptr<IndexedDBCallbacksWrapper> self(this); + + WebIDBDatabaseImpl* impl = + did_create_proxy_ ? 0 + : new WebIDBDatabaseImpl(database, database_callbacks_); + database_callbacks_ = NULL; + + callbacks_->onSuccess(impl, web_metadata); + callbacks_.reset(); +} + +void IndexedDBCallbacksWrapper::SetDatabaseCallbacks( + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> database_callbacks) { + database_callbacks_ = database_callbacks; +} +} // namespace content diff --git a/content/browser/indexed_db/indexed_db_callbacks_wrapper.h b/content/browser/indexed_db/indexed_db_callbacks_wrapper.h new file mode 100644 index 0000000..7fdd4d5 --- /dev/null +++ b/content/browser/indexed_db/indexed_db_callbacks_wrapper.h @@ -0,0 +1,95 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_CALLBACKS_WRAPPER_H_ +#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_CALLBACKS_WRAPPER_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "content/browser/indexed_db/indexed_db_database.h" +#include "content/browser/indexed_db/indexed_db_database_error.h" +#include "content/common/indexed_db/indexed_db_key.h" +#include "content/common/indexed_db/indexed_db_key_path.h" +#include "third_party/WebKit/public/platform/WebIDBCallbacks.h" + +namespace WebKit { +class WebIDBCallbacks; +} + +namespace content { +class IndexedDBCursor; +class WebIDBDatabaseImpl; + +class IndexedDBCallbacksWrapper + : public base::RefCounted<IndexedDBCallbacksWrapper> { + public: + static scoped_refptr<IndexedDBCallbacksWrapper> Create( + WebKit::WebIDBCallbacks* callbacks) { + return make_scoped_refptr(new IndexedDBCallbacksWrapper(callbacks)); + } + + virtual void OnError(scoped_refptr<IndexedDBDatabaseError> error); + // From IDBFactory.webkitGetDatabaseNames() + virtual void OnSuccess(const std::vector<string16>& string); + // From IDBObjectStore/IDBIndex.openCursor(), + // IDBIndex.openKeyCursor() + virtual void OnSuccess(scoped_refptr<IndexedDBCursor> cursor, + const IndexedDBKey& key, + const IndexedDBKey& primary_key, + std::vector<char>* value); + // From IDBObjectStore.add()/put(), IDBIndex.getKey() + virtual void OnSuccess(const IndexedDBKey& key); + // From IDBObjectStore/IDBIndex.get()/count(), and various methods + // that yield null/undefined. + virtual void OnSuccess(std::vector<char>* value); + // From IDBObjectStore/IDBIndex.get() (with key injection) + virtual void OnSuccess(std::vector<char>* data, + const IndexedDBKey& key, + const IndexedDBKeyPath& key_path); + // From IDBObjectStore/IDBIndex.count() + virtual void OnSuccess(int64 value); + + // From IDBFactor.deleteDatabase(), + // IDBObjectStore/IDBIndex.get(), IDBObjectStore.delete(), + // IDBObjectStore.clear() + virtual void OnSuccess(); + + // From IDBCursor.advance()/continue() + virtual void OnSuccess(const IndexedDBKey& key, + const IndexedDBKey& primary_key, + std::vector<char>* value); + // From IDBCursor.advance()/continue() + virtual void OnSuccessWithPrefetch( + const std::vector<IndexedDBKey>& keys, + const std::vector<IndexedDBKey>& primary_keys, + const std::vector<std::vector<char> >& values); + // From IDBFactory.open()/deleteDatabase() + virtual void OnBlocked(int64 /* existing_version */); + // From IDBFactory.open() + virtual void OnUpgradeNeeded(int64 /* old_version */, + scoped_refptr<IndexedDBDatabase> db, + const IndexedDBDatabaseMetadata& metadata); + virtual void OnSuccess(scoped_refptr<IndexedDBDatabase> db, + const IndexedDBDatabaseMetadata& metadata); + virtual void SetDatabaseCallbacks( + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> database_callbacks); + + private: + explicit IndexedDBCallbacksWrapper(WebKit::WebIDBCallbacks* callbacks); + virtual ~IndexedDBCallbacksWrapper(); + friend class base::RefCounted<IndexedDBCallbacksWrapper>; + scoped_ptr<WebIDBDatabaseImpl> web_database_impl_; + scoped_ptr<WebKit::WebIDBCallbacks> callbacks_; + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> database_callbacks_; + bool did_complete_; + bool did_create_proxy_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_CALLBACKS_WRAPPER_H_ diff --git a/content/browser/indexed_db/indexed_db_cleanup_on_io_error_unittest.cc b/content/browser/indexed_db/indexed_db_cleanup_on_io_error_unittest.cc new file mode 100644 index 0000000..af86147 --- /dev/null +++ b/content/browser/indexed_db/indexed_db_cleanup_on_io_error_unittest.cc @@ -0,0 +1,74 @@ +// 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 "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "content/browser/indexed_db/indexed_db_backing_store.h" +#include "content/browser/indexed_db/leveldb/leveldb_database.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityOrigin.h" + +using WebKit::WebSecurityOrigin; +using content::IndexedDBBackingStore; +using content::LevelDBComparator; +using content::LevelDBDatabase; +using content::LevelDBFactory; +using content::LevelDBSlice; +using content::LevelDBSnapshot; + +namespace { + +class BustedLevelDBDatabase : public LevelDBDatabase { + public: + static scoped_ptr<LevelDBDatabase> Open( + const base::FilePath& file_name, + const LevelDBComparator* /*comparator*/) { + return scoped_ptr<LevelDBDatabase>(new BustedLevelDBDatabase); + } + virtual bool Get(const LevelDBSlice& key, + std::vector<char>& value, + bool& found, + const LevelDBSnapshot* = 0) OVERRIDE { + // false means IO error. + return false; + } +}; + +class MockLevelDBFactory : public LevelDBFactory { + public: + MockLevelDBFactory() : destroy_called_(false) {} + virtual scoped_ptr<LevelDBDatabase> OpenLevelDB( + const base::FilePath& file_name, + const LevelDBComparator* comparator) OVERRIDE { + return BustedLevelDBDatabase::Open(file_name, comparator); + } + virtual bool DestroyLevelDB(const base::FilePath& file_name) OVERRIDE { + EXPECT_FALSE(destroy_called_); + destroy_called_ = true; + return false; + } + virtual ~MockLevelDBFactory() { EXPECT_TRUE(destroy_called_); } + + private: + bool destroy_called_; +}; + +TEST(IndexedDBIOErrorTest, CleanUpTest) { + WebSecurityOrigin origin( + WebSecurityOrigin::createFromString("http://localhost:81")); + base::ScopedTempDir temp_directory; + ASSERT_TRUE(temp_directory.CreateUniqueTempDir()); + const base::FilePath path = temp_directory.path(); + string16 dummy_file_identifier; + MockLevelDBFactory mock_leveldb_factory; + scoped_refptr<IndexedDBBackingStore> backing_store = + IndexedDBBackingStore::Open(origin.databaseIdentifier(), + UTF8ToUTF16(path.AsUTF8Unsafe()), + dummy_file_identifier, + &mock_leveldb_factory); +} + +} // namespace diff --git a/content/browser/indexed_db/indexed_db_context_impl.cc b/content/browser/indexed_db/indexed_db_context_impl.cc index 038ef94..0b61910 100644 --- a/content/browser/indexed_db/indexed_db_context_impl.cc +++ b/content/browser/indexed_db/indexed_db_context_impl.cc @@ -14,6 +14,7 @@ #include "base/string_util.h" #include "base/utf_string_conversions.h" #include "content/browser/indexed_db/indexed_db_quota_client.h" +#include "content/browser/indexed_db/webidbfactory_impl.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/indexed_db_info.h" #include "content/public/common/content_switches.h" @@ -40,15 +41,14 @@ const base::FilePath::CharType IndexedDBContextImpl::kIndexedDBExtension[] = namespace { -void GetAllOriginsAndPaths( - const base::FilePath& indexeddb_path, - std::vector<GURL>* origins, - std::vector<base::FilePath>* file_paths) { +void GetAllOriginsAndPaths(const base::FilePath& indexeddb_path, + std::vector<GURL>* origins, + std::vector<base::FilePath>* file_paths) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); if (indexeddb_path.empty()) return; - file_util::FileEnumerator file_enumerator(indexeddb_path, - false, file_util::FileEnumerator::DIRECTORIES); + file_util::FileEnumerator file_enumerator( + indexeddb_path, false, file_util::FileEnumerator::DIRECTORIES); for (base::FilePath file_path = file_enumerator.Next(); !file_path.empty(); file_path = file_enumerator.Next()) { if (file_path.Extension() == IndexedDBContextImpl::kIndexedDBExtension) { @@ -74,7 +74,8 @@ void ClearSessionOnlyOrigins( std::vector<base::FilePath>::const_iterator file_path_iter = file_paths.begin(); for (std::vector<GURL>::const_iterator iter = origins.begin(); - iter != origins.end(); ++iter, ++file_path_iter) { + iter != origins.end(); + ++iter, ++file_path_iter) { if (!special_storage_policy->IsStorageSessionOnly(*iter)) continue; if (special_storage_policy->IsStorageProtected(*iter)) @@ -108,7 +109,10 @@ WebIDBFactory* IndexedDBContextImpl::GetIDBFactory() { // Prime our cache of origins with existing databases so we can // detect when dbs are newly created. GetOriginSet(); - idb_factory_.reset(WebIDBFactory::create()); + if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kNewIndexedDB)) + idb_factory_.reset(new content::WebIDBFactoryImpl()); + else + idb_factory_.reset(WebIDBFactory::create()); } return idb_factory_.get(); } @@ -118,7 +122,8 @@ std::vector<GURL> IndexedDBContextImpl::GetAllOrigins() { std::vector<GURL> origins; std::set<GURL>* origins_set = GetOriginSet(); for (std::set<GURL>::const_iterator iter = origins_set->begin(); - iter != origins_set->end(); ++iter) { + iter != origins_set->end(); + ++iter) { origins.push_back(*iter); } return origins; @@ -128,7 +133,8 @@ std::vector<IndexedDBInfo> IndexedDBContextImpl::GetAllOriginsInfo() { std::vector<GURL> origins = GetAllOrigins(); std::vector<IndexedDBInfo> result; for (std::vector<GURL>::const_iterator iter = origins.begin(); - iter != origins.end(); ++iter) { + iter != origins.end(); + ++iter) { const GURL& origin_url = *iter; base::FilePath idb_directory = GetFilePath(origin_url); @@ -202,7 +208,7 @@ base::FilePath IndexedDBContextImpl::GetFilePath(const GURL& origin_url) { } base::FilePath IndexedDBContextImpl::GetFilePathForTesting( - const string16& origin_id) const { + const string16& origin_id) const { return GetIndexedDBFilePath(origin_id); } @@ -211,7 +217,8 @@ void IndexedDBContextImpl::ConnectionOpened(const GURL& origin_url, DCHECK_EQ(connections_[origin_url].count(connection), 0UL); if (quota_manager_proxy()) { quota_manager_proxy()->NotifyStorageAccessed( - quota::QuotaClient::kIndexedDatabase, origin_url, + quota::QuotaClient::kIndexedDatabase, + origin_url, quota::kStorageTypeTemporary); } connections_[origin_url].insert(connection); @@ -232,7 +239,8 @@ void IndexedDBContextImpl::ConnectionClosed(const GURL& origin_url, return; if (quota_manager_proxy()) { quota_manager_proxy()->NotifyStorageAccessed( - quota::QuotaClient::kIndexedDatabase, origin_url, + quota::QuotaClient::kIndexedDatabase, + origin_url, quota::kStorageTypeTemporary); } connections_[origin_url].erase(connection); @@ -271,8 +279,8 @@ quota::QuotaManagerProxy* IndexedDBContextImpl::quota_manager_proxy() { IndexedDBContextImpl::~IndexedDBContextImpl() { WebKit::WebIDBFactory* factory = idb_factory_.release(); if (factory) { - if (!BrowserThread::DeleteSoon(BrowserThread::WEBKIT_DEPRECATED, - FROM_HERE, factory)) + if (!BrowserThread::DeleteSoon( + BrowserThread::WEBKIT_DEPRECATED, FROM_HERE, factory)) delete factory; } @@ -293,18 +301,17 @@ IndexedDBContextImpl::~IndexedDBContextImpl() { // No WEBKIT thread here means we are running in a unit test where no clean // up is needed. BrowserThread::PostTask( - BrowserThread::WEBKIT_DEPRECATED, FROM_HERE, - base::Bind(&ClearSessionOnlyOrigins, - data_path_, - special_storage_policy_)); + BrowserThread::WEBKIT_DEPRECATED, + FROM_HERE, + base::Bind( + &ClearSessionOnlyOrigins, data_path_, special_storage_policy_)); } base::FilePath IndexedDBContextImpl::GetIndexedDBFilePath( const string16& origin_id) const { DCHECK(!data_path_.empty()); - base::FilePath::StringType id = - webkit_base::WebStringToFilePathString(origin_id).append( - FILE_PATH_LITERAL(".indexeddb")); + base::FilePath::StringType id = webkit_base::WebStringToFilePathString( + origin_id).append(FILE_PATH_LITERAL(".indexeddb")); return data_path_.Append(id.append(kIndexedDBExtension)); } @@ -331,18 +338,20 @@ void IndexedDBContextImpl::QueryDiskAndUpdateQuotaUsage( if (difference) { origin_size_map_[origin_url] = current_disk_usage; // quota_manager_proxy() is NULL in unit tests. - if (quota_manager_proxy()) + if (quota_manager_proxy()) { quota_manager_proxy()->NotifyStorageModified( quota::QuotaClient::kIndexedDatabase, origin_url, quota::kStorageTypeTemporary, difference); + } } } void IndexedDBContextImpl::GotUsageAndQuota(const GURL& origin_url, quota::QuotaStatusCode status, - int64 usage, int64 quota) { + int64 usage, + int64 quota) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); DCHECK(status == quota::kQuotaStatusOk || status == quota::kQuotaErrorAbort) << "status was " << status; @@ -350,13 +359,17 @@ void IndexedDBContextImpl::GotUsageAndQuota(const GURL& origin_url, // We seem to no longer care to wait around for the answer. return; } - BrowserThread::PostTask( - BrowserThread::WEBKIT_DEPRECATED, FROM_HERE, - base::Bind(&IndexedDBContextImpl::GotUpdatedQuota, this, origin_url, - usage, quota)); + BrowserThread::PostTask(BrowserThread::WEBKIT_DEPRECATED, + FROM_HERE, + base::Bind(&IndexedDBContextImpl::GotUpdatedQuota, + this, + origin_url, + usage, + quota)); } -void IndexedDBContextImpl::GotUpdatedQuota(const GURL& origin_url, int64 usage, +void IndexedDBContextImpl::GotUpdatedQuota(const GURL& origin_url, + int64 usage, int64 quota) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); space_available_map_[origin_url] = quota - usage; @@ -365,11 +378,13 @@ void IndexedDBContextImpl::GotUpdatedQuota(const GURL& origin_url, int64 usage, void IndexedDBContextImpl::QueryAvailableQuota(const GURL& origin_url) { if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); - if (quota_manager_proxy()) + if (quota_manager_proxy()) { BrowserThread::PostTask( - BrowserThread::IO, FROM_HERE, - base::Bind(&IndexedDBContextImpl::QueryAvailableQuota, this, - origin_url)); + BrowserThread::IO, + FROM_HERE, + base::Bind( + &IndexedDBContextImpl::QueryAvailableQuota, this, origin_url)); + } return; } DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); @@ -387,7 +402,8 @@ std::set<GURL>* IndexedDBContextImpl::GetOriginSet() { std::vector<GURL> origins; GetAllOriginsAndPaths(data_path_, &origins, NULL); for (std::vector<GURL>::const_iterator iter = origins.begin(); - iter != origins.end(); ++iter) { + iter != origins.end(); + ++iter) { origin_set_->insert(*iter); } } diff --git a/content/browser/indexed_db/indexed_db_context_impl.h b/content/browser/indexed_db/indexed_db_context_impl.h index 48c9e07..846b2b1 100644 --- a/content/browser/indexed_db/indexed_db_context_impl.h +++ b/content/browser/indexed_db/indexed_db_context_impl.h @@ -66,8 +66,8 @@ class CONTENT_EXPORT IndexedDBContextImpl OVERRIDE; // Methods called by IndexedDBDispatcherHost for quota support. - void ConnectionOpened(const GURL& origin_url, WebKit::WebIDBDatabase*); - void ConnectionClosed(const GURL& origin_url, WebKit::WebIDBDatabase*); + void ConnectionOpened(const GURL& origin_url, WebKit::WebIDBDatabase* db); + void ConnectionClosed(const GURL& origin_url, WebKit::WebIDBDatabase* db); void TransactionComplete(const GURL& origin_url); bool WouldBeOverQuota(const GURL& origin_url, int64 additional_bytes); bool IsOverQuota(const GURL& origin_url); diff --git a/content/browser/indexed_db/indexed_db_cursor.h b/content/browser/indexed_db/indexed_db_cursor.h new file mode 100644 index 0000000..536b359 --- /dev/null +++ b/content/browser/indexed_db/indexed_db_cursor.h @@ -0,0 +1,37 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_CURSOR_H_ +#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_CURSOR_H_ + +#include "base/memory/ref_counted.h" +#include "content/common/indexed_db/indexed_db_key.h" + +namespace content { + +class IndexedDBCallbacksWrapper; + +class IndexedDBCursor : public base::RefCounted<IndexedDBCursor> { + public: + virtual void Advance(unsigned long count, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) = 0; + virtual void ContinueFunction( + scoped_ptr<IndexedDBKey> key, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) = 0; + virtual void DeleteFunction( + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) = 0; + virtual void PrefetchContinue( + int number_to_fetch, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) = 0; + virtual void PrefetchReset(int used_prefetches, int unused_prefetches) = 0; + virtual void PostSuccessHandlerCallback() = 0; + + protected: + virtual ~IndexedDBCursor() {} + friend class base::RefCounted<IndexedDBCursor>; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_CURSOR_H_ diff --git a/content/browser/indexed_db/indexed_db_cursor_impl.cc b/content/browser/indexed_db/indexed_db_cursor_impl.cc new file mode 100644 index 0000000..a11e880 --- /dev/null +++ b/content/browser/indexed_db/indexed_db_cursor_impl.cc @@ -0,0 +1,223 @@ +// 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_cursor_impl.h" + +#include "base/logging.h" +#include "content/browser/indexed_db/indexed_db_backing_store.h" +#include "content/browser/indexed_db/indexed_db_callbacks_wrapper.h" +#include "content/browser/indexed_db/indexed_db_database_error.h" +#include "content/browser/indexed_db/indexed_db_database_impl.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_range.h" + +namespace content { + +class IndexedDBCursorImpl::CursorIterationOperation + : public IndexedDBTransaction::Operation { + public: + CursorIterationOperation(scoped_refptr<IndexedDBCursorImpl> cursor, + scoped_ptr<IndexedDBKey> key, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) + : cursor_(cursor), key_(key.Pass()), callbacks_(callbacks) {} + virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE; + + private: + scoped_refptr<IndexedDBCursorImpl> cursor_; + scoped_ptr<IndexedDBKey> key_; + scoped_refptr<IndexedDBCallbacksWrapper> callbacks_; +}; + +class IndexedDBCursorImpl::CursorAdvanceOperation + : public IndexedDBTransaction::Operation { + public: + CursorAdvanceOperation(scoped_refptr<IndexedDBCursorImpl> cursor, + unsigned long count, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) + : cursor_(cursor), count_(count), callbacks_(callbacks) {} + virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE; + + private: + scoped_refptr<IndexedDBCursorImpl> cursor_; + unsigned long count_; + scoped_refptr<IndexedDBCallbacksWrapper> callbacks_; +}; + +class IndexedDBCursorImpl::CursorPrefetchIterationOperation + : public IndexedDBTransaction::Operation { + public: + CursorPrefetchIterationOperation( + scoped_refptr<IndexedDBCursorImpl> cursor, + int number_to_fetch, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) + : cursor_(cursor), + number_to_fetch_(number_to_fetch), + callbacks_(callbacks) {} + virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE; + + private: + scoped_refptr<IndexedDBCursorImpl> cursor_; + int number_to_fetch_; + scoped_refptr<IndexedDBCallbacksWrapper> callbacks_; +}; + +IndexedDBCursorImpl::IndexedDBCursorImpl( + scoped_ptr<IndexedDBBackingStore::Cursor> cursor, + indexed_db::CursorType cursor_type, + IndexedDBDatabase::TaskType task_type, + IndexedDBTransaction* transaction, + int64 object_store_id) + : task_type_(task_type), + cursor_type_(cursor_type), + transaction_(transaction), + object_store_id_(object_store_id), + cursor_(cursor.Pass()), + closed_(false) { + transaction_->RegisterOpenCursor(this); +} + +IndexedDBCursorImpl::~IndexedDBCursorImpl() { + transaction_->UnregisterOpenCursor(this); +} + +void IndexedDBCursorImpl::ContinueFunction( + scoped_ptr<IndexedDBKey> key, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) { + IDB_TRACE("IndexedDBCursorImpl::continue"); + + transaction_->ScheduleTask( + task_type_, new CursorIterationOperation(this, key.Pass(), callbacks)); +} + +void IndexedDBCursorImpl::Advance( + unsigned long count, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) { + IDB_TRACE("IndexedDBCursorImpl::advance"); + + transaction_->ScheduleTask( + new CursorAdvanceOperation(this, count, callbacks)); +} + +void IndexedDBCursorImpl::CursorAdvanceOperation::Perform( + IndexedDBTransaction* /*transaction*/) { + IDB_TRACE("CursorAdvanceOperation"); + if (!cursor_->cursor_ || !cursor_->cursor_->Advance(count_)) { + cursor_->cursor_.reset(); + callbacks_->OnSuccess(static_cast<std::vector<char>*>(NULL)); + return; + } + + callbacks_->OnSuccess( + cursor_->key(), cursor_->primary_key(), cursor_->Value()); +} + +void IndexedDBCursorImpl::CursorIterationOperation::Perform( + IndexedDBTransaction* /*transaction*/) { + IDB_TRACE("CursorIterationOperation"); + if (!cursor_->cursor_ || !cursor_->cursor_->ContinueFunction(key_.get())) { + cursor_->cursor_.reset(); + callbacks_->OnSuccess(static_cast<std::vector<char>*>(NULL)); + return; + } + + callbacks_->OnSuccess( + cursor_->key(), cursor_->primary_key(), cursor_->Value()); +} + +void IndexedDBCursorImpl::DeleteFunction( + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) { + IDB_TRACE("IndexedDBCursorImpl::delete"); + DCHECK_NE(transaction_->mode(), indexed_db::TRANSACTION_READ_ONLY); + scoped_ptr<IndexedDBKeyRange> key_range = + make_scoped_ptr(new IndexedDBKeyRange(cursor_->primary_key())); + transaction_->database()->DeleteRange( + transaction_->id(), object_store_id_, key_range.Pass(), callbacks); +} + +void IndexedDBCursorImpl::PrefetchContinue( + int number_to_fetch, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) { + IDB_TRACE("IndexedDBCursorImpl::prefetch_continue"); + + transaction_->ScheduleTask( + task_type_, + new CursorPrefetchIterationOperation(this, number_to_fetch, callbacks)); +} + +void IndexedDBCursorImpl::CursorPrefetchIterationOperation::Perform( + IndexedDBTransaction* /*transaction*/) { + IDB_TRACE("CursorPrefetchIterationOperation"); + + std::vector<IndexedDBKey> found_keys; + std::vector<IndexedDBKey> found_primary_keys; + std::vector<std::vector<char> > found_values; + + if (cursor_->cursor_) + cursor_->saved_cursor_.reset(cursor_->cursor_->Clone()); + const size_t max_size_estimate = 10 * 1024 * 1024; + size_t size_estimate = 0; + + for (int i = 0; i < number_to_fetch_; ++i) { + if (!cursor_->cursor_ || !cursor_->cursor_->ContinueFunction(0)) { + cursor_->cursor_.reset(); + break; + } + + found_keys.push_back(cursor_->cursor_->key()); + found_primary_keys.push_back(cursor_->cursor_->primary_key()); + + switch (cursor_->cursor_type_) { + case indexed_db::CURSOR_KEY_ONLY: + found_values.push_back(std::vector<char>()); + break; + case indexed_db::CURSOR_KEY_AND_VALUE: { + std::vector<char> value; + value.swap(*cursor_->cursor_->Value()); + size_estimate += value.size(); + found_values.push_back(value); + break; + } + default: + NOTREACHED(); + } + size_estimate += cursor_->cursor_->key().size_estimate(); + size_estimate += cursor_->cursor_->primary_key().size_estimate(); + + if (size_estimate > max_size_estimate) + break; + } + + if (!found_keys.size()) { + callbacks_->OnSuccess(static_cast<std::vector<char>*>(NULL)); + return; + } + + callbacks_->OnSuccessWithPrefetch( + found_keys, found_primary_keys, found_values); +} + +void IndexedDBCursorImpl::PrefetchReset(int used_prefetches, int) { + IDB_TRACE("IndexedDBCursorImpl::prefetch_reset"); + cursor_.swap(saved_cursor_); + saved_cursor_.reset(); + + if (closed_) + return; + if (cursor_) { + for (int i = 0; i < used_prefetches; ++i) { + bool ok = cursor_->ContinueFunction(); + DCHECK(ok); + } + } +} + +void IndexedDBCursorImpl::Close() { + IDB_TRACE("IndexedDBCursorImpl::close"); + closed_ = true; + cursor_.reset(); + saved_cursor_.reset(); +} + +} // namespace content diff --git a/content/browser/indexed_db/indexed_db_cursor_impl.h b/content/browser/indexed_db/indexed_db_cursor_impl.h new file mode 100644 index 0000000..8b79d27 --- /dev/null +++ b/content/browser/indexed_db/indexed_db_cursor_impl.h @@ -0,0 +1,98 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_CURSOR_IMPL_H_ +#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_CURSOR_IMPL_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "content/browser/indexed_db/indexed_db_backing_store.h" +#include "content/browser/indexed_db/indexed_db_cursor.h" +#include "content/browser/indexed_db/indexed_db_database.h" +#include "content/common/indexed_db/indexed_db_key_range.h" + +namespace content { + +class IndexedDBDatabaseImpl; +class IndexedDBTransaction; + +class IndexedDBCursorImpl : public IndexedDBCursor { + public: + static scoped_refptr<IndexedDBCursorImpl> Create( + scoped_ptr<IndexedDBBackingStore::Cursor> cursor, + indexed_db::CursorType cursor_type, + IndexedDBTransaction* transaction, + int64 object_store_id) { + return make_scoped_refptr( + new IndexedDBCursorImpl(cursor.Pass(), + cursor_type, + IndexedDBDatabase::NORMAL_TASK, + transaction, + object_store_id)); + } + static scoped_refptr<IndexedDBCursorImpl> Create( + scoped_ptr<IndexedDBBackingStore::Cursor> cursor, + indexed_db::CursorType cursor_type, + IndexedDBDatabase::TaskType task_type, + IndexedDBTransaction* transaction, + int64 object_store_id) { + return make_scoped_refptr(new IndexedDBCursorImpl( + cursor.Pass(), cursor_type, task_type, transaction, object_store_id)); + } + + // IndexedDBCursor + virtual void Advance(unsigned long, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) + OVERRIDE; + virtual void ContinueFunction( + scoped_ptr<IndexedDBKey> key, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) OVERRIDE; + virtual void DeleteFunction( + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) OVERRIDE; + virtual void PrefetchContinue( + int number_to_fetch, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) OVERRIDE; + virtual void PrefetchReset(int used_prefetches, int unused_prefetches) + OVERRIDE; + virtual void PostSuccessHandlerCallback() OVERRIDE {} + + const IndexedDBKey& key() const { return cursor_->key(); } + const IndexedDBKey& primary_key() const { return cursor_->primary_key(); } + std::vector<char>* Value() const { + return (cursor_type_ == indexed_db::CURSOR_KEY_ONLY) ? NULL + : cursor_->Value(); + } + void Close(); + + private: + IndexedDBCursorImpl(scoped_ptr<IndexedDBBackingStore::Cursor> cursor, + indexed_db::CursorType cursor_type, + IndexedDBDatabase::TaskType task_type, + IndexedDBTransaction* transaction, + int64 object_store_id); + virtual ~IndexedDBCursorImpl(); + + class CursorIterationOperation; + class CursorAdvanceOperation; + class CursorPrefetchIterationOperation; + + IndexedDBDatabase::TaskType task_type_; + indexed_db::CursorType cursor_type_; + const scoped_refptr<IndexedDBTransaction> transaction_; + const int64 object_store_id_; + + // Must be destroyed before transaction_. + scoped_ptr<IndexedDBBackingStore::Cursor> cursor_; + // Must be destroyed before transaction_. + scoped_ptr<IndexedDBBackingStore::Cursor> saved_cursor_; + + bool closed_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_CURSOR_IMPL_H_ diff --git a/content/browser/indexed_db/indexed_db_database.h b/content/browser/indexed_db/indexed_db_database.h new file mode 100644 index 0000000..880e880 --- /dev/null +++ b/content/browser/indexed_db/indexed_db_database.h @@ -0,0 +1,131 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_DATABASE_H_ +#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_DATABASE_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/string16.h" +#include "content/browser/indexed_db/indexed_db.h" +#include "content/browser/indexed_db/indexed_db_database_error.h" +#include "content/common/indexed_db/indexed_db_key.h" +#include "content/common/indexed_db/indexed_db_key_path.h" +#include "content/common/indexed_db/indexed_db_key_range.h" + +namespace content { + +class IndexedDBCallbacksWrapper; +class IndexedDBDatabaseCallbacksWrapper; +struct IndexedDBDatabaseMetadata; + +// This is implemented by IndexedDBDatabaseImpl and optionally others (in order +// to proxy calls across process barriers). All calls to these classes should be +// non-blocking and trigger work on a background thread if necessary. +class IndexedDBDatabase : public base::RefCounted<IndexedDBDatabase> { + public: + virtual void CreateObjectStore(int64 transaction_id, + int64 object_store_id, + const string16& name, + const IndexedDBKeyPath& key_path, + bool auto_increment) = 0; + virtual void DeleteObjectStore(int64 transaction_id, + int64 object_store_id) = 0; + virtual void CreateTransaction( + int64 transaction_id, + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> callbacks, + const std::vector<int64>& object_store_ids, + uint16 mode) = 0; + virtual void Close( + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> callbacks) = 0; + + // Transaction-specific operations. + virtual void Commit(int64 transaction_id) = 0; + virtual void Abort(int64 transaction_id) = 0; + virtual void Abort(int64 transaction_id, + scoped_refptr<IndexedDBDatabaseError> error) = 0; + + virtual void CreateIndex(int64 transaction_id, + int64 object_store_id, + int64 index_id, + const string16& name, + const IndexedDBKeyPath& key_path, + bool unique, + bool multi_entry) = 0; + virtual void DeleteIndex(int64 transaction_id, + int64 object_store_id, + int64 index_id) = 0; + + enum TaskType { + NORMAL_TASK = 0, + PREEMPTIVE_TASK + }; + + enum PutMode { + ADD_OR_UPDATE, + ADD_ONLY, + CURSOR_UPDATE + }; + + static const int64 kMinimumIndexId = 30; + + typedef std::vector<IndexedDBKey> IndexKeys; + + virtual void Get(int64 transaction_id, + int64 object_store_id, + int64 index_id, + scoped_ptr<IndexedDBKeyRange> key_range, + bool key_only, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) = 0; + // This will swap() with value. + virtual void Put(int64 transaction_id, + int64 object_store_id, + std::vector<char>* value, + scoped_ptr<IndexedDBKey> key, + PutMode mode, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks, + const std::vector<int64>& index_ids, + const std::vector<IndexKeys>& index_keys) = 0; + virtual void 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) = 0; + virtual void SetIndexesReady(int64 transaction_id, + int64 object_store_id, + const std::vector<int64>& index_ids) = 0; + virtual void 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) = 0; + virtual void Count(int64 transaction_id, + int64 object_store_id, + int64 index_id, + scoped_ptr<IndexedDBKeyRange> key_range, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) = 0; + virtual void DeleteRange( + int64 transaction_id, + int64 object_store_id, + scoped_ptr<IndexedDBKeyRange> key_range, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) = 0; + virtual void Clear(int64 transaction_id, + int64 object_store_id, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) = 0; + + protected: + virtual ~IndexedDBDatabase() {} + friend class base::RefCounted<IndexedDBDatabase>; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_DATABASE_H_ diff --git a/content/browser/indexed_db/indexed_db_database_callbacks_wrapper.cc b/content/browser/indexed_db/indexed_db_database_callbacks_wrapper.cc new file mode 100644 index 0000000..6e99f0c --- /dev/null +++ b/content/browser/indexed_db/indexed_db_database_callbacks_wrapper.cc @@ -0,0 +1,42 @@ +// 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_callbacks_wrapper.h" + +namespace content { + +IndexedDBDatabaseCallbacksWrapper::IndexedDBDatabaseCallbacksWrapper( + WebKit::WebIDBDatabaseCallbacks* callbacks) + : callbacks_(callbacks) {} +IndexedDBDatabaseCallbacksWrapper::~IndexedDBDatabaseCallbacksWrapper() {} + +void IndexedDBDatabaseCallbacksWrapper::OnForcedClose() { + if (!callbacks_) + return; + callbacks_->onForcedClose(); + callbacks_.reset(); +} +void IndexedDBDatabaseCallbacksWrapper::OnVersionChange(int64 old_version, + int64 new_version) { + if (!callbacks_) + return; + callbacks_->onVersionChange(old_version, new_version); +} + +void IndexedDBDatabaseCallbacksWrapper::OnAbort( + int64 transaction_id, + scoped_refptr<IndexedDBDatabaseError> error) { + if (!callbacks_) + return; + callbacks_->onAbort( + transaction_id, + WebKit::WebIDBDatabaseError(error->code(), error->message())); +} +void IndexedDBDatabaseCallbacksWrapper::OnComplete(int64 transaction_id) { + if (!callbacks_) + return; + callbacks_->onComplete(transaction_id); +} + +} // namespace content diff --git a/content/browser/indexed_db/indexed_db_database_callbacks_wrapper.h b/content/browser/indexed_db/indexed_db_database_callbacks_wrapper.h new file mode 100644 index 0000000..920f2d2 --- /dev/null +++ b/content/browser/indexed_db/indexed_db_database_callbacks_wrapper.h @@ -0,0 +1,43 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_DATABASE_CALLBACKS_WRAPPER_H_ +#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_DATABASE_CALLBACKS_WRAPPER_H_ + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/string16.h" +#include "content/browser/indexed_db/indexed_db_database_error.h" +#include "third_party/WebKit/public/platform/WebIDBDatabaseCallbacks.h" + +namespace content { + +class IndexedDBDatabaseCallbacksWrapper + : public base::RefCounted<IndexedDBDatabaseCallbacksWrapper> { + public: + static scoped_refptr<IndexedDBDatabaseCallbacksWrapper> Create( + WebKit::WebIDBDatabaseCallbacks* callbacks) { + return make_scoped_refptr(new IndexedDBDatabaseCallbacksWrapper(callbacks)); + } + + virtual void OnForcedClose(); + virtual void OnVersionChange(int64 old_version, int64 new_version); + + virtual void OnAbort(int64 transaction_id, + scoped_refptr<IndexedDBDatabaseError> error); + virtual void OnComplete(int64 transaction_id); + + private: + explicit IndexedDBDatabaseCallbacksWrapper( + WebKit::WebIDBDatabaseCallbacks* callbacks); + virtual ~IndexedDBDatabaseCallbacksWrapper(); + friend class base::RefCounted<IndexedDBDatabaseCallbacksWrapper>; + + scoped_ptr<WebKit::WebIDBDatabaseCallbacks> callbacks_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_DATABASE_CALLBACKS_WRAPPER_H_ diff --git a/content/browser/indexed_db/indexed_db_database_error.h b/content/browser/indexed_db/indexed_db_database_error.h new file mode 100644 index 0000000..aa798c9 --- /dev/null +++ b/content/browser/indexed_db/indexed_db_database_error.h @@ -0,0 +1,43 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_DATABASE_ERROR_H_ +#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_DATABASE_ERROR_H_ + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/string16.h" +#include "third_party/WebKit/public/platform/WebIDBDatabaseError.h" + +namespace content { + +class IndexedDBDatabaseError : public base::RefCounted<IndexedDBDatabaseError> { + public: + static scoped_refptr<IndexedDBDatabaseError> Create(uint16 code, + const string16& message) { + // TODO(jsbell): Assert that this is a valid WebIDBDatabaseException code. + return make_scoped_refptr(new IndexedDBDatabaseError(code, message)); + } + static scoped_refptr<IndexedDBDatabaseError> Create( + const WebKit::WebIDBDatabaseError& other) { + return make_scoped_refptr( + new IndexedDBDatabaseError(other.code(), other.message())); + } + + uint16 code() const { return code_; } + const string16& message() const { return message_; } + + private: + IndexedDBDatabaseError(uint16 code, const string16& message) + : code_(code), message_(message) {} + ~IndexedDBDatabaseError() {} + friend class base::RefCounted<IndexedDBDatabaseError>; + const uint16 code_; + const string16 message_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_DATABASE_ERROR_H_ 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..9ecc611 --- /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/public/platform/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 diff --git a/content/browser/indexed_db/indexed_db_database_impl.h b/content/browser/indexed_db/indexed_db_database_impl.h new file mode 100644 index 0000000..d71cf0e --- /dev/null +++ b/content/browser/indexed_db/indexed_db_database_impl.h @@ -0,0 +1,204 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_DATABASE_IMPL_H_ +#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_DATABASE_IMPL_H_ + +#include <list> +#include <map> +#include <vector> + +#include "base/basictypes.h" +#include "content/browser/indexed_db/indexed_db_callbacks_wrapper.h" +#include "content/browser/indexed_db/indexed_db_metadata.h" +#include "content/browser/indexed_db/indexed_db_transaction_coordinator.h" +#include "content/browser/indexed_db/list_set.h" + +namespace content { + +class IndexedDBBackingStore; +class IndexedDBDatabase; +class IndexedDBFactoryImpl; +class IndexedDBTransaction; + +class IndexedDBDatabaseImpl : public IndexedDBDatabase { + public: + static scoped_refptr<IndexedDBDatabaseImpl> Create( + const string16& name, + IndexedDBBackingStore* database, + IndexedDBFactoryImpl* factory, + const string16& unique_identifier); + scoped_refptr<IndexedDBBackingStore> BackingStore() const; + + static const int64 kInvalidId = 0; + int64 id() const { return metadata_.id; } + void AddObjectStore(const IndexedDBObjectStoreMetadata& metadata, + int64 new_max_object_store_id); + void RemoveObjectStore(int64 object_store_id); + void AddIndex(int64 object_store_id, + const IndexedDBIndexMetadata& metadata, + int64 new_max_index_id); + void RemoveIndex(int64 object_store_id, int64 index_id); + + void OpenConnection( + scoped_refptr<IndexedDBCallbacksWrapper> callbacks, + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> database_callbacks, + int64 transaction_id, + int64 version); + void DeleteDatabase(scoped_refptr<IndexedDBCallbacksWrapper> callbacks); + const IndexedDBDatabaseMetadata& metadata() const { return metadata_; } + + // IndexedDBDatabase + virtual void CreateObjectStore(int64 transaction_id, + int64 object_store_id, + const string16& name, + const IndexedDBKeyPath& key_path, + bool auto_increment) OVERRIDE; + virtual void DeleteObjectStore(int64 transaction_id, int64 object_store_id) + OVERRIDE; + virtual void CreateTransaction( + int64 transaction_id, + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> callbacks, + const std::vector<int64>& object_store_ids, + uint16 mode) OVERRIDE; + virtual void Close(scoped_refptr<IndexedDBDatabaseCallbacksWrapper> callbacks) + OVERRIDE; + + virtual void Commit(int64 transaction_id) OVERRIDE; + virtual void Abort(int64 transaction_id) OVERRIDE; + virtual void Abort(int64 transaction_id, + scoped_refptr<IndexedDBDatabaseError> error) OVERRIDE; + + virtual void CreateIndex(int64 transaction_id, + int64 object_store_id, + int64 index_id, + const string16& name, + const IndexedDBKeyPath& key_path, + bool unique, + bool multi_entry) OVERRIDE; + virtual void DeleteIndex(int64 transaction_id, + int64 object_store_id, + int64 index_id) OVERRIDE; + + IndexedDBTransactionCoordinator& transaction_coordinator() { + return transaction_coordinator_; + } + + void TransactionStarted(IndexedDBTransaction* transaction); + void TransactionFinished(IndexedDBTransaction* transaction); + void TransactionFinishedAndCompleteFired(IndexedDBTransaction* transaction); + void TransactionFinishedAndAbortFired(IndexedDBTransaction* transaction); + + virtual void Get(int64 transaction_id, + int64 object_store_id, + int64 index_id, + scoped_ptr<IndexedDBKeyRange> key_range, + bool key_only, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) OVERRIDE; + virtual void Put(int64 transaction_id, + int64 object_store_id, + std::vector<char>* value, + scoped_ptr<IndexedDBKey> key, + PutMode mode, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks, + const std::vector<int64>& index_ids, + const std::vector<IndexKeys>& index_keys) OVERRIDE; + virtual void 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) OVERRIDE; + virtual void SetIndexesReady(int64 transaction_id, + int64 object_store_id, + const std::vector<int64>& index_ids) OVERRIDE; + virtual void OpenCursor(int64 transaction_id, + int64 object_store_id, + int64 index_id, + scoped_ptr<IndexedDBKeyRange> key_range, + indexed_db::CursorDirection, + bool key_only, + TaskType task_type, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) + OVERRIDE; + virtual void Count(int64 transaction_id, + int64 object_store_id, + int64 index_id, + scoped_ptr<IndexedDBKeyRange> key_range, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) + OVERRIDE; + virtual void DeleteRange(int64 transaction_id, + int64 object_store_id, + scoped_ptr<IndexedDBKeyRange> key_range, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) + OVERRIDE; + virtual void Clear(int64 transaction_id, + int64 object_store_id, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks) + OVERRIDE; + + private: + IndexedDBDatabaseImpl(const string16& name, + IndexedDBBackingStore* database, + IndexedDBFactoryImpl* factory, + const string16& unique_identifier); + virtual ~IndexedDBDatabaseImpl(); + + bool IsOpenConnectionBlocked() const; + bool OpenInternal(); + void RunVersionChangeTransaction( + scoped_refptr<IndexedDBCallbacksWrapper> callbacks, + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> database_callbacks, + int64 transaction_id, + int64 requested_version); + void RunVersionChangeTransactionFinal( + scoped_refptr<IndexedDBCallbacksWrapper> callbacks, + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> database_callbacks, + int64 transaction_id, + int64 requested_version); + size_t ConnectionCount() const; + void ProcessPendingCalls(); + + bool IsDeleteDatabaseBlocked() const; + void DeleteDatabaseFinal(scoped_refptr<IndexedDBCallbacksWrapper> callbacks); + + class VersionChangeOperation; + + // When a "versionchange" transaction aborts, these restore the back-end + // object hierarchy. + class VersionChangeAbortOperation; + + scoped_refptr<IndexedDBBackingStore> backing_store_; + IndexedDBDatabaseMetadata metadata_; + + string16 identifier_; + // This might not need to be a scoped_refptr since the factory's lifetime is + // that of the page group, but it's better to be conservitive than sorry. + scoped_refptr<IndexedDBFactoryImpl> factory_; + + IndexedDBTransactionCoordinator transaction_coordinator_; + IndexedDBTransaction* running_version_change_transaction_; + + typedef std::map<int64, IndexedDBTransaction*> TransactionMap; + TransactionMap transactions_; + + class PendingOpenCall; + typedef std::list<PendingOpenCall*> PendingOpenCallList; + PendingOpenCallList pending_open_calls_; + scoped_ptr<PendingOpenCall> pending_run_version_change_transaction_call_; + scoped_ptr<PendingOpenCall> pending_second_half_open_; + + class PendingDeleteCall; + typedef std::list<PendingDeleteCall*> PendingDeleteCallList; + PendingDeleteCallList pending_delete_calls_; + + typedef list_set<scoped_refptr<IndexedDBDatabaseCallbacksWrapper> > + DatabaseCallbacksSet; + DatabaseCallbacksSet database_callbacks_set_; + + bool closing_connection_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_DATABASE_IMPL_H_ diff --git a/content/browser/indexed_db/indexed_db_factory.h b/content/browser/indexed_db/indexed_db_factory.h new file mode 100644 index 0000000..b7d380a --- /dev/null +++ b/content/browser/indexed_db/indexed_db_factory.h @@ -0,0 +1,47 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_FACTORY_H_ +#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_FACTORY_H_ + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/string16.h" + +namespace content { + +class IndexedDBCallbacksWrapper; +class IndexedDBDatabase; +class IndexedDBDatabaseCallbacksWrapper; + +class IndexedDBFactory : public base::RefCounted<IndexedDBFactory> { + public: + static scoped_refptr<IndexedDBFactory> Create(); + + virtual void GetDatabaseNames( + scoped_refptr<IndexedDBCallbacksWrapper> callbacks, + const string16& database_identifier, + const string16& data_dir) = 0; + virtual void Open( + const string16& name, + int64 version, + int64 transaction_id, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks, + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> database_callbacks, + const string16& database_identifier, + const string16& data_dir) = 0; + virtual void DeleteDatabase( + const string16& name, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks, + const string16& database_identifier, + const string16& data_dir) = 0; + + protected: + virtual ~IndexedDBFactory() {} + friend class base::RefCounted<IndexedDBFactory>; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_FACTORY_H_ diff --git a/content/browser/indexed_db/indexed_db_factory_impl.cc b/content/browser/indexed_db/indexed_db_factory_impl.cc new file mode 100644 index 0000000..b8b6263 --- /dev/null +++ b/content/browser/indexed_db/indexed_db_factory_impl.cc @@ -0,0 +1,197 @@ +// 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_factory_impl.h" + +#include "base/logging.h" +#include "base/utf_string_conversions.h" +#include "content/browser/indexed_db/indexed_db_backing_store.h" +#include "content/browser/indexed_db/indexed_db_database_impl.h" +#include "content/browser/indexed_db/indexed_db_tracing.h" +#include "content/browser/indexed_db/indexed_db_transaction_coordinator.h" +#include "third_party/WebKit/public/platform/WebIDBDatabaseException.h" + +namespace content { + +template <typename K, typename M> +static void CleanWeakMap(std::map<K, base::WeakPtr<M> >& map) { + std::map<K, base::WeakPtr<M> > other; + other.swap(map); + + typename std::map<K, base::WeakPtr<M> >::const_iterator iter = other.begin(); + while (iter != other.end()) { + if (iter->second.get()) + map[iter->first] = iter->second; + ++iter; + } +} + +static string16 ComputeFileIdentifier(const string16& database_identifier) { + string16 suffix(ASCIIToUTF16("@1")); + string16 result(database_identifier); + result.insert(result.end(), suffix.begin(), suffix.end()); + return result; +} + +static string16 ComputeUniqueIdentifier(const string16& name, + const string16& database_identifier) { + return ComputeFileIdentifier(database_identifier) + name; +} + +IndexedDBFactoryImpl::IndexedDBFactoryImpl() {} + +IndexedDBFactoryImpl::~IndexedDBFactoryImpl() {} + +void IndexedDBFactoryImpl::RemoveIDBDatabaseBackend( + const string16& unique_identifier) { + DCHECK(database_backend_map_.find(unique_identifier) != + database_backend_map_.end()); + database_backend_map_.erase(unique_identifier); +} + +void IndexedDBFactoryImpl::GetDatabaseNames( + scoped_refptr<IndexedDBCallbacksWrapper> callbacks, + const string16& database_identifier, + const string16& data_directory) { + IDB_TRACE("IndexedDBFactoryImpl::get_database_names"); + scoped_refptr<IndexedDBBackingStore> backing_store = + OpenBackingStore(database_identifier, data_directory); + if (!backing_store) { + callbacks->OnError(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, + ASCIIToUTF16("Internal error opening backing store for " + "indexed_db.webkit_get_database_names."))); + return; + } + + callbacks->OnSuccess(backing_store->GetDatabaseNames()); +} + +void IndexedDBFactoryImpl::DeleteDatabase( + const string16& name, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks, + const string16& database_identifier, + const string16& data_directory) { + IDB_TRACE("IndexedDBFactoryImpl::delete_database"); + const string16 unique_identifier = + ComputeUniqueIdentifier(name, database_identifier); + + IndexedDBDatabaseMap::iterator it = + database_backend_map_.find(unique_identifier); + if (it != database_backend_map_.end()) { + // If there are any connections to the database, directly delete the + // database. + it->second->DeleteDatabase(callbacks); + return; + } + + // TODO(jsbell): Everything from now on should be done on another thread. + scoped_refptr<IndexedDBBackingStore> backing_store = + OpenBackingStore(database_identifier, data_directory); + if (!backing_store) { + callbacks->OnError(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, + ASCIIToUTF16("Internal error opening backing store " + "for indexed_db.delete_database."))); + return; + } + + scoped_refptr<IndexedDBDatabaseImpl> database_backend = + IndexedDBDatabaseImpl::Create( + name, backing_store.get(), this, unique_identifier); + if (!database_backend) { + callbacks->OnError(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, + ASCIIToUTF16("Internal error creating database backend for " + "indexed_db.delete_database."))); + return; + } + + database_backend_map_[unique_identifier] = database_backend.get(); + database_backend->DeleteDatabase(callbacks); + database_backend_map_.erase(unique_identifier); +} + +scoped_refptr<IndexedDBBackingStore> IndexedDBFactoryImpl::OpenBackingStore( + const string16& database_identifier, + const string16& data_directory) { + const string16 file_identifier = ComputeFileIdentifier(database_identifier); + const bool open_in_memory = data_directory.empty(); + + IndexedDBBackingStoreMap::iterator it2 = + backing_store_map_.find(file_identifier); + if (it2 != backing_store_map_.end() && it2->second.get()) + return it2->second.get(); + + scoped_refptr<IndexedDBBackingStore> backing_store; + if (open_in_memory) { + backing_store = IndexedDBBackingStore::OpenInMemory(file_identifier); + } else { + backing_store = IndexedDBBackingStore::Open( + database_identifier, data_directory, file_identifier); + } + + if (backing_store) { + CleanWeakMap(backing_store_map_); + backing_store_map_[file_identifier] = backing_store->GetWeakPtr(); + // If an in-memory database, bind lifetime to this factory instance. + if (open_in_memory) + session_only_backing_stores_.insert(backing_store); + + // All backing stores associated with this factory should be of the same + // type. + DCHECK(session_only_backing_stores_.empty() || open_in_memory); + + return backing_store; + } + + return 0; +} + +void IndexedDBFactoryImpl::Open( + const string16& name, + int64 version, + int64 transaction_id, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks, + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> database_callbacks, + const string16& database_identifier, + const string16& data_directory) { + IDB_TRACE("IndexedDBFactoryImpl::open"); + const string16 unique_identifier = + ComputeUniqueIdentifier(name, database_identifier); + + scoped_refptr<IndexedDBDatabaseImpl> database_backend; + IndexedDBDatabaseMap::iterator it = + database_backend_map_.find(unique_identifier); + if (it == database_backend_map_.end()) { + scoped_refptr<IndexedDBBackingStore> backing_store = + OpenBackingStore(database_identifier, data_directory); + if (!backing_store) { + callbacks->OnError(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, + ASCIIToUTF16( + "Internal error opening backing store for indexedDB.open."))); + return; + } + + database_backend = IndexedDBDatabaseImpl::Create( + name, backing_store.get(), this, unique_identifier); + if (!database_backend) { + callbacks->OnError(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, + ASCIIToUTF16( + "Internal error creating database backend for indexedDB.open."))); + return; + } + + database_backend_map_[unique_identifier] = database_backend.get(); + } else { + database_backend = it->second; + } + + database_backend->OpenConnection( + callbacks, database_callbacks, transaction_id, version); +} + +} // namespace content diff --git a/content/browser/indexed_db/indexed_db_factory_impl.h b/content/browser/indexed_db/indexed_db_factory_impl.h new file mode 100644 index 0000000..c049585 --- /dev/null +++ b/content/browser/indexed_db/indexed_db_factory_impl.h @@ -0,0 +1,77 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_FACTORY_IMPL_H_ +#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_FACTORY_IMPL_H_ + +#include <map> +#include <set> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "content/browser/indexed_db/indexed_db_callbacks_wrapper.h" +#include "content/browser/indexed_db/indexed_db_database_callbacks_wrapper.h" +#include "content/browser/indexed_db/indexed_db_factory.h" +#include "content/common/content_export.h" + +namespace content { + +class IndexedDBBackingStore; +class IndexedDBDatabaseImpl; + +class CONTENT_EXPORT IndexedDBFactoryImpl + : NON_EXPORTED_BASE(public IndexedDBFactory) { + public: + static scoped_refptr<IndexedDBFactoryImpl> Create() { + return make_scoped_refptr(new IndexedDBFactoryImpl()); + } + + // Notifications from weak pointers. + virtual void RemoveIDBDatabaseBackend(const string16& unique_identifier); + + virtual void GetDatabaseNames( + scoped_refptr<IndexedDBCallbacksWrapper> callbacks, + const string16& database_identifier, + const string16& data_dir) OVERRIDE; + virtual void Open( + const string16& name, + int64 version, + int64 transaction_id, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks, + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> database_callbacks, + const string16& database_identifier, + const string16& data_dir) OVERRIDE; + + virtual void DeleteDatabase( + const string16& name, + scoped_refptr<IndexedDBCallbacksWrapper> callbacks, + const string16& database_identifier, + const string16& data_dir) OVERRIDE; + + protected: + IndexedDBFactoryImpl(); + virtual ~IndexedDBFactoryImpl(); + virtual scoped_refptr<IndexedDBBackingStore> OpenBackingStore( + const string16& database_identifier, + const string16& data_dir); + + private: + typedef std::map<string16, scoped_refptr<IndexedDBDatabaseImpl> > + IndexedDBDatabaseMap; + IndexedDBDatabaseMap database_backend_map_; + + typedef std::map<string16, base::WeakPtr<IndexedDBBackingStore> > + IndexedDBBackingStoreMap; + IndexedDBBackingStoreMap backing_store_map_; + + std::set<scoped_refptr<IndexedDBBackingStore> > session_only_backing_stores_; + + // Only one instance of the factory should exist at any given time. + static IndexedDBFactoryImpl* idb_factory_backend_impl; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_FACTORY_IMPL_H_ diff --git a/content/browser/indexed_db/indexed_db_index_writer.cc b/content/browser/indexed_db/indexed_db_index_writer.cc new file mode 100644 index 0000000..6104111 --- /dev/null +++ b/content/browser/indexed_db/indexed_db_index_writer.cc @@ -0,0 +1,169 @@ +// 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_index_writer.h" + +#include "base/logging.h" +#include "base/utf_string_conversions.h" +#include "content/browser/indexed_db/indexed_db_backing_store.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.h" +#include "content/common/indexed_db/indexed_db_key_path.h" +#include "content/common/indexed_db/indexed_db_key_range.h" + +namespace content { + +IndexedDBObjectStoreImpl::IndexWriter::IndexWriter( + const IndexedDBIndexMetadata& index_metadata) + : index_metadata_(index_metadata) {} + +IndexedDBObjectStoreImpl::IndexWriter::IndexWriter( + const IndexedDBIndexMetadata& index_metadata, + const IndexedDBDatabase::IndexKeys& index_keys) + : index_metadata_(index_metadata), index_keys_(index_keys) {} + +IndexedDBObjectStoreImpl::IndexWriter::~IndexWriter() {} + +bool IndexedDBObjectStoreImpl::IndexWriter::VerifyIndexKeys( + IndexedDBBackingStore* backing_store, + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + int64 index_id, + bool* can_add_keys, + const IndexedDBKey& primary_key, + string16* error_message) const { + *can_add_keys = false; + for (size_t i = 0; i < index_keys_.size(); ++i) { + bool ok = AddingKeyAllowed(backing_store, + transaction, + database_id, + object_store_id, + index_id, + (index_keys_)[i], + primary_key, + can_add_keys); + if (!ok) + return false; + if (!*can_add_keys) { + if (error_message) + *error_message = ASCIIToUTF16("Unable to add key to index '") + + index_metadata_.name + + ASCIIToUTF16("': at least one key does not satisfy " + "the uniqueness requirements."); + return true; + } + } + *can_add_keys = true; + return true; +} + +void IndexedDBObjectStoreImpl::IndexWriter::WriteIndexKeys( + const IndexedDBBackingStore::RecordIdentifier& record_identifier, + IndexedDBBackingStore* backing_store, + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id) const { + int64 index_id = index_metadata_.id; + for (size_t i = 0; i < index_keys_.size(); ++i) { + bool ok = backing_store->PutIndexDataForRecord(transaction, + database_id, + object_store_id, + index_id, + index_keys_[i], + record_identifier); + // This should have already been verified as a valid write during + // verify_index_keys. + DCHECK(ok); + } +} + +bool IndexedDBObjectStoreImpl::IndexWriter::AddingKeyAllowed( + IndexedDBBackingStore* backing_store, + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + int64 index_id, + const IndexedDBKey& index_key, + const IndexedDBKey& primary_key, + bool* allowed) const { + *allowed = false; + if (!index_metadata_.unique) { + *allowed = true; + return true; + } + + scoped_ptr<IndexedDBKey> found_primary_key; + bool found = false; + bool ok = backing_store->KeyExistsInIndex(transaction, + database_id, + object_store_id, + index_id, + index_key, + &found_primary_key, + found); + if (!ok) + return false; + if (!found || + (primary_key.IsValid() && found_primary_key->IsEqual(primary_key))) + *allowed = true; + return true; +} + +bool IndexedDBObjectStoreImpl::MakeIndexWriters( + scoped_refptr<IndexedDBTransaction> transaction, + IndexedDBBackingStore* backing_store, + int64 database_id, + const IndexedDBObjectStoreMetadata& object_store, + const IndexedDBKey& primary_key, // makes a copy + bool key_was_generated, + const std::vector<int64>& index_ids, + const std::vector<IndexedDBDatabase::IndexKeys>& index_keys, + ScopedVector<IndexWriter>* index_writers, + string16* error_message, + bool* completed) { + DCHECK_EQ(index_ids.size(), index_keys.size()); + *completed = false; + + std::map<int64, IndexedDBDatabase::IndexKeys> index_key_map; + for (size_t i = 0; i < index_ids.size(); ++i) + index_key_map[index_ids[i]] = index_keys[i]; + + for (IndexedDBObjectStoreMetadata::IndexMap::const_iterator it = + object_store.indexes.begin(); + it != object_store.indexes.end(); + ++it) { + const IndexedDBIndexMetadata& index = it->second; + + IndexedDBDatabase::IndexKeys keys = index_key_map[it->first]; + // If the object_store is using auto_increment, then any indexes with an + // identical key_path need to also use the primary (generated) key as a key. + if (key_was_generated && (index.key_path == object_store.key_path)) + keys.push_back(primary_key); + + scoped_ptr<IndexWriter> index_writer(new IndexWriter(index, keys)); + bool can_add_keys = false; + bool backing_store_success = + index_writer->VerifyIndexKeys(backing_store, + transaction->BackingStoreTransaction(), + database_id, + object_store.id, + index.id, + &can_add_keys, + primary_key, + error_message); + if (!backing_store_success) + return false; + if (!can_add_keys) + return true; + + index_writers->push_back(index_writer.release()); + } + + *completed = true; + return true; +} + +} // namespace content diff --git a/content/browser/indexed_db/indexed_db_index_writer.h b/content/browser/indexed_db/indexed_db_index_writer.h new file mode 100644 index 0000000..372e6b5 --- /dev/null +++ b/content/browser/indexed_db/indexed_db_index_writer.h @@ -0,0 +1,82 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_INDEX_WRITER_H_ +#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_INDEX_WRITER_H_ + +#include <map> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/scoped_vector.h" +#include "content/browser/indexed_db/indexed_db_backing_store.h" +#include "content/browser/indexed_db/indexed_db_database_impl.h" +#include "content/browser/indexed_db/indexed_db_metadata.h" +#include "content/common/indexed_db/indexed_db_key_path.h" + +namespace content { + +class IndexedDBDatabaseImpl; +class IndexedDBTransaction; +struct IndexedDBObjectStoreMetadata; + +// TODO(alecflett): this namespace is temporary until we move its contents out +// to their own home. +namespace IndexedDBObjectStoreImpl { + +class IndexWriter { + public: + explicit IndexWriter(const IndexedDBIndexMetadata& index_metadata); + + IndexWriter(const IndexedDBIndexMetadata& index_metadata, + const IndexedDBDatabase::IndexKeys& index_keys); + + bool VerifyIndexKeys(IndexedDBBackingStore* store, + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + int64 index_id, + bool* can_add_keys, + const IndexedDBKey& primary_key, + string16* error_message = 0) const WARN_UNUSED_RESULT; + + void WriteIndexKeys(const IndexedDBBackingStore::RecordIdentifier& record, + IndexedDBBackingStore* store, + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id) const; + + ~IndexWriter(); + + private: + bool AddingKeyAllowed(IndexedDBBackingStore* store, + IndexedDBBackingStore::Transaction* transaction, + int64 database_id, + int64 object_store_id, + int64 index_id, + const IndexedDBKey& index_key, + const IndexedDBKey& primary_key, + bool* allowed) const WARN_UNUSED_RESULT; + + const IndexedDBIndexMetadata index_metadata_; + IndexedDBDatabase::IndexKeys index_keys_; +}; + +bool MakeIndexWriters( + scoped_refptr<IndexedDBTransaction> transaction, + IndexedDBBackingStore* store, + int64 database_id, + const IndexedDBObjectStoreMetadata& metadata, + const IndexedDBKey& primary_key, + bool key_was_generated, + const std::vector<int64>& index_ids, + const std::vector<IndexedDBDatabase::IndexKeys>& index_keys, + ScopedVector<IndexWriter>* index_writers, + string16* error_message, + bool* completed) WARN_UNUSED_RESULT; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_INDEX_WRITER_H_ diff --git a/content/browser/indexed_db/indexed_db_internals_ui.cc b/content/browser/indexed_db/indexed_db_internals_ui.cc index 7203768..0f7bc3e 100644 --- a/content/browser/indexed_db/indexed_db_internals_ui.cc +++ b/content/browser/indexed_db/indexed_db_internals_ui.cc @@ -301,8 +301,8 @@ void IndexedDBInternalsUI::OnDownloadStarted( net::Error error) { if (error != net::OK) { - LOG(ERROR) << "Error downloading database dump: " - << net::ErrorToString(error); + LOG(ERROR) + << "Error downloading database dump: " << net::ErrorToString(error); return; } diff --git a/content/browser/indexed_db/indexed_db_internals_ui.h b/content/browser/indexed_db/indexed_db_internals_ui.h index bc9a5da..1412ec0 100644 --- a/content/browser/indexed_db/indexed_db_internals_ui.h +++ b/content/browser/indexed_db/indexed_db_internals_ui.h @@ -13,7 +13,9 @@ #include "content/public/browser/web_ui_controller.h" #include "net/base/net_errors.h" -namespace base { class ListValue; } +namespace base { +class ListValue; +} namespace content { diff --git a/content/browser/indexed_db/indexed_db_leveldb_coding.cc b/content/browser/indexed_db/indexed_db_leveldb_coding.cc new file mode 100644 index 0000000..590fe93 --- /dev/null +++ b/content/browser/indexed_db/indexed_db_leveldb_coding.cc @@ -0,0 +1,1884 @@ +// 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_leveldb_coding.h" + +#include <iterator> +#include <limits> +#include <string> + +#include "base/logging.h" +#include "base/string16.h" +#include "base/sys_byteorder.h" +#include "content/browser/indexed_db/leveldb/leveldb_slice.h" +#include "content/common/indexed_db/indexed_db_key.h" +#include "content/common/indexed_db/indexed_db_key_path.h" +#include "third_party/WebKit/public/platform/WebIDBKeyPath.h" + +// LevelDB stores key/value pairs. Keys and values are strings of bytes, +// normally of type std::vector<char>. +// +// The keys in the backing store are variable-length tuples with different types +// of fields. Each key in the backing store starts with a ternary prefix: +// (database id, object store id, index id). For each, 0 is reserved for +// meta-data. +// The prefix makes sure that data for a specific database, object store, and +// index are grouped together. The locality is important for performance: common +// operations should only need a minimal number of seek operations. For example, +// all the meta-data for a database is grouped together so that reading that +// meta-data only requires one seek. +// +// Each key type has a class (in square brackets below) which knows how to +// encode, decode, and compare that key type. +// +// Global meta-data have keys with prefix (0,0,0), followed by a type byte: +// +// <0, 0, 0, 0> => +// IndexedDB/LevelDB schema version [SchemaVersionKey] +// <0, 0, 0, 1> => The maximum +// database id ever allocated [MaxDatabaseIdKey] +// <0, 0, 0, 2> => +// SerializedScriptValue version [DataVersionKey] +// <0, 0, 0, 100, database id> => Existence +// implies the database id is in the free list [DatabaseFreeListKey] +// <0, 0, 0, 201, utf16 origin name, utf16 database name> => Database id +// [DatabaseNameKey] +// +// +// Database meta-data: +// +// Again, the prefix is followed by a type byte. +// +// <database id, 0, 0, 0> => utf16 origin name [DatabaseMetaDataKey] +// <database id, 0, 0, 1> => utf16 database name [DatabaseMetaDataKey] +// <database id, 0, 0, 2> => utf16 user version data [DatabaseMetaDataKey] +// <database id, 0, 0, 3> => maximum object store id ever allocated +// [DatabaseMetaDataKey] +// <database id, 0, 0, 4> => user integer version (var int) +// [DatabaseMetaDataKey] +// +// +// Object store meta-data: +// +// The prefix is followed by a type byte, then a variable-length integer, +// and then another type byte. +// +// <database id, 0, 0, 50, object store id, 0> => utf16 object store name +// [ObjectStoreMetaDataKey] +// <database id, 0, 0, 50, object store id, 1> => utf16 key path +// [ObjectStoreMetaDataKey] +// <database id, 0, 0, 50, object store id, 2> => has auto increment +// [ObjectStoreMetaDataKey] +// <database id, 0, 0, 50, object store id, 3> => is evictable +// [ObjectStoreMetaDataKey] +// <database id, 0, 0, 50, object store id, 4> => last "version" number +// [ObjectStoreMetaDataKey] +// <database id, 0, 0, 50, object store id, 5> => maximum index id ever +// allocated [ObjectStoreMetaDataKey] +// <database id, 0, 0, 50, object store id, 6> => has key path (vs. null) +// [ObjectStoreMetaDataKey] +// <database id, 0, 0, 50, object store id, 7> => key generator current +// number [ObjectStoreMetaDataKey] +// +// +// Index meta-data: +// +// The prefix is followed by a type byte, then two variable-length integers, +// and then another type byte. +// +// <database id, 0, 0, 100, object store id, index id, 0> => utf16 index +// name [IndexMetaDataKey] +// <database id, 0, 0, 100, object store id, index id, 1> => are index keys +// unique [IndexMetaDataKey] +// <database id, 0, 0, 100, object store id, index id, 2> => utf16 key path +// [IndexMetaDataKey] +// <database id, 0, 0, 100, object store id, index id, 3> => is index +// multi-entry [IndexMetaDataKey] +// +// +// Other object store and index meta-data: +// +// The prefix is followed by a type byte. The object store and index id are +// variable length integers, the utf16 strings are variable length strings. +// +// <database id, 0, 0, 150, object store id> => existence +// implies the object store id is in the free list [ObjectStoreFreeListKey] +// <database id, 0, 0, 151, object store id, index id> => existence +// implies the index id is in the free list [IndexFreeListKey] +// <database id, 0, 0, 200, utf16 object store name> => object +// store id [ObjectStoreNamesKey] +// <database id, 0, 0, 201, object store id, utf16 index name> => index id +// [IndexNamesKey] +// +// +// Object store data: +// +// The prefix is followed by a type byte. The user key is an encoded +// IndexedDBKey. +// +// <database id, object store id, 1, user key> => "version", serialized +// script value [ObjectStoreDataKey] +// +// +// "Exists" entry: +// +// The prefix is followed by a type byte. The user key is an encoded +// IndexedDBKey. +// +// <database id, object store id, 2, user key> => "version" [ExistsEntryKey] +// +// +// Index data: +// +// The prefix is followed by a type byte. The index key is an encoded +// IndexedDBKey. The sequence number is a variable length integer. +// The primary key is an encoded IndexedDBKey. +// +// <database id, object store id, index id, index key, sequence number, +// primary key> => "version", primary key [IndexDataKey] +// +// (The sequence number is obsolete; it was used to allow two entries with +// the same user (index) key in non-unique indexes prior to the inclusion of +// the primary key in the data. The "version" field is used to weed out +// stale +// index data. Whenever new object store data is inserted, it gets a new +// "version" number, and new index data is written with this number. When +// the index is used for look-ups, entries are validated against the +// "exists" entries, and records with old "version" numbers are deleted +// when they are encountered in get_primary_key_via_index, +// IndexCursorImpl::load_current_row, and +// IndexKeyCursorImpl::load_current_row). + +using WebKit::WebIDBKey; +using WebKit::WebIDBKeyPath; + +namespace content { + +// As most of the IndexedDBKeys and encoded values are short, we +// initialize some Vectors with a default inline buffer size to reduce +// the memory re-allocations when the Vectors are appended. +static const size_t kDefaultInlineBufferSize = 32; + +static const unsigned char kIndexedDBKeyNullTypeByte = 0; +static const unsigned char kIndexedDBKeyStringTypeByte = 1; +static const unsigned char kIndexedDBKeyDateTypeByte = 2; +static const unsigned char kIndexedDBKeyNumberTypeByte = 3; +static const unsigned char kIndexedDBKeyArrayTypeByte = 4; +static const unsigned char kIndexedDBKeyMinKeyTypeByte = 5; + +static const unsigned char kIndexedDBKeyPathTypeCodedByte1 = 0; +static const unsigned char kIndexedDBKeyPathTypeCodedByte2 = 0; + +static const unsigned char kObjectStoreDataIndexId = 1; +static const unsigned char kExistsEntryIndexId = 2; + +static const unsigned char kSchemaVersionTypeByte = 0; +static const unsigned char kMaxDatabaseIdTypeByte = 1; +static const unsigned char kDataVersionTypeByte = 2; +static const unsigned char kMaxSimpleGlobalMetaDataTypeByte = + 3; // Insert before this and increment. +static const unsigned char kDatabaseFreeListTypeByte = 100; +static const unsigned char kDatabaseNameTypeByte = 201; + +static const unsigned char kObjectStoreMetaDataTypeByte = 50; +static const unsigned char kIndexMetaDataTypeByte = 100; +static const unsigned char kObjectStoreFreeListTypeByte = 150; +static const unsigned char kIndexFreeListTypeByte = 151; +static const unsigned char kObjectStoreNamesTypeByte = 200; +static const unsigned char kIndexNamesKeyTypeByte = 201; + +static const unsigned char kObjectMetaDataTypeMaximum = 255; +static const unsigned char kIndexMetaDataTypeMaximum = 255; + +const unsigned char kMinimumIndexId = 30; + +std::vector<char> EncodeByte(unsigned char c) { + std::vector<char> v; + v.reserve(kDefaultInlineBufferSize); + v.push_back(c); + + DCHECK_LE(v.size(), kDefaultInlineBufferSize); + return v; +} + +const char* DecodeByte(const char* p, + const char* limit, + unsigned char& found_char) { + DCHECK_GE(limit, p); + if (p >= limit) + return 0; + + found_char = *p++; + return p; +} + +std::vector<char> MaxIDBKey() { return EncodeByte(kIndexedDBKeyNullTypeByte); } + +std::vector<char> MinIDBKey() { + return EncodeByte(kIndexedDBKeyMinKeyTypeByte); +} + +std::vector<char> EncodeBool(bool b) { + std::vector<char> ret; + ret.reserve(kDefaultInlineBufferSize); + ret.push_back(b ? 1 : 0); + + DCHECK_LE(ret.size(), kDefaultInlineBufferSize); + return ret; +} + +bool DecodeBool(const char* p, const char* limit) { + DCHECK_GT(limit, p); + return !!*p; +} + +std::vector<char> EncodeInt(int64 nParam) { +#ifndef NDEBUG + // Exercised by unit tests in debug only. + DCHECK_GE(nParam, 0); +#endif + uint64 n = static_cast<uint64>(nParam); + std::vector<char> ret; + ret.reserve(kDefaultInlineBufferSize); + + do { + unsigned char c = n; + ret.push_back(c); + n >>= 8; + } while (n); + + DCHECK_LE(ret.size(), kDefaultInlineBufferSize); + return ret; +} + +static int CompareInts(int64 a, int64 b) { +#ifndef NDEBUG + // Exercised by unit tests in debug only. + DCHECK_GE(a, 0); + DCHECK_GE(b, 0); +#endif + int64 diff = a - b; + if (diff < 0) + return -1; + if (diff > 0) + return 1; + return 0; +} + +std::vector<char> EncodeVarInt(int64 nParam) { +#ifndef NDEBUG + // Exercised by unit tests in debug only. + DCHECK_GE(nParam, 0); +#endif + uint64 n = static_cast<uint64>(nParam); + std::vector<char> ret; + ret.reserve(kDefaultInlineBufferSize); + + do { + unsigned char c = n & 0x7f; + n >>= 7; + if (n) + c |= 0x80; + ret.push_back(c); + } while (n); + + DCHECK_LE(ret.size(), kDefaultInlineBufferSize); + return ret; +} + +const char* DecodeVarInt(const char* p, const char* limit, int64& found_int) { + DCHECK_GE(limit, p); + found_int = 0; + int shift = 0; + + do { + if (p >= limit) + return 0; + + unsigned char c = *p; + found_int |= static_cast<int64>(c & 0x7f) << shift; + shift += 7; + } while (*p++ & 0x80); + return p; +} + +std::vector<char> EncodeString(const string16& s) { + // Backing store is UTF-16BE, convert from host endianness. + size_t length = s.length(); + std::vector<char> ret(length * sizeof(char16)); + if (!length) + return ret; + + const char16* src = s.c_str(); + char16* dst = reinterpret_cast<char16*>(&*ret.begin()); + for (unsigned i = 0; i < length; ++i) + *dst++ = htons(*src++); + + return ret; +} + +string16 DecodeString(const char* p, const char* limit) { + // Backing store is UTF-16BE, convert to host endianness. + DCHECK_GE(limit, p); + DCHECK(!((limit - p) % sizeof(char16))); + + size_t length = (limit - p) / sizeof(char16); + string16 decoded; + decoded.reserve(length); + const char16* encoded = reinterpret_cast<const char16*>(p); + for (unsigned i = 0; i < length; ++i) + decoded.push_back(ntohs(*encoded++)); + return decoded; +} + +std::vector<char> EncodeStringWithLength(const string16& s) { + std::vector<char> result = EncodeVarInt(s.length()); + std::vector<char> encoded_value = EncodeString(s); + result.insert(result.end(), encoded_value.begin(), encoded_value.end()); + return result; +} + +const char* DecodeStringWithLength(const char* p, + const char* limit, + string16& found_string) { + DCHECK_GE(limit, p); + int64 len; + p = DecodeVarInt(p, limit, len); + if (!p || len < 0 || p + len * 2 > limit) + return 0; + + found_string = DecodeString(p, p + len * 2); + p += len * 2; + return p; +} + +int CompareEncodedStringsWithLength(const char*& p, + const char* limit_p, + const char*& q, + const char* limit_q, + bool& ok) { + DCHECK_NE(&p, &q); + DCHECK_GT(limit_p, p); + DCHECK_GT(limit_q, q); + int64 len_p, len_q; + p = DecodeVarInt(p, limit_p, len_p); + q = DecodeVarInt(q, limit_q, len_q); + if (!p || !q || len_p < 0 || len_q < 0) { + ok = false; + return 0; + } + DCHECK(p && q); + DCHECK_GE(len_p, 0); + DCHECK_GE(len_q, 0); + DCHECK_LE(p + len_p * 2, limit_p); + DCHECK_LE(q + len_q * 2, limit_q); + + const char* start_p = p; + const char* start_q = q; + p += len_p * 2; + q += len_q * 2; + + if (p > limit_p || q > limit_q) { + ok = false; + return 0; + } + + ok = true; + const size_t lmin = static_cast<size_t>(len_p < len_q ? len_p : len_q); + if (int x = memcmp(start_p, start_q, lmin * 2)) + return x; + + if (len_p == len_q) + return 0; + + return (len_p > len_q) ? 1 : -1; +} + +std::vector<char> EncodeDouble(double x) { + // TODO(jsbell): It would be nice if we could be byte order independent. + const char* p = reinterpret_cast<char*>(&x); + std::vector<char> v; + v.reserve(kDefaultInlineBufferSize); + v.insert(v.end(), p, p + sizeof(x)); + + DCHECK_LE(v.size(), kDefaultInlineBufferSize); + return v; +} + +const char* DecodeDouble(const char* p, const char* limit, double* d) { + if (p + sizeof(*d) > limit) + return 0; + + char* x = reinterpret_cast<char*>(d); + for (size_t i = 0; i < sizeof(*d); ++i) + *x++ = *p++; + return p; +} + +std::vector<char> EncodeIDBKey(const IndexedDBKey& key) { + std::vector<char> ret; + ret.reserve(kDefaultInlineBufferSize); + EncodeIDBKey(key, ret); + return ret; +} + +void EncodeIDBKey(const IndexedDBKey& key, std::vector<char>& into) { + size_t previous_size = into.size(); + DCHECK(key.IsValid()); + switch (key.type()) { + case WebIDBKey::NullType: + case WebIDBKey::InvalidType: + case WebIDBKey::MinType: { + NOTREACHED(); + into.push_back(kIndexedDBKeyNullTypeByte); + return; + } + case WebIDBKey::ArrayType: { + into.push_back(kIndexedDBKeyArrayTypeByte); + size_t length = key.array().size(); + std::vector<char> encoded_length = EncodeVarInt(length); + into.insert(into.end(), encoded_length.begin(), encoded_length.end()); + for (size_t i = 0; i < length; ++i) + EncodeIDBKey(key.array()[i], into); + DCHECK_GT(into.size(), previous_size); + return; + } + case WebIDBKey::StringType: { + into.push_back(kIndexedDBKeyStringTypeByte); + std::vector<char> tmp = EncodeStringWithLength(key.string()); + into.insert(into.end(), tmp.begin(), tmp.end()); + DCHECK_GT(into.size(), previous_size); + return; + } + case WebIDBKey::DateType: { + into.push_back(kIndexedDBKeyDateTypeByte); + std::vector<char> tmp = EncodeDouble(key.date()); + into.insert(into.end(), tmp.begin(), tmp.end()); + DCHECK_EQ(static_cast<size_t>(9), + static_cast<size_t>(into.size() - previous_size)); + return; + } + case WebIDBKey::NumberType: { + into.push_back(kIndexedDBKeyNumberTypeByte); + std::vector<char> tmp = EncodeDouble(key.number()); + into.insert(into.end(), tmp.begin(), tmp.end()); + DCHECK_EQ(static_cast<size_t>(9), + static_cast<size_t>(into.size() - previous_size)); + return; + } + } + + NOTREACHED(); +} + +const char* DecodeIDBKey(const char* p, + const char* limit, + scoped_ptr<IndexedDBKey>* found_key) { + DCHECK_GE(limit, p); + if (p >= limit) + return 0; + + unsigned char type = *p++; + + switch (type) { + case kIndexedDBKeyNullTypeByte: + *found_key = make_scoped_ptr(new IndexedDBKey()); + return p; + + case kIndexedDBKeyArrayTypeByte: { + int64 length; + p = DecodeVarInt(p, limit, length); + if (!p || length < 0) + return 0; + IndexedDBKey::KeyArray array; + while (length--) { + scoped_ptr<IndexedDBKey> key; + p = DecodeIDBKey(p, limit, &key); + if (!p) + return 0; + array.push_back(*key); + } + *found_key = make_scoped_ptr(new IndexedDBKey(array)); + return p; + } + case kIndexedDBKeyStringTypeByte: { + string16 s; + p = DecodeStringWithLength(p, limit, s); + if (!p) + return 0; + *found_key = make_scoped_ptr(new IndexedDBKey(s)); + return p; + } + case kIndexedDBKeyDateTypeByte: { + double d; + p = DecodeDouble(p, limit, &d); + if (!p) + return 0; + *found_key = make_scoped_ptr(new IndexedDBKey(d, WebIDBKey::DateType)); + return p; + } + case kIndexedDBKeyNumberTypeByte: { + double d; + p = DecodeDouble(p, limit, &d); + if (!p) + return 0; + *found_key = make_scoped_ptr(new IndexedDBKey(d, WebIDBKey::NumberType)); + return p; + } + } + + NOTREACHED(); + return 0; +} + +const char* ExtractEncodedIDBKey(const char* start, + const char* limit, + std::vector<char>* result = 0) { + DCHECK_GT(limit, start); + const char* p = start; + if (p >= limit) + return 0; + + unsigned char type = *p++; + + switch (type) { + case kIndexedDBKeyNullTypeByte: + case kIndexedDBKeyMinKeyTypeByte: + break; + case kIndexedDBKeyArrayTypeByte: { + int64 length; + p = DecodeVarInt(p, limit, length); + if (!p || length < 0) + return 0; + while (length--) { + p = ExtractEncodedIDBKey(p, limit); + if (!p) + return 0; + } + break; + } + case kIndexedDBKeyStringTypeByte: { + int64 length; + p = DecodeVarInt(p, limit, length); + if (!p || length < 0 || p + length * 2 > limit) + return 0; + p += length * 2; + break; + } + case kIndexedDBKeyDateTypeByte: + case kIndexedDBKeyNumberTypeByte: + if (p + sizeof(double) > limit) + return 0; + p += sizeof(double); + break; + } + + if (result) { + DCHECK(p); + DCHECK_LE(p, limit); + result->assign(start, p); + } + + return p; +} + +static WebIDBKey::Type KeyTypeByteToKeyType(unsigned char type) { + switch (type) { + case kIndexedDBKeyNullTypeByte: + return WebIDBKey::InvalidType; + case kIndexedDBKeyArrayTypeByte: + return WebIDBKey::ArrayType; + case kIndexedDBKeyStringTypeByte: + return WebIDBKey::StringType; + case kIndexedDBKeyDateTypeByte: + return WebIDBKey::DateType; + case kIndexedDBKeyNumberTypeByte: + return WebIDBKey::NumberType; + case kIndexedDBKeyMinKeyTypeByte: + return WebIDBKey::MinType; + } + + NOTREACHED(); + return WebIDBKey::InvalidType; +} + +static int CompareTypes(WebIDBKey::Type a, WebIDBKey::Type b) { return b - a; } + +int CompareEncodedIDBKeys(const char*& ptr_a, + const char* limit_a, + const char*& ptr_b, + const char* limit_b, + bool& ok) { + ok = true; + DCHECK_NE(&ptr_a, &ptr_b); + DCHECK_LT(ptr_a, limit_a); + DCHECK_LT(ptr_b, limit_b); + unsigned char type_a = *ptr_a++; + unsigned char type_b = *ptr_b++; + + if (int x = CompareTypes(KeyTypeByteToKeyType(type_a), + KeyTypeByteToKeyType(type_b))) + return x; + + switch (type_a) { + case kIndexedDBKeyNullTypeByte: + case kIndexedDBKeyMinKeyTypeByte: + // Null type or max type; no payload to compare. + return 0; + case kIndexedDBKeyArrayTypeByte: { + int64 length_a, length_b; + ptr_a = DecodeVarInt(ptr_a, limit_a, length_a); + ptr_b = DecodeVarInt(ptr_b, limit_b, length_b); + if (!ptr_a || !ptr_b || length_a < 0 || length_b < 0) { + ok = false; + return 0; + } + for (int64 i = 0; i < length_a && i < length_b; ++i) { + int result = CompareEncodedIDBKeys(ptr_a, limit_a, ptr_b, limit_b, ok); + if (!ok || result) + return result; + } + if (length_a < length_b) + return -1; + if (length_a > length_b) + return 1; + return 0; + } + case kIndexedDBKeyStringTypeByte: + return CompareEncodedStringsWithLength( + ptr_a, limit_a, ptr_b, limit_b, ok); + case kIndexedDBKeyDateTypeByte: + case kIndexedDBKeyNumberTypeByte: { + double d, e; + ptr_a = DecodeDouble(ptr_a, limit_a, &d); + ptr_b = DecodeDouble(ptr_b, limit_b, &e); + DCHECK(ptr_a); + DCHECK(ptr_b); + if (!ptr_a || !ptr_b) { + ok = false; + return 0; + } + if (d < e) + return -1; + if (d > e) + return 1; + return 0; + } + } + + NOTREACHED(); + return 0; +} + +int CompareEncodedIDBKeys(const std::vector<char>& key_a, + const std::vector<char>& key_b, + bool& ok) { + DCHECK_GE(key_a.size(), static_cast<size_t>(1)); + DCHECK_GE(key_b.size(), static_cast<size_t>(1)); + + const char* ptr_a = &*key_a.begin(); + const char* limit_a = &*key_a.rbegin() + 1; + const char* ptr_b = &*key_b.begin(); + const char* limit_b = &*key_b.rbegin() + 1; + + return CompareEncodedIDBKeys(ptr_a, limit_a, ptr_b, limit_b, ok); +} + +std::vector<char> EncodeIDBKeyPath(const IndexedDBKeyPath& key_path) { + // May be typed, or may be a raw string. An invalid leading + // byte is used to identify typed coding. New records are + // always written as typed. + std::vector<char> ret; + ret.reserve(kDefaultInlineBufferSize); + ret.push_back(kIndexedDBKeyPathTypeCodedByte1); + ret.push_back(kIndexedDBKeyPathTypeCodedByte2); + ret.push_back(static_cast<char>(key_path.type())); + switch (key_path.type()) { + case WebIDBKeyPath::NullType: + break; + case WebIDBKeyPath::StringType: { + std::vector<char> encoded_string = + EncodeStringWithLength(key_path.string()); + ret.insert(ret.end(), encoded_string.begin(), encoded_string.end()); + break; + } + case WebIDBKeyPath::ArrayType: { + const std::vector<string16>& array = key_path.array(); + size_t count = array.size(); + std::vector<char> encoded_count = EncodeVarInt(count); + ret.insert(ret.end(), encoded_count.begin(), encoded_count.end()); + for (size_t i = 0; i < count; ++i) { + std::vector<char> encoded_string = EncodeStringWithLength(array[i]); + ret.insert(ret.end(), encoded_string.begin(), encoded_string.end()); + } + break; + } + } + return ret; +} + +IndexedDBKeyPath DecodeIDBKeyPath(const char* p, const char* limit) { + // May be typed, or may be a raw string. An invalid leading + // byte sequence is used to identify typed coding. New records are + // always written as typed. + if (p == limit || + (limit - p >= 2 && (*p != kIndexedDBKeyPathTypeCodedByte1 || + *(p + 1) != kIndexedDBKeyPathTypeCodedByte2))) + return IndexedDBKeyPath(DecodeString(p, limit)); + p += 2; + + DCHECK_NE(p, limit); + WebIDBKeyPath::Type type = static_cast<WebIDBKeyPath::Type>(*p++); + switch (type) { + case WebIDBKeyPath::NullType: + DCHECK_EQ(p, limit); + return IndexedDBKeyPath(); + case WebIDBKeyPath::StringType: { + string16 string; + p = DecodeStringWithLength(p, limit, string); + DCHECK_EQ(p, limit); + return IndexedDBKeyPath(string); + } + case WebIDBKeyPath::ArrayType: { + std::vector<string16> array; + int64 count; + p = DecodeVarInt(p, limit, count); + DCHECK(p); + DCHECK_GE(count, 0); + while (count--) { + string16 string; + p = DecodeStringWithLength(p, limit, string); + DCHECK(p); + array.push_back(string); + } + DCHECK_EQ(p, limit); + return IndexedDBKeyPath(array); + } + } + NOTREACHED(); + return IndexedDBKeyPath(); +} + +namespace { + +template <typename KeyType> +int Compare(const LevelDBSlice& a, const LevelDBSlice& b, bool, bool& ok) { + KeyType key_a; + KeyType key_b; + + const char* ptr_a = KeyType::Decode(a.begin(), a.end(), &key_a); + DCHECK(ptr_a); + if (!ptr_a) { + ok = false; + return 0; + } + const char* ptr_b = KeyType::Decode(b.begin(), b.end(), &key_b); + DCHECK(ptr_b); + if (!ptr_b) { + ok = false; + return 0; + } + + ok = true; + return key_a.Compare(key_b); +} + +template <> +int Compare<ExistsEntryKey>(const LevelDBSlice& a, + const LevelDBSlice& b, + bool, + bool& ok) { + KeyPrefix prefix_a; + KeyPrefix prefix_b; + const char* ptr_a = KeyPrefix::Decode(a.begin(), a.end(), &prefix_a); + const char* ptr_b = KeyPrefix::Decode(b.begin(), b.end(), &prefix_b); + DCHECK(ptr_a); + DCHECK(ptr_b); + DCHECK(prefix_a.database_id_); + DCHECK(prefix_a.object_store_id_); + DCHECK_EQ(prefix_a.index_id_, ExistsEntryKey::kSpecialIndexNumber); + DCHECK(prefix_b.database_id_); + DCHECK(prefix_b.object_store_id_); + DCHECK_EQ(prefix_b.index_id_, ExistsEntryKey::kSpecialIndexNumber); + DCHECK_NE(ptr_a, a.end()); + DCHECK_NE(ptr_b, b.end()); + // Prefixes are not compared - it is assumed this was already done. + DCHECK(!prefix_a.Compare(prefix_b)); + + return CompareEncodedIDBKeys(ptr_a, a.end(), ptr_b, b.end(), ok); +} + +template <> +int Compare<ObjectStoreDataKey>(const LevelDBSlice& a, + const LevelDBSlice& b, + bool, + bool& ok) { + KeyPrefix prefix_a; + KeyPrefix prefix_b; + const char* ptr_a = KeyPrefix::Decode(a.begin(), a.end(), &prefix_a); + const char* ptr_b = KeyPrefix::Decode(b.begin(), b.end(), &prefix_b); + DCHECK(ptr_a); + DCHECK(ptr_b); + DCHECK(prefix_a.database_id_); + DCHECK(prefix_a.object_store_id_); + DCHECK_EQ(prefix_a.index_id_, ObjectStoreDataKey::kSpecialIndexNumber); + DCHECK(prefix_b.database_id_); + DCHECK(prefix_b.object_store_id_); + DCHECK_EQ(prefix_b.index_id_, ObjectStoreDataKey::kSpecialIndexNumber); + DCHECK_NE(ptr_a, a.end()); + DCHECK_NE(ptr_b, b.end()); + // Prefixes are not compared - it is assumed this was already done. + DCHECK(!prefix_a.Compare(prefix_b)); + + return CompareEncodedIDBKeys(ptr_a, a.end(), ptr_b, b.end(), ok); +} + +template <> +int Compare<IndexDataKey>(const LevelDBSlice& a, + const LevelDBSlice& b, + bool ignore_duplicates, + bool& ok) { + KeyPrefix prefix_a; + KeyPrefix prefix_b; + const char* ptr_a = KeyPrefix::Decode(a.begin(), a.end(), &prefix_a); + const char* ptr_b = KeyPrefix::Decode(b.begin(), b.end(), &prefix_b); + DCHECK(ptr_a); + DCHECK(ptr_b); + DCHECK(prefix_a.database_id_); + DCHECK(prefix_a.object_store_id_); + DCHECK_GE(prefix_a.index_id_, kMinimumIndexId); + DCHECK(prefix_b.database_id_); + DCHECK(prefix_b.object_store_id_); + DCHECK_GE(prefix_b.index_id_, kMinimumIndexId); + DCHECK_NE(ptr_a, a.end()); + DCHECK_NE(ptr_b, b.end()); + // Prefixes are not compared - it is assumed this was already done. + DCHECK(!prefix_a.Compare(prefix_b)); + + // index key + int result = CompareEncodedIDBKeys(ptr_a, a.end(), ptr_b, b.end(), ok); + if (!ok || result) + return result; + if (ignore_duplicates) + return 0; + + // sequence number [optional] + int64 sequence_number_a = -1; + int64 sequence_number_b = -1; + if (ptr_a != a.end()) + ptr_a = DecodeVarInt(ptr_a, a.end(), sequence_number_a); + if (ptr_b != b.end()) + ptr_b = DecodeVarInt(ptr_b, b.end(), sequence_number_b); + + // primary key [optional] + if (!ptr_a || !ptr_b) + return 0; + if (ptr_a == a.end() && ptr_b == b.end()) + return 0; + if (ptr_a == a.end()) + return -1; + if (ptr_b == b.end()) + return 1; + + result = CompareEncodedIDBKeys(ptr_a, a.end(), ptr_b, b.end(), ok); + if (!ok || result) + return result; + + return CompareInts(sequence_number_a, sequence_number_b); +} + +int Compare(const LevelDBSlice& a, + const LevelDBSlice& b, + bool index_keys, + bool& ok) { + const char* ptr_a = a.begin(); + const char* ptr_b = b.begin(); + const char* end_a = a.end(); + const char* end_b = b.end(); + + KeyPrefix prefix_a; + KeyPrefix prefix_b; + + ptr_a = KeyPrefix::Decode(ptr_a, end_a, &prefix_a); + ptr_b = KeyPrefix::Decode(ptr_b, end_b, &prefix_b); + DCHECK(ptr_a); + DCHECK(ptr_b); + if (!ptr_a || !ptr_b) { + ok = false; + return 0; + } + + ok = true; + if (int x = prefix_a.Compare(prefix_b)) + return x; + + if (prefix_a.type() == KeyPrefix::GLOBAL_METADATA) { + DCHECK_NE(ptr_a, end_a); + DCHECK_NE(ptr_b, end_b); + + unsigned char type_byte_a = *ptr_a++; + unsigned char type_byte_b = *ptr_b++; + + if (int x = type_byte_a - type_byte_b) + return x; + if (type_byte_a < kMaxSimpleGlobalMetaDataTypeByte) + return 0; + + const bool ignore_duplicates = false; + if (type_byte_a == kDatabaseFreeListTypeByte) + return Compare<DatabaseFreeListKey>(a, b, ignore_duplicates, ok); + if (type_byte_a == kDatabaseNameTypeByte) + return Compare<DatabaseNameKey>(a, b, ignore_duplicates, ok); + } + + if (prefix_a.type() == KeyPrefix::DATABASE_METADATA) { + DCHECK_NE(ptr_a, end_a); + DCHECK_NE(ptr_b, end_b); + + unsigned char type_byte_a = *ptr_a++; + unsigned char type_byte_b = *ptr_b++; + + if (int x = type_byte_a - type_byte_b) + return x; + if (type_byte_a < DatabaseMetaDataKey::MAX_SIMPLE_METADATA_TYPE) + return 0; + + const bool ignore_duplicates = false; + if (type_byte_a == kObjectStoreMetaDataTypeByte) + return Compare<ObjectStoreMetaDataKey>(a, b, ignore_duplicates, ok); + if (type_byte_a == kIndexMetaDataTypeByte) + return Compare<IndexMetaDataKey>(a, b, ignore_duplicates, ok); + if (type_byte_a == kObjectStoreFreeListTypeByte) + return Compare<ObjectStoreFreeListKey>(a, b, ignore_duplicates, ok); + if (type_byte_a == kIndexFreeListTypeByte) + return Compare<IndexFreeListKey>(a, b, ignore_duplicates, ok); + if (type_byte_a == kObjectStoreNamesTypeByte) + return Compare<ObjectStoreNamesKey>(a, b, ignore_duplicates, ok); + if (type_byte_a == kIndexNamesKeyTypeByte) + return Compare<IndexNamesKey>(a, b, ignore_duplicates, ok); + } + + if (prefix_a.type() == KeyPrefix::OBJECT_STORE_DATA) { + if (ptr_a == end_a && ptr_b == end_b) + return 0; + if (ptr_a == end_a) + return -1; + if (ptr_b == end_b) + return 1; // TODO(jsbell): This case of non-existing user keys should not + // have to be handled this way. + + const bool ignore_duplicates = false; + return Compare<ObjectStoreDataKey>(a, b, ignore_duplicates, ok); + } + if (prefix_a.type() == KeyPrefix::EXISTS_ENTRY) { + if (ptr_a == end_a && ptr_b == end_b) + return 0; + if (ptr_a == end_a) + return -1; + if (ptr_b == end_b) + return 1; // TODO(jsbell): This case of non-existing user keys should not + // have to be handled this way. + + const bool ignore_duplicates = false; + return Compare<ExistsEntryKey>(a, b, ignore_duplicates, ok); + } + if (prefix_a.type() == KeyPrefix::INDEX_DATA) { + if (ptr_a == end_a && ptr_b == end_b) + return 0; + if (ptr_a == end_a) + return -1; + if (ptr_b == end_b) + return 1; // TODO(jsbell): This case of non-existing user keys should not + // have to be handled this way. + + bool ignore_duplicates = index_keys; + return Compare<IndexDataKey>(a, b, ignore_duplicates, ok); + } + + NOTREACHED(); + ok = false; + return 0; +} + +} // namespace + +int Compare(const LevelDBSlice& a, const LevelDBSlice& b, bool index_keys) { + bool ok; + int result = Compare(a, b, index_keys, ok); + DCHECK(ok); + if (!ok) + return 0; + return result; +} + +KeyPrefix::KeyPrefix() + : database_id_(INVALID_TYPE), + object_store_id_(INVALID_TYPE), + index_id_(INVALID_TYPE) {} + +KeyPrefix::KeyPrefix(int64 database_id) + : database_id_(database_id), object_store_id_(0), index_id_(0) { + DCHECK(KeyPrefix::IsValidDatabaseId(database_id)); +} + +KeyPrefix::KeyPrefix(int64 database_id, int64 object_store_id) + : database_id_(database_id), + object_store_id_(object_store_id), + index_id_(0) { + DCHECK(KeyPrefix::IsValidDatabaseId(database_id)); + DCHECK(KeyPrefix::IsValidObjectStoreId(object_store_id)); +} + +KeyPrefix::KeyPrefix(int64 database_id, int64 object_store_id, int64 index_id) + : database_id_(database_id), + object_store_id_(object_store_id), + index_id_(index_id) { + DCHECK(KeyPrefix::IsValidDatabaseId(database_id)); + DCHECK(KeyPrefix::IsValidObjectStoreId(object_store_id)); + DCHECK(KeyPrefix::IsValidIndexId(index_id)); +} + +KeyPrefix::KeyPrefix(enum Type type, + int64 database_id, + int64 object_store_id, + int64 index_id) + : database_id_(database_id), + object_store_id_(object_store_id), + index_id_(index_id) { + DCHECK_EQ(type, INVALID_TYPE); + DCHECK(KeyPrefix::IsValidDatabaseId(database_id)); + DCHECK(KeyPrefix::IsValidObjectStoreId(object_store_id)); +} + +KeyPrefix KeyPrefix::CreateWithSpecialIndex(int64 database_id, + int64 object_store_id, + int64 index_id) { + DCHECK(KeyPrefix::IsValidDatabaseId(database_id)); + DCHECK(KeyPrefix::IsValidObjectStoreId(object_store_id)); + DCHECK(index_id); + return KeyPrefix(INVALID_TYPE, database_id, object_store_id, index_id); +} + +bool KeyPrefix::IsValidDatabaseId(int64 database_id) { + return (database_id > 0) && (database_id < KeyPrefix::kMaxDatabaseId); +} + +bool KeyPrefix::IsValidObjectStoreId(int64 object_store_id) { + return (object_store_id > 0) && + (object_store_id < KeyPrefix::kMaxObjectStoreId); +} + +bool KeyPrefix::IsValidIndexId(int64 index_id) { + return (index_id >= kMinimumIndexId) && (index_id < KeyPrefix::kMaxIndexId); +} + +const char* KeyPrefix::Decode(const char* start, + const char* limit, + KeyPrefix* result) { + if (start == limit) + return 0; + + unsigned char first_byte = *start++; + + int database_id_bytes = ((first_byte >> 5) & 0x7) + 1; + int object_store_id_bytes = ((first_byte >> 2) & 0x7) + 1; + int index_id_bytes = (first_byte & 0x3) + 1; + + if (start + database_id_bytes + object_store_id_bytes + index_id_bytes > + limit) + return 0; + + result->database_id_ = DecodeInt(start, start + database_id_bytes); + start += database_id_bytes; + result->object_store_id_ = DecodeInt(start, start + object_store_id_bytes); + start += object_store_id_bytes; + result->index_id_ = DecodeInt(start, start + index_id_bytes); + start += index_id_bytes; + + return start; +} + +std::vector<char> KeyPrefix::EncodeEmpty() { + const std::vector<char> result(4, 0); + DCHECK(EncodeInternal(0, 0, 0) == std::vector<char>(4, 0)); + return result; +} + +std::vector<char> KeyPrefix::Encode() const { + DCHECK(database_id_ != kInvalidId); + DCHECK(object_store_id_ != kInvalidId); + DCHECK(index_id_ != kInvalidId); + return EncodeInternal(database_id_, object_store_id_, index_id_); +} + +std::vector<char> KeyPrefix::EncodeInternal(int64 database_id, + int64 object_store_id, + int64 index_id) { + std::vector<char> database_id_string = + EncodeIntSafely(database_id, kMaxDatabaseId); + std::vector<char> object_store_id_string = + EncodeIntSafely(object_store_id, kMaxObjectStoreId); + std::vector<char> index_id_string = EncodeIntSafely(index_id, kMaxIndexId); + + DCHECK(database_id_string.size() <= kMaxDatabaseIdSizeBytes); + DCHECK(object_store_id_string.size() <= kMaxObjectStoreIdSizeBytes); + DCHECK(index_id_string.size() <= kMaxIndexIdSizeBytes); + + unsigned char first_byte = + (database_id_string.size() - 1) + << (kMaxObjectStoreIdSizeBits + kMaxIndexIdSizeBits) | + (object_store_id_string.size() - 1) << kMaxIndexIdSizeBits | + (index_id_string.size() - 1); + COMPILE_ASSERT(kMaxDatabaseIdSizeBits + kMaxObjectStoreIdSizeBits + + kMaxIndexIdSizeBits == + sizeof(first_byte) * 8, + CANT_ENCODE_IDS); + std::vector<char> ret; + ret.reserve(kDefaultInlineBufferSize); + ret.push_back(first_byte); + ret.insert(ret.end(), database_id_string.begin(), database_id_string.end()); + ret.insert( + ret.end(), object_store_id_string.begin(), object_store_id_string.end()); + ret.insert(ret.end(), index_id_string.begin(), index_id_string.end()); + + DCHECK_LE(ret.size(), kDefaultInlineBufferSize); + return ret; +} + +int KeyPrefix::Compare(const KeyPrefix& other) const { + DCHECK(database_id_ != kInvalidId); + DCHECK(object_store_id_ != kInvalidId); + DCHECK(index_id_ != kInvalidId); + + if (database_id_ != other.database_id_) + return CompareInts(database_id_, other.database_id_); + if (object_store_id_ != other.object_store_id_) + return CompareInts(object_store_id_, other.object_store_id_); + if (index_id_ != other.index_id_) + return CompareInts(index_id_, other.index_id_); + return 0; +} + +KeyPrefix::Type KeyPrefix::type() const { + DCHECK(database_id_ != kInvalidId); + DCHECK(object_store_id_ != kInvalidId); + DCHECK(index_id_ != kInvalidId); + + if (!database_id_) + return GLOBAL_METADATA; + if (!object_store_id_) + return DATABASE_METADATA; + if (index_id_ == kObjectStoreDataIndexId) + return OBJECT_STORE_DATA; + if (index_id_ == kExistsEntryIndexId) + return EXISTS_ENTRY; + if (index_id_ >= kMinimumIndexId) + return INDEX_DATA; + + NOTREACHED(); + return INVALID_TYPE; +} + +std::vector<char> SchemaVersionKey::Encode() { + std::vector<char> ret = KeyPrefix::EncodeEmpty(); + ret.push_back(kSchemaVersionTypeByte); + return ret; +} + +std::vector<char> MaxDatabaseIdKey::Encode() { + std::vector<char> ret = KeyPrefix::EncodeEmpty(); + ret.push_back(kMaxDatabaseIdTypeByte); + return ret; +} + +std::vector<char> DataVersionKey::Encode() { + std::vector<char> ret = KeyPrefix::EncodeEmpty(); + ret.push_back(kDataVersionTypeByte); + return ret; +} + +DatabaseFreeListKey::DatabaseFreeListKey() : database_id_(-1) {} + +const char* DatabaseFreeListKey::Decode(const char* start, + const char* limit, + DatabaseFreeListKey* result) { + KeyPrefix prefix; + const char* p = KeyPrefix::Decode(start, limit, &prefix); + if (!p) + return 0; + DCHECK(!prefix.database_id_); + DCHECK(!prefix.object_store_id_); + DCHECK(!prefix.index_id_); + if (p == limit) + return 0; + unsigned char type_byte = 0; + p = DecodeByte(p, limit, type_byte); + DCHECK_EQ(type_byte, kDatabaseFreeListTypeByte); + if (p == limit) + return 0; + return DecodeVarInt(p, limit, result->database_id_); +} + +std::vector<char> DatabaseFreeListKey::Encode(int64 database_id) { + std::vector<char> ret = KeyPrefix::EncodeEmpty(); + ret.push_back(kDatabaseFreeListTypeByte); + std::vector<char> tmp = EncodeVarInt(database_id); + ret.insert(ret.end(), tmp.begin(), tmp.end()); + return ret; +} + +std::vector<char> DatabaseFreeListKey::EncodeMaxKey() { + return Encode(std::numeric_limits<int64>::max()); +} + +int64 DatabaseFreeListKey::DatabaseId() const { + DCHECK_GE(database_id_, 0); + return database_id_; +} + +int DatabaseFreeListKey::Compare(const DatabaseFreeListKey& other) const { + DCHECK_GE(database_id_, 0); + return CompareInts(database_id_, other.database_id_); +} + +const char* DatabaseNameKey::Decode(const char* start, + const char* limit, + DatabaseNameKey* result) { + KeyPrefix prefix; + const char* p = KeyPrefix::Decode(start, limit, &prefix); + if (!p) + return p; + DCHECK(!prefix.database_id_); + DCHECK(!prefix.object_store_id_); + DCHECK(!prefix.index_id_); + if (p == limit) + return 0; + unsigned char type_byte = 0; + p = DecodeByte(p, limit, type_byte); + DCHECK_EQ(type_byte, kDatabaseNameTypeByte); + if (p == limit) + return 0; + p = DecodeStringWithLength(p, limit, result->origin_); + if (!p) + return 0; + return DecodeStringWithLength(p, limit, result->database_name_); +} + +std::vector<char> DatabaseNameKey::Encode(const string16& origin, + const string16& database_name) { + std::vector<char> ret = KeyPrefix::EncodeEmpty(); + ret.push_back(kDatabaseNameTypeByte); + std::vector<char> tmp = EncodeStringWithLength(origin); + ret.insert(ret.end(), tmp.begin(), tmp.end()); + tmp = EncodeStringWithLength(database_name); + ret.insert(ret.end(), tmp.begin(), tmp.end()); + return ret; +} + +std::vector<char> DatabaseNameKey::EncodeMinKeyForOrigin( + const string16& origin) { + return Encode(origin, string16()); +} + +std::vector<char> DatabaseNameKey::EncodeStopKeyForOrigin( + const string16& origin) { + // just after origin in collation order + return EncodeMinKeyForOrigin(origin + base::char16('\x01')); +} + +int DatabaseNameKey::Compare(const DatabaseNameKey& other) { + if (int x = origin_.compare(other.origin_)) + return x; + return database_name_.compare(other.database_name_); +} + +std::vector<char> DatabaseMetaDataKey::Encode(int64 database_id, + MetaDataType meta_data_type) { + KeyPrefix prefix(database_id); + std::vector<char> ret = prefix.Encode(); + ret.push_back(meta_data_type); + return ret; +} + +ObjectStoreMetaDataKey::ObjectStoreMetaDataKey() + : object_store_id_(-1), meta_data_type_(-1) {} + +const char* ObjectStoreMetaDataKey::Decode(const char* start, + const char* limit, + ObjectStoreMetaDataKey* result) { + KeyPrefix prefix; + const char* p = KeyPrefix::Decode(start, limit, &prefix); + if (!p) + return 0; + DCHECK(prefix.database_id_); + DCHECK(!prefix.object_store_id_); + DCHECK(!prefix.index_id_); + if (p == limit) + return 0; + unsigned char type_byte = 0; + p = DecodeByte(p, limit, type_byte); + DCHECK_EQ(type_byte, kObjectStoreMetaDataTypeByte); + if (p == limit) + return 0; + p = DecodeVarInt(p, limit, result->object_store_id_); + if (!p) + return 0; + DCHECK(result->object_store_id_); + if (p == limit) + return 0; + return DecodeByte(p, limit, result->meta_data_type_); +} + +std::vector<char> ObjectStoreMetaDataKey::Encode(int64 database_id, + int64 object_store_id, + unsigned char meta_data_type) { + KeyPrefix prefix(database_id); + std::vector<char> ret = prefix.Encode(); + ret.push_back(kObjectStoreMetaDataTypeByte); + std::vector<char> tmp = EncodeVarInt(object_store_id); + ret.insert(ret.end(), tmp.begin(), tmp.end()); + ret.push_back(meta_data_type); + return ret; +} + +std::vector<char> ObjectStoreMetaDataKey::EncodeMaxKey(int64 database_id) { + return Encode(database_id, + std::numeric_limits<int64>::max(), + kObjectMetaDataTypeMaximum); +} + +std::vector<char> ObjectStoreMetaDataKey::EncodeMaxKey(int64 database_id, + int64 object_store_id) { + return Encode(database_id, object_store_id, kObjectMetaDataTypeMaximum); +} + +int64 ObjectStoreMetaDataKey::ObjectStoreId() const { + DCHECK_GE(object_store_id_, 0); + return object_store_id_; +} +unsigned char ObjectStoreMetaDataKey::MetaDataType() const { + return meta_data_type_; +} + +int ObjectStoreMetaDataKey::Compare(const ObjectStoreMetaDataKey& other) { + DCHECK_GE(object_store_id_, 0); + if (int x = CompareInts(object_store_id_, other.object_store_id_)) + return x; + int64 result = meta_data_type_ - other.meta_data_type_; + if (result < 0) + return -1; + return (result > 0) ? 1 : result; +} + +IndexMetaDataKey::IndexMetaDataKey() + : object_store_id_(-1), index_id_(-1), meta_data_type_(0) {} + +const char* IndexMetaDataKey::Decode(const char* start, + const char* limit, + IndexMetaDataKey* result) { + KeyPrefix prefix; + const char* p = KeyPrefix::Decode(start, limit, &prefix); + if (!p) + return 0; + DCHECK(prefix.database_id_); + DCHECK(!prefix.object_store_id_); + DCHECK(!prefix.index_id_); + if (p == limit) + return 0; + unsigned char type_byte = 0; + p = DecodeByte(p, limit, type_byte); + DCHECK_EQ(type_byte, kIndexMetaDataTypeByte); + if (p == limit) + return 0; + p = DecodeVarInt(p, limit, result->object_store_id_); + if (!p) + return 0; + p = DecodeVarInt(p, limit, result->index_id_); + if (!p) + return 0; + if (p == limit) + return 0; + return DecodeByte(p, limit, result->meta_data_type_); +} + +std::vector<char> IndexMetaDataKey::Encode(int64 database_id, + int64 object_store_id, + int64 index_id, + unsigned char meta_data_type) { + KeyPrefix prefix(database_id); + std::vector<char> ret = prefix.Encode(); + ret.push_back(kIndexMetaDataTypeByte); + std::vector<char> tmp = EncodeVarInt(object_store_id); + ret.insert(ret.end(), tmp.begin(), tmp.end()); + tmp = EncodeVarInt(index_id); + ret.insert(ret.end(), tmp.begin(), tmp.end()); + tmp = EncodeByte(meta_data_type); + ret.insert(ret.end(), tmp.begin(), tmp.end()); + return ret; +} + +std::vector<char> IndexMetaDataKey::EncodeMaxKey(int64 database_id, + int64 object_store_id) { + return Encode(database_id, + object_store_id, + std::numeric_limits<int64>::max(), + kIndexMetaDataTypeMaximum); +} + +std::vector<char> IndexMetaDataKey::EncodeMaxKey(int64 database_id, + int64 object_store_id, + int64 index_id) { + return Encode( + database_id, object_store_id, index_id, kIndexMetaDataTypeMaximum); +} + +int IndexMetaDataKey::Compare(const IndexMetaDataKey& other) { + DCHECK_GE(object_store_id_, 0); + DCHECK_GE(index_id_, 0); + + if (int x = CompareInts(object_store_id_, other.object_store_id_)) + return x; + if (int x = CompareInts(index_id_, other.index_id_)) + return x; + return meta_data_type_ - other.meta_data_type_; +} + +int64 IndexMetaDataKey::IndexId() const { + DCHECK_GE(index_id_, 0); + return index_id_; +} + +ObjectStoreFreeListKey::ObjectStoreFreeListKey() : object_store_id_(-1) {} + +const char* ObjectStoreFreeListKey::Decode(const char* start, + const char* limit, + ObjectStoreFreeListKey* result) { + KeyPrefix prefix; + const char* p = KeyPrefix::Decode(start, limit, &prefix); + if (!p) + return 0; + DCHECK(prefix.database_id_); + DCHECK(!prefix.object_store_id_); + DCHECK(!prefix.index_id_); + if (p == limit) + return 0; + unsigned char type_byte = 0; + p = DecodeByte(p, limit, type_byte); + DCHECK_EQ(type_byte, kObjectStoreFreeListTypeByte); + if (p == limit) + return 0; + return DecodeVarInt(p, limit, result->object_store_id_); +} + +std::vector<char> ObjectStoreFreeListKey::Encode(int64 database_id, + int64 object_store_id) { + KeyPrefix prefix(database_id); + std::vector<char> ret = prefix.Encode(); + ret.push_back(kObjectStoreFreeListTypeByte); + std::vector<char> tmp = EncodeVarInt(object_store_id); + ret.insert(ret.end(), tmp.begin(), tmp.end()); + return ret; +} + +std::vector<char> ObjectStoreFreeListKey::EncodeMaxKey(int64 database_id) { + return Encode(database_id, std::numeric_limits<int64>::max()); +} + +int64 ObjectStoreFreeListKey::ObjectStoreId() const { + DCHECK_GE(object_store_id_, 0); + return object_store_id_; +} + +int ObjectStoreFreeListKey::Compare(const ObjectStoreFreeListKey& other) { + // TODO(jsbell): It may seem strange that we're not comparing database id's, + // but that comparison will have been made earlier. + // We should probably make this more clear, though... + DCHECK_GE(object_store_id_, 0); + return CompareInts(object_store_id_, other.object_store_id_); +} + +IndexFreeListKey::IndexFreeListKey() : object_store_id_(-1), index_id_(-1) {} + +const char* IndexFreeListKey::Decode(const char* start, + const char* limit, + IndexFreeListKey* result) { + KeyPrefix prefix; + const char* p = KeyPrefix::Decode(start, limit, &prefix); + if (!p) + return 0; + DCHECK(prefix.database_id_); + DCHECK(!prefix.object_store_id_); + DCHECK(!prefix.index_id_); + if (p == limit) + return 0; + unsigned char type_byte = 0; + p = DecodeByte(p, limit, type_byte); + DCHECK_EQ(type_byte, kIndexFreeListTypeByte); + if (p == limit) + return 0; + p = DecodeVarInt(p, limit, result->object_store_id_); + if (!p) + return 0; + return DecodeVarInt(p, limit, result->index_id_); +} + +std::vector<char> IndexFreeListKey::Encode(int64 database_id, + int64 object_store_id, + int64 index_id) { + KeyPrefix prefix(database_id); + std::vector<char> ret = prefix.Encode(); + ret.push_back(kIndexFreeListTypeByte); + std::vector<char> tmp = EncodeVarInt(object_store_id); + ret.insert(ret.end(), tmp.begin(), tmp.end()); + tmp = EncodeVarInt(index_id); + ret.insert(ret.end(), tmp.begin(), tmp.end()); + return ret; +} + +std::vector<char> IndexFreeListKey::EncodeMaxKey(int64 database_id, + int64 object_store_id) { + return Encode( + database_id, object_store_id, std::numeric_limits<int64>::max()); +} + +int IndexFreeListKey::Compare(const IndexFreeListKey& other) { + DCHECK_GE(object_store_id_, 0); + DCHECK_GE(index_id_, 0); + if (int x = CompareInts(object_store_id_, other.object_store_id_)) + return x; + return CompareInts(index_id_, other.index_id_); +} + +int64 IndexFreeListKey::ObjectStoreId() const { + DCHECK_GE(object_store_id_, 0); + return object_store_id_; +} + +int64 IndexFreeListKey::IndexId() const { + DCHECK_GE(index_id_, 0); + return index_id_; +} + +// TODO(jsbell): We never use this to look up object store ids, +// because a mapping is kept in the IndexedDBDatabaseImpl. Can the +// mapping become unreliable? Can we remove this? +const char* ObjectStoreNamesKey::Decode(const char* start, + const char* limit, + ObjectStoreNamesKey* result) { + KeyPrefix prefix; + const char* p = KeyPrefix::Decode(start, limit, &prefix); + if (!p) + return 0; + DCHECK(prefix.database_id_); + DCHECK(!prefix.object_store_id_); + DCHECK(!prefix.index_id_); + if (p == limit) + return 0; + unsigned char type_byte = 0; + p = DecodeByte(p, limit, type_byte); + DCHECK_EQ(type_byte, kObjectStoreNamesTypeByte); + return DecodeStringWithLength(p, limit, result->object_store_name_); +} + +std::vector<char> ObjectStoreNamesKey::Encode( + int64 database_id, + const string16& object_store_name) { + KeyPrefix prefix(database_id); + std::vector<char> ret = prefix.Encode(); + ret.push_back(kObjectStoreNamesTypeByte); + std::vector<char> tmp = EncodeStringWithLength(object_store_name); + ret.insert(ret.end(), tmp.begin(), tmp.end()); + return ret; +} + +int ObjectStoreNamesKey::Compare(const ObjectStoreNamesKey& other) { + return object_store_name_.compare(other.object_store_name_); +} + +IndexNamesKey::IndexNamesKey() : object_store_id_(-1) {} + +// TODO(jsbell): We never use this to look up index ids, because a mapping +// is kept at a higher level. +const char* IndexNamesKey::Decode(const char* start, + const char* limit, + IndexNamesKey* result) { + KeyPrefix prefix; + const char* p = KeyPrefix::Decode(start, limit, &prefix); + if (!p) + return 0; + DCHECK(prefix.database_id_); + DCHECK(!prefix.object_store_id_); + DCHECK(!prefix.index_id_); + if (p == limit) + return 0; + unsigned char type_byte = 0; + p = DecodeByte(p, limit, type_byte); + DCHECK_EQ(type_byte, kIndexNamesKeyTypeByte); + if (p == limit) + return 0; + p = DecodeVarInt(p, limit, result->object_store_id_); + if (!p) + return 0; + return DecodeStringWithLength(p, limit, result->index_name_); +} + +std::vector<char> IndexNamesKey::Encode(int64 database_id, + int64 object_store_id, + const string16& index_name) { + KeyPrefix prefix(database_id); + std::vector<char> ret = prefix.Encode(); + ret.push_back(kIndexNamesKeyTypeByte); + std::vector<char> tmp = EncodeVarInt(object_store_id); + ret.insert(ret.end(), tmp.begin(), tmp.end()); + tmp = EncodeStringWithLength(index_name); + ret.insert(ret.end(), tmp.begin(), tmp.end()); + return ret; +} + +int IndexNamesKey::Compare(const IndexNamesKey& other) { + DCHECK_GE(object_store_id_, 0); + if (int x = CompareInts(object_store_id_, other.object_store_id_)) + return x; + return index_name_.compare(other.index_name_); +} + +ObjectStoreDataKey::ObjectStoreDataKey() {} +ObjectStoreDataKey::~ObjectStoreDataKey() {} + +const char* ObjectStoreDataKey::Decode(const char* start, + const char* end, + ObjectStoreDataKey* result) { + KeyPrefix prefix; + const char* p = KeyPrefix::Decode(start, end, &prefix); + if (!p) + return 0; + DCHECK(prefix.database_id_); + DCHECK(prefix.object_store_id_); + DCHECK_EQ(prefix.index_id_, kSpecialIndexNumber); + if (p == end) + return 0; + return ExtractEncodedIDBKey(p, end, &result->encoded_user_key_); +} + +std::vector<char> ObjectStoreDataKey::Encode( + int64 database_id, + int64 object_store_id, + const std::vector<char> encoded_user_key) { + KeyPrefix prefix(KeyPrefix::CreateWithSpecialIndex( + database_id, object_store_id, kSpecialIndexNumber)); + std::vector<char> ret = prefix.Encode(); + ret.insert(ret.end(), encoded_user_key.begin(), encoded_user_key.end()); + + return ret; +} + +std::vector<char> ObjectStoreDataKey::Encode(int64 database_id, + int64 object_store_id, + const IndexedDBKey& user_key) { + return Encode(database_id, object_store_id, EncodeIDBKey(user_key)); +} + +int ObjectStoreDataKey::Compare(const ObjectStoreDataKey& other, bool& ok) { + return CompareEncodedIDBKeys(encoded_user_key_, other.encoded_user_key_, ok); +} + +scoped_ptr<IndexedDBKey> ObjectStoreDataKey::user_key() const { + scoped_ptr<IndexedDBKey> key; + DecodeIDBKey(&encoded_user_key_[0], + &encoded_user_key_[0] + encoded_user_key_.size(), + &key); + return key.Pass(); +} + +const int64 ObjectStoreDataKey::kSpecialIndexNumber = kObjectStoreDataIndexId; + +ExistsEntryKey::ExistsEntryKey() {} +ExistsEntryKey::~ExistsEntryKey() {} + +const char* ExistsEntryKey::Decode(const char* start, + const char* end, + ExistsEntryKey* result) { + KeyPrefix prefix; + const char* p = KeyPrefix::Decode(start, end, &prefix); + if (!p) + return 0; + DCHECK(prefix.database_id_); + DCHECK(prefix.object_store_id_); + DCHECK_EQ(prefix.index_id_, kSpecialIndexNumber); + if (p == end) + return 0; + return ExtractEncodedIDBKey(p, end, &result->encoded_user_key_); +} + +std::vector<char> ExistsEntryKey::Encode(int64 database_id, + int64 object_store_id, + const std::vector<char>& encoded_key) { + KeyPrefix prefix(KeyPrefix::CreateWithSpecialIndex( + database_id, object_store_id, kSpecialIndexNumber)); + std::vector<char> ret = prefix.Encode(); + ret.insert(ret.end(), encoded_key.begin(), encoded_key.end()); + return ret; +} + +std::vector<char> ExistsEntryKey::Encode(int64 database_id, + int64 object_store_id, + const IndexedDBKey& user_key) { + return Encode(database_id, object_store_id, EncodeIDBKey(user_key)); +} + +int ExistsEntryKey::Compare(const ExistsEntryKey& other, bool& ok) { + return CompareEncodedIDBKeys(encoded_user_key_, other.encoded_user_key_, ok); +} + +scoped_ptr<IndexedDBKey> ExistsEntryKey::user_key() const { + scoped_ptr<IndexedDBKey> key; + DecodeIDBKey(&encoded_user_key_[0], + &encoded_user_key_[0] + encoded_user_key_.size(), + &key); + return key.Pass(); +} + +const int64 ExistsEntryKey::kSpecialIndexNumber = kExistsEntryIndexId; + +IndexDataKey::IndexDataKey() + : database_id_(-1), + object_store_id_(-1), + index_id_(-1), + sequence_number_(-1) {} + +IndexDataKey::~IndexDataKey() {} + +const char* IndexDataKey::Decode(const char* start, + const char* limit, + IndexDataKey* result) { + KeyPrefix prefix; + const char* p = KeyPrefix::Decode(start, limit, &prefix); + if (!p) + return 0; + DCHECK(prefix.database_id_); + DCHECK(prefix.object_store_id_); + DCHECK_GE(prefix.index_id_, kMinimumIndexId); + result->database_id_ = prefix.database_id_; + result->object_store_id_ = prefix.object_store_id_; + result->index_id_ = prefix.index_id_; + result->sequence_number_ = -1; + result->encoded_primary_key_ = MinIDBKey(); + + p = ExtractEncodedIDBKey(p, limit, &result->encoded_user_key_); + if (!p) + return 0; + + // [optional] sequence number + if (p == limit) + return p; + p = DecodeVarInt(p, limit, result->sequence_number_); + if (!p) + return 0; + + // [optional] primary key + if (p == limit) + return p; + p = ExtractEncodedIDBKey(p, limit, &result->encoded_primary_key_); + if (!p) + return 0; + + return p; +} + +std::vector<char> IndexDataKey::Encode( + int64 database_id, + int64 object_store_id, + int64 index_id, + const std::vector<char>& encoded_user_key, + const std::vector<char>& encoded_primary_key, + int64 sequence_number) { + KeyPrefix prefix(database_id, object_store_id, index_id); + std::vector<char> ret = prefix.Encode(); + ret.insert(ret.end(), encoded_user_key.begin(), encoded_user_key.end()); + std::vector<char> tmp = EncodeVarInt(sequence_number); + ret.insert(ret.end(), tmp.begin(), tmp.end()); + ret.insert(ret.end(), encoded_primary_key.begin(), encoded_primary_key.end()); + return ret; +} + +std::vector<char> IndexDataKey::Encode(int64 database_id, + int64 object_store_id, + int64 index_id, + const IndexedDBKey& user_key) { + return Encode(database_id, + object_store_id, + index_id, + EncodeIDBKey(user_key), + MinIDBKey()); +} + +std::vector<char> IndexDataKey::EncodeMinKey(int64 database_id, + int64 object_store_id, + int64 index_id) { + return Encode( + database_id, object_store_id, index_id, MinIDBKey(), MinIDBKey()); +} + +std::vector<char> IndexDataKey::EncodeMaxKey(int64 database_id, + int64 object_store_id, + int64 index_id) { + return Encode(database_id, + object_store_id, + index_id, + MaxIDBKey(), + MaxIDBKey(), + std::numeric_limits<int64>::max()); +} + +int IndexDataKey::Compare(const IndexDataKey& other, + bool ignore_duplicates, + bool& ok) { + DCHECK_GE(database_id_, 0); + DCHECK_GE(object_store_id_, 0); + DCHECK_GE(index_id_, 0); + int result = + CompareEncodedIDBKeys(encoded_user_key_, other.encoded_user_key_, ok); + if (!ok || result) + return result; + if (ignore_duplicates) + return 0; + result = CompareEncodedIDBKeys( + encoded_primary_key_, other.encoded_primary_key_, ok); + if (!ok || result) + return result; + return CompareInts(sequence_number_, other.sequence_number_); +} + +int64 IndexDataKey::DatabaseId() const { + DCHECK_GE(database_id_, 0); + return database_id_; +} + +int64 IndexDataKey::ObjectStoreId() const { + DCHECK_GE(object_store_id_, 0); + return object_store_id_; +} + +int64 IndexDataKey::IndexId() const { + DCHECK_GE(index_id_, 0); + return index_id_; +} + +scoped_ptr<IndexedDBKey> IndexDataKey::user_key() const { + scoped_ptr<IndexedDBKey> key; + DecodeIDBKey(&encoded_user_key_[0], + &encoded_user_key_[0] + encoded_user_key_.size(), + &key); + return key.Pass(); +} + +scoped_ptr<IndexedDBKey> IndexDataKey::primary_key() const { + scoped_ptr<IndexedDBKey> key; + DecodeIDBKey(&encoded_primary_key_[0], + &encoded_primary_key_[0] + encoded_primary_key_.size(), + &key); + return key.Pass(); +} + +} // namespace content diff --git a/content/browser/indexed_db/indexed_db_leveldb_coding.h b/content/browser/indexed_db/indexed_db_leveldb_coding.h new file mode 100644 index 0000000..ff8f5a1 --- /dev/null +++ b/content/browser/indexed_db/indexed_db_leveldb_coding.h @@ -0,0 +1,467 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_LEVELDB_CODING_H_ +#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_LEVELDB_CODING_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/string16.h" +#include "content/common/indexed_db/indexed_db_key.h" +#include "content/common/indexed_db/indexed_db_key_path.h" + +namespace content { + +class LevelDBSlice; + +CONTENT_EXPORT extern const unsigned char kMinimumIndexId; + +CONTENT_EXPORT std::vector<char> EncodeByte(unsigned char); +CONTENT_EXPORT const char* DecodeByte(const char* p, + const char* limit, + unsigned char& found_char); +CONTENT_EXPORT std::vector<char> MaxIDBKey(); +CONTENT_EXPORT std::vector<char> MinIDBKey(); +CONTENT_EXPORT std::vector<char> EncodeBool(bool value); +CONTENT_EXPORT bool DecodeBool(const char* begin, const char* end); +CONTENT_EXPORT std::vector<char> EncodeInt(int64); +inline std::vector<char> EncodeIntSafely(int64 nParam, int64 max) { + DCHECK_LE(nParam, max); + return EncodeInt(nParam); +} + +template <class T> int64 DecodeInt(T begin, T end) { + // TODO(alecflett): Make this a DCHECK_LE(). + DCHECK(begin <= end); + int64 ret = 0; + + int shift = 0; + while (begin < end) { + unsigned char c = *begin++; + ret |= static_cast<int64>(c) << shift; + shift += 8; + } + + return ret; +} + +CONTENT_EXPORT std::vector<char> EncodeVarInt(int64); +CONTENT_EXPORT const char* DecodeVarInt(const char* p, + const char* limit, + int64& found_int); +CONTENT_EXPORT std::vector<char> EncodeString(const string16& string); +CONTENT_EXPORT string16 DecodeString(const char* p, const char* end); +CONTENT_EXPORT std::vector<char> EncodeStringWithLength(const string16& string); +CONTENT_EXPORT const char* DecodeStringWithLength(const char* p, + const char* limit, + string16& found_string); +CONTENT_EXPORT int CompareEncodedStringsWithLength(const char*& p, + const char* limit_p, + const char*& q, + const char* limit_q, + bool& ok); +CONTENT_EXPORT std::vector<char> EncodeDouble(double value); +CONTENT_EXPORT const char* DecodeDouble(const char* p, + const char* limit, + double* value); +void EncodeIDBKey(const IndexedDBKey& key, std::vector<char>& into); +CONTENT_EXPORT std::vector<char> EncodeIDBKey(const IndexedDBKey& key); +CONTENT_EXPORT const char* DecodeIDBKey(const char* p, + const char* limit, + scoped_ptr<IndexedDBKey>* found_key); +CONTENT_EXPORT const char* ExtractEncodedIDBKey(const char* start, + const char* limit, + std::vector<char>* result); +CONTENT_EXPORT int CompareEncodedIDBKeys(const std::vector<char>& a, + const std::vector<char>& b, + bool& ok); +CONTENT_EXPORT std::vector<char> EncodeIDBKeyPath( + const IndexedDBKeyPath& key_path); +CONTENT_EXPORT IndexedDBKeyPath DecodeIDBKeyPath(const char* start, + const char* limit); + +CONTENT_EXPORT int Compare(const LevelDBSlice& a, + const LevelDBSlice& b, + bool index_keys = false); + +class KeyPrefix { + public: + KeyPrefix(); + explicit KeyPrefix(int64 database_id); + KeyPrefix(int64 database_id, int64 object_store_id); + KeyPrefix(int64 database_id, int64 object_store_id, int64 index_id); + static KeyPrefix CreateWithSpecialIndex(int64 database_id, + int64 object_store_id, + int64 index_id); + + static const char* Decode(const char* start, + const char* limit, + KeyPrefix* result); + std::vector<char> Encode() const; + static std::vector<char> EncodeEmpty(); + int Compare(const KeyPrefix& other) const; + + enum Type { + GLOBAL_METADATA, + DATABASE_METADATA, + OBJECT_STORE_DATA, + EXISTS_ENTRY, + INDEX_DATA, + INVALID_TYPE + }; + + static const size_t kMaxDatabaseIdSizeBits = 3; + static const size_t kMaxObjectStoreIdSizeBits = 3; + static const size_t kMaxIndexIdSizeBits = 2; + + static const size_t kMaxDatabaseIdSizeBytes = + 1ULL << kMaxDatabaseIdSizeBits; // 8 + static const size_t kMaxObjectStoreIdSizeBytes = + 1ULL << kMaxObjectStoreIdSizeBits; // 8 + static const size_t kMaxIndexIdSizeBytes = 1ULL << kMaxIndexIdSizeBits; // 4 + + static const size_t kMaxDatabaseIdBits = + kMaxDatabaseIdSizeBytes * 8 - 1; // 63 + static const size_t kMaxObjectStoreIdBits = + kMaxObjectStoreIdSizeBytes * 8 - 1; // 63 + static const size_t kMaxIndexIdBits = kMaxIndexIdSizeBytes * 8 - 1; // 31 + + static const int64 kMaxDatabaseId = + (1ULL << kMaxDatabaseIdBits) - 1; // max signed int64 + static const int64 kMaxObjectStoreId = + (1ULL << kMaxObjectStoreIdBits) - 1; // max signed int64 + static const int64 kMaxIndexId = + (1ULL << kMaxIndexIdBits) - 1; // max signed int32 + + static bool IsValidDatabaseId(int64 database_id); + static bool IsValidObjectStoreId(int64 index_id); + static bool IsValidIndexId(int64 index_id); + static bool ValidIds(int64 database_id, + int64 object_store_id, + int64 index_id) { + return IsValidDatabaseId(database_id) && + IsValidObjectStoreId(object_store_id) && IsValidIndexId(index_id); + } + static bool ValidIds(int64 database_id, int64 object_store_id) { + return IsValidDatabaseId(database_id) && + IsValidObjectStoreId(object_store_id); + } + + Type type() const; + + int64 database_id_; + int64 object_store_id_; + int64 index_id_; + + static const int64 kInvalidId = -1; + + private: + static std::vector<char> EncodeInternal(int64 database_id, + int64 object_store_id, + int64 index_id); + // Special constructor for CreateWithSpecialIndex() + KeyPrefix(enum Type, + int64 database_id, + int64 object_store_id, + int64 index_id); +}; + +class SchemaVersionKey { + public: + CONTENT_EXPORT static std::vector<char> Encode(); +}; + +class MaxDatabaseIdKey { + public: + CONTENT_EXPORT static std::vector<char> Encode(); +}; + +class DataVersionKey { + public: + static std::vector<char> Encode(); +}; + +class DatabaseFreeListKey { + public: + DatabaseFreeListKey(); + static const char* Decode(const char* start, + const char* limit, + DatabaseFreeListKey* result); + CONTENT_EXPORT static std::vector<char> Encode(int64 database_id); + static CONTENT_EXPORT std::vector<char> EncodeMaxKey(); + int64 DatabaseId() const; + int Compare(const DatabaseFreeListKey& other) const; + + private: + int64 database_id_; +}; + +class DatabaseNameKey { + public: + static const char* Decode(const char* start, + const char* limit, + DatabaseNameKey* result); + CONTENT_EXPORT static std::vector<char> Encode(const string16& origin, + const string16& database_name); + static std::vector<char> EncodeMinKeyForOrigin(const string16& origin); + static std::vector<char> EncodeStopKeyForOrigin(const string16& origin); + string16 origin() const { return origin_; } + string16 database_name() const { return database_name_; } + int Compare(const DatabaseNameKey& other); + + private: + string16 origin_; // TODO(jsbell): Store encoded strings, or just pointers. + string16 database_name_; +}; + +class DatabaseMetaDataKey { + public: + enum MetaDataType { + ORIGIN_NAME = 0, + DATABASE_NAME = 1, + USER_VERSION = 2, + MAX_OBJECT_STORE_ID = 3, + USER_INT_VERSION = 4, + MAX_SIMPLE_METADATA_TYPE = 5 + }; + + CONTENT_EXPORT static std::vector<char> Encode(int64 database_id, + MetaDataType type); +}; + +class ObjectStoreMetaDataKey { + public: + enum MetaDataType { + NAME = 0, + KEY_PATH = 1, + AUTO_INCREMENT = 2, + EVICTABLE = 3, + LAST_VERSION = 4, + MAX_INDEX_ID = 5, + HAS_KEY_PATH = 6, + KEY_GENERATOR_CURRENT_NUMBER = 7 + }; + + ObjectStoreMetaDataKey(); + static const char* Decode(const char* start, + const char* limit, + ObjectStoreMetaDataKey* result); + CONTENT_EXPORT static std::vector<char> Encode(int64 database_id, + int64 object_store_id, + unsigned char meta_data_type); + CONTENT_EXPORT static std::vector<char> EncodeMaxKey(int64 database_id); + CONTENT_EXPORT static std::vector<char> EncodeMaxKey(int64 database_id, + int64 object_store_id); + int64 ObjectStoreId() const; + unsigned char MetaDataType() const; + int Compare(const ObjectStoreMetaDataKey& other); + + private: + int64 object_store_id_; + unsigned char meta_data_type_; +}; + +class IndexMetaDataKey { + public: + enum MetaDataType { + NAME = 0, + UNIQUE = 1, + KEY_PATH = 2, + MULTI_ENTRY = 3 + }; + + IndexMetaDataKey(); + static const char* Decode(const char* start, + const char* limit, + IndexMetaDataKey* result); + CONTENT_EXPORT static std::vector<char> Encode(int64 database_id, + int64 object_store_id, + int64 index_id, + unsigned char meta_data_type); + CONTENT_EXPORT static std::vector<char> EncodeMaxKey(int64 database_id, + int64 object_store_id); + CONTENT_EXPORT static std::vector<char> EncodeMaxKey(int64 database_id, + int64 object_store_id, + int64 index_id); + int Compare(const IndexMetaDataKey& other); + int64 IndexId() const; + unsigned char meta_data_type() const { return meta_data_type_; } + + private: + int64 object_store_id_; + int64 index_id_; + unsigned char meta_data_type_; +}; + +class ObjectStoreFreeListKey { + public: + ObjectStoreFreeListKey(); + static const char* Decode(const char* start, + const char* limit, + ObjectStoreFreeListKey* result); + CONTENT_EXPORT static std::vector<char> Encode(int64 database_id, + int64 object_store_id); + CONTENT_EXPORT static std::vector<char> EncodeMaxKey(int64 database_id); + int64 ObjectStoreId() const; + int Compare(const ObjectStoreFreeListKey& other); + + private: + int64 object_store_id_; +}; + +class IndexFreeListKey { + public: + IndexFreeListKey(); + static const char* Decode(const char* start, + const char* limit, + IndexFreeListKey* result); + CONTENT_EXPORT static std::vector<char> Encode(int64 database_id, + int64 object_store_id, + int64 index_id); + CONTENT_EXPORT static std::vector<char> EncodeMaxKey(int64 database_id, + int64 object_store_id); + int Compare(const IndexFreeListKey& other); + int64 ObjectStoreId() const; + int64 IndexId() const; + + private: + int64 object_store_id_; + int64 index_id_; +}; + +class ObjectStoreNamesKey { + public: + // TODO(jsbell): We never use this to look up object store ids, + // because a mapping is kept in the IndexedDBDatabaseImpl. Can the + // mapping become unreliable? Can we remove this? + static const char* Decode(const char* start, + const char* limit, + ObjectStoreNamesKey* result); + CONTENT_EXPORT static std::vector<char> Encode( + int64 database_id, + const string16& object_store_name); + int Compare(const ObjectStoreNamesKey& other); + string16 object_store_name() const { return object_store_name_; } + + private: + string16 + object_store_name_; // TODO(jsbell): Store the encoded string, or just + // pointers to it. +}; + +class IndexNamesKey { + public: + IndexNamesKey(); + // TODO(jsbell): We never use this to look up index ids, because a mapping + // is kept at a higher level. + static const char* Decode(const char* start, + const char* limit, + IndexNamesKey* result); + CONTENT_EXPORT static std::vector<char> Encode(int64 database_id, + int64 object_store_id, + const string16& index_name); + int Compare(const IndexNamesKey& other); + string16 index_name() const { return index_name_; } + + private: + int64 object_store_id_; + string16 index_name_; +}; + +class ObjectStoreDataKey { + public: + static const char* Decode(const char* start, + const char* end, + ObjectStoreDataKey* result); + CONTENT_EXPORT static std::vector<char> Encode( + int64 database_id, + int64 object_store_id, + const std::vector<char> encoded_user_key); + static std::vector<char> Encode(int64 database_id, + int64 object_store_id, + const IndexedDBKey& user_key); + int Compare(const ObjectStoreDataKey& other, bool& ok); + scoped_ptr<IndexedDBKey> user_key() const; + static const int64 kSpecialIndexNumber; + ObjectStoreDataKey(); + ~ObjectStoreDataKey(); + + private: + std::vector<char> encoded_user_key_; +}; + +class ExistsEntryKey { + public: + ExistsEntryKey(); + ~ExistsEntryKey(); + + static const char* Decode(const char* start, + const char* end, + ExistsEntryKey* result); + CONTENT_EXPORT static std::vector<char> Encode( + int64 database_id, + int64 object_store_id, + const std::vector<char>& encoded_key); + static std::vector<char> Encode(int64 database_id, + int64 object_store_id, + const IndexedDBKey& user_key); + int Compare(const ExistsEntryKey& other, bool& ok); + scoped_ptr<IndexedDBKey> user_key() const; + + static const int64 kSpecialIndexNumber; + + private: + std::vector<char> encoded_user_key_; + DISALLOW_COPY_AND_ASSIGN(ExistsEntryKey); +}; + +class IndexDataKey { + public: + IndexDataKey(); + ~IndexDataKey(); + static const char* Decode(const char* start, + const char* limit, + IndexDataKey* result); + CONTENT_EXPORT static std::vector<char> Encode( + int64 database_id, + int64 object_store_id, + int64 index_id, + const std::vector<char>& encoded_user_key, + const std::vector<char>& encoded_primary_key, + int64 sequence_number = 0); + static std::vector<char> Encode(int64 database_id, + int64 object_store_id, + int64 index_id, + const IndexedDBKey& user_key); + static std::vector<char> EncodeMinKey(int64 database_id, + int64 object_store_id, + int64 index_id); + CONTENT_EXPORT static std::vector<char> EncodeMaxKey(int64 database_id, + int64 object_store_id, + int64 index_id); + int Compare(const IndexDataKey& other, bool ignore_duplicates, bool& ok); + int64 DatabaseId() const; + int64 ObjectStoreId() const; + int64 IndexId() const; + scoped_ptr<IndexedDBKey> user_key() const; + scoped_ptr<IndexedDBKey> primary_key() const; + + private: + int64 database_id_; + int64 object_store_id_; + int64 index_id_; + std::vector<char> encoded_user_key_; + std::vector<char> encoded_primary_key_; + int64 sequence_number_; + + DISALLOW_COPY_AND_ASSIGN(IndexDataKey); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_LEVELDB_CODING_H_ diff --git a/content/browser/indexed_db/indexed_db_leveldb_coding_unittest.cc b/content/browser/indexed_db/indexed_db_leveldb_coding_unittest.cc new file mode 100644 index 0000000..6bbcf9a --- /dev/null +++ b/content/browser/indexed_db/indexed_db_leveldb_coding_unittest.cc @@ -0,0 +1,755 @@ +// 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_leveldb_coding.h" + +#include <limits> +#include <string> +#include <vector> + +#include "base/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "content/browser/indexed_db/leveldb/leveldb_slice.h" +#include "content/common/indexed_db/indexed_db_key.h" +#include "content/common/indexed_db/indexed_db_key_path.h" +#include "testing/gtest/include/gtest/gtest.h" + +using WebKit::WebIDBKey; +using WebKit::WebIDBKeyPath; + +namespace content { + +namespace { + +static IndexedDBKey CreateArrayIDBKey() { + return IndexedDBKey(IndexedDBKey::KeyArray()); +} + +static IndexedDBKey CreateArrayIDBKey(const IndexedDBKey& key1) { + IndexedDBKey::KeyArray array; + array.push_back(key1); + return IndexedDBKey(array); +} + +static IndexedDBKey CreateArrayIDBKey(const IndexedDBKey& key1, + const IndexedDBKey& key2) { + IndexedDBKey::KeyArray array; + array.push_back(key1); + array.push_back(key2); + return IndexedDBKey(array); +} + +TEST(IndexedDBLevelDBCodingTest, EncodeByte) { + std::vector<char> expected; + expected.push_back(0); + unsigned char c; + + c = 0; + expected[0] = c; + EXPECT_EQ(expected, EncodeByte(c)); + + c = 1; + expected[0] = c; + EXPECT_EQ(expected, EncodeByte(c)); + + c = 255; + expected[0] = c; + EXPECT_EQ(expected, EncodeByte(c)); +} + +TEST(IndexedDBLevelDBCodingTest, DecodeByte) { + std::vector<unsigned char> test_cases; + test_cases.push_back(0); + test_cases.push_back(1); + test_cases.push_back(255); + + for (size_t i = 0; i < test_cases.size(); ++i) { + unsigned char n = test_cases[i]; + std::vector<char> v = EncodeByte(n); + + unsigned char res; + const char* p = DecodeByte(&*v.begin(), &*v.rbegin() + 1, res); + EXPECT_EQ(n, res); + EXPECT_EQ(&*v.rbegin() + 1, p); + } +} + +TEST(IndexedDBLevelDBCodingTest, EncodeBool) { + { + std::vector<char> expected; + expected.push_back(1); + EXPECT_EQ(expected, EncodeBool(true)); + } + { + std::vector<char> expected; + expected.push_back(0); + EXPECT_EQ(expected, EncodeBool(false)); + } +} + +static int CompareKeys(const std::vector<char>& a, const std::vector<char>& b) { + bool ok; + int result = CompareEncodedIDBKeys(a, b, ok); + EXPECT_TRUE(ok); + return result; +} + +TEST(IndexedDBLevelDBCodingTest, MaxIDBKey) { + std::vector<char> max_key = MaxIDBKey(); + + std::vector<char> min_key = MinIDBKey(); + std::vector<char> array_key = + EncodeIDBKey(IndexedDBKey(IndexedDBKey::KeyArray())); + std::vector<char> string_key = + EncodeIDBKey(IndexedDBKey(ASCIIToUTF16("Hello world"))); + std::vector<char> number_key = + EncodeIDBKey(IndexedDBKey(3.14, WebIDBKey::NumberType)); + std::vector<char> date_key = + EncodeIDBKey(IndexedDBKey(1000000, WebIDBKey::DateType)); + + EXPECT_GT(CompareKeys(max_key, min_key), 0); + EXPECT_GT(CompareKeys(max_key, array_key), 0); + EXPECT_GT(CompareKeys(max_key, string_key), 0); + EXPECT_GT(CompareKeys(max_key, number_key), 0); + EXPECT_GT(CompareKeys(max_key, date_key), 0); +} + +TEST(IndexedDBLevelDBCodingTest, MinIDBKey) { + std::vector<char> min_key = MinIDBKey(); + + std::vector<char> max_key = MaxIDBKey(); + std::vector<char> array_key = + EncodeIDBKey(IndexedDBKey(IndexedDBKey::KeyArray())); + std::vector<char> string_key = + EncodeIDBKey(IndexedDBKey(ASCIIToUTF16("Hello world"))); + std::vector<char> number_key = + EncodeIDBKey(IndexedDBKey(3.14, WebIDBKey::NumberType)); + std::vector<char> date_key = + EncodeIDBKey(IndexedDBKey(1000000, WebIDBKey::DateType)); + + EXPECT_LT(CompareKeys(min_key, max_key), 0); + EXPECT_LT(CompareKeys(min_key, array_key), 0); + EXPECT_LT(CompareKeys(min_key, string_key), 0); + EXPECT_LT(CompareKeys(min_key, number_key), 0); + EXPECT_LT(CompareKeys(min_key, date_key), 0); +} + +TEST(IndexedDBLevelDBCodingTest, EncodeInt) { + EXPECT_EQ(static_cast<size_t>(1), EncodeInt(0).size()); + EXPECT_EQ(static_cast<size_t>(1), EncodeInt(1).size()); + EXPECT_EQ(static_cast<size_t>(1), EncodeInt(255).size()); + EXPECT_EQ(static_cast<size_t>(2), EncodeInt(256).size()); + EXPECT_EQ(static_cast<size_t>(4), EncodeInt(0xffffffff).size()); +#ifdef NDEBUG + EXPECT_EQ(static_cast<size_t>(8), EncodeInt(-1).size()); +#endif +} + +TEST(IndexedDBLevelDBCodingTest, DecodeBool) { + { + std::vector<char> encoded; + encoded.push_back(1); + EXPECT_TRUE(DecodeBool(&*encoded.begin(), &*encoded.rbegin() + 1)); + } + { + std::vector<char> encoded; + encoded.push_back(0); + EXPECT_FALSE(DecodeBool(&*encoded.begin(), &*encoded.rbegin() + 1)); + } +} + +TEST(IndexedDBLevelDBCodingTest, DecodeInt) { + std::vector<int64> test_cases; + test_cases.push_back(0); + test_cases.push_back(1); + test_cases.push_back(255); + test_cases.push_back(256); + test_cases.push_back(65535); + test_cases.push_back(655536); + test_cases.push_back(7711192431755665792ll); + test_cases.push_back(0x7fffffffffffffffll); +#ifdef NDEBUG + test_cases.push_back(-3); +#endif + + for (size_t i = 0; i < test_cases.size(); ++i) { + int64 n = test_cases[i]; + std::vector<char> v = EncodeInt(n); + EXPECT_EQ(n, DecodeInt(&*v.begin(), &*v.rbegin() + 1)); + } +} + +TEST(IndexedDBLevelDBCodingTest, EncodeVarInt) { + EXPECT_EQ(static_cast<size_t>(1), EncodeVarInt(0).size()); + EXPECT_EQ(static_cast<size_t>(1), EncodeVarInt(1).size()); + EXPECT_EQ(static_cast<size_t>(2), EncodeVarInt(255).size()); + EXPECT_EQ(static_cast<size_t>(2), EncodeVarInt(256).size()); + EXPECT_EQ(static_cast<size_t>(5), EncodeVarInt(0xffffffff).size()); + EXPECT_EQ(static_cast<size_t>(8), EncodeVarInt(0xfffffffffffffLL).size()); + EXPECT_EQ(static_cast<size_t>(9), EncodeVarInt(0x7fffffffffffffffLL).size()); +#ifdef NDEBUG + EXPECT_EQ(static_cast<size_t>(10), EncodeVarInt(-100).size()); +#endif +} + +TEST(IndexedDBLevelDBCodingTest, DecodeVarInt) { + std::vector<int64> test_cases; + test_cases.push_back(0); + test_cases.push_back(1); + test_cases.push_back(255); + test_cases.push_back(256); + test_cases.push_back(65535); + test_cases.push_back(655536); + test_cases.push_back(7711192431755665792ll); + test_cases.push_back(0x7fffffffffffffffll); +#ifdef NDEBUG + test_cases.push_back(-3); +#endif + + for (size_t i = 0; i < test_cases.size(); ++i) { + int64 n = test_cases[i]; + std::vector<char> v = EncodeVarInt(n); + + int64 res; + const char* p = DecodeVarInt(&*v.begin(), &*v.rbegin() + 1, res); + EXPECT_EQ(n, res); + EXPECT_EQ(&*v.rbegin() + 1, p); + + p = DecodeVarInt(&*v.begin(), &*v.rbegin(), res); + EXPECT_EQ(0, p); + p = DecodeVarInt(&*v.begin(), &*v.begin(), res); + EXPECT_EQ(0, p); + } +} + +TEST(IndexedDBLevelDBCodingTest, EncodeString) { + const char16 test_string_a[] = {'f', 'o', 'o', '\0'}; + const char16 test_string_b[] = {0xdead, 0xbeef, '\0'}; + + EXPECT_EQ(static_cast<size_t>(0), EncodeString(ASCIIToUTF16("")).size()); + EXPECT_EQ(static_cast<size_t>(2), EncodeString(ASCIIToUTF16("a")).size()); + EXPECT_EQ(static_cast<size_t>(6), EncodeString(ASCIIToUTF16("foo")).size()); + EXPECT_EQ(static_cast<size_t>(6), + EncodeString(string16(test_string_a)).size()); + EXPECT_EQ(static_cast<size_t>(4), + EncodeString(string16(test_string_b)).size()); +} + +TEST(IndexedDBLevelDBCodingTest, DecodeString) { + const char16 test_string_a[] = {'f', 'o', 'o', '\0'}; + const char16 test_string_b[] = {0xdead, 0xbeef, '\0'}; + const char empty[] = {0}; + std::vector<char> v; + + EXPECT_EQ(string16(), DecodeString(empty, empty)); + + v = EncodeString(ASCIIToUTF16("a")); + EXPECT_EQ(ASCIIToUTF16("a"), DecodeString(&*v.begin(), &*v.rbegin() + 1)); + + v = EncodeString(ASCIIToUTF16("foo")); + EXPECT_EQ(ASCIIToUTF16("foo"), DecodeString(&*v.begin(), &*v.rbegin() + 1)); + + v = EncodeString(string16(test_string_a)); + EXPECT_EQ(string16(test_string_a), + DecodeString(&*v.begin(), &*v.rbegin() + 1)); + + v = EncodeString(string16(test_string_b)); + EXPECT_EQ(string16(test_string_b), + DecodeString(&*v.begin(), &*v.rbegin() + 1)); +} + +TEST(IndexedDBLevelDBCodingTest, EncodeStringWithLength) { + const char16 test_string_a[] = {'f', 'o', 'o', '\0'}; + const char16 test_string_b[] = {0xdead, 0xbeef, '\0'}; + + EXPECT_EQ(static_cast<size_t>(1), EncodeStringWithLength(string16()).size()); + EXPECT_EQ(static_cast<size_t>(3), + EncodeStringWithLength(ASCIIToUTF16("a")).size()); + EXPECT_EQ(static_cast<size_t>(7), + EncodeStringWithLength(string16(test_string_a)).size()); + EXPECT_EQ(static_cast<size_t>(5), + EncodeStringWithLength(string16(test_string_b)).size()); +} + +TEST(IndexedDBLevelDBCodingTest, DecodeStringWithLength) { + const char16 test_string_a[] = {'f', 'o', 'o', '\0'}; + const char16 test_string_b[] = {0xdead, 0xbeef, '\0'}; + + const int kLongStringLen = 1234; + char16 long_string[kLongStringLen + 1]; + for (int i = 0; i < kLongStringLen; ++i) + long_string[i] = i; + long_string[kLongStringLen] = 0; + + std::vector<string16> test_cases; + test_cases.push_back(ASCIIToUTF16("")); + test_cases.push_back(ASCIIToUTF16("a")); + test_cases.push_back(ASCIIToUTF16("foo")); + test_cases.push_back(string16(test_string_a)); + test_cases.push_back(string16(test_string_b)); + test_cases.push_back(string16(long_string)); + + for (size_t i = 0; i < test_cases.size(); ++i) { + string16 s = test_cases[i]; + std::vector<char> v = EncodeStringWithLength(s); + string16 res; + const char* p = DecodeStringWithLength(&*v.begin(), &*v.rbegin() + 1, res); + EXPECT_EQ(s, res); + EXPECT_EQ(&*v.rbegin() + 1, p); + + EXPECT_EQ(0, DecodeStringWithLength(&*v.begin(), &*v.rbegin(), res)); + EXPECT_EQ(0, DecodeStringWithLength(&*v.begin(), &*v.begin(), res)); + } +} + +static int CompareStrings(const char* p, + const char* limit_p, + const char* q, + const char* limit_q) { + bool ok; + int result = CompareEncodedStringsWithLength(p, limit_p, q, limit_q, ok); + EXPECT_TRUE(ok); + EXPECT_EQ(p, limit_p); + EXPECT_EQ(q, limit_q); + return result; +} + +TEST(IndexedDBLevelDBCodingTest, CompareEncodedStringsWithLength) { + const char16 test_string_a[] = {0x1000, 0x1000, '\0'}; + const char16 test_string_b[] = {0x1000, 0x1000, 0x1000, '\0'}; + const char16 test_string_c[] = {0x1000, 0x1000, 0x1001, '\0'}; + const char16 test_string_d[] = {0x1001, 0x1000, 0x1000, '\0'}; + const char16 test_string_e[] = {0xd834, 0xdd1e, '\0'}; + const char16 test_string_f[] = {0xfffd, '\0'}; + + std::vector<string16> test_cases; + test_cases.push_back(ASCIIToUTF16("")); + test_cases.push_back(ASCIIToUTF16("a")); + test_cases.push_back(ASCIIToUTF16("b")); + test_cases.push_back(ASCIIToUTF16("baaa")); + test_cases.push_back(ASCIIToUTF16("baab")); + test_cases.push_back(ASCIIToUTF16("c")); + test_cases.push_back(string16(test_string_a)); + test_cases.push_back(string16(test_string_b)); + test_cases.push_back(string16(test_string_c)); + test_cases.push_back(string16(test_string_d)); + test_cases.push_back(string16(test_string_e)); + test_cases.push_back(string16(test_string_f)); + + for (size_t i = 0; i < test_cases.size() - 1; ++i) { + string16 a = test_cases[i]; + string16 b = test_cases[i + 1]; + + EXPECT_LT(a.compare(b), 0); + EXPECT_GT(b.compare(a), 0); + EXPECT_EQ(a.compare(a), 0); + EXPECT_EQ(b.compare(b), 0); + + std::vector<char> encoded_a = EncodeStringWithLength(a); + EXPECT_TRUE(encoded_a.size()); + std::vector<char> encoded_b = EncodeStringWithLength(b); + EXPECT_TRUE(encoded_a.size()); + + const char* p = &*encoded_a.begin(); + const char* limit_p = &*encoded_a.rbegin() + 1; + const char* q = &*encoded_b.begin(); + const char* limit_q = &*encoded_b.rbegin() + 1; + + EXPECT_LT(CompareStrings(p, limit_p, q, limit_q), 0); + EXPECT_GT(CompareStrings(q, limit_q, p, limit_p), 0); + EXPECT_EQ(CompareStrings(p, limit_p, p, limit_p), 0); + EXPECT_EQ(CompareStrings(q, limit_q, q, limit_q), 0); + } +} + +TEST(IndexedDBLevelDBCodingTest, EncodeDouble) { + EXPECT_EQ(static_cast<size_t>(8), EncodeDouble(0).size()); + EXPECT_EQ(static_cast<size_t>(8), EncodeDouble(3.14).size()); +} + +TEST(IndexedDBLevelDBCodingTest, DecodeDouble) { + std::vector<char> v; + const char* p; + double d; + + v = EncodeDouble(3.14); + p = DecodeDouble(&*v.begin(), &*v.rbegin() + 1, &d); + EXPECT_EQ(3.14, d); + EXPECT_EQ(&*v.rbegin() + 1, p); + + v = EncodeDouble(-3.14); + p = DecodeDouble(&*v.begin(), &*v.rbegin() + 1, &d); + EXPECT_EQ(-3.14, d); + EXPECT_EQ(&*v.rbegin() + 1, p); + + v = EncodeDouble(3.14); + p = DecodeDouble(&*v.begin(), &*v.rbegin(), &d); + EXPECT_EQ(0, p); +} + +TEST(IndexedDBLevelDBCodingTest, EncodeDecodeIDBKey) { + IndexedDBKey expected_key; + scoped_ptr<IndexedDBKey> decoded_key; + std::vector<char> v; + const char* p; + + expected_key = IndexedDBKey(1234, WebIDBKey::NumberType); + v = EncodeIDBKey(expected_key); + p = DecodeIDBKey(&*v.begin(), &*v.rbegin() + 1, &decoded_key); + EXPECT_TRUE(decoded_key->IsEqual(expected_key)); + EXPECT_EQ(&*v.rbegin() + 1, p); + EXPECT_EQ(0, DecodeIDBKey(&*v.begin(), &*v.rbegin(), &decoded_key)); + + expected_key = IndexedDBKey(ASCIIToUTF16("Hello World!")); + v = EncodeIDBKey(expected_key); + p = DecodeIDBKey(&*v.begin(), &*v.rbegin() + 1, &decoded_key); + EXPECT_TRUE(decoded_key->IsEqual(expected_key)); + EXPECT_EQ(&*v.rbegin() + 1, p); + EXPECT_EQ(0, DecodeIDBKey(&*v.begin(), &*v.rbegin(), &decoded_key)); + + expected_key = IndexedDBKey(IndexedDBKey::KeyArray()); + v = EncodeIDBKey(expected_key); + p = DecodeIDBKey(&*v.begin(), &*v.rbegin() + 1, &decoded_key); + EXPECT_TRUE(decoded_key->IsEqual(expected_key)); + EXPECT_EQ(&*v.rbegin() + 1, p); + EXPECT_EQ(0, DecodeIDBKey(&*v.begin(), &*v.rbegin(), &decoded_key)); + + expected_key = IndexedDBKey(7890, WebIDBKey::DateType); + v = EncodeIDBKey(expected_key); + p = DecodeIDBKey(&*v.begin(), &*v.rbegin() + 1, &decoded_key); + EXPECT_TRUE(decoded_key->IsEqual(expected_key)); + EXPECT_EQ(&*v.rbegin() + 1, p); + EXPECT_EQ(0, DecodeIDBKey(&*v.begin(), &*v.rbegin(), &decoded_key)); + + IndexedDBKey::KeyArray array; + array.push_back(IndexedDBKey(1234, WebIDBKey::NumberType)); + array.push_back(IndexedDBKey(ASCIIToUTF16("Hello World!"))); + array.push_back(IndexedDBKey(7890, WebIDBKey::DateType)); + expected_key = IndexedDBKey(array); + v = EncodeIDBKey(expected_key); + p = DecodeIDBKey(&*v.begin(), &*v.rbegin() + 1, &decoded_key); + EXPECT_TRUE(decoded_key->IsEqual(expected_key)); + EXPECT_EQ(&*v.rbegin() + 1, p); + EXPECT_EQ(0, DecodeIDBKey(&*v.begin(), &*v.rbegin(), &decoded_key)); +} + +TEST(IndexedDBLevelDBCodingTest, EncodeIDBKeyPath) { + const unsigned char kIDBKeyPathTypeCodedByte1 = 0; + const unsigned char kIDBKeyPathTypeCodedByte2 = 0; + { + IndexedDBKeyPath key_path; + EXPECT_EQ(key_path.type(), WebIDBKeyPath::NullType); + std::vector<char> v = EncodeIDBKeyPath(key_path); + EXPECT_EQ(v.size(), 3U); + EXPECT_EQ(v[0], kIDBKeyPathTypeCodedByte1); + EXPECT_EQ(v[1], kIDBKeyPathTypeCodedByte2); + EXPECT_EQ(v[2], WebIDBKeyPath::NullType); + } + + { + std::vector<string16> test_cases; + test_cases.push_back(string16()); + test_cases.push_back(ASCIIToUTF16("foo")); + test_cases.push_back(ASCIIToUTF16("foo.bar")); + + for (size_t i = 0; i < test_cases.size(); ++i) { + IndexedDBKeyPath key_path = IndexedDBKeyPath(test_cases[i]); + std::vector<char> v = EncodeIDBKeyPath(key_path); + EXPECT_EQ(v.size(), EncodeStringWithLength(test_cases[i]).size() + 3); + const char* p = &*v.begin(); + const char* limit = &*v.rbegin() + 1; + EXPECT_EQ(*p++, kIDBKeyPathTypeCodedByte1); + EXPECT_EQ(*p++, kIDBKeyPathTypeCodedByte2); + EXPECT_EQ(*p++, WebIDBKeyPath::StringType); + string16 string; + p = DecodeStringWithLength(p, limit, string); + EXPECT_EQ(string, test_cases[i]); + EXPECT_EQ(p, limit); + } + } + + { + std::vector<string16> test_case; + test_case.push_back(string16()); + test_case.push_back(ASCIIToUTF16("foo")); + test_case.push_back(ASCIIToUTF16("foo.bar")); + + IndexedDBKeyPath key_path(test_case); + EXPECT_EQ(key_path.type(), WebIDBKeyPath::ArrayType); + std::vector<char> v = EncodeIDBKeyPath(key_path); + const char* p = &*v.begin(); + const char* limit = &*v.rbegin() + 1; + EXPECT_EQ(*p++, kIDBKeyPathTypeCodedByte1); + EXPECT_EQ(*p++, kIDBKeyPathTypeCodedByte2); + EXPECT_EQ(*p++, WebIDBKeyPath::ArrayType); + int64 count; + p = DecodeVarInt(p, limit, count); + EXPECT_EQ(count, static_cast<int64>(test_case.size())); + for (size_t i = 0; i < static_cast<size_t>(count); ++i) { + string16 string; + p = DecodeStringWithLength(p, limit, string); + EXPECT_EQ(string, test_case[i]); + } + EXPECT_EQ(p, limit); + } +} + +TEST(IndexedDBLevelDBCodingTest, DecodeIDBKeyPath) { + const unsigned char kIDBKeyPathTypeCodedByte1 = 0; + const unsigned char kIDBKeyPathTypeCodedByte2 = 0; + const char empty[] = {0}; + { + // Legacy encoding of string key paths. + std::vector<string16> test_cases; + test_cases.push_back(string16()); + test_cases.push_back(ASCIIToUTF16("foo")); + test_cases.push_back(ASCIIToUTF16("foo.bar")); + + for (size_t i = 0; i < test_cases.size(); ++i) { + std::vector<char> v = EncodeString(test_cases[i]); + const char* begin; + const char* end; + if (!v.size()) { + begin = empty; + end = empty; + } else { + begin = &*v.begin(); + end = &*v.rbegin() + 1; + } + IndexedDBKeyPath key_path = DecodeIDBKeyPath(begin, end); + EXPECT_EQ(key_path.type(), WebIDBKeyPath::StringType); + EXPECT_EQ(test_cases[i], key_path.string()); + } + } + { + std::vector<char> v; + v.push_back(kIDBKeyPathTypeCodedByte1); + v.push_back(kIDBKeyPathTypeCodedByte2); + v.push_back(WebIDBKeyPath::NullType); + IndexedDBKeyPath key_path = DecodeIDBKeyPath(&*v.begin(), &*v.rbegin() + 1); + EXPECT_EQ(key_path.type(), WebIDBKeyPath::NullType); + EXPECT_TRUE(key_path.IsNull()); + } + { + std::vector<string16> test_cases; + test_cases.push_back(string16()); + test_cases.push_back(ASCIIToUTF16("foo")); + test_cases.push_back(ASCIIToUTF16("foo.bar")); + + for (size_t i = 0; i < test_cases.size(); ++i) { + std::vector<char> v; + v.push_back(kIDBKeyPathTypeCodedByte1); + v.push_back(kIDBKeyPathTypeCodedByte2); + v.push_back(WebIDBKeyPath::StringType); + std::vector<char> test_case = EncodeStringWithLength(test_cases[i]); + v.insert(v.end(), test_case.begin(), test_case.end()); + IndexedDBKeyPath key_path = + DecodeIDBKeyPath(&*v.begin(), &*v.rbegin() + 1); + EXPECT_EQ(key_path.type(), WebIDBKeyPath::StringType); + EXPECT_EQ(test_cases[i], key_path.string()); + } + } + { + std::vector<string16> test_case; + test_case.push_back(string16()); + test_case.push_back(ASCIIToUTF16("foo")); + test_case.push_back(ASCIIToUTF16("foo.bar")); + + std::vector<char> v; + v.push_back(kIDBKeyPathTypeCodedByte1); + v.push_back(kIDBKeyPathTypeCodedByte2); + v.push_back(WebIDBKeyPath::ArrayType); + std::vector<char> int_value = EncodeVarInt(test_case.size()); + v.insert(v.end(), int_value.begin(), int_value.end()); + for (size_t i = 0; i < test_case.size(); ++i) { + std::vector<char> test_case_value = EncodeStringWithLength(test_case[i]); + v.insert(v.end(), test_case_value.begin(), test_case_value.end()); + } + IndexedDBKeyPath key_path = DecodeIDBKeyPath(&*v.begin(), &*v.rbegin() + 1); + EXPECT_EQ(key_path.type(), WebIDBKeyPath::ArrayType); + EXPECT_EQ(key_path.array().size(), test_case.size()); + for (size_t i = 0; i < test_case.size(); ++i) + EXPECT_EQ(key_path.array()[i], test_case[i]); + } +} + +TEST(IndexedDBLevelDBCodingTest, ExtractAndCompareIDBKeys) { + std::vector<IndexedDBKey> keys; + + keys.push_back(IndexedDBKey(-10, WebIDBKey::NumberType)); + keys.push_back(IndexedDBKey(0, WebIDBKey::NumberType)); + keys.push_back(IndexedDBKey(3.14, WebIDBKey::NumberType)); + + keys.push_back(IndexedDBKey(0, WebIDBKey::DateType)); + keys.push_back(IndexedDBKey(100, WebIDBKey::DateType)); + keys.push_back(IndexedDBKey(100000, WebIDBKey::DateType)); + + keys.push_back(IndexedDBKey(ASCIIToUTF16(""))); + keys.push_back(IndexedDBKey(ASCIIToUTF16("a"))); + keys.push_back(IndexedDBKey(ASCIIToUTF16("b"))); + keys.push_back(IndexedDBKey(ASCIIToUTF16("baaa"))); + keys.push_back(IndexedDBKey(ASCIIToUTF16("baab"))); + keys.push_back(IndexedDBKey(ASCIIToUTF16("c"))); + + keys.push_back(CreateArrayIDBKey()); + keys.push_back(CreateArrayIDBKey(IndexedDBKey(0, WebIDBKey::NumberType))); + keys.push_back(CreateArrayIDBKey(IndexedDBKey(0, WebIDBKey::NumberType), + IndexedDBKey(3.14, WebIDBKey::NumberType))); + keys.push_back(CreateArrayIDBKey(IndexedDBKey(0, WebIDBKey::DateType))); + keys.push_back(CreateArrayIDBKey(IndexedDBKey(0, WebIDBKey::DateType), + IndexedDBKey(0, WebIDBKey::DateType))); + keys.push_back(CreateArrayIDBKey(IndexedDBKey(ASCIIToUTF16("")))); + keys.push_back(CreateArrayIDBKey(IndexedDBKey(ASCIIToUTF16("")), + IndexedDBKey(ASCIIToUTF16("a")))); + keys.push_back(CreateArrayIDBKey(CreateArrayIDBKey())); + keys.push_back(CreateArrayIDBKey(CreateArrayIDBKey(), CreateArrayIDBKey())); + keys.push_back(CreateArrayIDBKey(CreateArrayIDBKey(CreateArrayIDBKey()))); + keys.push_back(CreateArrayIDBKey( + CreateArrayIDBKey(CreateArrayIDBKey(CreateArrayIDBKey())))); + + for (size_t i = 0; i < keys.size() - 1; ++i) { + const IndexedDBKey& key_a = keys[i]; + const IndexedDBKey& key_b = keys[i + 1]; + + EXPECT_TRUE(key_a.IsLessThan(key_b)); + + std::vector<char> encoded_a = EncodeIDBKey(key_a); + EXPECT_TRUE(encoded_a.size()); + std::vector<char> encoded_b = EncodeIDBKey(key_b); + EXPECT_TRUE(encoded_b.size()); + + std::vector<char> extracted_a; + std::vector<char> extracted_b; + + const char* p = ExtractEncodedIDBKey( + &*encoded_a.begin(), &*encoded_a.rbegin() + 1, &extracted_a); + EXPECT_EQ(&*encoded_a.rbegin() + 1, p); + EXPECT_EQ(encoded_a, extracted_a); + + const char* q = ExtractEncodedIDBKey( + &*encoded_b.begin(), &*encoded_b.rbegin() + 1, &extracted_b); + EXPECT_EQ(&*encoded_b.rbegin() + 1, q); + EXPECT_EQ(encoded_b, extracted_b); + + EXPECT_LT(CompareKeys(extracted_a, extracted_b), 0); + EXPECT_GT(CompareKeys(extracted_b, extracted_a), 0); + EXPECT_EQ(CompareKeys(extracted_a, extracted_a), 0); + EXPECT_EQ(CompareKeys(extracted_b, extracted_b), 0); + + EXPECT_EQ(0, + ExtractEncodedIDBKey( + &*encoded_a.begin(), &*encoded_a.rbegin(), &extracted_a)); + } +} + +TEST(IndexedDBLevelDBCodingTest, ComparisonTest) { + std::vector<std::vector<char> > keys; + keys.push_back(SchemaVersionKey::Encode()); + keys.push_back(MaxDatabaseIdKey::Encode()); + keys.push_back(DatabaseFreeListKey::Encode(0)); + keys.push_back(DatabaseFreeListKey::EncodeMaxKey()); + keys.push_back(DatabaseNameKey::Encode(ASCIIToUTF16(""), ASCIIToUTF16(""))); + keys.push_back(DatabaseNameKey::Encode(ASCIIToUTF16(""), ASCIIToUTF16("a"))); + keys.push_back(DatabaseNameKey::Encode(ASCIIToUTF16("a"), ASCIIToUTF16("a"))); + keys.push_back( + DatabaseMetaDataKey::Encode(1, DatabaseMetaDataKey::ORIGIN_NAME)); + keys.push_back( + DatabaseMetaDataKey::Encode(1, DatabaseMetaDataKey::DATABASE_NAME)); + keys.push_back( + DatabaseMetaDataKey::Encode(1, DatabaseMetaDataKey::USER_VERSION)); + keys.push_back( + DatabaseMetaDataKey::Encode(1, DatabaseMetaDataKey::MAX_OBJECT_STORE_ID)); + keys.push_back( + DatabaseMetaDataKey::Encode(1, DatabaseMetaDataKey::USER_INT_VERSION)); + keys.push_back( + ObjectStoreMetaDataKey::Encode(1, 1, ObjectStoreMetaDataKey::NAME)); + keys.push_back( + ObjectStoreMetaDataKey::Encode(1, 1, ObjectStoreMetaDataKey::KEY_PATH)); + keys.push_back(ObjectStoreMetaDataKey::Encode( + 1, 1, ObjectStoreMetaDataKey::AUTO_INCREMENT)); + keys.push_back( + ObjectStoreMetaDataKey::Encode(1, 1, ObjectStoreMetaDataKey::EVICTABLE)); + keys.push_back(ObjectStoreMetaDataKey::Encode( + 1, 1, ObjectStoreMetaDataKey::LAST_VERSION)); + keys.push_back(ObjectStoreMetaDataKey::Encode( + 1, 1, ObjectStoreMetaDataKey::MAX_INDEX_ID)); + keys.push_back(ObjectStoreMetaDataKey::Encode( + 1, 1, ObjectStoreMetaDataKey::HAS_KEY_PATH)); + keys.push_back(ObjectStoreMetaDataKey::Encode( + 1, 1, ObjectStoreMetaDataKey::KEY_GENERATOR_CURRENT_NUMBER)); + keys.push_back(ObjectStoreMetaDataKey::EncodeMaxKey(1, 1)); + keys.push_back(ObjectStoreMetaDataKey::EncodeMaxKey(1, 2)); + keys.push_back(ObjectStoreMetaDataKey::EncodeMaxKey(1)); + keys.push_back(IndexMetaDataKey::Encode(1, 1, 30, IndexMetaDataKey::NAME)); + keys.push_back(IndexMetaDataKey::Encode(1, 1, 30, IndexMetaDataKey::UNIQUE)); + keys.push_back( + IndexMetaDataKey::Encode(1, 1, 30, IndexMetaDataKey::KEY_PATH)); + keys.push_back( + IndexMetaDataKey::Encode(1, 1, 30, IndexMetaDataKey::MULTI_ENTRY)); + keys.push_back(IndexMetaDataKey::Encode(1, 1, 31, 0)); + keys.push_back(IndexMetaDataKey::Encode(1, 1, 31, 1)); + keys.push_back(IndexMetaDataKey::EncodeMaxKey(1, 1, 31)); + keys.push_back(IndexMetaDataKey::EncodeMaxKey(1, 1, 32)); + keys.push_back(IndexMetaDataKey::EncodeMaxKey(1, 1)); + keys.push_back(IndexMetaDataKey::EncodeMaxKey(1, 2)); + keys.push_back(ObjectStoreFreeListKey::Encode(1, 1)); + keys.push_back(ObjectStoreFreeListKey::EncodeMaxKey(1)); + keys.push_back(IndexFreeListKey::Encode(1, 1, kMinimumIndexId)); + keys.push_back(IndexFreeListKey::EncodeMaxKey(1, 1)); + keys.push_back(IndexFreeListKey::Encode(1, 2, kMinimumIndexId)); + keys.push_back(IndexFreeListKey::EncodeMaxKey(1, 2)); + keys.push_back(ObjectStoreNamesKey::Encode(1, ASCIIToUTF16(""))); + keys.push_back(ObjectStoreNamesKey::Encode(1, ASCIIToUTF16("a"))); + keys.push_back(IndexNamesKey::Encode(1, 1, ASCIIToUTF16(""))); + keys.push_back(IndexNamesKey::Encode(1, 1, ASCIIToUTF16("a"))); + keys.push_back(IndexNamesKey::Encode(1, 2, ASCIIToUTF16("a"))); + keys.push_back(ObjectStoreDataKey::Encode(1, 1, MinIDBKey())); + keys.push_back(ObjectStoreDataKey::Encode(1, 1, MaxIDBKey())); + keys.push_back(ExistsEntryKey::Encode(1, 1, MinIDBKey())); + keys.push_back(ExistsEntryKey::Encode(1, 1, MaxIDBKey())); + keys.push_back(IndexDataKey::Encode(1, 1, 30, MinIDBKey(), MinIDBKey(), 0)); + keys.push_back(IndexDataKey::Encode(1, 1, 30, MinIDBKey(), MinIDBKey(), 1)); + keys.push_back(IndexDataKey::Encode(1, 1, 30, MinIDBKey(), MaxIDBKey(), 0)); + keys.push_back(IndexDataKey::Encode(1, 1, 30, MinIDBKey(), MaxIDBKey(), 1)); + keys.push_back(IndexDataKey::Encode(1, 1, 30, MaxIDBKey(), MinIDBKey(), 0)); + keys.push_back(IndexDataKey::Encode(1, 1, 30, MaxIDBKey(), MinIDBKey(), 1)); + keys.push_back(IndexDataKey::Encode(1, 1, 30, MaxIDBKey(), MaxIDBKey(), 0)); + keys.push_back(IndexDataKey::Encode(1, 1, 30, MaxIDBKey(), MaxIDBKey(), 1)); + keys.push_back(IndexDataKey::Encode(1, 1, 31, MinIDBKey(), MinIDBKey(), 0)); + keys.push_back(IndexDataKey::Encode(1, 2, 30, MinIDBKey(), MinIDBKey(), 0)); + keys.push_back( + IndexDataKey::EncodeMaxKey(1, 2, std::numeric_limits<int32>::max() - 1)); + + for (size_t i = 0; i < keys.size(); ++i) { + const LevelDBSlice key_a(keys[i]); + EXPECT_EQ(Compare(key_a, key_a), 0); + + for (size_t j = i + 1; j < keys.size(); ++j) { + const LevelDBSlice key_b(keys[j]); + EXPECT_LT(Compare(key_a, key_b), 0); + EXPECT_GT(Compare(key_b, key_a), 0); + } + } +} + +TEST(IndexedDBLevelDBCodingTest, EncodeVarIntVSEncodeByteTest) { + std::vector<unsigned char> test_cases; + test_cases.push_back(0); + test_cases.push_back(1); + test_cases.push_back(127); + + for (size_t i = 0; i < test_cases.size(); ++i) { + unsigned char n = test_cases[i]; + + std::vector<char> vA = EncodeByte(n); + std::vector<char> vB = EncodeVarInt(static_cast<int64>(n)); + + EXPECT_EQ(vA.size(), vB.size()); + EXPECT_EQ(*vA.begin(), *vB.begin()); + } +} + +} // namespace + +} // namespace content diff --git a/content/browser/indexed_db/indexed_db_metadata.cc b/content/browser/indexed_db/indexed_db_metadata.cc new file mode 100644 index 0000000..80d3142 --- /dev/null +++ b/content/browser/indexed_db/indexed_db_metadata.cc @@ -0,0 +1,39 @@ +// 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_metadata.h" + +namespace content { + +IndexedDBObjectStoreMetadata::IndexedDBObjectStoreMetadata( + const string16& name, + int64 id, + const IndexedDBKeyPath& key_path, + bool auto_increment, + int64 max_index_id) + : name(name), + id(id), + key_path(key_path), + auto_increment(auto_increment), + max_index_id(max_index_id) {} + +IndexedDBObjectStoreMetadata::IndexedDBObjectStoreMetadata() {} +IndexedDBObjectStoreMetadata::~IndexedDBObjectStoreMetadata() {} + +IndexedDBDatabaseMetadata::IndexedDBDatabaseMetadata() + : int_version(NO_INT_VERSION) {} +IndexedDBDatabaseMetadata::IndexedDBDatabaseMetadata(const string16& name, + int64 id, + const string16& version, + int64 int_version, + int64 max_object_store_id) + : name(name), + id(id), + version(version), + int_version(int_version), + max_object_store_id(max_object_store_id) {} + +IndexedDBDatabaseMetadata::~IndexedDBDatabaseMetadata() {} + +} // namespace content diff --git a/content/browser/indexed_db/indexed_db_metadata.h b/content/browser/indexed_db/indexed_db_metadata.h new file mode 100644 index 0000000..29f6a9f --- /dev/null +++ b/content/browser/indexed_db/indexed_db_metadata.h @@ -0,0 +1,84 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_METADATA_H_ +#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_METADATA_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/string16.h" +#include "content/common/indexed_db/indexed_db_key_path.h" + +namespace content { + +struct IndexedDBIndexMetadata { + IndexedDBIndexMetadata() {} + IndexedDBIndexMetadata(const string16& name, + int64 id, + const IndexedDBKeyPath& key_path, + bool unique, + bool multi_entry) + : name(name), + id(id), + key_path(key_path), + unique(unique), + multi_entry(multi_entry) {} + string16 name; + int64 id; + IndexedDBKeyPath key_path; + bool unique; + bool multi_entry; + + static const int64 kInvalidId = -1; +}; + +struct CONTENT_EXPORT IndexedDBObjectStoreMetadata { + IndexedDBObjectStoreMetadata(); + IndexedDBObjectStoreMetadata(const string16& name, + int64 id, + const IndexedDBKeyPath& key_path, + bool auto_increment, + int64 max_index_id); + ~IndexedDBObjectStoreMetadata(); + string16 name; + int64 id; + IndexedDBKeyPath key_path; + bool auto_increment; + int64 max_index_id; + + static const int64 kInvalidId = -1; + + typedef std::map<int64, IndexedDBIndexMetadata> IndexMap; + IndexMap indexes; +}; + +struct CONTENT_EXPORT IndexedDBDatabaseMetadata { + // TODO(jsbell): These can probably be collapsed into 0. + enum { + NO_INT_VERSION = -1, + DEFAULT_INT_VERSION = 0 + }; + + typedef std::map<int64, IndexedDBObjectStoreMetadata> ObjectStoreMap; + + IndexedDBDatabaseMetadata(); + IndexedDBDatabaseMetadata(const string16& name, + int64 id, + const string16& version, + int64 int_version, + int64 max_object_store_id); + ~IndexedDBDatabaseMetadata(); + + string16 name; + int64 id; + string16 version; + int64 int_version; + int64 max_object_store_id; + + ObjectStoreMap object_stores; +}; +} + +#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_METADATA_H_ diff --git a/content/browser/indexed_db/indexed_db_quota_client.cc b/content/browser/indexed_db/indexed_db_quota_client.cc index 25f9668..bbce16c 100644 --- a/content/browser/indexed_db/indexed_db_quota_client.cc +++ b/content/browser/indexed_db/indexed_db_quota_client.cc @@ -26,36 +26,33 @@ quota::QuotaStatusCode DeleteOriginDataOnWebKitThread( return quota::kQuotaStatusOk; } -int64 GetOriginUsageOnWebKitThread( - IndexedDBContextImpl* context, - const GURL& origin) { +int64 GetOriginUsageOnWebKitThread(IndexedDBContextImpl* context, + const GURL& origin) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); return context->GetOriginDiskUsage(origin); } -void GetAllOriginsOnWebKitThread( - IndexedDBContextImpl* context, - std::set<GURL>* origins_to_return) { +void GetAllOriginsOnWebKitThread(IndexedDBContextImpl* context, + std::set<GURL>* origins_to_return) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); std::vector<GURL> all_origins = context->GetAllOrigins(); origins_to_return->insert(all_origins.begin(), all_origins.end()); } -void DidGetOrigins( - const IndexedDBQuotaClient::GetOriginsCallback& callback, - const std::set<GURL>* origins) { +void DidGetOrigins(const IndexedDBQuotaClient::GetOriginsCallback& callback, + const std::set<GURL>* origins) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); callback.Run(*origins); } -void GetOriginsForHostOnWebKitThread( - IndexedDBContextImpl* context, - const std::string& host, - std::set<GURL>* origins_to_return) { +void GetOriginsForHostOnWebKitThread(IndexedDBContextImpl* context, + const std::string& host, + std::set<GURL>* origins_to_return) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED)); std::vector<GURL> all_origins = context->GetAllOrigins(); for (std::vector<GURL>::const_iterator iter = all_origins.begin(); - iter != all_origins.end(); ++iter) { + iter != all_origins.end(); + ++iter) { if (host == net::GetHostOrSpecFromURL(*iter)) origins_to_return->insert(*iter); } @@ -69,24 +66,17 @@ IndexedDBQuotaClient::IndexedDBQuotaClient( base::MessageLoopProxy* webkit_thread_message_loop, IndexedDBContextImpl* indexed_db_context) : webkit_thread_message_loop_(webkit_thread_message_loop), - indexed_db_context_(indexed_db_context) { -} + indexed_db_context_(indexed_db_context) {} -IndexedDBQuotaClient::~IndexedDBQuotaClient() { -} +IndexedDBQuotaClient::~IndexedDBQuotaClient() {} -QuotaClient::ID IndexedDBQuotaClient::id() const { - return kIndexedDatabase; -} +QuotaClient::ID IndexedDBQuotaClient::id() const { return kIndexedDatabase; } -void IndexedDBQuotaClient::OnQuotaManagerDestroyed() { - delete this; -} +void IndexedDBQuotaClient::OnQuotaManagerDestroyed() { delete this; } -void IndexedDBQuotaClient::GetOriginUsage( - const GURL& origin_url, - quota::StorageType type, - const GetUsageCallback& callback) { +void IndexedDBQuotaClient::GetOriginUsage(const GURL& origin_url, + quota::StorageType type, + const GetUsageCallback& callback) { DCHECK(!callback.is_null()); DCHECK(indexed_db_context_.get()); @@ -99,9 +89,8 @@ void IndexedDBQuotaClient::GetOriginUsage( base::PostTaskAndReplyWithResult( webkit_thread_message_loop_, FROM_HERE, - base::Bind(&GetOriginUsageOnWebKitThread, - indexed_db_context_, - origin_url), + base::Bind( + &GetOriginUsageOnWebKitThread, indexed_db_context_, origin_url), callback); } @@ -123,9 +112,7 @@ void IndexedDBQuotaClient::GetOriginsForType( base::Bind(&GetAllOriginsOnWebKitThread, indexed_db_context_, base::Unretained(origins_to_return)), - base::Bind(&DidGetOrigins, - callback, - base::Owned(origins_to_return))); + base::Bind(&DidGetOrigins, callback, base::Owned(origins_to_return))); } void IndexedDBQuotaClient::GetOriginsForHost( @@ -148,15 +135,12 @@ void IndexedDBQuotaClient::GetOriginsForHost( indexed_db_context_, host, base::Unretained(origins_to_return)), - base::Bind(&DidGetOrigins, - callback, - base::Owned(origins_to_return))); + base::Bind(&DidGetOrigins, callback, base::Owned(origins_to_return))); } -void IndexedDBQuotaClient::DeleteOriginData( - const GURL& origin, - quota::StorageType type, - const DeletionCallback& callback) { +void IndexedDBQuotaClient::DeleteOriginData(const GURL& origin, + quota::StorageType type, + const DeletionCallback& callback) { if (type != quota::kStorageTypeTemporary) { callback.Run(quota::kQuotaErrorNotSupported); return; @@ -165,9 +149,7 @@ void IndexedDBQuotaClient::DeleteOriginData( base::PostTaskAndReplyWithResult( webkit_thread_message_loop_, FROM_HERE, - base::Bind(&DeleteOriginDataOnWebKitThread, - indexed_db_context_, - origin), + base::Bind(&DeleteOriginDataOnWebKitThread, indexed_db_context_, origin), callback); } diff --git a/content/browser/indexed_db/indexed_db_quota_client.h b/content/browser/indexed_db/indexed_db_quota_client.h index e79f4aa..605e705 100644 --- a/content/browser/indexed_db/indexed_db_quota_client.h +++ b/content/browser/indexed_db/indexed_db_quota_client.h @@ -25,9 +25,8 @@ class IndexedDBContextImpl; class IndexedDBQuotaClient : public quota::QuotaClient, public quota::QuotaTaskObserver { public: - CONTENT_EXPORT IndexedDBQuotaClient( - base::MessageLoopProxy* tracker_thread, - IndexedDBContextImpl* indexed_db_context); + CONTENT_EXPORT IndexedDBQuotaClient(base::MessageLoopProxy* tracker_thread, + IndexedDBContextImpl* indexed_db_context); CONTENT_EXPORT virtual ~IndexedDBQuotaClient(); // QuotaClient method overrides @@ -44,6 +43,7 @@ class IndexedDBQuotaClient : public quota::QuotaClient, virtual void DeleteOriginData(const GURL& origin, quota::StorageType type, const DeletionCallback& callback) OVERRIDE; + private: scoped_refptr<base::MessageLoopProxy> webkit_thread_message_loop_; scoped_refptr<IndexedDBContextImpl> indexed_db_context_; diff --git a/content/browser/indexed_db/indexed_db_quota_client_unittest.cc b/content/browser/indexed_db/indexed_db_quota_client_unittest.cc index 0d8628ac..bcd037a 100644 --- a/content/browser/indexed_db/indexed_db_quota_client_unittest.cc +++ b/content/browser/indexed_db/indexed_db_quota_client_unittest.cc @@ -48,15 +48,15 @@ class IndexedDBQuotaClientTest : public testing::Test { io_thread_(BrowserThread::IO, &message_loop_) { browser_context_.reset(new TestBrowserContext()); idb_context_ = static_cast<IndexedDBContextImpl*>( - BrowserContext::GetDefaultStoragePartition(browser_context_.get())-> - GetIndexedDBContext()); + BrowserContext::GetDefaultStoragePartition(browser_context_.get()) + ->GetIndexedDBContext()); message_loop_.RunUntilIdle(); setup_temp_dir(); } void setup_temp_dir() { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); - base::FilePath indexeddb_dir = temp_dir_.path().Append( - IndexedDBContextImpl::kIndexedDBDirectory); + base::FilePath indexeddb_dir = + temp_dir_.path().Append(IndexedDBContextImpl::kIndexedDBDirectory); ASSERT_TRUE(file_util::CreateDirectory(indexeddb_dir)); idb_context()->set_data_path_for_testing(indexeddb_dir); } @@ -71,13 +71,13 @@ class IndexedDBQuotaClientTest : public testing::Test { base::MessageLoop::current()->RunUntilIdle(); } - int64 GetOriginUsage( - quota::QuotaClient* client, - const GURL& origin, - quota::StorageType type) { + int64 GetOriginUsage(quota::QuotaClient* client, + const GURL& origin, + quota::StorageType type) { usage_ = -1; client->GetOriginUsage( - origin, type, + origin, + type, base::Bind(&IndexedDBQuotaClientTest::OnGetOriginUsageComplete, weak_factory_.GetWeakPtr())); base::MessageLoop::current()->RunUntilIdle(); @@ -85,9 +85,8 @@ class IndexedDBQuotaClientTest : public testing::Test { return usage_; } - const std::set<GURL>& GetOriginsForType( - quota::QuotaClient* client, - quota::StorageType type) { + const std::set<GURL>& GetOriginsForType(quota::QuotaClient* client, + quota::StorageType type) { origins_.clear(); client->GetOriginsForType( type, @@ -97,13 +96,13 @@ class IndexedDBQuotaClientTest : public testing::Test { return origins_; } - const std::set<GURL>& GetOriginsForHost( - quota::QuotaClient* client, - quota::StorageType type, - const std::string& host) { + const std::set<GURL>& GetOriginsForHost(quota::QuotaClient* client, + quota::StorageType type, + const std::string& host) { origins_.clear(); client->GetOriginsForHost( - type, host, + type, + host, base::Bind(&IndexedDBQuotaClientTest::OnGetOriginsComplete, weak_factory_.GetWeakPtr())); base::MessageLoop::current()->RunUntilIdle(); @@ -114,7 +113,8 @@ class IndexedDBQuotaClientTest : public testing::Test { const GURL& origin_url) { delete_status_ = quota::kQuotaStatusUnknown; client->DeleteOriginData( - origin_url, kTemp, + origin_url, + kTemp, base::Bind(&IndexedDBQuotaClientTest::OnDeleteOriginComplete, weak_factory_.GetWeakPtr())); base::MessageLoop::current()->RunUntilIdle(); @@ -141,9 +141,7 @@ class IndexedDBQuotaClientTest : public testing::Test { } private: - void OnGetOriginUsageComplete(int64 usage) { - usage_ = usage; - } + void OnGetOriginUsageComplete(int64 usage) { usage_ = usage; } void OnGetOriginsComplete(const std::set<GURL>& origins) { origins_ = origins; @@ -169,9 +167,7 @@ class IndexedDBQuotaClientTest : public testing::Test { }; TEST_F(IndexedDBQuotaClientTest, GetOriginUsage) { - IndexedDBQuotaClient client( - base::MessageLoopProxy::current(), - idb_context()); + IndexedDBQuotaClient client(base::MessageLoopProxy::current(), idb_context()); AddFakeIndexedDB(kOriginA, 6); AddFakeIndexedDB(kOriginB, 3); @@ -188,9 +184,7 @@ TEST_F(IndexedDBQuotaClientTest, GetOriginUsage) { } TEST_F(IndexedDBQuotaClientTest, GetOriginsForHost) { - IndexedDBQuotaClient client( - base::MessageLoopProxy::current(), - idb_context()); + IndexedDBQuotaClient client(base::MessageLoopProxy::current(), idb_context()); EXPECT_EQ(kOriginA.host(), kOriginB.host()); EXPECT_NE(kOriginA.host(), kOriginOther.host()); @@ -214,9 +208,7 @@ TEST_F(IndexedDBQuotaClientTest, GetOriginsForHost) { } TEST_F(IndexedDBQuotaClientTest, GetOriginsForType) { - IndexedDBQuotaClient client( - base::MessageLoopProxy::current(), - idb_context()); + IndexedDBQuotaClient client(base::MessageLoopProxy::current(), idb_context()); EXPECT_TRUE(GetOriginsForType(&client, kTemp).empty()); EXPECT_TRUE(GetOriginsForType(&client, kPerm).empty()); @@ -230,9 +222,7 @@ TEST_F(IndexedDBQuotaClientTest, GetOriginsForType) { } TEST_F(IndexedDBQuotaClientTest, DeleteOrigin) { - IndexedDBQuotaClient client( - base::MessageLoopProxy::current(), - idb_context()); + IndexedDBQuotaClient client(base::MessageLoopProxy::current(), idb_context()); AddFakeIndexedDB(kOriginA, 1000); AddFakeIndexedDB(kOriginB, 50); diff --git a/content/browser/indexed_db/indexed_db_tracing.h b/content/browser/indexed_db/indexed_db_tracing.h new file mode 100644 index 0000000..86ddaa34b --- /dev/null +++ b/content/browser/indexed_db/indexed_db_tracing.h @@ -0,0 +1,11 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_TRACING_H_ +#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_TRACING_H_ + +#include "base/debug/trace_event.h" +#define IDB_TRACE(a) TRACE_EVENT0("IndexedDB", (a)); + +#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_TRACING_H_ diff --git a/content/browser/indexed_db/indexed_db_transaction.cc b/content/browser/indexed_db/indexed_db_transaction.cc new file mode 100644 index 0000000..b0a9946 --- /dev/null +++ b/content/browser/indexed_db/indexed_db_transaction.cc @@ -0,0 +1,308 @@ +// 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_transaction.h" + +#include <vector> +#include "base/logging.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_database_callbacks_wrapper.h" +#include "content/browser/indexed_db/indexed_db_database_impl.h" +#include "content/browser/indexed_db/indexed_db_tracing.h" +#include "content/browser/indexed_db/indexed_db_transaction_coordinator.h" +#include "third_party/WebKit/public/platform/WebIDBDatabaseException.h" + +namespace content { + +IndexedDBTransaction::TaskQueue::TaskQueue() {} +IndexedDBTransaction::TaskQueue::~TaskQueue() { clear(); } + +void IndexedDBTransaction::TaskQueue::clear() { + while (!queue_.empty()) + scoped_ptr<Operation> task(pop()); +} + +scoped_ptr<IndexedDBTransaction::Operation> +IndexedDBTransaction::TaskQueue::pop() { + DCHECK(!queue_.empty()); + scoped_ptr<Operation> task(queue_.front()); + queue_.pop(); + return task.Pass(); +} + +IndexedDBTransaction::TaskStack::TaskStack() {} +IndexedDBTransaction::TaskStack::~TaskStack() { clear(); } + +void IndexedDBTransaction::TaskStack::clear() { + while (!stack_.empty()) + scoped_ptr<Operation> task(pop()); +} + +scoped_ptr<IndexedDBTransaction::Operation> +IndexedDBTransaction::TaskStack::pop() { + DCHECK(!stack_.empty()); + scoped_ptr<Operation> task(stack_.top()); + stack_.pop(); + return task.Pass(); +} + +scoped_refptr<IndexedDBTransaction> IndexedDBTransaction::Create( + int64 id, + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> callbacks, + const std::vector<int64>& object_store_ids, + indexed_db::TransactionMode mode, + IndexedDBDatabaseImpl* database) { + std::set<int64> object_store_hash_set; + for (size_t i = 0; i < object_store_ids.size(); ++i) + object_store_hash_set.insert(object_store_ids[i]); + + return make_scoped_refptr(new IndexedDBTransaction( + id, callbacks, object_store_hash_set, mode, database)); +} + +IndexedDBTransaction::IndexedDBTransaction( + int64 id, + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> callbacks, + const std::set<int64>& object_store_ids, + indexed_db::TransactionMode mode, + IndexedDBDatabaseImpl* database) + : id_(id), + object_store_ids_(object_store_ids), + mode_(mode), + state_(UNUSED), + commit_pending_(false), + callbacks_(callbacks), + database_(database), + transaction_(database->BackingStore().get()), + pending_preemptive_events_(0) { + database_->transaction_coordinator().DidCreateTransaction(this); +} + +IndexedDBTransaction::~IndexedDBTransaction() { + // It shouldn't be possible for this object to get deleted until it's either + // complete or aborted. + DCHECK_EQ(state_, FINISHED); + DCHECK(preemptive_task_queue_.empty()); + DCHECK(task_queue_.empty()); + DCHECK(abort_task_stack_.empty()); +} + +void IndexedDBTransaction::ScheduleTask(IndexedDBDatabase::TaskType type, + Operation* task, + Operation* abort_task) { + if (state_ == FINISHED) + return; + + if (type == IndexedDBDatabase::NORMAL_TASK) + task_queue_.push(task); + else + preemptive_task_queue_.push(task); + + if (abort_task) + abort_task_stack_.push(abort_task); + + if (state_ == UNUSED) + Start(); + else if (state_ == RUNNING && !task_timer_.IsRunning()) + task_timer_.Start(FROM_HERE, + base::TimeDelta::FromSeconds(0), + this, + &IndexedDBTransaction::TaskTimerFired); +} + +void IndexedDBTransaction::Abort() { + Abort(IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, + ASCIIToUTF16("Internal error (unknown cause)"))); +} + +void IndexedDBTransaction::Abort(scoped_refptr<IndexedDBDatabaseError> error) { + IDB_TRACE("IndexedDBTransaction::abort"); + if (state_ == FINISHED) + return; + + bool was_running = state_ == RUNNING; + + // The last reference to this object may be released while performing the + // abort steps below. We therefore take a self reference to keep ourselves + // alive while executing this method. + scoped_refptr<IndexedDBTransaction> protect(this); + + state_ = FINISHED; + task_timer_.Stop(); + + if (was_running) + transaction_.Rollback(); + + // Run the abort tasks, if any. + while (!abort_task_stack_.empty()) { + scoped_ptr<Operation> task(abort_task_stack_.pop()); + task->Perform(0); + } + preemptive_task_queue_.clear(); + task_queue_.clear(); + + // Backing store resources (held via cursors) must be released + // before script callbacks are fired, as the script callbacks may + // release references and allow the backing store itself to be + // released, and order is critical. + CloseOpenCursors(); + transaction_.Reset(); + + // Transactions must also be marked as completed before the + // front-end is notified, as the transaction completion unblocks + // operations like closing connections. + database_->transaction_coordinator().DidFinishTransaction(this); +#ifndef NDEBUG + DCHECK(!database_->transaction_coordinator().IsActive(this)); +#endif + database_->TransactionFinished(this); + + if (callbacks_) + callbacks_->OnAbort(id_, error); + + database_->TransactionFinishedAndAbortFired(this); + + database_ = NULL; +} + +bool IndexedDBTransaction::IsTaskQueueEmpty() const { + return preemptive_task_queue_.empty() && task_queue_.empty(); +} + +bool IndexedDBTransaction::HasPendingTasks() const { + return pending_preemptive_events_ || !IsTaskQueueEmpty(); +} + +void IndexedDBTransaction::RegisterOpenCursor(IndexedDBCursorImpl* cursor) { + open_cursors_.insert(cursor); +} + +void IndexedDBTransaction::UnregisterOpenCursor(IndexedDBCursorImpl* cursor) { + open_cursors_.erase(cursor); +} + +void IndexedDBTransaction::Run() { + // TransactionCoordinator has started this transaction. Schedule a timer + // to process the first task. + DCHECK(state_ == START_PENDING || state_ == RUNNING); + DCHECK(!task_timer_.IsRunning()); + + task_timer_.Start(FROM_HERE, + base::TimeDelta::FromSeconds(0), + this, + &IndexedDBTransaction::TaskTimerFired); +} + +void IndexedDBTransaction::Start() { + DCHECK_EQ(state_, UNUSED); + + state_ = START_PENDING; + database_->transaction_coordinator().DidStartTransaction(this); + database_->TransactionStarted(this); +} + +void IndexedDBTransaction::Commit() { + IDB_TRACE("IndexedDBTransaction::commit"); + + // In multiprocess ports, front-end may have requested a commit but + // an abort has already been initiated asynchronously by the + // back-end. + if (state_ == FINISHED) + return; + + DCHECK(state_ == UNUSED || state_ == RUNNING); + commit_pending_ = true; + + // Front-end has requested a commit, but there may be tasks like + // create_index which are considered synchronous by the front-end + // but are processed asynchronously. + if (HasPendingTasks()) + return; + + // The last reference to this object may be released while performing the + // commit steps below. We therefore take a self reference to keep ourselves + // alive while executing this method. + scoped_refptr<IndexedDBTransaction> protect(this); + + // TODO(jsbell): Run abort tasks if commit fails? http://crbug.com/241843 + abort_task_stack_.clear(); + + bool unused = state_ == UNUSED; + state_ = FINISHED; + + bool committed = unused || transaction_.Commit(); + + // Backing store resources (held via cursors) must be released + // before script callbacks are fired, as the script callbacks may + // release references and allow the backing store itself to be + // released, and order is critical. + CloseOpenCursors(); + transaction_.Reset(); + + // Transactions must also be marked as completed before the + // front-end is notified, as the transaction completion unblocks + // operations like closing connections. + if (!unused) + database_->transaction_coordinator().DidFinishTransaction(this); + database_->TransactionFinished(this); + + if (committed) { + callbacks_->OnComplete(id_); + database_->TransactionFinishedAndCompleteFired(this); + } else { + callbacks_->OnAbort( + id_, + IndexedDBDatabaseError::Create( + WebKit::WebIDBDatabaseExceptionUnknownError, + ASCIIToUTF16("Internal error committing transaction."))); + database_->TransactionFinishedAndAbortFired(this); + } + + database_ = NULL; +} + +void IndexedDBTransaction::TaskTimerFired() { + IDB_TRACE("IndexedDBTransaction::task_timer_fired"); + DCHECK(!IsTaskQueueEmpty()); + + if (state_ == START_PENDING) { + transaction_.begin(); + state_ = RUNNING; + } + + // The last reference to this object may be released while performing the + // tasks. Take take a self reference to keep this object alive so that + // the loop termination conditions can be checked. + scoped_refptr<IndexedDBTransaction> protect(this); + + TaskQueue* task_queue = + pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_; + while (!task_queue->empty() && state_ != FINISHED) { + DCHECK_EQ(state_, RUNNING); + scoped_ptr<Operation> task(task_queue->pop()); + task->Perform(this); + + // Event itself may change which queue should be processed next. + task_queue = + pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_; + } + + // If there are no pending tasks, we haven't already committed/aborted, + // and the front-end requested a commit, it is now safe to do so. + if (!HasPendingTasks() && state_ != FINISHED && commit_pending_) + Commit(); +} + +void IndexedDBTransaction::CloseOpenCursors() { + for (std::set<IndexedDBCursorImpl*>::iterator i = open_cursors_.begin(); + i != open_cursors_.end(); + ++i) + (*i)->Close(); + open_cursors_.clear(); +} + +} // namespace content diff --git a/content/browser/indexed_db/indexed_db_transaction.h b/content/browser/indexed_db/indexed_db_transaction.h new file mode 100644 index 0000000..ca0f929 --- /dev/null +++ b/content/browser/indexed_db/indexed_db_transaction.h @@ -0,0 +1,150 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_TRANSACTION_H_ +#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_TRANSACTION_H_ + +#include <queue> +#include <set> +#include <stack> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/timer.h" +#include "content/browser/indexed_db/indexed_db_backing_store.h" +#include "content/browser/indexed_db/indexed_db_database.h" +#include "content/browser/indexed_db/indexed_db_database_error.h" + +namespace content { + +class IndexedDBDatabaseImpl; +class IndexedDBCursorImpl; +class IndexedDBDatabaseCallbacksWrapper; + +class IndexedDBTransaction : public base::RefCounted<IndexedDBTransaction> { + public: + static scoped_refptr<IndexedDBTransaction> Create( + int64 transaction_id, + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> callbacks, + const std::vector<int64>& scope, + indexed_db::TransactionMode, + IndexedDBDatabaseImpl* db); + + virtual void Abort(); + void Commit(); + + class Operation { + public: + Operation() {} + virtual ~Operation() {} + virtual void Perform(IndexedDBTransaction* transaction) = 0; + }; + + void Abort(scoped_refptr<IndexedDBDatabaseError> error); + void Run(); + indexed_db::TransactionMode mode() const { return mode_; } + const std::set<int64>& scope() const { return object_store_ids_; } + void ScheduleTask(Operation* task, Operation* abort_task = NULL) { + ScheduleTask(IndexedDBDatabase::NORMAL_TASK, task, abort_task); + } + void ScheduleTask(IndexedDBDatabase::TaskType, + Operation* task, + Operation* abort_task = NULL); + void RegisterOpenCursor(IndexedDBCursorImpl* cursor); + void UnregisterOpenCursor(IndexedDBCursorImpl* cursor); + void AddPreemptiveEvent() { pending_preemptive_events_++; } + void DidCompletePreemptiveEvent() { + pending_preemptive_events_--; + DCHECK_GE(pending_preemptive_events_, 0); + } + IndexedDBBackingStore::Transaction* BackingStoreTransaction() { + return &transaction_; + } + int64 id() const { return id_; } + + IndexedDBDatabaseImpl* database() const { return database_.get(); } + IndexedDBDatabaseCallbacksWrapper* connection() const { + return callbacks_.get(); + } + + protected: + virtual ~IndexedDBTransaction(); + friend class base::RefCounted<IndexedDBTransaction>; + + private: + IndexedDBTransaction( + int64 id, + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> callbacks, + const std::set<int64>& object_store_ids, + indexed_db::TransactionMode, + IndexedDBDatabaseImpl* db); + + enum State { + UNUSED, // Created, but no tasks yet. + START_PENDING, // Enqueued tasks, but backing store transaction not yet + // started. + RUNNING, // Backing store transaction started but not yet finished. + FINISHED, // Either aborted or committed. + }; + + void Start(); + + bool IsTaskQueueEmpty() const; + bool HasPendingTasks() const; + + void TaskTimerFired(); + void CloseOpenCursors(); + + const int64 id_; + const std::set<int64> object_store_ids_; + const indexed_db::TransactionMode mode_; + + State state_; + bool commit_pending_; + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> callbacks_; + scoped_refptr<IndexedDBDatabaseImpl> database_; + + class TaskQueue { + public: + TaskQueue(); + ~TaskQueue(); + bool empty() const { return queue_.empty(); } + void push(Operation* task) { queue_.push(task); } + scoped_ptr<Operation> pop(); + void clear(); + + private: + std::queue<Operation*> queue_; + }; + + class TaskStack { + public: + TaskStack(); + ~TaskStack(); + bool empty() const { return stack_.empty(); } + void push(Operation* task) { stack_.push(task); } + scoped_ptr<Operation> pop(); + void clear(); + + private: + std::stack<Operation*> stack_; + }; + + TaskQueue task_queue_; + TaskQueue preemptive_task_queue_; + TaskStack abort_task_stack_; + + IndexedDBBackingStore::Transaction transaction_; + + base::OneShotTimer<IndexedDBTransaction> task_timer_; + int pending_preemptive_events_; + + std::set<IndexedDBCursorImpl*> open_cursors_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_TRANSACTION_H_ diff --git a/content/browser/indexed_db/indexed_db_transaction_coordinator.cc b/content/browser/indexed_db/indexed_db_transaction_coordinator.cc new file mode 100644 index 0000000..06d6fa0 --- /dev/null +++ b/content/browser/indexed_db/indexed_db_transaction_coordinator.cc @@ -0,0 +1,135 @@ +// 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_transaction_coordinator.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "content/browser/indexed_db/indexed_db_transaction.h" + +namespace content { + +IndexedDBTransactionCoordinator::IndexedDBTransactionCoordinator() {} + +IndexedDBTransactionCoordinator::~IndexedDBTransactionCoordinator() { + // TODO(jsbell): DCHECK that all queues are empty. http://crbug.com/241821 +} + +void IndexedDBTransactionCoordinator::DidCreateTransaction( + IndexedDBTransaction* transaction) { + DCHECK(transactions_.find(transaction) == transactions_.end()); + transactions_[transaction] = transaction; +} + +void IndexedDBTransactionCoordinator::DidStartTransaction( + IndexedDBTransaction* transaction) { + DCHECK(transactions_.find(transaction) != transactions_.end()); + + queued_transactions_.insert(transaction); + ProcessStartedTransactions(); +} + +void IndexedDBTransactionCoordinator::DidFinishTransaction( + IndexedDBTransaction* transaction) { + DCHECK(transactions_.find(transaction) != transactions_.end()); + + if (queued_transactions_.has(transaction)) { + DCHECK(started_transactions_.find(transaction) == + started_transactions_.end()); + queued_transactions_.erase(transaction); + } else { + if (started_transactions_.find(transaction) != started_transactions_.end()) + started_transactions_.erase(transaction); + } + transactions_.erase(transaction); + + ProcessStartedTransactions(); +} + +#ifndef NDEBUG +// Verifies internal consistency while returning whether anything is found. +bool IndexedDBTransactionCoordinator::IsActive( + IndexedDBTransaction* transaction) { + bool found = false; + if (queued_transactions_.has(transaction)) + found = true; + if (started_transactions_.find(transaction) != started_transactions_.end()) { + DCHECK(!found); + found = true; + } + DCHECK_EQ(found, (transactions_.find(transaction) != transactions_.end())); + return found; +} +#endif + +void IndexedDBTransactionCoordinator::ProcessStartedTransactions() { + if (queued_transactions_.empty()) + return; + + DCHECK(started_transactions_.empty() || + (*started_transactions_.begin())->mode() != + indexed_db::TRANSACTION_VERSION_CHANGE); + + list_set<IndexedDBTransaction*>::const_iterator it = + queued_transactions_.begin(); + while (it != queued_transactions_.end()) { + IndexedDBTransaction* transaction = *it; + ++it; + if (CanRunTransaction(transaction)) { + queued_transactions_.erase(transaction); + started_transactions_.insert(transaction); + transaction->Run(); + } + } +} + +static bool DoScopesOverlap(const std::set<int64>& scope1, + const std::set<int64>& scope2) { + for (std::set<int64>::const_iterator it = scope1.begin(); it != scope1.end(); + ++it) { + if (scope2.find(*it) != scope2.end()) + return true; + } + return false; +} + +bool IndexedDBTransactionCoordinator::CanRunTransaction( + IndexedDBTransaction* transaction) { + DCHECK(queued_transactions_.has(transaction)); + switch (transaction->mode()) { + case indexed_db::TRANSACTION_VERSION_CHANGE: + DCHECK(queued_transactions_.size() == 1); + DCHECK(started_transactions_.empty()); + return true; + + case indexed_db::TRANSACTION_READ_ONLY: + return true; + + case indexed_db::TRANSACTION_READ_WRITE: + for (std::set<IndexedDBTransaction*>::const_iterator it = + started_transactions_.begin(); + it != started_transactions_.end(); + ++it) { + IndexedDBTransaction* other = *it; + if (other->mode() == indexed_db::TRANSACTION_READ_WRITE && + DoScopesOverlap(transaction->scope(), other->scope())) + return false; + } + for (list_set<IndexedDBTransaction*>::const_iterator it = + queued_transactions_.begin(); + *it != transaction; + ++it) { + DCHECK(it != queued_transactions_.end()); + IndexedDBTransaction* other = *it; + if (other->mode() == indexed_db::TRANSACTION_READ_WRITE && + DoScopesOverlap(transaction->scope(), other->scope())) + return false; + } + return true; + } + NOTREACHED(); + return false; +} + +} // namespace content diff --git a/content/browser/indexed_db/indexed_db_transaction_coordinator.h b/content/browser/indexed_db/indexed_db_transaction_coordinator.h new file mode 100644 index 0000000..1e01e9e --- /dev/null +++ b/content/browser/indexed_db/indexed_db_transaction_coordinator.h @@ -0,0 +1,50 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_TRANSACTION_COORDINATOR_H_ +#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_TRANSACTION_COORDINATOR_H_ + +#include <map> +#include <set> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "content/browser/indexed_db/list_set.h" + +namespace content { + +class IndexedDBTransaction; + +// Transactions are executed in the order the were created. +class IndexedDBTransactionCoordinator { + public: + IndexedDBTransactionCoordinator(); + ~IndexedDBTransactionCoordinator(); + + // Called by transactions as they start and finish. + void DidCreateTransaction(IndexedDBTransaction* transaction); + void DidStartTransaction(IndexedDBTransaction* transaction); + void DidFinishTransaction(IndexedDBTransaction* transaction); + +#ifndef NDEBUG + bool IsActive(IndexedDBTransaction* transaction); +#endif + + // TODO(jsbell): API to handle closed connections. http://crbug.com/241821 + + private: + void ProcessStartedTransactions(); + bool CanRunTransaction(IndexedDBTransaction* transaction); + + // This is just an efficient way to keep references to all transactions. + std::map<IndexedDBTransaction*, scoped_refptr<IndexedDBTransaction> > + transactions_; + // Transactions in different states are grouped below. + list_set<IndexedDBTransaction*> queued_transactions_; + std::set<IndexedDBTransaction*> started_transactions_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_TRANSACTION_COORDINATOR_H_ diff --git a/content/browser/indexed_db/indexed_db_unittest.cc b/content/browser/indexed_db/indexed_db_unittest.cc index d4a9fb9..9e90b8a 100644 --- a/content/browser/indexed_db/indexed_db_unittest.cc +++ b/content/browser/indexed_db/indexed_db_unittest.cc @@ -55,10 +55,9 @@ TEST_F(IndexedDBTest, ClearSessionOnlyDatabases) { // Create some indexedDB paths. // With the levelDB backend, these are directories. - IndexedDBContextImpl* idb_context = - static_cast<IndexedDBContextImpl*>( - BrowserContext::GetDefaultStoragePartition(&browser_context)-> - GetIndexedDBContext()); + IndexedDBContextImpl* idb_context = static_cast<IndexedDBContextImpl*>( + BrowserContext::GetDefaultStoragePartition(&browser_context) + ->GetIndexedDBContext()); // Override the storage policy with our own. idb_context->special_storage_policy_ = special_storage_policy; @@ -99,10 +98,9 @@ TEST_F(IndexedDBTest, SetForceKeepSessionState) { // Create some indexedDB paths. // With the levelDB backend, these are directories. - IndexedDBContextImpl* idb_context = - static_cast<IndexedDBContextImpl*>( - BrowserContext::GetDefaultStoragePartition(&browser_context)-> - GetIndexedDBContext()); + IndexedDBContextImpl* idb_context = static_cast<IndexedDBContextImpl*>( + BrowserContext::GetDefaultStoragePartition(&browser_context) + ->GetIndexedDBContext()); // Override the storage policy with our own. idb_context->special_storage_policy_ = special_storage_policy; @@ -128,20 +126,16 @@ TEST_F(IndexedDBTest, SetForceKeepSessionState) { EXPECT_TRUE(file_util::DirectoryExists(session_only_path)); } -class MockWebIDBDatabase : public WebKit::WebIDBDatabase -{ +class MockWebIDBDatabase : public WebKit::WebIDBDatabase { public: MockWebIDBDatabase(bool expect_force_close) - : expect_force_close_(expect_force_close), - force_close_called_(false) {} + : expect_force_close_(expect_force_close), force_close_called_(false) {} - virtual ~MockWebIDBDatabase() - { + virtual ~MockWebIDBDatabase() { EXPECT_TRUE(force_close_called_ == expect_force_close_); } - virtual void forceClose() - { + virtual void forceClose() { ASSERT_TRUE(expect_force_close_); force_close_called_ = true; } @@ -151,7 +145,6 @@ class MockWebIDBDatabase : public WebKit::WebIDBDatabase bool force_close_called_; }; - TEST_F(IndexedDBTest, ForceCloseOpenDatabasesOnDelete) { base::ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); @@ -165,10 +158,9 @@ TEST_F(IndexedDBTest, ForceCloseOpenDatabasesOnDelete) { const GURL kTestOrigin("http://test/"); - IndexedDBContextImpl* idb_context = - static_cast<IndexedDBContextImpl*>( - BrowserContext::GetDefaultStoragePartition(&browser_context)-> - GetIndexedDBContext()); + IndexedDBContextImpl* idb_context = static_cast<IndexedDBContextImpl*>( + BrowserContext::GetDefaultStoragePartition(&browser_context) + ->GetIndexedDBContext()); idb_context->quota_manager_proxy_ = NULL; idb_context->set_data_path_for_testing(temp_dir.path()); diff --git a/content/browser/indexed_db/leveldb/avltree.h b/content/browser/indexed_db/leveldb/avltree.h new file mode 100644 index 0000000..104d69b --- /dev/null +++ b/content/browser/indexed_db/leveldb/avltree.h @@ -0,0 +1,979 @@ +// 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. + +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Based on Abstract AVL Tree Template v1.5 by Walt Karas + * <http://geocities.com/wkaras/gen_cpp/avl_tree.html>. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CONTENT_BROWSER_INDEXED_DB_LEVELDB_AVLTREE_H_ +#define CONTENT_BROWSER_INDEXED_DB_LEVELDB_AVLTREE_H_ + +#include "base/logging.h" +#include "content/browser/indexed_db/leveldb/fixed_array.h" + +namespace content { + +// Here is the reference class for BSet. +// +// class BSet +// { +// public: +// +// class ANY_bitref +// { +// public: +// operator bool (); +// void operator = (bool b); +// }; +// +// // Does not have to initialize bits. +// BSet(); +// +// // Must return a valid value for index when 0 <= index < maxDepth +// ANY_bitref operator [] (unsigned index); +// +// // Set all bits to 1. +// void set(); +// +// // Set all bits to 0. +// void reset(); +// }; + +template <unsigned maxDepth> class AVLTreeDefaultBSet { + public: + bool& operator[](unsigned i) { +#if defined(ADDRESS_SANITIZER) + CHECK(i < maxDepth); +#endif + return m_data[i]; + } + void set() { + for (unsigned i = 0; i < maxDepth; ++i) + m_data[i] = true; + } + void reset() { + for (unsigned i = 0; i < maxDepth; ++i) + m_data[i] = false; + } + + private: + FixedArray<bool, maxDepth> m_data; +}; + +// How to determine maxDepth: +// d Minimum number of nodes +// 2 2 +// 3 4 +// 4 7 +// 5 12 +// 6 20 +// 7 33 +// 8 54 +// 9 88 +// 10 143 +// 11 232 +// 12 376 +// 13 609 +// 14 986 +// 15 1,596 +// 16 2,583 +// 17 4,180 +// 18 6,764 +// 19 10,945 +// 20 17,710 +// 21 28,656 +// 22 46,367 +// 23 75,024 +// 24 121,392 +// 25 196,417 +// 26 317,810 +// 27 514,228 +// 28 832,039 +// 29 1,346,268 +// 30 2,178,308 +// 31 3,524,577 +// 32 5,702,886 +// 33 9,227,464 +// 34 14,930,351 +// 35 24,157,816 +// 36 39,088,168 +// 37 63,245,985 +// 38 102,334,154 +// 39 165,580,140 +// 40 267,914,295 +// 41 433,494,436 +// 42 701,408,732 +// 43 1,134,903,169 +// 44 1,836,311,902 +// 45 2,971,215,072 +// +// E.g., if, in a particular instantiation, the maximum number of nodes in a +// tree instance is 1,000,000, the maximum depth should be 28. +// You pick 28 because MN(28) is 832,039, which is less than or equal to +// 1,000,000, and MN(29) is 1,346,268, which is strictly greater than 1,000,000. + +template <class Abstractor, + unsigned maxDepth = 32, + class BSet = AVLTreeDefaultBSet<maxDepth> > +class AVLTree { + public: + typedef typename Abstractor::key key; + typedef typename Abstractor::handle handle; + typedef typename Abstractor::size size; + + enum SearchType { + EQUAL = 1, + LESS = 2, + GREATER = 4, + LESS_EQUAL = EQUAL | LESS, + GREATER_EQUAL = EQUAL | GREATER + }; + + Abstractor& abstractor() { return abs; } + + inline handle insert(handle h); + + inline handle search(key k, SearchType st = EQUAL); + inline handle search_least(); + inline handle search_greatest(); + + inline handle remove(key k); + + inline handle subst(handle new_node); + + void purge() { abs.root = null(); } + + bool is_empty() { return abs.root == null(); } + + AVLTree() { abs.root = null(); } + + class Iterator { + public: + // Initialize depth to invalid value, to indicate iterator is + // invalid. (Depth is zero-base.) + Iterator() { depth = ~0U; } + + void start_iter(AVLTree& tree, key k, SearchType st = EQUAL) { + // Mask of high bit in an int. + const int kMaskHighBit = (int) ~((~(unsigned) 0) >> 1); + + // Save the tree that we're going to iterate through in a + // member variable. + tree_ = &tree; + + int cmp, target_cmp; + handle h = tree_->abs.root; + unsigned d = 0; + + depth = ~0U; + + if (h == null()) { + // Tree is empty. + return; + } + + if (st & LESS) { + // Key can be greater than key of starting node. + target_cmp = 1; + } else if (st & GREATER) { + // Key can be less than key of starting node. + target_cmp = -1; + } else { + // Key must be same as key of starting node. + target_cmp = 0; + } + + for (;;) { + cmp = cmp_k_n(k, h); + if (cmp == 0) { + if (st & EQUAL) { + // Equal node was sought and found as starting node. + depth = d; + break; + } + cmp = -target_cmp; + } else if (target_cmp != 0) { + if (!((cmp ^ target_cmp) & kMaskHighBit)) { + // cmp and target_cmp are both negative or both positive. + depth = d; + } + } + h = cmp < 0 ? get_lt(h) : get_gt(h); + if (h == null()) + break; + branch[d] = cmp > 0; + path_h[d++] = h; + } + } + + void start_iter_least(AVLTree& tree) { + tree_ = &tree; + + handle h = tree_->abs.root; + + depth = ~0U; + + branch.reset(); + + while (h != null()) { + if (depth != ~0U) + path_h[depth] = h; + depth++; + h = get_lt(h); + } + } + + void start_iter_greatest(AVLTree& tree) { + tree_ = &tree; + + handle h = tree_->abs.root; + + depth = ~0U; + + branch.set(); + + while (h != null()) { + if (depth != ~0U) + path_h[depth] = h; + depth++; + h = get_gt(h); + } + } + + handle operator*() { + if (depth == ~0U) + return null(); + + return depth == 0 ? tree_->abs.root : path_h[depth - 1]; + } + + void operator++() { + if (depth != ~0U) { + handle h = get_gt(**this); + if (h == null()) { + do { + if (depth == 0) { + depth = ~0U; + break; + } + depth--; + } while (branch[depth]); + } else { + branch[depth] = true; + path_h[depth++] = h; + for (;;) { + h = get_lt(h); + if (h == null()) + break; + branch[depth] = false; + path_h[depth++] = h; + } + } + } + } + + void operator--() { + if (depth != ~0U) { + handle h = get_lt(**this); + if (h == null()) { + do { + if (depth == 0) { + depth = ~0U; + break; + } + depth--; + } while (!branch[depth]); + } else { + branch[depth] = false; + path_h[depth++] = h; + for (;;) { + h = get_gt(h); + if (h == null()) + break; + branch[depth] = true; + path_h[depth++] = h; + } + } + } + } + + void operator++(int) { ++(*this); } + void operator--(int) { --(*this); } + + protected: + + // Tree being iterated over. + AVLTree* tree_; + + // Records a path into the tree. If branch[n] is true, indicates + // take greater branch from the nth node in the path, otherwise + // take the less branch. branch[0] gives branch from root, and + // so on. + BSet branch; + + // Zero-based depth of path into tree. + unsigned depth; + + // Handles of nodes in path from root to current node (returned by *). + handle path_h[maxDepth - 1]; + + int cmp_k_n(key k, handle h) { return tree_->abs.compare_key_node(k, h); } + int cmp_n_n(handle h1, handle h2) { + return tree_->abs.compare_node_node(h1, h2); + } + handle get_lt(handle h) { return tree_->abs.get_less(h); } + handle get_gt(handle h) { return tree_->abs.get_greater(h); } + handle null() { return tree_->abs.null(); } + }; + + template <typename fwd_iter> bool build(fwd_iter p, size num_nodes) { + if (num_nodes == 0) { + abs.root = null(); + return true; + } + + // Gives path to subtree being built. If branch[N] is false, branch + // less from the node at depth N, if true branch greater. + BSet branch; + + // If rem[N] is true, then for the current subtree at depth N, it's + // greater subtree has one more node than it's less subtree. + BSet rem; + + // Depth of root node of current subtree. + unsigned depth = 0; + + // Number of nodes in current subtree. + size num_sub = num_nodes; + + // The algorithm relies on a stack of nodes whose less subtree has + // been built, but whose right subtree has not yet been built. The + // stack is implemented as linked list. The nodes are linked + // together by having the "greater" handle of a node set to the + // next node in the list. "less_parent" is the handle of the first + // node in the list. + handle less_parent = null(); + + // h is root of current subtree, child is one of its children. + handle h, child; + + for (;;) { + while (num_sub > 2) { + // Subtract one for root of subtree. + num_sub--; + rem[depth] = !!(num_sub & 1); + branch[depth++] = false; + num_sub >>= 1; + } + + if (num_sub == 2) { + // Build a subtree with two nodes, slanting to greater. + // I arbitrarily chose to always have the extra node in the + // greater subtree when there is an odd number of nodes to + // split between the two subtrees. + + h = *p; + p++; + child = *p; + p++; + set_lt(child, null()); + set_gt(child, null()); + set_bf(child, 0); + set_gt(h, child); + set_lt(h, null()); + set_bf(h, 1); + } else { // num_sub == 1 + // Build a subtree with one node. + + h = *p; + p++; + set_lt(h, null()); + set_gt(h, null()); + set_bf(h, 0); + } + + while (depth) { + depth--; + if (!branch[depth]) { + // We've completed a less subtree. + break; + } + + // We've completed a greater subtree, so attach it to + // its parent (that is less than it). We pop the parent + // off the stack of less parents. + child = h; + h = less_parent; + less_parent = get_gt(h); + set_gt(h, child); + // num_sub = 2 * (num_sub - rem[depth]) + rem[depth] + 1 + num_sub <<= 1; + num_sub += 1 - rem[depth]; + if (num_sub & (num_sub - 1)) { + // num_sub is not a power of 2 + set_bf(h, 0); + } else { + // num_sub is a power of 2 + set_bf(h, 1); + } + } + + if (num_sub == num_nodes) { + // We've completed the full tree. + break; + } + + // The subtree we've completed is the less subtree of the + // next node in the sequence. + + child = h; + h = *p; + p++; + set_lt(h, child); + + // Put h into stack of less parents. + set_gt(h, less_parent); + less_parent = h; + + // Proceed to creating greater than subtree of h. + branch[depth] = true; + num_sub += rem[depth++]; + + } // end for (;;) + + abs.root = h; + + return true; + } + + protected: + + friend class Iterator; + + // Create a class whose sole purpose is to take advantage of + // the "empty member" optimization. + struct abs_plus_root : public Abstractor { + // The handle of the root element in the AVL tree. + handle root; + }; + + abs_plus_root abs; + + handle get_lt(handle h) { return abs.get_less(h); } + void set_lt(handle h, handle lh) { abs.set_less(h, lh); } + + handle get_gt(handle h) { return abs.get_greater(h); } + void set_gt(handle h, handle gh) { abs.set_greater(h, gh); } + + int get_bf(handle h) { return abs.get_balance_factor(h); } + void set_bf(handle h, int bf) { abs.set_balance_factor(h, bf); } + + int cmp_k_n(key k, handle h) { return abs.compare_key_node(k, h); } + int cmp_n_n(handle h1, handle h2) { return abs.compare_node_node(h1, h2); } + + handle null() { return abs.null(); } + + private: + + // Balances subtree, returns handle of root node of subtree + // after balancing. + handle balance(handle bal_h) { + handle deep_h; + + // Either the "greater than" or the "less than" subtree of + // this node has to be 2 levels deeper (or else it wouldn't + // need balancing). + + if (get_bf(bal_h) > 0) { + // "Greater than" subtree is deeper. + + deep_h = get_gt(bal_h); + + if (get_bf(deep_h) < 0) { + handle old_h = bal_h; + bal_h = get_lt(deep_h); + + set_gt(old_h, get_lt(bal_h)); + set_lt(deep_h, get_gt(bal_h)); + set_lt(bal_h, old_h); + set_gt(bal_h, deep_h); + + int bf = get_bf(bal_h); + if (bf != 0) { + if (bf > 0) { + set_bf(old_h, -1); + set_bf(deep_h, 0); + } else { + set_bf(deep_h, 1); + set_bf(old_h, 0); + } + set_bf(bal_h, 0); + } else { + set_bf(old_h, 0); + set_bf(deep_h, 0); + } + } else { + set_gt(bal_h, get_lt(deep_h)); + set_lt(deep_h, bal_h); + if (get_bf(deep_h) == 0) { + set_bf(deep_h, -1); + set_bf(bal_h, 1); + } else { + set_bf(deep_h, 0); + set_bf(bal_h, 0); + } + bal_h = deep_h; + } + } else { + // "Less than" subtree is deeper. + + deep_h = get_lt(bal_h); + + if (get_bf(deep_h) > 0) { + handle old_h = bal_h; + bal_h = get_gt(deep_h); + set_lt(old_h, get_gt(bal_h)); + set_gt(deep_h, get_lt(bal_h)); + set_gt(bal_h, old_h); + set_lt(bal_h, deep_h); + + int bf = get_bf(bal_h); + if (bf != 0) { + if (bf < 0) { + set_bf(old_h, 1); + set_bf(deep_h, 0); + } else { + set_bf(deep_h, -1); + set_bf(old_h, 0); + } + set_bf(bal_h, 0); + } else { + set_bf(old_h, 0); + set_bf(deep_h, 0); + } + } else { + set_lt(bal_h, get_gt(deep_h)); + set_gt(deep_h, bal_h); + if (get_bf(deep_h) == 0) { + set_bf(deep_h, 1); + set_bf(bal_h, -1); + } else { + set_bf(deep_h, 0); + set_bf(bal_h, 0); + } + bal_h = deep_h; + } + } + + return bal_h; + } + +}; + +template <class Abstractor, unsigned maxDepth, class BSet> +inline typename AVLTree<Abstractor, maxDepth, BSet>::handle +AVLTree<Abstractor, maxDepth, BSet>::insert(handle h) { + set_lt(h, null()); + set_gt(h, null()); + set_bf(h, 0); + + if (abs.root == null()) { + abs.root = h; + } else { + // Last unbalanced node encountered in search for insertion point. + handle unbal = null(); + // Parent of last unbalanced node. + handle parent_unbal = null(); + // Balance factor of last unbalanced node. + int unbal_bf; + + // Zero-based depth in tree. + unsigned depth = 0, unbal_depth = 0; + + // Records a path into the tree. If branch[n] is true, indicates + // take greater branch from the nth node in the path, otherwise + // take the less branch. branch[0] gives branch from root, and + // so on. + BSet branch; + + handle hh = abs.root; + handle parent = null(); + int cmp; + + do { + if (get_bf(hh) != 0) { + unbal = hh; + parent_unbal = parent; + unbal_depth = depth; + } + cmp = cmp_n_n(h, hh); + if (cmp == 0) { + // Duplicate key. + return hh; + } + parent = hh; + hh = cmp < 0 ? get_lt(hh) : get_gt(hh); + branch[depth++] = cmp > 0; + } while (hh != null()); + + // Add node to insert as leaf of tree. + if (cmp < 0) + set_lt(parent, h); + else + set_gt(parent, h); + + depth = unbal_depth; + + if (unbal == null()) { + hh = abs.root; + } else { + cmp = branch[depth++] ? 1 : -1; + unbal_bf = get_bf(unbal); + if (cmp < 0) + unbal_bf--; + else // cmp > 0 + unbal_bf++; + hh = cmp < 0 ? get_lt(unbal) : get_gt(unbal); + if ((unbal_bf != -2) && (unbal_bf != 2)) { + // No rebalancing of tree is necessary. + set_bf(unbal, unbal_bf); + unbal = null(); + } + } + + if (hh != null()) { + while (h != hh) { + cmp = branch[depth++] ? 1 : -1; + if (cmp < 0) { + set_bf(hh, -1); + hh = get_lt(hh); + } else { // cmp > 0 + set_bf(hh, 1); + hh = get_gt(hh); + } + } + } + + if (unbal != null()) { + unbal = balance(unbal); + if (parent_unbal == null()) { + abs.root = unbal; + } else { + depth = unbal_depth - 1; + cmp = branch[depth] ? 1 : -1; + if (cmp < 0) + set_lt(parent_unbal, unbal); + else // cmp > 0 + set_gt(parent_unbal, unbal); + } + } + } + + return h; +} + +template <class Abstractor, unsigned maxDepth, class BSet> +inline typename AVLTree<Abstractor, maxDepth, BSet>::handle +AVLTree<Abstractor, maxDepth, BSet>::search( + key k, + typename AVLTree<Abstractor, maxDepth, BSet>::SearchType st) { + const int kMaskHighBit = (int) ~((~(unsigned) 0) >> 1); + + int cmp, target_cmp; + handle match_h = null(); + handle h = abs.root; + + if (st & LESS) + target_cmp = 1; + else if (st & GREATER) + target_cmp = -1; + else + target_cmp = 0; + + while (h != null()) { + cmp = cmp_k_n(k, h); + if (cmp == 0) { + if (st & EQUAL) { + match_h = h; + break; + } + cmp = -target_cmp; + } else if (target_cmp != 0) { + if (!((cmp ^ target_cmp) & kMaskHighBit)) { + // cmp and target_cmp are both positive or both negative. + match_h = h; + } + } + h = cmp < 0 ? get_lt(h) : get_gt(h); + } + + return match_h; +} + +template <class Abstractor, unsigned maxDepth, class BSet> +inline typename AVLTree<Abstractor, maxDepth, BSet>::handle +AVLTree<Abstractor, maxDepth, BSet>::search_least() { + handle h = abs.root, parent = null(); + + while (h != null()) { + parent = h; + h = get_lt(h); + } + + return parent; +} + +template <class Abstractor, unsigned maxDepth, class BSet> +inline typename AVLTree<Abstractor, maxDepth, BSet>::handle +AVLTree<Abstractor, maxDepth, BSet>::search_greatest() { + handle h = abs.root, parent = null(); + + while (h != null()) { + parent = h; + h = get_gt(h); + } + + return parent; +} + +template <class Abstractor, unsigned maxDepth, class BSet> +inline typename AVLTree<Abstractor, maxDepth, BSet>::handle +AVLTree<Abstractor, maxDepth, BSet>::remove(key k) { + // Zero-based depth in tree. + unsigned depth = 0, rm_depth; + + // Records a path into the tree. If branch[n] is true, indicates + // take greater branch from the nth node in the path, otherwise + // take the less branch. branch[0] gives branch from root, and + // so on. + BSet branch; + + handle h = abs.root; + handle parent = null(), child; + int cmp, cmp_shortened_sub_with_path = 0; + + for (;;) { + if (h == null()) { + // No node in tree with given key. + return null(); + } + cmp = cmp_k_n(k, h); + if (cmp == 0) { + // Found node to remove. + break; + } + parent = h; + h = cmp < 0 ? get_lt(h) : get_gt(h); + branch[depth++] = cmp > 0; + cmp_shortened_sub_with_path = cmp; + } + handle rm = h; + handle parent_rm = parent; + rm_depth = depth; + + // If the node to remove is not a leaf node, we need to get a + // leaf node, or a node with a single leaf as its child, to put + // in the place of the node to remove. We will get the greatest + // node in the less subtree (of the node to remove), or the least + // node in the greater subtree. We take the leaf node from the + // deeper subtree, if there is one. + + if (get_bf(h) < 0) { + child = get_lt(h); + branch[depth] = false; + cmp = -1; + } else { + child = get_gt(h); + branch[depth] = true; + cmp = 1; + } + depth++; + + if (child != null()) { + cmp = -cmp; + do { + parent = h; + h = child; + if (cmp < 0) { + child = get_lt(h); + branch[depth] = false; + } else { + child = get_gt(h); + branch[depth] = true; + } + depth++; + } while (child != null()); + + if (parent == rm) { + // Only went through do loop once. Deleted node will be replaced + // in the tree structure by one of its immediate children. + cmp_shortened_sub_with_path = -cmp; + } else { + cmp_shortened_sub_with_path = cmp; + } + + // Get the handle of the opposite child, which may not be null. + child = cmp > 0 ? get_lt(h) : get_gt(h); + } + + if (parent == null()) { + // There were only 1 or 2 nodes in this tree. + abs.root = child; + } else if (cmp_shortened_sub_with_path < 0) { + set_lt(parent, child); + } else { + set_gt(parent, child); + } + + // "path" is the parent of the subtree being eliminated or reduced + // from a depth of 2 to 1. If "path" is the node to be removed, we + // set path to the node we're about to poke into the position of the + // node to be removed. + handle path = parent == rm ? h : parent; + + if (h != rm) { + // Poke in the replacement for the node to be removed. + set_lt(h, get_lt(rm)); + set_gt(h, get_gt(rm)); + set_bf(h, get_bf(rm)); + if (parent_rm == null()) { + abs.root = h; + } else { + depth = rm_depth - 1; + if (branch[depth]) + set_gt(parent_rm, h); + else + set_lt(parent_rm, h); + } + } + + if (path != null()) { + // Create a temporary linked list from the parent of the path node + // to the root node. + h = abs.root; + parent = null(); + depth = 0; + while (h != path) { + if (branch[depth++]) { + child = get_gt(h); + set_gt(h, parent); + } else { + child = get_lt(h); + set_lt(h, parent); + } + parent = h; + h = child; + } + + // Climb from the path node to the root node using the linked + // list, restoring the tree structure and rebalancing as necessary. + bool reduced_depth = true; + int bf; + cmp = cmp_shortened_sub_with_path; + for (;;) { + if (reduced_depth) { + bf = get_bf(h); + if (cmp < 0) + bf++; + else // cmp > 0 + bf--; + if ((bf == -2) || (bf == 2)) { + h = balance(h); + bf = get_bf(h); + } else { + set_bf(h, bf); + } + reduced_depth = (bf == 0); + } + if (parent == null()) + break; + child = h; + h = parent; + cmp = branch[--depth] ? 1 : -1; + if (cmp < 0) { + parent = get_lt(h); + set_lt(h, child); + } else { + parent = get_gt(h); + set_gt(h, child); + } + } + abs.root = h; + } + + return rm; +} + +template <class Abstractor, unsigned maxDepth, class BSet> +inline typename AVLTree<Abstractor, maxDepth, BSet>::handle +AVLTree<Abstractor, maxDepth, BSet>::subst(handle new_node) { + handle h = abs.root; + handle parent = null(); + int cmp, last_cmp; + + // Search for node already in tree with same key. + for (;;) { + if (h == null()) { + // No node in tree with same key as new node. + return null(); + } + cmp = cmp_n_n(new_node, h); + if (cmp == 0) { + // Found the node to substitute new one for. + break; + } + last_cmp = cmp; + parent = h; + h = cmp < 0 ? get_lt(h) : get_gt(h); + } + + // Copy tree housekeeping fields from node in tree to new node. + set_lt(new_node, get_lt(h)); + set_gt(new_node, get_gt(h)); + set_bf(new_node, get_bf(h)); + + if (parent == null()) { + // New node is also new root. + abs.root = new_node; + } else { + // Make parent point to new node. + if (last_cmp < 0) + set_lt(parent, new_node); + else + set_gt(parent, new_node); + } + + return h; +} + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_LEVELDB_AVLTREE_H_ diff --git a/content/browser/indexed_db/leveldb/fixed_array.h b/content/browser/indexed_db/leveldb/fixed_array.h new file mode 100644 index 0000000..8e67a63 --- /dev/null +++ b/content/browser/indexed_db/leveldb/fixed_array.h @@ -0,0 +1,62 @@ +// 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. + +/* + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CONTENT_BROWSER_INDEXED_DB_LEVELDB_FIXED_ARRAY_H_ +#define CONTENT_BROWSER_INDEXED_DB_LEVELDB_FIXED_ARRAY_H_ + +#include "base/logging.h" + +namespace content { + +template <typename T, size_t Size> class FixedArray { + public: + T& operator[](size_t i) { +#if defined(ADDRESS_SANITIZER) + CHECK(i < Size); +#endif + return m_data[i]; + } + + const T& operator[](size_t i) const { +#if defined(ADDRESS_SANITIZER) + CHECK(i < Size); +#endif + return m_data[i]; + } + + T* data() { return m_data; } + size_t size() const { return Size; } + + private: + T m_data[Size]; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_LEVELDB_FIXED_ARRAY_H_ diff --git a/content/browser/indexed_db/leveldb/leveldb_comparator.h b/content/browser/indexed_db/leveldb/leveldb_comparator.h new file mode 100644 index 0000000..920d97e --- /dev/null +++ b/content/browser/indexed_db/leveldb/leveldb_comparator.h @@ -0,0 +1,24 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_COMPARATOR_H_ +#define CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_COMPARATOR_H_ + +#include "base/string16.h" + +namespace content { + +class LevelDBSlice; + +class LevelDBComparator { + public: + virtual ~LevelDBComparator() {} + + virtual int Compare(const LevelDBSlice& a, const LevelDBSlice& b) const = 0; + virtual const char* Name() const = 0; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_COMPARATOR_H_ diff --git a/content/browser/indexed_db/leveldb/leveldb_database.cc b/content/browser/indexed_db/leveldb/leveldb_database.cc new file mode 100644 index 0000000..b843230 --- /dev/null +++ b/content/browser/indexed_db/leveldb/leveldb_database.cc @@ -0,0 +1,362 @@ +// 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/leveldb/leveldb_database.h" + +#include <string> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram.h" +#include "base/string16.h" +#include "base/sys_info.h" +#include "base/utf_string_conversions.h" +#include "content/browser/indexed_db/leveldb/leveldb_comparator.h" +#include "content/browser/indexed_db/leveldb/leveldb_iterator.h" +#include "content/browser/indexed_db/leveldb/leveldb_slice.h" +#include "content/browser/indexed_db/leveldb/leveldb_write_batch.h" +#include "third_party/leveldatabase/env_idb.h" +#include "third_party/leveldatabase/src/helpers/memenv/memenv.h" +#include "third_party/leveldatabase/src/include/leveldb/comparator.h" +#include "third_party/leveldatabase/src/include/leveldb/db.h" +#include "third_party/leveldatabase/src/include/leveldb/env.h" +#include "third_party/leveldatabase/src/include/leveldb/slice.h" + +namespace content { + +static leveldb::Slice MakeSlice(const std::vector<char>& value) { + DCHECK_GT(value.size(), static_cast<size_t>(0)); + return leveldb::Slice(&*value.begin(), value.size()); +} + +static leveldb::Slice MakeSlice(const LevelDBSlice& s) { + return leveldb::Slice(s.begin(), s.end() - s.begin()); +} + +static LevelDBSlice MakeLevelDBSlice(const leveldb::Slice& s) { + return LevelDBSlice(s.data(), s.data() + s.size()); +} + +class ComparatorAdapter : public leveldb::Comparator { + public: + explicit ComparatorAdapter(const LevelDBComparator* comparator) + : comparator_(comparator) {} + + virtual int Compare(const leveldb::Slice& a, const leveldb::Slice& b) const + OVERRIDE { + return comparator_->Compare(MakeLevelDBSlice(a), MakeLevelDBSlice(b)); + } + + virtual const char* Name() const OVERRIDE { return comparator_->Name(); } + + // TODO(jsbell): Support the methods below in the future. + virtual void FindShortestSeparator(std::string* start, + const leveldb::Slice& limit) const + OVERRIDE {} + virtual void FindShortSuccessor(std::string* key) const OVERRIDE {} + + private: + const LevelDBComparator* comparator_; +}; + +LevelDBSnapshot::LevelDBSnapshot(LevelDBDatabase* db) + : db_(db->db_.get()), snapshot_(db_->GetSnapshot()) {} + +LevelDBSnapshot::~LevelDBSnapshot() { db_->ReleaseSnapshot(snapshot_); } + +LevelDBDatabase::LevelDBDatabase() {} + +LevelDBDatabase::~LevelDBDatabase() { + // db_'s destructor uses comparator_adapter_; order of deletion is important. + db_.reset(); + comparator_adapter_.reset(); + env_.reset(); +} + +static leveldb::Status OpenDB(leveldb::Comparator* comparator, + leveldb::Env* env, + const base::FilePath& path, + leveldb::DB** db) { + leveldb::Options options; + options.comparator = comparator; + options.create_if_missing = true; + options.paranoid_checks = true; + + // Marking compression as explicitly off so snappy support can be + // compiled in for other leveldb clients without implicitly enabling + // it for IndexedDB. http://crbug.com/81384 + options.compression = leveldb::kNoCompression; + + // 20 max_open_files is the minimum LevelDB allows but its cache behaves + // poorly with less than 4 files per shard. As of this writing the latest + // leveldb (1.9) hardcodes 16 shards. See + // https://code.google.com/p/chromium/issues/detail?id=227313#c11 + options.max_open_files = 80; + options.env = env; + + // ChromiumEnv assumes UTF8, converts back to FilePath before using. + return leveldb::DB::Open(options, path.AsUTF8Unsafe(), db); +} + +bool LevelDBDatabase::Destroy(const base::FilePath& file_name) { + leveldb::Options options; + options.env = leveldb::IDBEnv(); + // ChromiumEnv assumes UTF8, converts back to FilePath before using. + const leveldb::Status s = + leveldb::DestroyDB(file_name.AsUTF8Unsafe(), options); + return s.ok(); +} + +static void HistogramFreeSpace(const char* type, + const base::FilePath& file_name) { + string16 name = ASCIIToUTF16("WebCore.IndexedDB.LevelDB.Open") + + ASCIIToUTF16(type) + ASCIIToUTF16("FreeDiskSpace"); + int64 free_disk_space_in_k_bytes = + base::SysInfo::AmountOfFreeDiskSpace(file_name) / 1024; + if (free_disk_space_in_k_bytes < 0) { + base::Histogram::FactoryGet( + "WebCore.IndexedDB.LevelDB.FreeDiskSpaceFailure", + 1, + 2 /*boundary*/, + 2 /*boundary*/ + 1, + base::HistogramBase::kUmaTargetedHistogramFlag)->Add(1 /*sample*/); + return; + } + int clamped_disk_space_k_bytes = + free_disk_space_in_k_bytes > INT_MAX ? INT_MAX + : free_disk_space_in_k_bytes; + const uint64 histogram_max = static_cast<uint64>(1e9); + COMPILE_ASSERT(histogram_max <= INT_MAX, histogram_max_too_big); + base::Histogram::FactoryGet(UTF16ToUTF8(name), + 1, + histogram_max, + 11 /*buckets*/, + base::HistogramBase::kUmaTargetedHistogramFlag) + ->Add(clamped_disk_space_k_bytes); +} + +static void HistogramLevelDBError(const char* histogram_name, + const leveldb::Status& s) { + DCHECK(!s.ok()); + enum { + LEVEL_DB_NOT_FOUND, + LEVEL_DB_CORRUPTION, + LEVEL_DB_IO_ERROR, + LEVEL_DB_OTHER, + LEVEL_DB_MAX_ERROR + }; + int leveldb_error = LEVEL_DB_OTHER; + if (s.IsNotFound()) + leveldb_error = LEVEL_DB_NOT_FOUND; + else if (s.IsCorruption()) + leveldb_error = LEVEL_DB_CORRUPTION; + else if (s.IsIOError()) + leveldb_error = LEVEL_DB_IO_ERROR; + base::Histogram::FactoryGet(histogram_name, + 1, + LEVEL_DB_MAX_ERROR, + LEVEL_DB_MAX_ERROR + 1, + base::HistogramBase::kUmaTargetedHistogramFlag) + ->Add(leveldb_error); +} + +scoped_ptr<LevelDBDatabase> LevelDBDatabase::Open( + const base::FilePath& file_name, + const LevelDBComparator* comparator) { + scoped_ptr<ComparatorAdapter> comparator_adapter( + new ComparatorAdapter(comparator)); + + leveldb::DB* db; + const leveldb::Status s = + OpenDB(comparator_adapter.get(), leveldb::IDBEnv(), file_name, &db); + + if (!s.ok()) { + HistogramLevelDBError("WebCore.IndexedDB.LevelDBOpenErrors", s); + HistogramFreeSpace("Failure", file_name); + + LOG(ERROR) << "Failed to open LevelDB database from " + << file_name.AsUTF8Unsafe() << "," << s.ToString(); + return scoped_ptr<LevelDBDatabase>(); + } + + HistogramFreeSpace("Success", file_name); + + scoped_ptr<LevelDBDatabase> result(new LevelDBDatabase); + result->db_ = make_scoped_ptr(db); + result->comparator_adapter_ = comparator_adapter.Pass(); + result->comparator_ = comparator; + + return result.Pass(); +} + +scoped_ptr<LevelDBDatabase> LevelDBDatabase::OpenInMemory( + const LevelDBComparator* comparator) { + scoped_ptr<ComparatorAdapter> comparator_adapter( + new ComparatorAdapter(comparator)); + scoped_ptr<leveldb::Env> in_memory_env(leveldb::NewMemEnv(leveldb::IDBEnv())); + + leveldb::DB* db; + const leveldb::Status s = OpenDB( + comparator_adapter.get(), in_memory_env.get(), base::FilePath(), &db); + + if (!s.ok()) { + LOG(ERROR) << "Failed to open in-memory LevelDB database: " << s.ToString(); + return scoped_ptr<LevelDBDatabase>(); + } + + scoped_ptr<LevelDBDatabase> result(new LevelDBDatabase); + result->env_ = in_memory_env.Pass(); + result->db_ = make_scoped_ptr(db); + result->comparator_adapter_ = comparator_adapter.Pass(); + result->comparator_ = comparator; + + return result.Pass(); +} + +bool LevelDBDatabase::Put(const LevelDBSlice& key, + const std::vector<char>& value) { + leveldb::WriteOptions write_options; + write_options.sync = true; + + const leveldb::Status s = + db_->Put(write_options, MakeSlice(key), MakeSlice(value)); + if (s.ok()) + return true; + LOG(ERROR) << "LevelDB put failed: " << s.ToString(); + return false; +} + +bool LevelDBDatabase::Remove(const LevelDBSlice& key) { + leveldb::WriteOptions write_options; + write_options.sync = true; + + const leveldb::Status s = db_->Delete(write_options, MakeSlice(key)); + if (s.ok()) + return true; + if (s.IsNotFound()) + return false; + LOG(ERROR) << "LevelDB remove failed: " << s.ToString(); + return false; +} + +bool LevelDBDatabase::Get(const LevelDBSlice& key, + std::vector<char>& value, + bool& found, + const LevelDBSnapshot* snapshot) { + found = false; + std::string result; + leveldb::ReadOptions read_options; + read_options.verify_checksums = true; // TODO(jsbell): Disable this if the + // performance impact is too great. + read_options.snapshot = snapshot ? snapshot->snapshot_ : 0; + + const leveldb::Status s = db_->Get(read_options, MakeSlice(key), &result); + if (s.ok()) { + found = true; + value.clear(); + value.insert(value.end(), result.begin(), result.end()); + return true; + } + if (s.IsNotFound()) + return true; + LOG(ERROR) << "LevelDB get failed: " << s.ToString(); + return false; +} + +bool LevelDBDatabase::Write(LevelDBWriteBatch& write_batch) { + leveldb::WriteOptions write_options; + write_options.sync = true; + + const leveldb::Status s = + db_->Write(write_options, write_batch.write_batch_.get()); + if (s.ok()) + return true; + HistogramLevelDBError("WebCore.IndexedDB.LevelDBWriteErrors", s); + LOG(ERROR) << "LevelDB write failed: " << s.ToString(); + return false; +} + +namespace { +class IteratorImpl : public LevelDBIterator { + public: + virtual ~IteratorImpl() {} + + virtual bool IsValid() const OVERRIDE; + virtual void SeekToLast() OVERRIDE; + virtual void Seek(const LevelDBSlice& target) OVERRIDE; + virtual void Next() OVERRIDE; + virtual void Prev() OVERRIDE; + virtual LevelDBSlice Key() const OVERRIDE; + virtual LevelDBSlice Value() const OVERRIDE; + + private: + friend class content::LevelDBDatabase; + IteratorImpl(scoped_ptr<leveldb::Iterator> iterator); + void CheckStatus(); + + scoped_ptr<leveldb::Iterator> iterator_; +}; +} + +IteratorImpl::IteratorImpl(scoped_ptr<leveldb::Iterator> it) + : iterator_(it.Pass()) {} + +void IteratorImpl::CheckStatus() { + const leveldb::Status s = iterator_->status(); + if (!s.ok()) + LOG(ERROR) << "LevelDB iterator error: " << s.ToString(); +} + +bool IteratorImpl::IsValid() const { return iterator_->Valid(); } + +void IteratorImpl::SeekToLast() { + iterator_->SeekToLast(); + CheckStatus(); +} + +void IteratorImpl::Seek(const LevelDBSlice& target) { + iterator_->Seek(MakeSlice(target)); + CheckStatus(); +} + +void IteratorImpl::Next() { + DCHECK(IsValid()); + iterator_->Next(); + CheckStatus(); +} + +void IteratorImpl::Prev() { + DCHECK(IsValid()); + iterator_->Prev(); + CheckStatus(); +} + +LevelDBSlice IteratorImpl::Key() const { + DCHECK(IsValid()); + return MakeLevelDBSlice(iterator_->key()); +} + +LevelDBSlice IteratorImpl::Value() const { + DCHECK(IsValid()); + return MakeLevelDBSlice(iterator_->value()); +} + +scoped_ptr<LevelDBIterator> LevelDBDatabase::CreateIterator( + const LevelDBSnapshot* snapshot) { + leveldb::ReadOptions read_options; + read_options.verify_checksums = true; // TODO(jsbell): Disable this if the + // performance impact is too great. + read_options.snapshot = snapshot ? snapshot->snapshot_ : 0; + scoped_ptr<leveldb::Iterator> i(db_->NewIterator(read_options)); + if (!i) // TODO(jsbell): Double check if we actually need to check this. + return scoped_ptr<LevelDBIterator>(); + return scoped_ptr<LevelDBIterator>(new IteratorImpl(i.Pass())); +} + +const LevelDBComparator* LevelDBDatabase::Comparator() const { + return comparator_; +} + +} // namespace content diff --git a/content/browser/indexed_db/leveldb/leveldb_database.h b/content/browser/indexed_db/leveldb/leveldb_database.h new file mode 100644 index 0000000..0c7427b --- /dev/null +++ b/content/browser/indexed_db/leveldb/leveldb_database.h @@ -0,0 +1,75 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_DATABASE_H_ +#define CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_DATABASE_H_ + +#include <vector> + +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/string16.h" +#include "content/common/content_export.h" + +namespace leveldb { +class Comparator; +class DB; +class Env; +class Snapshot; +} + +namespace content { + +class LevelDBComparator; +class LevelDBDatabase; +class LevelDBIterator; +class LevelDBSlice; +class LevelDBWriteBatch; + +class LevelDBSnapshot { + private: + friend class LevelDBDatabase; + friend class LevelDBTransaction; + + explicit LevelDBSnapshot(LevelDBDatabase* db); + ~LevelDBSnapshot(); + + leveldb::DB* db_; + const leveldb::Snapshot* snapshot_; +}; + +class CONTENT_EXPORT LevelDBDatabase { + public: + static scoped_ptr<LevelDBDatabase> Open(const base::FilePath& file_name, + const LevelDBComparator* comparator); + static scoped_ptr<LevelDBDatabase> OpenInMemory( + const LevelDBComparator* comparator); + static bool Destroy(const base::FilePath& file_name); + virtual ~LevelDBDatabase(); + + bool Put(const LevelDBSlice& key, const std::vector<char>& value); + bool Remove(const LevelDBSlice& key); + virtual bool Get(const LevelDBSlice& key, + std::vector<char>& value, + bool& found, + const LevelDBSnapshot* = 0); + bool Write(LevelDBWriteBatch& batch); + scoped_ptr<LevelDBIterator> CreateIterator(const LevelDBSnapshot* = 0); + const LevelDBComparator* Comparator() const; + + protected: + LevelDBDatabase(); + + private: + friend class LevelDBSnapshot; + + scoped_ptr<leveldb::Env> env_; + scoped_ptr<leveldb::Comparator> comparator_adapter_; + scoped_ptr<leveldb::DB> db_; + const LevelDBComparator* comparator_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_DATABASE_H_ diff --git a/content/browser/indexed_db/leveldb/leveldb_iterator.h b/content/browser/indexed_db/leveldb/leveldb_iterator.h new file mode 100644 index 0000000..b78747f --- /dev/null +++ b/content/browser/indexed_db/leveldb/leveldb_iterator.h @@ -0,0 +1,26 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_ITERATOR_H_ +#define CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_ITERATOR_H_ + +#include "content/browser/indexed_db/leveldb/leveldb_slice.h" + +namespace content { + +class LevelDBIterator { + public: + virtual ~LevelDBIterator() {} + virtual bool IsValid() const = 0; + virtual void SeekToLast() = 0; + virtual void Seek(const LevelDBSlice& target) = 0; + virtual void Next() = 0; + virtual void Prev() = 0; + virtual LevelDBSlice Key() const = 0; + virtual LevelDBSlice Value() const = 0; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_ITERATOR_H_ diff --git a/content/browser/indexed_db/leveldb/leveldb_slice.h b/content/browser/indexed_db/leveldb/leveldb_slice.h new file mode 100644 index 0000000..7962b69 --- /dev/null +++ b/content/browser/indexed_db/leveldb/leveldb_slice.h @@ -0,0 +1,38 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_SLICE_H_ +#define CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_SLICE_H_ + +#include <vector> + +#include "base/logging.h" + +namespace content { + +class LevelDBSlice { + public: + LevelDBSlice(const char* begin, const char* end) : begin_(begin), end_(end) { + DCHECK_GE(end_, begin_); + } + + explicit LevelDBSlice(const std::vector<char>& v) + : begin_(&*v.begin()), end_(&*v.rbegin() + 1) { + DCHECK_GT(v.size(), static_cast<size_t>(0)); + DCHECK_GE(end_, begin_); + } + + ~LevelDBSlice() {} + + const char* begin() const { return begin_; } + const char* end() const { return end_; } + + private: + const char* begin_; + const char* end_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_SLICE_H_ diff --git a/content/browser/indexed_db/leveldb/leveldb_transaction.cc b/content/browser/indexed_db/leveldb/leveldb_transaction.cc new file mode 100644 index 0000000..a8b3a3d --- /dev/null +++ b/content/browser/indexed_db/leveldb/leveldb_transaction.cc @@ -0,0 +1,490 @@ +// 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/leveldb/leveldb_transaction.h" + +#include "base/logging.h" +#include "content/browser/indexed_db/leveldb/leveldb_database.h" +#include "content/browser/indexed_db/leveldb/leveldb_slice.h" +#include "content/browser/indexed_db/leveldb/leveldb_write_batch.h" +#include "third_party/leveldatabase/src/include/leveldb/db.h" + +namespace content { + +scoped_refptr<LevelDBTransaction> LevelDBTransaction::Create( + LevelDBDatabase* db) { + return make_scoped_refptr(new LevelDBTransaction(db)); +} + +LevelDBTransaction::LevelDBTransaction(LevelDBDatabase* db) + : db_(db), snapshot_(db), comparator_(db->Comparator()), finished_(false) { + tree_.abstractor().comparator_ = comparator_; +} + +LevelDBTransaction::AVLTreeNode::AVLTreeNode() {} +LevelDBTransaction::AVLTreeNode::~AVLTreeNode() {} + +void LevelDBTransaction::ClearTree() { + TreeType::Iterator iterator; + iterator.start_iter_least(tree_); + + std::vector<AVLTreeNode*> nodes; + + while (*iterator) { + nodes.push_back(*iterator); + ++iterator; + } + tree_.purge(); + + for (size_t i = 0; i < nodes.size(); ++i) + delete nodes[i]; +} + +LevelDBTransaction::~LevelDBTransaction() { ClearTree(); } + +static void InitVector(const LevelDBSlice& slice, std::vector<char>* vector) { + vector->clear(); + vector->insert(vector->end(), slice.begin(), slice.end()); +} + +void LevelDBTransaction::Set(const LevelDBSlice& key, + const std::vector<char>& value, + bool deleted) { + DCHECK(!finished_); + bool new_node = false; + AVLTreeNode* node = tree_.search(key); + + if (!node) { + node = new AVLTreeNode; + InitVector(key, &node->key); + tree_.insert(node); + new_node = true; + } + node->value = value; + node->deleted = deleted; + + if (new_node) + NotifyIteratorsOfTreeChange(); +} + +void LevelDBTransaction::Put(const LevelDBSlice& key, + const std::vector<char>& value) { + Set(key, value, false); +} + +void LevelDBTransaction::Remove(const LevelDBSlice& key) { + Set(key, std::vector<char>(), true); +} + +bool LevelDBTransaction::Get(const LevelDBSlice& key, + std::vector<char>& value, + bool& found) { + found = false; + DCHECK(!finished_); + AVLTreeNode* node = tree_.search(key); + + if (node) { + if (node->deleted) + return true; + + value = node->value; + found = true; + return true; + } + + bool ok = db_->Get(key, value, found, &snapshot_); + if (!ok) { + DCHECK(!found); + return false; + } + return true; +} + +bool LevelDBTransaction::Commit() { + DCHECK(!finished_); + + if (tree_.is_empty()) { + finished_ = true; + return true; + } + + scoped_ptr<LevelDBWriteBatch> write_batch = LevelDBWriteBatch::Create(); + + TreeType::Iterator iterator; + iterator.start_iter_least(tree_); + + while (*iterator) { + AVLTreeNode* node = *iterator; + if (!node->deleted) + write_batch->Put(LevelDBSlice(node->key), LevelDBSlice(node->value)); + else + write_batch->Remove(LevelDBSlice(node->key)); + ++iterator; + } + + if (!db_->Write(*write_batch)) + return false; + + ClearTree(); + finished_ = true; + return true; +} + +void LevelDBTransaction::Rollback() { + DCHECK(!finished_); + finished_ = true; + ClearTree(); +} + +scoped_ptr<LevelDBIterator> LevelDBTransaction::CreateIterator() { + return TransactionIterator::Create(this).PassAs<LevelDBIterator>(); +} + +scoped_ptr<LevelDBTransaction::TreeIterator> +LevelDBTransaction::TreeIterator::Create(LevelDBTransaction* transaction) { + return make_scoped_ptr(new TreeIterator(transaction)); +} + +bool LevelDBTransaction::TreeIterator::IsValid() const { return !!*iterator_; } + +void LevelDBTransaction::TreeIterator::SeekToLast() { + iterator_.start_iter_greatest(*tree_); + if (IsValid()) + key_ = (*iterator_)->key; +} + +void LevelDBTransaction::TreeIterator::Seek(const LevelDBSlice& target) { + iterator_.start_iter(*tree_, target, TreeType::EQUAL); + if (!IsValid()) + iterator_.start_iter(*tree_, target, TreeType::GREATER); + + if (IsValid()) + key_ = (*iterator_)->key; +} + +void LevelDBTransaction::TreeIterator::Next() { + DCHECK(IsValid()); + ++iterator_; + if (IsValid()) { + DCHECK(transaction_->comparator_->Compare(LevelDBSlice((*iterator_)->key), + LevelDBSlice(key_)) > + 0); + (void) transaction_; + key_ = (*iterator_)->key; + } +} + +void LevelDBTransaction::TreeIterator::Prev() { + DCHECK(IsValid()); + --iterator_; + if (IsValid()) { + DCHECK(tree_->abstractor().comparator_->Compare( + LevelDBSlice((*iterator_)->key), LevelDBSlice(key_)) < + 0); + key_ = (*iterator_)->key; + } +} + +LevelDBSlice LevelDBTransaction::TreeIterator::Key() const { + DCHECK(IsValid()); + return LevelDBSlice(key_); +} + +LevelDBSlice LevelDBTransaction::TreeIterator::Value() const { + DCHECK(IsValid()); + DCHECK(!IsDeleted()); + return LevelDBSlice((*iterator_)->value); +} + +bool LevelDBTransaction::TreeIterator::IsDeleted() const { + DCHECK(IsValid()); + return (*iterator_)->deleted; +} + +void LevelDBTransaction::TreeIterator::Reset() { + DCHECK(IsValid()); + iterator_.start_iter(*tree_, LevelDBSlice(key_), TreeType::EQUAL); + DCHECK(IsValid()); +} + +LevelDBTransaction::TreeIterator::~TreeIterator() {} + +LevelDBTransaction::TreeIterator::TreeIterator(LevelDBTransaction* transaction) + : tree_(&transaction->tree_), transaction_(transaction) {} + +scoped_ptr<LevelDBTransaction::TransactionIterator> +LevelDBTransaction::TransactionIterator::Create( + scoped_refptr<LevelDBTransaction> transaction) { + return make_scoped_ptr(new TransactionIterator(transaction)); +} + +LevelDBTransaction::TransactionIterator::TransactionIterator( + scoped_refptr<LevelDBTransaction> transaction) + : transaction_(transaction), + comparator_(transaction_->comparator_), + tree_iterator_(TreeIterator::Create(transaction_.get())), + db_iterator_(transaction_->db_->CreateIterator(&transaction_->snapshot_)), + current_(0), + direction_(FORWARD), + tree_changed_(false) { + transaction_->RegisterIterator(this); +} + +LevelDBTransaction::TransactionIterator::~TransactionIterator() { + transaction_->UnregisterIterator(this); +} + +bool LevelDBTransaction::TransactionIterator::IsValid() const { + return !!current_; +} + +void LevelDBTransaction::TransactionIterator::SeekToLast() { + tree_iterator_->SeekToLast(); + db_iterator_->SeekToLast(); + direction_ = REVERSE; + + HandleConflictsAndDeletes(); + SetCurrentIteratorToLargestKey(); +} + +void LevelDBTransaction::TransactionIterator::Seek(const LevelDBSlice& target) { + tree_iterator_->Seek(target); + db_iterator_->Seek(target); + direction_ = FORWARD; + + HandleConflictsAndDeletes(); + SetCurrentIteratorToSmallestKey(); +} + +void LevelDBTransaction::TransactionIterator::Next() { + DCHECK(IsValid()); + if (tree_changed_) + RefreshTreeIterator(); + + if (direction_ != FORWARD) { + // Ensure the non-current iterator is positioned after Key(). + + LevelDBIterator* non_current = + (current_ == db_iterator_.get()) ? tree_iterator_.get() + : db_iterator_.get(); + + non_current->Seek(Key()); + if (non_current->IsValid() && + !comparator_->Compare(non_current->Key(), Key())) + non_current->Next(); // Take an extra step so the non-current key is + // strictly greater than Key(). + + DCHECK(!non_current->IsValid() || + comparator_->Compare(non_current->Key(), Key()) > 0); + + direction_ = FORWARD; + } + + current_->Next(); + HandleConflictsAndDeletes(); + SetCurrentIteratorToSmallestKey(); +} + +void LevelDBTransaction::TransactionIterator::Prev() { + DCHECK(IsValid()); + if (tree_changed_) + RefreshTreeIterator(); + + if (direction_ != REVERSE) { + // Ensure the non-current iterator is positioned before Key(). + + LevelDBIterator* non_current = + (current_ == db_iterator_.get()) ? tree_iterator_.get() + : db_iterator_.get(); + + non_current->Seek(Key()); + if (non_current->IsValid()) { + // Iterator is at first entry >= Key(). + // Step back once to entry < key. + // This is why we don't check for the keys being the same before + // stepping, like we do in Next() above. + non_current->Prev(); + } else { + non_current->SeekToLast(); // Iterator has no entries >= Key(). Position + // at last entry. + } + DCHECK(!non_current->IsValid() || + comparator_->Compare(non_current->Key(), Key()) < 0); + + direction_ = REVERSE; + } + + current_->Prev(); + HandleConflictsAndDeletes(); + SetCurrentIteratorToLargestKey(); +} + +LevelDBSlice LevelDBTransaction::TransactionIterator::Key() const { + DCHECK(IsValid()); + if (tree_changed_) + RefreshTreeIterator(); + return current_->Key(); +} + +LevelDBSlice LevelDBTransaction::TransactionIterator::Value() const { + DCHECK(IsValid()); + if (tree_changed_) + RefreshTreeIterator(); + return current_->Value(); +} + +void LevelDBTransaction::TransactionIterator::TreeChanged() { + tree_changed_ = true; +} + +void LevelDBTransaction::TransactionIterator::RefreshTreeIterator() const { + DCHECK(tree_changed_); + + tree_changed_ = false; + + if (tree_iterator_->IsValid() && tree_iterator_.get() == current_) { + tree_iterator_->Reset(); + return; + } + + if (db_iterator_->IsValid()) { + + // There could be new nodes in the tree that we should iterate over. + + if (direction_ == FORWARD) { + // Try to seek tree iterator to something greater than the db iterator. + tree_iterator_->Seek(db_iterator_->Key()); + if (tree_iterator_->IsValid() && + !comparator_->Compare(tree_iterator_->Key(), db_iterator_->Key())) + tree_iterator_->Next(); // If equal, take another step so the tree + // iterator is strictly greater. + } else { + // If going backward, seek to a key less than the db iterator. + DCHECK_EQ(REVERSE, direction_); + tree_iterator_->Seek(db_iterator_->Key()); + if (tree_iterator_->IsValid()) + tree_iterator_->Prev(); + } + } +} + +bool LevelDBTransaction::TransactionIterator::TreeIteratorIsLower() const { + return comparator_->Compare(tree_iterator_->Key(), db_iterator_->Key()) < 0; +} + +bool LevelDBTransaction::TransactionIterator::TreeIteratorIsHigher() const { + return comparator_->Compare(tree_iterator_->Key(), db_iterator_->Key()) > 0; +} + +void LevelDBTransaction::TransactionIterator::HandleConflictsAndDeletes() { + bool loop = true; + + while (loop) { + loop = false; + + if (tree_iterator_->IsValid() && db_iterator_->IsValid() && + !comparator_->Compare(tree_iterator_->Key(), db_iterator_->Key())) { + // For equal keys, the tree iterator takes precedence, so move the + // database iterator another step. + if (direction_ == FORWARD) + db_iterator_->Next(); + else + db_iterator_->Prev(); + } + + // Skip over delete markers in the tree iterator until it catches up with + // the db iterator. + if (tree_iterator_->IsValid() && tree_iterator_->IsDeleted()) { + if (direction_ == FORWARD && + (!db_iterator_->IsValid() || TreeIteratorIsLower())) { + tree_iterator_->Next(); + loop = true; + } else if (direction_ == REVERSE && + (!db_iterator_->IsValid() || TreeIteratorIsHigher())) { + tree_iterator_->Prev(); + loop = true; + } + } + } +} + +void +LevelDBTransaction::TransactionIterator::SetCurrentIteratorToSmallestKey() { + LevelDBIterator* smallest = 0; + + if (tree_iterator_->IsValid()) + smallest = tree_iterator_.get(); + + if (db_iterator_->IsValid()) { + if (!smallest || + comparator_->Compare(db_iterator_->Key(), smallest->Key()) < 0) + smallest = db_iterator_.get(); + } + + current_ = smallest; +} + +void LevelDBTransaction::TransactionIterator::SetCurrentIteratorToLargestKey() { + LevelDBIterator* largest = 0; + + if (tree_iterator_->IsValid()) + largest = tree_iterator_.get(); + + if (db_iterator_->IsValid()) { + if (!largest || + comparator_->Compare(db_iterator_->Key(), largest->Key()) > 0) + largest = db_iterator_.get(); + } + + current_ = largest; +} + +void LevelDBTransaction::RegisterIterator(TransactionIterator* iterator) { + DCHECK(iterators_.find(iterator) == iterators_.end()); + iterators_.insert(iterator); +} + +void LevelDBTransaction::UnregisterIterator(TransactionIterator* iterator) { + DCHECK(iterators_.find(iterator) != iterators_.end()); + iterators_.erase(iterator); +} + +void LevelDBTransaction::NotifyIteratorsOfTreeChange() { + for (std::set<TransactionIterator*>::iterator i = iterators_.begin(); + i != iterators_.end(); + ++i) { + TransactionIterator* transaction_iterator = *i; + transaction_iterator->TreeChanged(); + } +} + +scoped_ptr<LevelDBWriteOnlyTransaction> LevelDBWriteOnlyTransaction::Create( + LevelDBDatabase* db) { + return make_scoped_ptr(new LevelDBWriteOnlyTransaction(db)); +} + +LevelDBWriteOnlyTransaction::LevelDBWriteOnlyTransaction(LevelDBDatabase* db) + : db_(db), write_batch_(LevelDBWriteBatch::Create()), finished_(false) {} + +LevelDBWriteOnlyTransaction::~LevelDBWriteOnlyTransaction() { + write_batch_->Clear(); +} + +void LevelDBWriteOnlyTransaction::Remove(const LevelDBSlice& key) { + DCHECK(!finished_); + write_batch_->Remove(key); +} + +bool LevelDBWriteOnlyTransaction::Commit() { + DCHECK(!finished_); + + if (!db_->Write(*write_batch_)) + return false; + + finished_ = true; + write_batch_->Clear(); + return true; +} + +} // namespace content diff --git a/content/browser/indexed_db/leveldb/leveldb_transaction.h b/content/browser/indexed_db/leveldb/leveldb_transaction.h new file mode 100644 index 0000000..39d1a30 --- /dev/null +++ b/content/browser/indexed_db/leveldb/leveldb_transaction.h @@ -0,0 +1,179 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_TRANSACTION_H_ +#define CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_TRANSACTION_H_ + +#include <set> +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "content/browser/indexed_db/leveldb/avltree.h" +#include "content/browser/indexed_db/leveldb/leveldb_comparator.h" +#include "content/browser/indexed_db/leveldb/leveldb_database.h" +#include "content/browser/indexed_db/leveldb/leveldb_iterator.h" +#include "content/browser/indexed_db/leveldb/leveldb_slice.h" + +namespace content { + +class LevelDBWriteBatch; + +class CONTENT_EXPORT LevelDBTransaction + : public base::RefCounted<LevelDBTransaction> { + public: + static scoped_refptr<LevelDBTransaction> Create(LevelDBDatabase* db); + + void Put(const LevelDBSlice& key, const std::vector<char>& value); + void Remove(const LevelDBSlice& key); + bool Get(const LevelDBSlice& key, std::vector<char>& value, bool& found); + bool Commit(); + void Rollback(); + + scoped_ptr<LevelDBIterator> CreateIterator(); + + private: + LevelDBTransaction(LevelDBDatabase* db); + virtual ~LevelDBTransaction(); + friend class base::RefCounted<LevelDBTransaction>; + + struct AVLTreeNode { + AVLTreeNode(); + ~AVLTreeNode(); + std::vector<char> key; + std::vector<char> value; + bool deleted; + + AVLTreeNode* less; + AVLTreeNode* greater; + int balance_factor; + DISALLOW_COPY_AND_ASSIGN(AVLTreeNode); + }; + + struct AVLTreeAbstractor { + typedef AVLTreeNode* handle; + typedef size_t size; + typedef LevelDBSlice key; + + handle get_less(handle h) { return h->less; } + void set_less(handle h, handle less) { h->less = less; } + handle get_greater(handle h) { return h->greater; } + void set_greater(handle h, handle greater) { h->greater = greater; } + + int get_balance_factor(handle h) { return h->balance_factor; } + void set_balance_factor(handle h, int bf) { h->balance_factor = bf; } + + int compare_key_key(const key& ka, const key& kb) { + return comparator_->Compare(ka, kb); + } + int compare_key_node(const key& k, handle h) { + return compare_key_key(k, key(h->key)); + } + int compare_node_node(handle ha, handle hb) { + return compare_key_key(key(ha->key), key(hb->key)); + } + + static handle null() { return 0; } + + const LevelDBComparator* comparator_; + }; + + typedef AVLTree<AVLTreeAbstractor> TreeType; + + class TreeIterator : public LevelDBIterator { + public: + static scoped_ptr<TreeIterator> Create(LevelDBTransaction* transaction); + virtual ~TreeIterator(); + + virtual bool IsValid() const OVERRIDE; + virtual void SeekToLast() OVERRIDE; + virtual void Seek(const LevelDBSlice& slice) OVERRIDE; + virtual void Next() OVERRIDE; + virtual void Prev() OVERRIDE; + virtual LevelDBSlice Key() const OVERRIDE; + virtual LevelDBSlice Value() const OVERRIDE; + bool IsDeleted() const; + void Reset(); + + private: + TreeIterator(LevelDBTransaction* transaction); + mutable TreeType::Iterator iterator_; // Dereferencing this is non-const. + TreeType* tree_; + LevelDBTransaction* transaction_; + std::vector<char> key_; + }; + + class TransactionIterator : public LevelDBIterator { + public: + virtual ~TransactionIterator(); + static scoped_ptr<TransactionIterator> Create( + scoped_refptr<LevelDBTransaction> transaction); + + virtual bool IsValid() const OVERRIDE; + virtual void SeekToLast() OVERRIDE; + virtual void Seek(const LevelDBSlice& target) OVERRIDE; + virtual void Next() OVERRIDE; + virtual void Prev() OVERRIDE; + virtual LevelDBSlice Key() const OVERRIDE; + virtual LevelDBSlice Value() const OVERRIDE; + void TreeChanged(); + + private: + TransactionIterator(scoped_refptr<LevelDBTransaction> transaction); + void HandleConflictsAndDeletes(); + void SetCurrentIteratorToSmallestKey(); + void SetCurrentIteratorToLargestKey(); + void RefreshTreeIterator() const; + bool TreeIteratorIsLower() const; + bool TreeIteratorIsHigher() const; + + scoped_refptr<LevelDBTransaction> transaction_; + const LevelDBComparator* comparator_; + mutable scoped_ptr<TreeIterator> tree_iterator_; + scoped_ptr<LevelDBIterator> db_iterator_; + LevelDBIterator* current_; + + enum Direction { + FORWARD, + REVERSE + }; + Direction direction_; + mutable bool tree_changed_; + }; + + void Set(const LevelDBSlice& key, + const std::vector<char>& value, + bool deleted); + void ClearTree(); + void RegisterIterator(TransactionIterator* iterator); + void UnregisterIterator(TransactionIterator* iterator); + void NotifyIteratorsOfTreeChange(); + + LevelDBDatabase* db_; + const LevelDBSnapshot snapshot_; + const LevelDBComparator* comparator_; + TreeType tree_; + bool finished_; + std::set<TransactionIterator*> iterators_; +}; + +class LevelDBWriteOnlyTransaction { + public: + static scoped_ptr<LevelDBWriteOnlyTransaction> Create(LevelDBDatabase* db); + + ~LevelDBWriteOnlyTransaction(); + void Remove(const LevelDBSlice& key); + bool Commit(); + + private: + LevelDBWriteOnlyTransaction(LevelDBDatabase* db); + + LevelDBDatabase* db_; + scoped_ptr<LevelDBWriteBatch> write_batch_; + bool finished_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_TRANSACTION_H_ diff --git a/content/browser/indexed_db/leveldb/leveldb_unittest.cc b/content/browser/indexed_db/leveldb/leveldb_unittest.cc new file mode 100644 index 0000000..6508e09 --- /dev/null +++ b/content/browser/indexed_db/leveldb/leveldb_unittest.cc @@ -0,0 +1,193 @@ +// 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 <algorithm> +#include <cstring> +#include <vector> + +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/platform_file.h" +#include "base/string16.h" +#include "content/browser/indexed_db/leveldb/leveldb_comparator.h" +#include "content/browser/indexed_db/leveldb/leveldb_database.h" +#include "content/browser/indexed_db/leveldb/leveldb_iterator.h" +#include "content/browser/indexed_db/leveldb/leveldb_slice.h" +#include "content/browser/indexed_db/leveldb/leveldb_transaction.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace content { + +namespace { + +class SimpleComparator : public LevelDBComparator { + public: + virtual int Compare(const LevelDBSlice& a, const LevelDBSlice& b) const + OVERRIDE { + size_t len = std::min(a.end() - a.begin(), b.end() - b.begin()); + return memcmp(a.begin(), b.begin(), len); + } + virtual const char* Name() const OVERRIDE { return "temp_comparator"; } +}; + +std::vector<char> EncodeString(const std::string& s) { + std::vector<char> ret(s.size()); + for (size_t i = 0; i < s.size(); ++i) + ret[i] = s[i]; + return ret; +} + +TEST(LevelDBDatabaseTest, CorruptionTest) { + base::ScopedTempDir temp_directory; + ASSERT_TRUE(temp_directory.CreateUniqueTempDir()); + + const std::vector<char> key = EncodeString("key"); + const std::vector<char> put_value = EncodeString("value"); + std::vector<char> got_value; + SimpleComparator comparator; + + scoped_ptr<LevelDBDatabase> leveldb = + LevelDBDatabase::Open(temp_directory.path(), &comparator); + EXPECT_TRUE(leveldb); + bool success = leveldb->Put(LevelDBSlice(key), put_value); + EXPECT_TRUE(success); + leveldb.Pass(); + EXPECT_FALSE(leveldb); + + leveldb = LevelDBDatabase::Open(temp_directory.path(), &comparator); + EXPECT_TRUE(leveldb); + bool found = false; + success = leveldb->Get(LevelDBSlice(key), got_value, found); + EXPECT_TRUE(success); + EXPECT_TRUE(found); + EXPECT_EQ(put_value, got_value); + leveldb.Pass(); + EXPECT_FALSE(leveldb); + + base::FilePath file_path = temp_directory.path().AppendASCII("CURRENT"); + base::PlatformFile handle = base::CreatePlatformFile( + file_path, + base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE, + NULL, + NULL); + base::TruncatePlatformFile(handle, 0); + base::ClosePlatformFile(handle); + + leveldb = LevelDBDatabase::Open(temp_directory.path(), &comparator); + EXPECT_FALSE(leveldb); + + bool destroyed = LevelDBDatabase::Destroy(temp_directory.path()); + EXPECT_TRUE(destroyed); + + leveldb = LevelDBDatabase::Open(temp_directory.path(), &comparator); + EXPECT_TRUE(leveldb); + success = leveldb->Get(LevelDBSlice(key), got_value, found); + EXPECT_TRUE(success); + EXPECT_FALSE(found); +} + +TEST(LevelDBDatabaseTest, Transaction) { + base::ScopedTempDir temp_directory; + ASSERT_TRUE(temp_directory.CreateUniqueTempDir()); + + const std::vector<char> key = EncodeString("key"); + std::vector<char> got_value; + SimpleComparator comparator; + + scoped_ptr<LevelDBDatabase> leveldb = + LevelDBDatabase::Open(temp_directory.path(), &comparator); + EXPECT_TRUE(leveldb); + + const std::vector<char> old_value = EncodeString("value"); + bool success = leveldb->Put(LevelDBSlice(key), old_value); + EXPECT_TRUE(success); + + scoped_refptr<LevelDBTransaction> transaction = + LevelDBTransaction::Create(leveldb.get()); + + const std::vector<char> new_value = EncodeString("new value"); + success = leveldb->Put(LevelDBSlice(key), new_value); + EXPECT_TRUE(success); + + bool found = false; + success = transaction->Get(LevelDBSlice(key), got_value, found); + EXPECT_TRUE(success); + EXPECT_TRUE(found); + EXPECT_EQ( + comparator.Compare(LevelDBSlice(got_value), LevelDBSlice(old_value)), 0); + + found = false; + success = leveldb->Get(LevelDBSlice(key), got_value, found); + EXPECT_TRUE(success); + EXPECT_TRUE(found); + EXPECT_EQ( + comparator.Compare(LevelDBSlice(got_value), LevelDBSlice(new_value)), 0); + + const std::vector<char> added_key = EncodeString("added key"); + const std::vector<char> added_value = EncodeString("added value"); + success = leveldb->Put(LevelDBSlice(added_key), added_value); + EXPECT_TRUE(success); + + success = leveldb->Get(LevelDBSlice(added_key), got_value, found); + EXPECT_TRUE(success); + EXPECT_TRUE(found); + EXPECT_EQ( + comparator.Compare(LevelDBSlice(got_value), LevelDBSlice(added_value)), + 0); + + success = transaction->Get(LevelDBSlice(added_key), got_value, found); + EXPECT_TRUE(success); + EXPECT_FALSE(found); +} + +TEST(LevelDBDatabaseTest, TransactionIterator) { + base::ScopedTempDir temp_directory; + ASSERT_TRUE(temp_directory.CreateUniqueTempDir()); + + const std::vector<char> key1 = EncodeString("key1"); + const std::vector<char> value1 = EncodeString("value1"); + const std::vector<char> key2 = EncodeString("key2"); + const std::vector<char> value2 = EncodeString("value2"); + + SimpleComparator comparator; + bool success; + + scoped_ptr<LevelDBDatabase> leveldb = + LevelDBDatabase::Open(temp_directory.path(), &comparator); + EXPECT_TRUE(leveldb); + + success = leveldb->Put(LevelDBSlice(key1), value1); + EXPECT_TRUE(success); + success = leveldb->Put(LevelDBSlice(key2), value2); + EXPECT_TRUE(success); + + scoped_refptr<LevelDBTransaction> transaction = + LevelDBTransaction::Create(leveldb.get()); + + success = leveldb->Remove(LevelDBSlice(key2)); + EXPECT_TRUE(success); + + scoped_ptr<LevelDBIterator> it = transaction->CreateIterator(); + + const char empty[] = { 0 }; + it->Seek(LevelDBSlice(empty, empty)); + + EXPECT_TRUE(it->IsValid()); + EXPECT_EQ(comparator.Compare(LevelDBSlice(it->Key()), LevelDBSlice(key1)), 0); + EXPECT_EQ(comparator.Compare(it->Value(), LevelDBSlice(value1)), 0); + + it->Next(); + + EXPECT_TRUE(it->IsValid()); + EXPECT_EQ(comparator.Compare(it->Key(), LevelDBSlice(key2)), 0); + EXPECT_EQ(comparator.Compare(it->Value(), LevelDBSlice(value2)), 0); + + it->Next(); + + EXPECT_FALSE(it->IsValid()); +} + +} // namespace + +} // namespace content diff --git a/content/browser/indexed_db/leveldb/leveldb_write_batch.cc b/content/browser/indexed_db/leveldb/leveldb_write_batch.cc new file mode 100644 index 0000000..b15c4fa --- /dev/null +++ b/content/browser/indexed_db/leveldb/leveldb_write_batch.cc @@ -0,0 +1,37 @@ +// 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/leveldb/leveldb_write_batch.h" + +#include "content/browser/indexed_db/leveldb/leveldb_slice.h" +#include "third_party/leveldatabase/src/include/leveldb/slice.h" +#include "third_party/leveldatabase/src/include/leveldb/write_batch.h" + +namespace content { + +scoped_ptr<LevelDBWriteBatch> LevelDBWriteBatch::Create() { + return make_scoped_ptr(new LevelDBWriteBatch); +} + +LevelDBWriteBatch::LevelDBWriteBatch() + : write_batch_(new leveldb::WriteBatch) {} + +LevelDBWriteBatch::~LevelDBWriteBatch() {} + +static leveldb::Slice MakeSlice(const LevelDBSlice& s) { + return leveldb::Slice(s.begin(), s.end() - s.begin()); +} + +void LevelDBWriteBatch::Put(const LevelDBSlice& key, + const LevelDBSlice& value) { + write_batch_->Put(MakeSlice(key), MakeSlice(value)); +} + +void LevelDBWriteBatch::Remove(const LevelDBSlice& key) { + write_batch_->Delete(MakeSlice(key)); +} + +void LevelDBWriteBatch::Clear() { write_batch_->Clear(); } + +} // namespace content diff --git a/content/browser/indexed_db/leveldb/leveldb_write_batch.h b/content/browser/indexed_db/leveldb/leveldb_write_batch.h new file mode 100644 index 0000000..2849687 --- /dev/null +++ b/content/browser/indexed_db/leveldb/leveldb_write_batch.h @@ -0,0 +1,38 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_WRITE_BATCH_H_ +#define CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_WRITE_BATCH_H_ + +#include "base/memory/scoped_ptr.h" + +namespace leveldb { +class WriteBatch; +} + +namespace content { + +class LevelDBSlice; + +// Wrapper around leveldb::WriteBatch. +// This class holds a collection of updates to apply atomically to a database. +class LevelDBWriteBatch { + public: + static scoped_ptr<LevelDBWriteBatch> Create(); + ~LevelDBWriteBatch(); + + void Put(const LevelDBSlice& key, const LevelDBSlice& value); + void Remove(const LevelDBSlice& key); // Add remove operation to the batch. + void Clear(); + + private: + friend class LevelDBDatabase; + LevelDBWriteBatch(); + + scoped_ptr<leveldb::WriteBatch> write_batch_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_WRITE_BATCH_H_ diff --git a/content/browser/indexed_db/list_set.h b/content/browser/indexed_db/list_set.h new file mode 100644 index 0000000..2ddd65d --- /dev/null +++ b/content/browser/indexed_db/list_set.h @@ -0,0 +1,158 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_LIST_SET_H_ +#define CONTENT_BROWSER_INDEXED_DB_LIST_SET_H_ + +#include <algorithm> +#include <iterator> +#include <list> +#include <set> +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" + +// +// A container class that provides fast containment test (like a set) +// but maintains insertion order for iteration (like list). +// +// Member types of value (primitives and objects by value), raw pointers +// and scoped_refptr<> are supported. +// +template <typename T> class list_set { + public: + list_set() {} + list_set(const list_set<T>& other) : list_(other.list_), set_(other.set_) {} + list_set& operator=(const list_set<T>& other) { + list_ = other.list_; + set_ = other.set_; + return *this; + } + + void insert(const T& elem) { + if (set_.find(elem) != set_.end()) + return; + set_.insert(elem); + list_.push_back(elem); + } + + void erase(const T& elem) { + if (set_.find(elem) == set_.end()) + return; + set_.erase(elem); + typename std::list<T>::iterator it = + std::find(list_.begin(), list_.end(), elem); + DCHECK(it != list_.end()); + list_.erase(it); + } + + bool has(const T& elem) { return set_.find(elem) != set_.end(); } + + size_t size() const { + DCHECK_EQ(list_.size(), set_.size()); + return set_.size(); + } + + bool empty() const { + DCHECK_EQ(list_.empty(), set_.empty()); + return set_.empty(); + } + + class const_iterator; + + class iterator { + public: + typedef iterator self_type; + typedef T value_type; + typedef T& reference; + typedef T* pointer; + typedef std::bidirectional_iterator_tag iterator_category; + typedef std::ptrdiff_t difference_type; + + inline iterator(typename std::list<T>::iterator it) : it_(it) {} + inline self_type& operator++() { + ++it_; + return *this; + } + inline self_type operator++(int) { + self_type result(*this); + ++(*this); + return result; + } + inline self_type& operator--() { + --it_; + return *this; + } + inline self_type operator--(int) { + self_type result(*this); + --(*this); + return result; + } + inline value_type& operator*() { return *it_; } + inline value_type* operator->() { return &(*it_); } + inline bool operator==(const iterator& rhs) const { return it_ == rhs.it_; } + inline bool operator!=(const iterator& rhs) const { return it_ != rhs.it_; } + + inline operator const_iterator() const { return const_iterator(it_); } + + private: + typename std::list<T>::iterator it_; + }; + + class const_iterator { + public: + typedef const_iterator self_type; + typedef T value_type; + typedef T& reference; + typedef T* pointer; + typedef std::bidirectional_iterator_tag iterator_category; + typedef std::ptrdiff_t difference_type; + + inline const_iterator(typename std::list<T>::const_iterator it) : it_(it) {} + inline self_type& operator++() { + ++it_; + return *this; + } + inline self_type operator++(int) { + self_type result(*this); + ++(*this); + return result; + } + inline self_type& operator--() { + --it_; + return *this; + } + inline self_type operator--(int) { + self_type result(*this); + --(*this); + return result; + } + inline const value_type& operator*() { return *it_; } + inline const value_type* operator->() { return &(*it_); } + inline bool operator==(const const_iterator& rhs) const { + return it_ == rhs.it_; + } + inline bool operator!=(const const_iterator& rhs) const { + return it_ != rhs.it_; + } + + private: + typename std::list<T>::const_iterator it_; + }; + + iterator begin() { return iterator(list_.begin()); } + iterator end() { return iterator(list_.end()); } + const_iterator begin() const { return const_iterator(list_.begin()); } + const_iterator end() const { return const_iterator(list_.end()); } + + private: + std::list<T> list_; + std::set<T> set_; +}; + +// Prevent instantiation of list_set<scoped_ptr<T>> as the current +// implementation would fail. +// TODO(jsbell): Support scoped_ptr through specialization. +template <typename T> class list_set<scoped_ptr<T> >; + +#endif // CONTENT_BROWSER_INDEXED_DB_LIST_SET_H_ diff --git a/content/browser/indexed_db/list_set_unittest.cc b/content/browser/indexed_db/list_set_unittest.cc new file mode 100644 index 0000000..af5d8cf --- /dev/null +++ b/content/browser/indexed_db/list_set_unittest.cc @@ -0,0 +1,239 @@ +// Copyright (c) 2012 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 "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "content/browser/indexed_db/list_set.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace content { + +TEST(ListSetTest, ListSetIterator) { + list_set<int> set; + for (int i = 3; i > 0; --i) + set.insert(i); + + list_set<int>::iterator it = set.begin(); + EXPECT_EQ(3, *it); + ++it; + EXPECT_EQ(2, *it); + it++; + EXPECT_EQ(1, *it); + --it; + EXPECT_EQ(2, *it); + it--; + EXPECT_EQ(3, *it); + ++it; + EXPECT_EQ(2, *it); + it++; + EXPECT_EQ(1, *it); + ++it; + EXPECT_EQ(set.end(), it); +} + +TEST(ListSetTest, ListSetConstIterator) { + list_set<int> set; + for (int i = 5; i > 0; --i) + set.insert(i); + + const list_set<int>& ref = set; + + list_set<int>::const_iterator it = ref.begin(); + for (int i = 5; i > 0; --i) { + EXPECT_EQ(i, *it); + ++it; + } + EXPECT_EQ(ref.end(), it); +} + +TEST(ListSetTest, ListSetPrimitive) { + list_set<int> set; + EXPECT_TRUE(set.empty()); + EXPECT_EQ(static_cast<size_t>(0), set.size()); + { + list_set<int>::iterator it = set.begin(); + EXPECT_EQ(set.end(), it); + } + + for (int i = 5; i > 0; --i) + set.insert(i); + EXPECT_EQ(static_cast<size_t>(5), set.size()); + EXPECT_FALSE(set.empty()); + + set.erase(3); + EXPECT_EQ(static_cast<size_t>(4), set.size()); + + EXPECT_TRUE(set.has(2)); + set.erase(2); + EXPECT_FALSE(set.has(2)); + EXPECT_EQ(static_cast<size_t>(3), set.size()); + + { + list_set<int>::iterator it = set.begin(); + EXPECT_EQ(5, *it); + ++it; + EXPECT_EQ(4, *it); + ++it; + EXPECT_EQ(1, *it); + ++it; + EXPECT_EQ(set.end(), it); + } + + set.erase(1); + set.erase(4); + set.erase(5); + + EXPECT_EQ(static_cast<size_t>(0), set.size()); + EXPECT_TRUE(set.empty()); + { + list_set<int>::iterator it = set.begin(); + EXPECT_EQ(set.end(), it); + } +} + +template <typename T> class Wrapped { + public: + Wrapped(const T& value) : value_(value) {} + Wrapped(const Wrapped<T>& other) : value_(other.value_) {} + Wrapped& operator=(const Wrapped<T>& rhs) { + value_ = rhs.value_; + return *this; + } + int value() const { return value_; } + bool operator<(const Wrapped<T>& rhs) const { return value_ < rhs.value_; } + bool operator==(const Wrapped<T>& rhs) const { return value_ == rhs.value_; } + + private: + T value_; +}; + +TEST(ListSetTest, ListSetObject) { + list_set<Wrapped<int> > set; + EXPECT_EQ(static_cast<size_t>(0), set.size()); + { + list_set<Wrapped<int> >::iterator it = set.begin(); + EXPECT_EQ(set.end(), it); + } + + set.insert(Wrapped<int>(0)); + set.insert(Wrapped<int>(1)); + set.insert(Wrapped<int>(2)); + + EXPECT_EQ(static_cast<size_t>(3), set.size()); + + { + list_set<Wrapped<int> >::iterator it = set.begin(); + EXPECT_EQ(0, it->value()); + ++it; + EXPECT_EQ(1, it->value()); + ++it; + EXPECT_EQ(2, it->value()); + ++it; + EXPECT_EQ(set.end(), it); + } + + set.erase(Wrapped<int>(0)); + set.erase(Wrapped<int>(1)); + set.erase(Wrapped<int>(2)); + + EXPECT_EQ(static_cast<size_t>(0), set.size()); + { + list_set<Wrapped<int> >::iterator it = set.begin(); + EXPECT_EQ(set.end(), it); + } +} + +TEST(ListSetTest, ListSetPointer) { + scoped_ptr<Wrapped<int> > w0(new Wrapped<int>(0)); + scoped_ptr<Wrapped<int> > w1(new Wrapped<int>(1)); + scoped_ptr<Wrapped<int> > w2(new Wrapped<int>(2)); + + list_set<Wrapped<int>*> set; + EXPECT_EQ(static_cast<size_t>(0), set.size()); + { + list_set<Wrapped<int>*>::iterator it = set.begin(); + EXPECT_EQ(set.end(), it); + } + + set.insert(w0.get()); + set.insert(w1.get()); + set.insert(w2.get()); + + EXPECT_EQ(static_cast<size_t>(3), set.size()); + + { + list_set<Wrapped<int>*>::iterator it = set.begin(); + EXPECT_EQ(0, (*it)->value()); + ++it; + EXPECT_EQ(1, (*it)->value()); + ++it; + EXPECT_EQ(2, (*it)->value()); + ++it; + EXPECT_EQ(set.end(), it); + } + + set.erase(w0.get()); + set.erase(w1.get()); + set.erase(w2.get()); + + EXPECT_EQ(static_cast<size_t>(0), set.size()); + { + list_set<Wrapped<int>*>::iterator it = set.begin(); + EXPECT_EQ(set.end(), it); + } +} + +template <typename T> +class RefCounted : public base::RefCounted<RefCounted<T> > { + public: + RefCounted(const T& value) : value_(value) {} + T value() { return value_; } + + private: + virtual ~RefCounted() {} + friend class base::RefCounted<RefCounted<T> >; + T value_; +}; + +TEST(ListSetTest, ListSetRefCounted) { + list_set<scoped_refptr<RefCounted<int> > > set; + EXPECT_EQ(static_cast<size_t>(0), set.size()); + { + list_set<scoped_refptr<RefCounted<int> > >::iterator it = set.begin(); + EXPECT_EQ(set.end(), it); + } + + scoped_refptr<RefCounted<int> > r0(new RefCounted<int>(0)); + scoped_refptr<RefCounted<int> > r1(new RefCounted<int>(1)); + scoped_refptr<RefCounted<int> > r2(new RefCounted<int>(2)); + + set.insert(r0); + set.insert(r1); + set.insert(r2); + + EXPECT_EQ(static_cast<size_t>(3), set.size()); + + { + list_set<scoped_refptr<RefCounted<int> > >::iterator it = set.begin(); + EXPECT_EQ(0, (*it)->value()); + ++it; + EXPECT_EQ(1, (*it)->value()); + ++it; + EXPECT_EQ(2, (*it)->value()); + ++it; + EXPECT_EQ(set.end(), it); + } + + set.erase(r0); + set.erase(r1); + set.erase(r2); + + EXPECT_EQ(static_cast<size_t>(0), set.size()); + { + list_set<scoped_refptr<RefCounted<int> > >::iterator it = set.begin(); + EXPECT_EQ(set.end(), it); + } +} + +} // namespace content diff --git a/content/browser/indexed_db/webidbcursor_impl.cc b/content/browser/indexed_db/webidbcursor_impl.cc new file mode 100644 index 0000000..629ebaf --- /dev/null +++ b/content/browser/indexed_db/webidbcursor_impl.cc @@ -0,0 +1,51 @@ +// 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/webidbcursor_impl.h" + +#include "content/browser/indexed_db/indexed_db_callbacks_wrapper.h" +#include "content/browser/indexed_db/indexed_db_cursor.h" +#include "content/common/indexed_db/indexed_db_key.h" +#include "third_party/WebKit/public/platform/WebIDBKey.h" + +using WebKit::WebIDBCallbacks; + +namespace content { + +WebIDBCursorImpl::WebIDBCursorImpl( + scoped_refptr<IndexedDBCursor> idb_cursor_backend) + : idb_cursor_backend_(idb_cursor_backend) {} + +WebIDBCursorImpl::~WebIDBCursorImpl() {} + +void WebIDBCursorImpl::advance(unsigned long count, + WebIDBCallbacks* callbacks) { + idb_cursor_backend_->Advance(count, + IndexedDBCallbacksWrapper::Create(callbacks)); +} + +void WebIDBCursorImpl::continueFunction(const WebKit::WebIDBKey& key, + WebIDBCallbacks* callbacks) { + idb_cursor_backend_->ContinueFunction( + make_scoped_ptr(new IndexedDBKey(key)), + IndexedDBCallbacksWrapper::Create(callbacks)); +} + +void WebIDBCursorImpl::deleteFunction(WebIDBCallbacks* callbacks) { + idb_cursor_backend_->DeleteFunction( + IndexedDBCallbacksWrapper::Create(callbacks)); +} + +void WebIDBCursorImpl::prefetchContinue(int number_to_fetch, + WebKit::WebIDBCallbacks* callbacks) { + idb_cursor_backend_->PrefetchContinue( + number_to_fetch, IndexedDBCallbacksWrapper::Create(callbacks)); +} + +void WebIDBCursorImpl::prefetchReset(int used_prefetches, + int unused_prefetches) { + idb_cursor_backend_->PrefetchReset(used_prefetches, unused_prefetches); +} + +} // namespace content diff --git a/content/browser/indexed_db/webidbcursor_impl.h b/content/browser/indexed_db/webidbcursor_impl.h new file mode 100644 index 0000000..ec89877 --- /dev/null +++ b/content/browser/indexed_db/webidbcursor_impl.h @@ -0,0 +1,33 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_WEBIDBCURSOR_IMPL_H_ +#define CONTENT_BROWSER_INDEXED_DB_WEBIDBCURSOR_IMPL_H_ + +#include "base/memory/ref_counted.h" +#include "third_party/WebKit/public/platform/WebIDBCursor.h" + +namespace content { +class IndexedDBCursor; + +class WebIDBCursorImpl : public WebKit::WebIDBCursor { + public: + explicit WebIDBCursorImpl(scoped_refptr<IndexedDBCursor> cursor); + virtual ~WebIDBCursorImpl(); + + virtual void advance(unsigned long, WebKit::WebIDBCallbacks* callbacks); + virtual void continueFunction(const WebKit::WebIDBKey& key, + WebKit::WebIDBCallbacks* callbacks); + virtual void deleteFunction(WebKit::WebIDBCallbacks* callbacks); + virtual void prefetchContinue(int number_to_fetch, + WebKit::WebIDBCallbacks* callbacks); + virtual void prefetchReset(int used_prefetches, int unused_prefetches); + + private: + scoped_refptr<IndexedDBCursor> idb_cursor_backend_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_WEBIDBCURSOR_IMPL_H_ diff --git a/content/browser/indexed_db/webidbdatabase_impl.cc b/content/browser/indexed_db/webidbdatabase_impl.cc new file mode 100644 index 0000000..7c2c7e3 --- /dev/null +++ b/content/browser/indexed_db/webidbdatabase_impl.cc @@ -0,0 +1,278 @@ +// 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/webidbdatabase_impl.h" + +#include <vector> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "content/browser/indexed_db/indexed_db_callbacks_wrapper.h" +#include "content/browser/indexed_db/indexed_db_cursor.h" +#include "content/browser/indexed_db/indexed_db_database.h" +#include "content/browser/indexed_db/indexed_db_metadata.h" +#include "content/common/indexed_db/indexed_db_key_range.h" +#include "third_party/WebKit/public/platform/WebData.h" +#include "third_party/WebKit/public/platform/WebIDBCallbacks.h" +#include "third_party/WebKit/public/platform/WebIDBDatabaseCallbacks.h" +#include "third_party/WebKit/public/platform/WebIDBDatabaseError.h" +#include "third_party/WebKit/public/platform/WebIDBKey.h" +#include "third_party/WebKit/public/platform/WebIDBKeyRange.h" +#include "third_party/WebKit/public/platform/WebIDBMetadata.h" + +using WebKit::WebString; +using WebKit::WebIDBKey; +using WebKit::WebData; +using WebKit::WebIDBKeyPath; +using WebKit::WebIDBKeyRange; +using WebKit::WebIDBDatabaseCallbacks; +using WebKit::WebIDBCallbacks; +using WebKit::WebVector; +using WebKit::WebIDBDatabaseError; + +namespace content { + +WebIDBDatabaseImpl::WebIDBDatabaseImpl( + scoped_refptr<IndexedDBDatabase> database_backend, + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> database_callbacks) + : database_backend_(database_backend), + database_callbacks_(database_callbacks) {} + +WebIDBDatabaseImpl::~WebIDBDatabaseImpl() {} + +void WebIDBDatabaseImpl::createObjectStore(long long transaction_id, + long long object_store_id, + const WebString& name, + const WebIDBKeyPath& key_path, + bool auto_increment) { + database_backend_->CreateObjectStore(transaction_id, + object_store_id, + name, + IndexedDBKeyPath(key_path), + auto_increment); +} + +void WebIDBDatabaseImpl::deleteObjectStore(long long transaction_id, + long long object_store_id) { + database_backend_->DeleteObjectStore(transaction_id, object_store_id); +} + +void WebIDBDatabaseImpl::createTransaction( + long long id, + WebIDBDatabaseCallbacks* /*callbacks*/, + const WebVector<long long>& object_store_ids, + unsigned short mode) { + if (!database_callbacks_) + return; + std::vector<int64> object_store_id_list(object_store_ids.size()); + for (size_t i = 0; i < object_store_ids.size(); ++i) + object_store_id_list[i] = object_store_ids[i]; + database_backend_->CreateTransaction( + id, database_callbacks_.get(), object_store_id_list, mode); +} + +void WebIDBDatabaseImpl::close() { + // Use the callbacks passed in to the constructor so that the backend in + // multi-process chromium knows which database connection is closing. + if (!database_callbacks_) + return; + database_backend_->Close(database_callbacks_); + database_callbacks_ = NULL; +} + +void WebIDBDatabaseImpl::forceClose() { + if (!database_callbacks_) + return; + database_backend_->Close(database_callbacks_); + database_callbacks_->OnForcedClose(); + database_callbacks_ = NULL; +} + +void WebIDBDatabaseImpl::abort(long long transaction_id) { + if (database_backend_) + database_backend_->Abort(transaction_id); +} + +void WebIDBDatabaseImpl::abort(long long transaction_id, + const WebIDBDatabaseError& error) { + if (database_backend_) + database_backend_->Abort(transaction_id, + IndexedDBDatabaseError::Create(error)); +} + +void WebIDBDatabaseImpl::commit(long long transaction_id) { + if (database_backend_) + database_backend_->Commit(transaction_id); +} + +void WebIDBDatabaseImpl::openCursor(long long transaction_id, + long long object_store_id, + long long index_id, + const WebIDBKeyRange& key_range, + unsigned short direction, + bool key_only, + TaskType task_type, + WebIDBCallbacks* callbacks) { + if (database_backend_) + database_backend_->OpenCursor( + transaction_id, + object_store_id, + index_id, + make_scoped_ptr(new IndexedDBKeyRange(key_range)), + static_cast<indexed_db::CursorDirection>(direction), + key_only, + static_cast<IndexedDBDatabase::TaskType>(task_type), + IndexedDBCallbacksWrapper::Create(callbacks)); +} + +void WebIDBDatabaseImpl::count(long long transaction_id, + long long object_store_id, + long long index_id, + const WebIDBKeyRange& key_range, + WebIDBCallbacks* callbacks) { + if (database_backend_) + database_backend_->Count(transaction_id, + object_store_id, + index_id, + make_scoped_ptr(new IndexedDBKeyRange(key_range)), + IndexedDBCallbacksWrapper::Create(callbacks)); +} + +void WebIDBDatabaseImpl::get(long long transaction_id, + long long object_store_id, + long long index_id, + const WebIDBKeyRange& key_range, + bool key_only, + WebIDBCallbacks* callbacks) { + if (database_backend_) + database_backend_->Get(transaction_id, + object_store_id, + index_id, + make_scoped_ptr(new IndexedDBKeyRange(key_range)), + key_only, + IndexedDBCallbacksWrapper::Create(callbacks)); +} + +void WebIDBDatabaseImpl::put(long long transaction_id, + long long object_store_id, + const WebData& value, + const WebIDBKey& key, + PutMode put_mode, + WebIDBCallbacks* callbacks, + const WebVector<long long>& web_index_ids, + const WebVector<WebIndexKeys>& web_index_keys) { + if (!database_backend_) + return; + + DCHECK_EQ(web_index_ids.size(), web_index_keys.size()); + std::vector<int64> index_ids(web_index_ids.size()); + std::vector<IndexedDBDatabase::IndexKeys> index_keys(web_index_keys.size()); + + for (size_t i = 0; i < web_index_ids.size(); ++i) { + index_ids[i] = web_index_ids[i]; + IndexedDBKey::KeyArray index_key_list; + for (size_t j = 0; j < web_index_keys[i].size(); ++j) + index_key_list.push_back(IndexedDBKey(web_index_keys[i][j])); + index_keys[i] = index_key_list; + } + + std::vector<char> value_buffer(value.data(), value.data() + value.size()); + database_backend_->Put(transaction_id, + object_store_id, + &value_buffer, + make_scoped_ptr(new IndexedDBKey(key)), + static_cast<IndexedDBDatabase::PutMode>(put_mode), + IndexedDBCallbacksWrapper::Create(callbacks), + index_ids, + index_keys); +} + +void WebIDBDatabaseImpl::setIndexKeys( + long long transaction_id, + long long object_store_id, + const WebIDBKey& primary_key, + const WebVector<long long>& web_index_ids, + const WebVector<WebIndexKeys>& web_index_keys) { + if (!database_backend_) + return; + + DCHECK_EQ(web_index_ids.size(), web_index_keys.size()); + std::vector<int64> index_ids(web_index_ids.size()); + std::vector<IndexedDBDatabase::IndexKeys> index_keys(web_index_keys.size()); + + for (size_t i = 0; i < web_index_ids.size(); ++i) { + index_ids[i] = web_index_ids[i]; + IndexedDBKey::KeyArray index_key_list; + for (size_t j = 0; j < web_index_keys[i].size(); ++j) + index_key_list.push_back(IndexedDBKey(web_index_keys[i][j])); + index_keys[i] = index_key_list; + } + database_backend_->SetIndexKeys( + transaction_id, + object_store_id, + make_scoped_ptr(new IndexedDBKey(primary_key)), + index_ids, + index_keys); +} + +void WebIDBDatabaseImpl::setIndexesReady( + long long transaction_id, + long long object_store_id, + const WebVector<long long>& web_index_ids) { + if (!database_backend_) + return; + + std::vector<int64> index_ids(web_index_ids.size()); + for (size_t i = 0; i < web_index_ids.size(); ++i) + index_ids[i] = web_index_ids[i]; + database_backend_->SetIndexesReady( + transaction_id, object_store_id, index_ids); +} + +void WebIDBDatabaseImpl::deleteRange(long long transaction_id, + long long object_store_id, + const WebIDBKeyRange& key_range, + WebIDBCallbacks* callbacks) { + if (database_backend_) + database_backend_->DeleteRange( + transaction_id, + object_store_id, + make_scoped_ptr(new IndexedDBKeyRange(key_range)), + IndexedDBCallbacksWrapper::Create(callbacks)); +} + +void WebIDBDatabaseImpl::clear(long long transaction_id, + long long object_store_id, + WebIDBCallbacks* callbacks) { + if (database_backend_) + database_backend_->Clear(transaction_id, + object_store_id, + IndexedDBCallbacksWrapper::Create(callbacks)); +} + +void WebIDBDatabaseImpl::createIndex(long long transaction_id, + long long object_store_id, + long long index_id, + const WebString& name, + const WebIDBKeyPath& key_path, + bool unique, + bool multi_entry) { + if (database_backend_) + database_backend_->CreateIndex(transaction_id, + object_store_id, + index_id, + name, + IndexedDBKeyPath(key_path), + unique, + multi_entry); +} + +void WebIDBDatabaseImpl::deleteIndex(long long transaction_id, + long long object_store_id, + long long index_id) { + if (database_backend_) + database_backend_->DeleteIndex(transaction_id, object_store_id, index_id); +} + +} // namespace WebKit diff --git a/content/browser/indexed_db/webidbdatabase_impl.h b/content/browser/indexed_db/webidbdatabase_impl.h new file mode 100644 index 0000000..52c137f --- /dev/null +++ b/content/browser/indexed_db/webidbdatabase_impl.h @@ -0,0 +1,110 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_WEBIDBDATABASE_IMPL_H_ +#define CONTENT_BROWSER_INDEXED_DB_WEBIDBDATABASE_IMPL_H_ + +#include "base/memory/ref_counted.h" +#include "content/browser/indexed_db/indexed_db_database.h" +#include "content/browser/indexed_db/indexed_db_database_callbacks_wrapper.h" +#include "third_party/WebKit/public/platform/WebIDBDatabase.h" + +namespace WebKit { +class WebIDBDatabaseCallbacks; +class WebIDBDatabaseError; +class WebIDBDatabaseMetadata; +} + +namespace content { +class IndexedDBDatabase; +class IndexedDBDatabaseCallbacksWrapper; + +// See comment in WebIDBFactory for a high level overview these classes. +class WebIDBDatabaseImpl : public WebKit::WebIDBDatabase { + public: + WebIDBDatabaseImpl( + scoped_refptr<IndexedDBDatabase> db, + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> callbacks); + virtual ~WebIDBDatabaseImpl(); + + virtual void createObjectStore(long long transaction_id, + long long object_store_id, + const WebKit::WebString& name, + const WebKit::WebIDBKeyPath& key_path, + bool auto_increment); + virtual void deleteObjectStore(long long object_store_id, + long long transaction_id); + virtual void createTransaction(long long id, + WebKit::WebIDBDatabaseCallbacks* callbacks, + const WebKit::WebVector<long long>& scope, + unsigned short mode); + virtual void forceClose(); + virtual void close(); + virtual void abort(long long transaction_id); + virtual void abort(long long transaction_id, + const WebKit::WebIDBDatabaseError& error); + virtual void commit(long long transaction_id); + + virtual void get(long long transaction_id, + long long object_store_id, + long long index_id, + const WebKit::WebIDBKeyRange& range, + bool key_only, + WebKit::WebIDBCallbacks* callbacks); + virtual void put(long long transaction_id, + long long object_store_id, + const WebKit::WebData& value, + const WebKit::WebIDBKey& key, + PutMode mode, + WebKit::WebIDBCallbacks* callbacks, + const WebKit::WebVector<long long>& index_ids, + const WebKit::WebVector<WebIndexKeys>& index_keys); + virtual void setIndexKeys(long long transaction_id, + long long object_store_id, + const WebKit::WebIDBKey& key, + const WebKit::WebVector<long long>& index_ids, + const WebKit::WebVector<WebIndexKeys>& index_keys); + virtual void setIndexesReady(long long transaction_id, + long long object_store_id, + const WebKit::WebVector<long long>& index_ids); + virtual void openCursor(long long transaction_id, + long long object_store_id, + long long index_id, + const WebKit::WebIDBKeyRange& range, + unsigned short direction, + bool key_only, + TaskType task_type, + WebKit::WebIDBCallbacks* callbacks); + virtual void count(long long transaction_id, + long long object_store_id, + long long index_id, + const WebKit::WebIDBKeyRange& range, + WebKit::WebIDBCallbacks* callbacks); + virtual void deleteRange(long long transaction_id, + long long object_store_id, + const WebKit::WebIDBKeyRange& range, + WebKit::WebIDBCallbacks* callbacks); + virtual void clear(long long transaction_id, + long long object_store_id, + WebKit::WebIDBCallbacks* callbacks); + + virtual void createIndex(long long transaction_id, + long long object_store_id, + long long index_id, + const WebKit::WebString& name, + const WebKit::WebIDBKeyPath& key_path, + bool unique, + bool multi_entry); + virtual void deleteIndex(long long transaction_id, + long long object_store_id, + long long index_id); + + private: + scoped_refptr<IndexedDBDatabase> database_backend_; + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> database_callbacks_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_WEBIDBDATABASE_IMPL_H_ diff --git a/content/browser/indexed_db/webidbfactory_impl.cc b/content/browser/indexed_db/webidbfactory_impl.cc new file mode 100644 index 0000000..1fc78fa --- /dev/null +++ b/content/browser/indexed_db/webidbfactory_impl.cc @@ -0,0 +1,68 @@ +// 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/webidbfactory_impl.h" + +#include "base/memory/scoped_ptr.h" +#include "content/browser/indexed_db/indexed_db_callbacks_wrapper.h" +#include "content/browser/indexed_db/indexed_db_factory.h" +#include "content/browser/indexed_db/indexed_db_factory_impl.h" +#include "third_party/WebKit/public/platform/WebIDBDatabaseCallbacks.h" +#include "third_party/WebKit/public/platform/WebIDBDatabaseError.h" + +using WebKit::WebIDBCallbacks; +using WebKit::WebIDBDatabaseCallbacks; +using WebKit::WebIDBFactory; +using WebKit::WebString; + +namespace content { + +WebIDBFactoryImpl::WebIDBFactoryImpl() + : idb_factory_backend_(IndexedDBFactoryImpl::Create()) {} + +WebIDBFactoryImpl::~WebIDBFactoryImpl() {} + +void WebIDBFactoryImpl::getDatabaseNames(WebIDBCallbacks* callbacks, + const WebString& database_identifier, + const WebString& data_dir) { + idb_factory_backend_->GetDatabaseNames( + IndexedDBCallbacksWrapper::Create(callbacks), + database_identifier, + data_dir); +} + +void WebIDBFactoryImpl::open(const WebString& name, + long long version, + long long transaction_id, + WebIDBCallbacks* callbacks, + WebIDBDatabaseCallbacks* database_callbacks, + const WebString& database_identifier, + const WebString& data_dir) { + scoped_refptr<IndexedDBCallbacksWrapper> callbacks_proxy = + IndexedDBCallbacksWrapper::Create(callbacks); + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> database_callbacks_proxy = + IndexedDBDatabaseCallbacksWrapper::Create(database_callbacks); + + callbacks_proxy->SetDatabaseCallbacks(database_callbacks_proxy); + idb_factory_backend_->Open(name, + version, + transaction_id, + callbacks_proxy.get(), + database_callbacks_proxy.get(), + database_identifier, + data_dir); +} + +void WebIDBFactoryImpl::deleteDatabase(const WebString& name, + WebIDBCallbacks* callbacks, + const WebString& database_identifier, + const WebString& data_dir) { + idb_factory_backend_->DeleteDatabase( + name, + IndexedDBCallbacksWrapper::Create(callbacks), + database_identifier, + data_dir); +} + +} // namespace WebKit diff --git a/content/browser/indexed_db/webidbfactory_impl.h b/content/browser/indexed_db/webidbfactory_impl.h new file mode 100644 index 0000000..3835285 --- /dev/null +++ b/content/browser/indexed_db/webidbfactory_impl.h @@ -0,0 +1,41 @@ +// 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. + +#ifndef CONTENT_BROWSER_INDEXED_DB_WEBIDBFACTORY_IMPL_H_ +#define CONTENT_BROWSER_INDEXED_DB_WEBIDBFACTORY_IMPL_H_ + +#include "base/memory/ref_counted.h" +#include "third_party/WebKit/public/platform/WebIDBFactory.h" + +namespace content { + +class IndexedDBFactory; + +class WebIDBFactoryImpl : public WebKit::WebIDBFactory { + public: + WebIDBFactoryImpl(); + virtual ~WebIDBFactoryImpl(); + + virtual void getDatabaseNames(WebKit::WebIDBCallbacks* callbacks, + const WebKit::WebString& database_identifier, + const WebKit::WebString& data_dir); + virtual void open(const WebKit::WebString& name, + long long version, + long long transaction_id, + WebKit::WebIDBCallbacks* callbacks, + WebKit::WebIDBDatabaseCallbacks* database_callbacks, + const WebKit::WebString& database_identifier, + const WebKit::WebString& data_dir); + virtual void deleteDatabase(const WebKit::WebString& name, + WebKit::WebIDBCallbacks* callbacks, + const WebKit::WebString& database_identifier, + const WebKit::WebString& data_dir); + + private: + scoped_refptr<IndexedDBFactory> idb_factory_backend_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_INDEXED_DB_WEBIDBFACTORY_IMPL_H_ |