diff options
author | alecflett@chromium.org <alecflett@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-30 23:03:03 +0000 |
---|---|---|
committer | alecflett@chromium.org <alecflett@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-30 23:03:03 +0000 |
commit | 71c1cbb0f63b371bc4167c9735e400457b376df2 (patch) | |
tree | 3cea7d2f94064ab40d9f9b8c8b6cff1e9bfb8035 | |
parent | 9a76be05b5dba40ff07f6ecb8b57f87aaf3b377e (diff) | |
download | chromium_src-71c1cbb0f63b371bc4167c9735e400457b376df2.zip chromium_src-71c1cbb0f63b371bc4167c9735e400457b376df2.tar.gz chromium_src-71c1cbb0f63b371bc4167c9735e400457b376df2.tar.bz2 |
Migrate the IndexedDB backend from Blink to Chromium
To get the IDB backend off the (deprecated) WebKit thread, remove
intermediate proxying, and let us take advantage of base utilities,
we're moving the code from Blink to Chromium.
This patch is basically a glorified copy/paste of the Blink IDB
backend code, with Chromium coding style applied, WTF dependencies
replaced with STL and base/, redundant classes removed, etc. It
introduces some new temporary proxy classes
(content/browser/webidb*_impl.*) to allow us build both the old and
new backends.
The new backend is currently disabled by default. It can be enabled
using a new (and temporary) command line switch: --new-indexeddb Once
we've done some further cleanup and are confident that the new backend
is stable, and the bots have moved from DumpRenderTree to
content_shell, we'll switch to the new backend by default. Once that
has survived through a dev channel release, we'll delete the Blink
code and eliminate unnecessary proxy classes.
BUG=234278
R=alecflett@chromium.org, dgrogan@chromium.org, piman@chromium.org
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=202215
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=202604
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=203164
Review URL: https://codereview.chromium.org/15564008
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@203257 0039d316-1c4b-4281-b951-d872f2087c98
71 files changed, 14745 insertions, 425 deletions
diff --git a/android_webview/tools/third_party_files_whitelist.txt b/android_webview/tools/third_party_files_whitelist.txt index 4f67aee..b135b85 100644 --- a/android_webview/tools/third_party_files_whitelist.txt +++ b/android_webview/tools/third_party_files_whitelist.txt @@ -73,6 +73,10 @@ chrome_frame/CFInstance.js chrome/tools/test/generate_mime_tests.pl # Copyright Alf Watt; BSD license. Not used on Android. content/browser/geolocation/osx_wifi.h +# Copyright The Chromium Authors and Apple Inc. Taken from Blink. +content/browser/indexed_db/leveldb/avltree.h +# Copyright The Chromium Authors and Apple Inc. Taken from Blink. +content/browser/indexed_db/leveldb/fixed_array.h # Copyright The Chromium Authors, Apple Inc and Graham Dennis; BSD license. Not # used on Android. content/browser/renderer_host/render_widget_host_view_mac.mm 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_ diff --git a/content/common/indexed_db/indexed_db_key.cc b/content/common/indexed_db/indexed_db_key.cc index 4083fd3..3d24ade 100644 --- a/content/common/indexed_db/indexed_db_key.cc +++ b/content/common/indexed_db/indexed_db_key.cc @@ -4,6 +4,7 @@ #include "content/common/indexed_db/indexed_db_key.h" +#include <string> #include "base/logging.h" #include "third_party/WebKit/public/platform/WebString.h" #include "third_party/WebKit/public/platform/WebVector.h" @@ -60,7 +61,7 @@ static IndexedDBKey::KeyArray CopyKeyArray(const WebIDBKey& other) { } return result; } -} // namespace +} // namespace IndexedDBKey::IndexedDBKey() : type_(WebIDBKey::NullType), @@ -104,6 +105,8 @@ IndexedDBKey::IndexedDBKey(const string16& key) IndexedDBKey::~IndexedDBKey() {} int IndexedDBKey::Compare(const IndexedDBKey& other) const { + DCHECK(IsValid()); + DCHECK(other.IsValid()); if (type_ != other.type_) return type_ > other.type_ ? -1 : 1; @@ -121,15 +124,17 @@ int IndexedDBKey::Compare(const IndexedDBKey& other) const { case WebIDBKey::StringType: return -other.string_.compare(string_); case WebIDBKey::DateType: + return (date_ < other.date_) ? -1 : (date_ > other.date_) ? 1 : 0; case WebIDBKey::NumberType: return (number_ < other.number_) ? -1 : (number_ > other.number_) ? 1 : 0; case WebIDBKey::InvalidType: case WebIDBKey::NullType: - default: - // This is a placeholder for WebKit::WebIDBKey::MinType + case WebIDBKey::MinType: NOTREACHED(); return 0; } + NOTREACHED(); + return 0; } bool IndexedDBKey::IsLessThan(const IndexedDBKey& other) const { @@ -140,6 +145,20 @@ bool IndexedDBKey::IsEqual(const IndexedDBKey& other) const { return !Compare(other); } +bool IndexedDBKey::IsValid() const { + if (type_ == WebIDBKey::InvalidType || type_ == WebIDBKey::NullType) + return false; + + if (type_ == WebIDBKey::ArrayType) { + for (size_t i = 0; i < array_.size(); i++) { + if (!array_[i].IsValid()) + return false; + } + } + + return true; +} + IndexedDBKey::operator WebIDBKey() const { switch (type_) { case WebIDBKey::ArrayType: @@ -154,11 +173,12 @@ IndexedDBKey::operator WebIDBKey() const { return WebIDBKey::createInvalid(); case WebIDBKey::NullType: return WebIDBKey::createNull(); - default: - // This is a placeholder for WebKit::WebIDBKey::MinType + case WebIDBKey::MinType: NOTREACHED(); return WebIDBKey::createInvalid(); } + NOTREACHED(); + return WebIDBKey::createInvalid(); } } // namespace content diff --git a/content/common/indexed_db/indexed_db_param_traits.cc b/content/common/indexed_db/indexed_db_param_traits.cc index 8593f75..58019ce 100644 --- a/content/common/indexed_db/indexed_db_param_traits.cc +++ b/content/common/indexed_db/indexed_db_param_traits.cc @@ -4,6 +4,8 @@ #include "content/common/indexed_db/indexed_db_param_traits.h" +#include <string> +#include <vector> #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" @@ -19,7 +21,7 @@ using WebKit::WebIDBKeyPath; namespace IPC { void ParamTraits<IndexedDBKey>::Write(Message* m, const param_type& p) { - WriteParam(m, int(p.type())); + WriteParam(m, static_cast<int>(p.type())); switch (p.type()) { case WebIDBKey::ArrayType: WriteParam(m, p.array()); @@ -36,8 +38,7 @@ void ParamTraits<IndexedDBKey>::Write(Message* m, const param_type& p) { case WebIDBKey::InvalidType: case WebIDBKey::NullType: return; - default: - // This is a placeholder for WebKit::WebIDBKey::MinType + case WebIDBKey::MinType: NOTREACHED(); return; } @@ -78,16 +79,17 @@ bool ParamTraits<IndexedDBKey>::Read(const Message* m, case WebIDBKey::NullType: *r = IndexedDBKey(web_type); return true; - default: - // This is a placeholder for WebKit::WebIDBKey::MinType + case WebIDBKey::MinType: NOTREACHED(); return false; } + NOTREACHED(); + return false; } void ParamTraits<IndexedDBKey>::Log(const param_type& p, std::string* l) { l->append("<IndexedDBKey>("); - LogParam(int(p.type()), l); + LogParam(static_cast<int>(p.type()), l); l->append(", "); l->append("["); std::vector<IndexedDBKey>::const_iterator it = p.array().begin(); @@ -107,7 +109,7 @@ void ParamTraits<IndexedDBKey>::Log(const param_type& p, std::string* l) { } void ParamTraits<IndexedDBKeyPath>::Write(Message* m, const param_type& p) { - WriteParam(m, int(p.type())); + WriteParam(m, static_cast<int>(p.type())); switch (p.type()) { case WebIDBKeyPath::ArrayType: WriteParam(m, p.array()); @@ -153,7 +155,7 @@ bool ParamTraits<IndexedDBKeyPath>::Read(const Message* m, void ParamTraits<IndexedDBKeyPath>::Log(const param_type& p, std::string* l) { l->append("<IndexedDBKeyPath>("); - LogParam(int(p.type()), l); + LogParam(static_cast<int>(p.type()), l); l->append(", "); LogParam(p.string(), l); l->append(", "); diff --git a/content/content_browser.gypi b/content/content_browser.gypi index d7b81c0..7a3d3f5 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -542,12 +542,56 @@ 'browser/in_process_webkit/indexed_db_dispatcher_host.h', 'browser/in_process_webkit/webkit_thread.cc', 'browser/in_process_webkit/webkit_thread.h', + 'browser/indexed_db/indexed_db.h', + 'browser/indexed_db/indexed_db_backing_store.cc', + 'browser/indexed_db/indexed_db_backing_store.h', + 'browser/indexed_db/indexed_db_callbacks_wrapper.cc', + 'browser/indexed_db/indexed_db_callbacks_wrapper.h', 'browser/indexed_db/indexed_db_context_impl.cc', 'browser/indexed_db/indexed_db_context_impl.h', + 'browser/indexed_db/indexed_db_cursor.h', + 'browser/indexed_db/indexed_db_cursor_impl.cc', + 'browser/indexed_db/indexed_db_cursor_impl.h', + 'browser/indexed_db/indexed_db_database.h', + 'browser/indexed_db/indexed_db_database_callbacks_wrapper.cc', + 'browser/indexed_db/indexed_db_database_callbacks_wrapper.h', + 'browser/indexed_db/indexed_db_database_error.h', + 'browser/indexed_db/indexed_db_database_impl.cc', + 'browser/indexed_db/indexed_db_database_impl.h', + 'browser/indexed_db/indexed_db_factory.h', + 'browser/indexed_db/indexed_db_factory_impl.cc', + 'browser/indexed_db/indexed_db_factory_impl.h', + 'browser/indexed_db/indexed_db_index_writer.cc', + 'browser/indexed_db/indexed_db_index_writer.h', 'browser/indexed_db/indexed_db_internals_ui.cc', 'browser/indexed_db/indexed_db_internals_ui.h', + 'browser/indexed_db/indexed_db_leveldb_coding.cc', + 'browser/indexed_db/indexed_db_leveldb_coding.h', + 'browser/indexed_db/indexed_db_metadata.cc', + 'browser/indexed_db/indexed_db_metadata.h', 'browser/indexed_db/indexed_db_quota_client.cc', 'browser/indexed_db/indexed_db_quota_client.h', + 'browser/indexed_db/indexed_db_transaction.cc', + 'browser/indexed_db/indexed_db_transaction.h', + 'browser/indexed_db/indexed_db_transaction_coordinator.cc', + 'browser/indexed_db/indexed_db_transaction_coordinator.h', + 'browser/indexed_db/leveldb/avltree.h', + 'browser/indexed_db/leveldb/fixed_array.h', + 'browser/indexed_db/leveldb/leveldb_comparator.h', + 'browser/indexed_db/leveldb/leveldb_database.cc', + 'browser/indexed_db/leveldb/leveldb_database.h', + 'browser/indexed_db/leveldb/leveldb_iterator.h', + 'browser/indexed_db/leveldb/leveldb_slice.h', + 'browser/indexed_db/leveldb/leveldb_transaction.cc', + 'browser/indexed_db/leveldb/leveldb_transaction.h', + 'browser/indexed_db/leveldb/leveldb_write_batch.cc', + 'browser/indexed_db/leveldb/leveldb_write_batch.h', + 'browser/indexed_db/webidbcursor_impl.cc', + 'browser/indexed_db/webidbcursor_impl.h', + 'browser/indexed_db/webidbdatabase_impl.cc', + 'browser/indexed_db/webidbdatabase_impl.h', + 'browser/indexed_db/webidbfactory_impl.cc', + 'browser/indexed_db/webidbfactory_impl.h', 'browser/loader/async_resource_handler.cc', 'browser/loader/async_resource_handler.h', 'browser/loader/buffered_resource_handler.cc', @@ -1101,6 +1145,7 @@ '../net/net.gyp:http_server', '../printing/printing.gyp:printing', '../third_party/WebKit/Source/WebKit/chromium/WebKit.gyp:webkit', + '../third_party/leveldatabase/leveldatabase.gyp:leveldatabase', '../ui/surface/surface.gyp:surface', '../webkit/support/webkit_support.gyp:webkit_resources', '../webkit/support/webkit_support.gyp:webkit_storage', diff --git a/content/content_tests.gypi b/content/content_tests.gypi index ab2c2ac..d884392 100644 --- a/content/content_tests.gypi +++ b/content/content_tests.gypi @@ -233,6 +233,7 @@ 'type': '<(gtest_target_type)', 'defines!': ['CONTENT_IMPLEMENTATION'], 'dependencies': [ + 'content_browser', 'content_common', 'test_support_content', 'browser/speech/proto/speech_proto.gyp:speech_proto', diff --git a/content/public/common/content_switches.cc b/content/public/common/content_switches.cc index 0682c1d..dc71e4c 100644 --- a/content/public/common/content_switches.cc +++ b/content/public/common/content_switches.cc @@ -554,6 +554,10 @@ const char kNoSandbox[] = "no-sandbox"; // finishes. const char kAllowNoSandboxJob[] = "allow-no-sandbox-job"; +// Use the new IndexedDB backend implemented in Chromium. By default, +// the old backend implemented in Blink is used. +const char kNewIndexedDB[] = "new-indexeddb"; + // Specifies a command that should be used to launch the plugin process. Useful // for running the plugin process through purify or quantify. Ex: // --plugin-launcher="path\to\purify /Run=yes" diff --git a/content/public/common/content_switches.h b/content/public/common/content_switches.h index 870a943..5a7404f 100644 --- a/content/public/common/content_switches.h +++ b/content/public/common/content_switches.h @@ -167,6 +167,7 @@ extern const char kLoadPlugin[]; CONTENT_EXPORT extern const char kLoggingLevel[]; extern const char kLogPluginMessages[]; extern const char kMemoryMetrics[]; +extern const char kNewIndexedDB[]; CONTENT_EXPORT extern const char kNoReferrers[]; CONTENT_EXPORT extern const char kNoSandbox[]; CONTENT_EXPORT extern const char kAllowNoSandboxJob[]; diff --git a/content/renderer/renderer_webkitplatformsupport_impl.cc b/content/renderer/renderer_webkitplatformsupport_impl.cc index 133bfc2..80c8b53 100644 --- a/content/renderer/renderer_webkitplatformsupport_impl.cc +++ b/content/renderer/renderer_webkitplatformsupport_impl.cc @@ -362,8 +362,9 @@ RendererWebKitPlatformSupportImpl::createLocalStorageNamespace( WebIDBFactory* RendererWebKitPlatformSupportImpl::idbFactory() { if (!web_idb_factory_) { - if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) - web_idb_factory_.reset(WebIDBFactory::create()); + if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess) && + !CommandLine::ForCurrentProcess()->HasSwitch(switches::kNewIndexedDB)) + web_idb_factory_.reset(WebIDBFactory::create()); else web_idb_factory_.reset(new RendererWebIDBFactoryImpl()); } |