Home | History | Annotate | Download | only in indexeddb
      1 /*
      2  * Copyright (C) 2010 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  *
      8  * 1.  Redistributions of source code must retain the above copyright
      9  *     notice, this list of conditions and the following disclaimer.
     10  * 2.  Redistributions in binary form must reproduce the above copyright
     11  *     notice, this list of conditions and the following disclaimer in the
     12  *     documentation and/or other materials provided with the distribution.
     13  *
     14  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     16  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     17  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     18  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     19  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     20  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     21  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     23  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "modules/indexeddb/IDBCursor.h"
     28 
     29 #include "bindings/v8/ExceptionState.h"
     30 #include "bindings/v8/IDBBindingUtilities.h"
     31 #include "core/dom/ExceptionCode.h"
     32 #include "core/dom/ScriptExecutionContext.h"
     33 #include "core/inspector/ScriptCallStack.h"
     34 #include "modules/indexeddb/IDBAny.h"
     35 #include "modules/indexeddb/IDBCallbacks.h"
     36 #include "modules/indexeddb/IDBCursorBackendInterface.h"
     37 #include "modules/indexeddb/IDBKey.h"
     38 #include "modules/indexeddb/IDBObjectStore.h"
     39 #include "modules/indexeddb/IDBRequest.h"
     40 #include "modules/indexeddb/IDBTracing.h"
     41 #include "modules/indexeddb/IDBTransaction.h"
     42 #include <limits>
     43 
     44 namespace WebCore {
     45 
     46 PassRefPtr<IDBCursor> IDBCursor::create(PassRefPtr<IDBCursorBackendInterface> backend, IndexedDB::CursorDirection direction, IDBRequest* request, IDBAny* source, IDBTransaction* transaction)
     47 {
     48     return adoptRef(new IDBCursor(backend, direction, request, source, transaction));
     49 }
     50 
     51 const AtomicString& IDBCursor::directionNext()
     52 {
     53     DEFINE_STATIC_LOCAL(AtomicString, next, ("next", AtomicString::ConstructFromLiteral));
     54     return next;
     55 }
     56 
     57 const AtomicString& IDBCursor::directionNextUnique()
     58 {
     59     DEFINE_STATIC_LOCAL(AtomicString, nextunique, ("nextunique", AtomicString::ConstructFromLiteral));
     60     return nextunique;
     61 }
     62 
     63 const AtomicString& IDBCursor::directionPrev()
     64 {
     65     DEFINE_STATIC_LOCAL(AtomicString, prev, ("prev", AtomicString::ConstructFromLiteral));
     66     return prev;
     67 }
     68 
     69 const AtomicString& IDBCursor::directionPrevUnique()
     70 {
     71     DEFINE_STATIC_LOCAL(AtomicString, prevunique, ("prevunique", AtomicString::ConstructFromLiteral));
     72     return prevunique;
     73 }
     74 
     75 
     76 IDBCursor::IDBCursor(PassRefPtr<IDBCursorBackendInterface> backend, IndexedDB::CursorDirection direction, IDBRequest* request, IDBAny* source, IDBTransaction* transaction)
     77     : m_backend(backend)
     78     , m_request(request)
     79     , m_direction(direction)
     80     , m_source(source)
     81     , m_transaction(transaction)
     82     , m_transactionNotifier(transaction, this)
     83     , m_gotValue(false)
     84 {
     85     ASSERT(m_backend);
     86     ASSERT(m_request);
     87     ASSERT(m_source->type() == IDBAny::IDBObjectStoreType || m_source->type() == IDBAny::IDBIndexType);
     88     ASSERT(m_transaction);
     89     ScriptWrappable::init(this);
     90 }
     91 
     92 IDBCursor::~IDBCursor()
     93 {
     94 }
     95 
     96 PassRefPtr<IDBRequest> IDBCursor::update(ScriptState* state, ScriptValue& value, ExceptionState& es)
     97 {
     98     IDB_TRACE("IDBCursor::update");
     99 
    100     if (!m_gotValue) {
    101         es.throwDOMException(InvalidStateError, IDBDatabase::noValueErrorMessage);
    102         return 0;
    103     }
    104     if (isKeyCursor()) {
    105         es.throwDOMException(InvalidStateError, IDBDatabase::isKeyCursorErrorMessage);
    106         return 0;
    107     }
    108     if (isDeleted()) {
    109         es.throwDOMException(InvalidStateError, IDBDatabase::sourceDeletedErrorMessage);
    110         return 0;
    111     }
    112     if (m_transaction->isFinished()) {
    113         es.throwDOMException(TransactionInactiveError, IDBDatabase::transactionFinishedErrorMessage);
    114         return 0;
    115     }
    116     if (!m_transaction->isActive()) {
    117         es.throwDOMException(TransactionInactiveError, IDBDatabase::transactionInactiveErrorMessage);
    118         return 0;
    119     }
    120     if (m_transaction->isReadOnly()) {
    121         es.throwDOMException(ReadOnlyError);
    122         return 0;
    123     }
    124 
    125     RefPtr<IDBObjectStore> objectStore = effectiveObjectStore();
    126     const IDBKeyPath& keyPath = objectStore->metadata().keyPath;
    127     const bool usesInLineKeys = !keyPath.isNull();
    128     if (usesInLineKeys) {
    129         RefPtr<IDBKey> keyPathKey = createIDBKeyFromScriptValueAndKeyPath(m_request->requestState(), value, keyPath);
    130         if (!keyPathKey || !keyPathKey->isEqual(m_currentPrimaryKey.get())) {
    131             es.throwDOMException(DataError, "The effective object store of this cursor uses in-line keys and evaluating the key path of the value parameter results in a different value than the cursor's effective key.");
    132             return 0;
    133         }
    134     }
    135 
    136     return objectStore->put(IDBDatabaseBackendInterface::CursorUpdate, IDBAny::create(this), state, value, m_currentPrimaryKey, es);
    137 }
    138 
    139 void IDBCursor::advance(unsigned long count, ExceptionState& es)
    140 {
    141     IDB_TRACE("IDBCursor::advance");
    142     if (!m_gotValue) {
    143         es.throwDOMException(InvalidStateError, IDBDatabase::noValueErrorMessage);
    144         return;
    145     }
    146     if (isDeleted()) {
    147         es.throwDOMException(InvalidStateError, IDBDatabase::sourceDeletedErrorMessage);
    148         return;
    149     }
    150 
    151     if (m_transaction->isFinished()) {
    152         es.throwDOMException(TransactionInactiveError, IDBDatabase::transactionFinishedErrorMessage);
    153         return;
    154     }
    155     if (!m_transaction->isActive()) {
    156         es.throwDOMException(TransactionInactiveError, IDBDatabase::transactionInactiveErrorMessage);
    157         return;
    158     }
    159 
    160     if (!count) {
    161         es.throwTypeError();
    162         return;
    163     }
    164 
    165     m_request->setPendingCursor(this);
    166     m_gotValue = false;
    167     m_backend->advance(count, m_request);
    168 }
    169 
    170 void IDBCursor::continueFunction(ScriptExecutionContext* context, const ScriptValue& keyValue, ExceptionState& es)
    171 {
    172     DOMRequestState requestState(context);
    173     RefPtr<IDBKey> key = keyValue.isUndefined() ? 0 : scriptValueToIDBKey(&requestState, keyValue);
    174     continueFunction(key.release(), es);
    175 }
    176 
    177 void IDBCursor::continueFunction(PassRefPtr<IDBKey> key, ExceptionState& es)
    178 {
    179     IDB_TRACE("IDBCursor::continue");
    180     if (key && !key->isValid()) {
    181         es.throwDOMException(DataError, IDBDatabase::notValidKeyErrorMessage);
    182         return;
    183     }
    184 
    185     if (m_transaction->isFinished()) {
    186         es.throwDOMException(TransactionInactiveError, IDBDatabase::transactionFinishedErrorMessage);
    187         return;
    188     }
    189     if (!m_transaction->isActive()) {
    190         es.throwDOMException(TransactionInactiveError, IDBDatabase::transactionInactiveErrorMessage);
    191         return;
    192     }
    193 
    194     if (!m_gotValue) {
    195         es.throwDOMException(InvalidStateError, IDBDatabase::noValueErrorMessage);
    196         return;
    197     }
    198 
    199     if (isDeleted()) {
    200         es.throwDOMException(InvalidStateError, IDBDatabase::sourceDeletedErrorMessage);
    201         return;
    202     }
    203 
    204     if (key) {
    205         ASSERT(m_currentKey);
    206         if (m_direction == IndexedDB::CursorNext || m_direction == IndexedDB::CursorNextNoDuplicate) {
    207             if (!m_currentKey->isLessThan(key.get())) {
    208                 es.throwDOMException(DataError, "The parameter is less than or equal to this cursor's position.");
    209                 return;
    210             }
    211         } else {
    212             if (!key->isLessThan(m_currentKey.get())) {
    213                 es.throwDOMException(DataError, "The parameter is greater than or equal to this cursor's position.");
    214                 return;
    215             }
    216         }
    217     }
    218 
    219     // FIXME: We're not using the context from when continue was called, which means the callback
    220     //        will be on the original context openCursor was called on. Is this right?
    221     m_request->setPendingCursor(this);
    222     m_gotValue = false;
    223     m_backend->continueFunction(key, m_request);
    224 }
    225 
    226 PassRefPtr<IDBRequest> IDBCursor::deleteFunction(ScriptExecutionContext* context, ExceptionState& es)
    227 {
    228     IDB_TRACE("IDBCursor::delete");
    229     if (m_transaction->isFinished()) {
    230         es.throwDOMException(TransactionInactiveError, IDBDatabase::transactionFinishedErrorMessage);
    231         return 0;
    232     }
    233     if (!m_transaction->isActive()) {
    234         es.throwDOMException(TransactionInactiveError, IDBDatabase::transactionInactiveErrorMessage);
    235         return 0;
    236     }
    237     if (m_transaction->isReadOnly()) {
    238         es.throwDOMException(ReadOnlyError);
    239         return 0;
    240     }
    241 
    242     if (!m_gotValue) {
    243         es.throwDOMException(InvalidStateError, IDBDatabase::noValueErrorMessage);
    244         return 0;
    245     }
    246     if (isKeyCursor()) {
    247         es.throwDOMException(InvalidStateError, IDBDatabase::isKeyCursorErrorMessage);
    248         return 0;
    249     }
    250     if (isDeleted()) {
    251         es.throwDOMException(InvalidStateError, IDBDatabase::sourceDeletedErrorMessage);
    252         return 0;
    253     }
    254 
    255     RefPtr<IDBKeyRange> keyRange = IDBKeyRange::only(m_currentPrimaryKey, es);
    256     ASSERT(!es.hadException());
    257 
    258     RefPtr<IDBRequest> request = IDBRequest::create(context, IDBAny::create(this), m_transaction.get());
    259     m_transaction->backendDB()->deleteRange(m_transaction->id(), effectiveObjectStore()->id(), keyRange, request);
    260     return request.release();
    261 }
    262 
    263 void IDBCursor::postSuccessHandlerCallback()
    264 {
    265     if (m_backend)
    266         m_backend->postSuccessHandlerCallback();
    267 }
    268 
    269 void IDBCursor::close()
    270 {
    271     // The notifier may be the last reference to this cursor.
    272     RefPtr<IDBCursor> protect(this);
    273     m_transactionNotifier.cursorFinished();
    274     if (m_request) {
    275         m_request->finishCursor();
    276         m_request.clear();
    277     }
    278     m_backend.clear();
    279 }
    280 
    281 void IDBCursor::setValueReady(DOMRequestState* state, PassRefPtr<IDBKey> key, PassRefPtr<IDBKey> primaryKey, ScriptValue& value)
    282 {
    283     m_currentKey = key;
    284     m_currentKeyValue = idbKeyToScriptValue(state, m_currentKey);
    285 
    286     m_currentPrimaryKey = primaryKey;
    287     m_currentPrimaryKeyValue = idbKeyToScriptValue(state, m_currentPrimaryKey);
    288 
    289     if (isCursorWithValue()) {
    290         RefPtr<IDBObjectStore> objectStore = effectiveObjectStore();
    291         const IDBObjectStoreMetadata metadata = objectStore->metadata();
    292         if (metadata.autoIncrement && !metadata.keyPath.isNull()) {
    293 #ifndef NDEBUG
    294             RefPtr<IDBKey> expectedKey = createIDBKeyFromScriptValueAndKeyPath(m_request->requestState(), value, metadata.keyPath);
    295             ASSERT(!expectedKey || expectedKey->isEqual(m_currentPrimaryKey.get()));
    296 #endif
    297             bool injected = injectIDBKeyIntoScriptValue(m_request->requestState(), m_currentPrimaryKey, value, metadata.keyPath);
    298             // FIXME: There is no way to report errors here. Move this into onSuccessWithContinuation so that we can abort the transaction there. See: https://bugs.webkit.org/show_bug.cgi?id=92278
    299             ASSERT_UNUSED(injected, injected);
    300         }
    301     }
    302     m_currentValue = value;
    303 
    304     m_gotValue = true;
    305 }
    306 
    307 PassRefPtr<IDBObjectStore> IDBCursor::effectiveObjectStore()
    308 {
    309     if (m_source->type() == IDBAny::IDBObjectStoreType)
    310         return m_source->idbObjectStore();
    311     RefPtr<IDBIndex> index = m_source->idbIndex();
    312     return index->objectStore();
    313 }
    314 
    315 bool IDBCursor::isDeleted() const
    316 {
    317     if (m_source->type() == IDBAny::IDBObjectStoreType)
    318         return m_source->idbObjectStore()->isDeleted();
    319     return m_source->idbIndex()->isDeleted();
    320 }
    321 
    322 IndexedDB::CursorDirection IDBCursor::stringToDirection(const String& directionString, ExceptionState& es)
    323 {
    324     if (directionString.isNull() || directionString == IDBCursor::directionNext())
    325         return IndexedDB::CursorNext;
    326     if (directionString == IDBCursor::directionNextUnique())
    327         return IndexedDB::CursorNextNoDuplicate;
    328     if (directionString == IDBCursor::directionPrev())
    329         return IndexedDB::CursorPrev;
    330     if (directionString == IDBCursor::directionPrevUnique())
    331         return IndexedDB::CursorPrevNoDuplicate;
    332 
    333     es.throwTypeError();
    334     return IndexedDB::CursorNext;
    335 }
    336 
    337 const AtomicString& IDBCursor::directionToString(unsigned short direction)
    338 {
    339     switch (direction) {
    340     case IndexedDB::CursorNext:
    341         return IDBCursor::directionNext();
    342 
    343     case IndexedDB::CursorNextNoDuplicate:
    344         return IDBCursor::directionNextUnique();
    345 
    346     case IndexedDB::CursorPrev:
    347         return IDBCursor::directionPrev();
    348 
    349     case IndexedDB::CursorPrevNoDuplicate:
    350         return IDBCursor::directionPrevUnique();
    351 
    352     default:
    353         ASSERT_NOT_REACHED();
    354         return IDBCursor::directionNext();
    355     }
    356 }
    357 
    358 } // namespace WebCore
    359