IndexedDB 2.0: Encapsulate cursor iteration parameters for easy future expansion.
[WebKit-https.git] / Source / WebCore / Modules / indexeddb / IDBCursor.cpp
1 /*
2  * Copyright (C) 2015 Apple 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  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "IDBCursor.h"
28
29 #if ENABLE(INDEXED_DATABASE)
30
31 #include "ExceptionCode.h"
32 #include "IDBBindingUtilities.h"
33 #include "IDBDatabase.h"
34 #include "IDBDatabaseException.h"
35 #include "IDBGetResult.h"
36 #include "IDBIndex.h"
37 #include "IDBIterateCursorData.h"
38 #include "IDBObjectStore.h"
39 #include "IDBRequest.h"
40 #include "IDBTransaction.h"
41 #include "Logging.h"
42 #include "ScriptExecutionContext.h"
43 #include <heap/StrongInlines.h>
44 #include <runtime/JSCJSValueInlines.h>
45 #include <wtf/NeverDestroyed.h>
46
47 using namespace JSC;
48
49 namespace WebCore {
50
51 const AtomicString& IDBCursor::directionNext()
52 {
53     static NeverDestroyed<AtomicString> next("next", AtomicString::ConstructFromLiteral);
54     return next;
55 }
56
57 const AtomicString& IDBCursor::directionNextUnique()
58 {
59     static NeverDestroyed<AtomicString> nextunique("nextunique", AtomicString::ConstructFromLiteral);
60     return nextunique;
61 }
62
63 const AtomicString& IDBCursor::directionPrev()
64 {
65     static NeverDestroyed<AtomicString> prev("prev", AtomicString::ConstructFromLiteral);
66     return prev;
67 }
68
69 const AtomicString& IDBCursor::directionPrevUnique()
70 {
71     static NeverDestroyed<AtomicString> prevunique("prevunique", AtomicString::ConstructFromLiteral);
72     return prevunique;
73 }
74
75 Optional<IndexedDB::CursorDirection> IDBCursor::stringToDirection(const String& directionString)
76 {
77     if (directionString == directionNext())
78         return IndexedDB::CursorDirection::Next;
79     if (directionString == directionNextUnique())
80         return IndexedDB::CursorDirection::NextNoDuplicate;
81     if (directionString == directionPrev())
82         return IndexedDB::CursorDirection::Prev;
83     if (directionString == directionPrevUnique())
84         return IndexedDB::CursorDirection::PrevNoDuplicate;
85
86     return Nullopt;
87 }
88
89 const AtomicString& IDBCursor::directionToString(IndexedDB::CursorDirection direction)
90 {
91     switch (direction) {
92     case IndexedDB::CursorDirection::Next:
93         return directionNext();
94
95     case IndexedDB::CursorDirection::NextNoDuplicate:
96         return directionNextUnique();
97
98     case IndexedDB::CursorDirection::Prev:
99         return directionPrev();
100
101     case IndexedDB::CursorDirection::PrevNoDuplicate:
102         return directionPrevUnique();
103
104     default:
105         ASSERT_NOT_REACHED();
106         return directionNext();
107     }
108 }
109
110 Ref<IDBCursor> IDBCursor::create(IDBTransaction& transaction, IDBObjectStore& objectStore, const IDBCursorInfo& info)
111 {
112     return adoptRef(*new IDBCursor(transaction, objectStore, info));
113 }
114
115 Ref<IDBCursor> IDBCursor::create(IDBTransaction& transaction, IDBIndex& index, const IDBCursorInfo& info)
116 {
117     return adoptRef(*new IDBCursor(transaction, index, info));
118 }
119
120 IDBCursor::IDBCursor(IDBTransaction& transaction, IDBObjectStore& objectStore, const IDBCursorInfo& info)
121     : ActiveDOMObject(transaction.scriptExecutionContext())
122     , m_info(info)
123     , m_objectStore(&objectStore)
124 {
125     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
126
127     suspendIfNeeded();
128 }
129
130 IDBCursor::IDBCursor(IDBTransaction& transaction, IDBIndex& index, const IDBCursorInfo& info)
131     : ActiveDOMObject(transaction.scriptExecutionContext())
132     , m_info(info)
133     , m_index(&index)
134 {
135     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
136
137     suspendIfNeeded();
138 }
139
140 IDBCursor::~IDBCursor()
141 {
142     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
143 }
144
145 bool IDBCursor::sourcesDeleted() const
146 {
147     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
148
149     if (m_objectStore)
150         return m_objectStore->isDeleted();
151
152     ASSERT(m_index);
153     return m_index->isDeleted() || m_index->objectStore().isDeleted();
154 }
155
156 IDBObjectStore& IDBCursor::effectiveObjectStore() const
157 {
158     if (m_objectStore)
159         return *m_objectStore;
160
161     ASSERT(m_index);
162     return m_index->objectStore();
163 }
164
165 IDBTransaction& IDBCursor::transaction() const
166 {
167     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
168     return effectiveObjectStore().transaction();
169 }
170
171 const String& IDBCursor::direction() const
172 {
173     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
174     return directionToString(m_info.cursorDirection());
175 }
176
177 ExceptionOr<Ref<IDBRequest>> IDBCursor::update(ExecState& state, JSValue value)
178 {
179     LOG(IndexedDB, "IDBCursor::update");
180     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
181
182     if (sourcesDeleted())
183         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'update' on 'IDBCursor': The cursor's source or effective object store has been deleted.") };
184
185     if (!transaction().isActive())
186         return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'update' on 'IDBCursor': The transaction is inactive or finished.") };
187
188     if (transaction().isReadOnly())
189         return Exception { IDBDatabaseException::ReadOnlyError, ASCIILiteral("Failed to execute 'update' on 'IDBCursor': The record may not be updated inside a read-only transaction.") };
190
191     if (!m_gotValue)
192         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'update' on 'IDBCursor': The cursor is being iterated or has iterated past its end.") };
193
194     if (!isKeyCursorWithValue())
195         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'update' on 'IDBCursor': The cursor is a key cursor.") };
196
197     auto& objectStore = effectiveObjectStore();
198     auto& optionalKeyPath = objectStore.info().keyPath();
199     const bool usesInLineKeys = !!optionalKeyPath;
200     if (usesInLineKeys) {
201         RefPtr<IDBKey> keyPathKey = maybeCreateIDBKeyFromScriptValueAndKeyPath(state, value, optionalKeyPath.value());
202         IDBKeyData keyPathKeyData(keyPathKey.get());
203         if (!keyPathKey || keyPathKeyData != m_currentPrimaryKeyData)
204             return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'update' on 'IDBCursor': 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.") };
205     }
206
207     auto putResult = effectiveObjectStore().putForCursorUpdate(state, value, m_currentPrimaryKey.get());
208     if (putResult.hasException())
209         return putResult.releaseException();
210
211     auto request = putResult.releaseReturnValue();
212     request->setSource(*this);
213     ++m_outstandingRequestCount;
214
215     return WTFMove(request);
216 }
217
218 ExceptionOr<void> IDBCursor::advance(unsigned count)
219 {
220     LOG(IndexedDB, "IDBCursor::advance");
221     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
222
223     if (!m_request)
224         return Exception { IDBDatabaseException::InvalidStateError };
225
226     if (!count)
227         return Exception { TypeError, ASCIILiteral("Failed to execute 'advance' on 'IDBCursor': A count argument with value 0 (zero) was supplied, must be greater than 0.") };
228
229     if (sourcesDeleted())
230         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'advance' on 'IDBCursor': The cursor's source or effective object store has been deleted.") };
231
232     if (!transaction().isActive())
233         return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'advance' on 'IDBCursor': The transaction is inactive or finished.") };
234
235     if (!m_gotValue)
236         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'advance' on 'IDBCursor': The cursor is being iterated or has iterated past its end.") };
237
238     m_gotValue = false;
239
240     uncheckedIterateCursor(IDBKeyData(), count);
241
242     return { };
243 }
244
245 ExceptionOr<void> IDBCursor::continueFunction(ExecState& execState, JSValue keyValue)
246 {
247     RefPtr<IDBKey> key;
248     if (!keyValue.isUndefined())
249         key = scriptValueToIDBKey(execState, keyValue);
250
251     return continueFunction(key.get());
252 }
253
254 ExceptionOr<void> IDBCursor::continueFunction(const IDBKeyData& key)
255 {
256     LOG(IndexedDB, "IDBCursor::continueFunction (to key %s)", key.loggingString().utf8().data());
257     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
258
259     if (!m_request)
260         return Exception { IDBDatabaseException::InvalidStateError };
261
262     if (sourcesDeleted())
263         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'continue' on 'IDBCursor': The cursor's source or effective object store has been deleted.") };
264
265     if (!transaction().isActive())
266         return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'continue' on 'IDBCursor': The transaction is inactive or finished.") };
267
268     if (!m_gotValue)
269         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'continue' on 'IDBCursor': The cursor is being iterated or has iterated past its end.") };
270
271     if (!key.isNull() && !key.isValid())
272         return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'continue' on 'IDBCursor': The parameter is not a valid key.") };
273
274     if (m_info.isDirectionForward()) {
275         if (!key.isNull() && key.compare(m_currentKeyData) <= 0)
276             return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'continue' on 'IDBCursor': The parameter is less than or equal to this cursor's position.") };
277     } else {
278         if (!key.isNull() && key.compare(m_currentKeyData) >= 0)
279             return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'continue' on 'IDBCursor': The parameter is greater than or equal to this cursor's position.") };
280     }
281
282     m_gotValue = false;
283
284     uncheckedIterateCursor(key, 0);
285
286     return { };
287 }
288
289 void IDBCursor::uncheckedIterateCursor(const IDBKeyData& key, unsigned count)
290 {
291     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
292
293     ++m_outstandingRequestCount;
294
295     m_request->willIterateCursor(*this);
296     transaction().iterateCursor(*this, { key, count });
297 }
298
299 ExceptionOr<Ref<WebCore::IDBRequest>> IDBCursor::deleteFunction(ExecState& state)
300 {
301     LOG(IndexedDB, "IDBCursor::deleteFunction");
302     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
303
304     if (sourcesDeleted())
305         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'delete' on 'IDBCursor': The cursor's source or effective object store has been deleted.") };
306
307     if (!transaction().isActive())
308         return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'delete' on 'IDBCursor': The transaction is inactive or finished.") };
309
310     if (transaction().isReadOnly())
311         return Exception { IDBDatabaseException::ReadOnlyError, ASCIILiteral("Failed to execute 'delete' on 'IDBCursor': The record may not be deleted inside a read-only transaction.") };
312
313     if (!m_gotValue)
314         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'delete' on 'IDBCursor': The cursor is being iterated or has iterated past its end.") };
315
316     if (!isKeyCursorWithValue())
317         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'delete' on 'IDBCursor': The cursor is a key cursor.") };
318
319     auto result = effectiveObjectStore().deleteFunction(state, m_currentPrimaryKey.get());
320     if (result.hasException())
321         return result.releaseException();
322
323     auto request = result.releaseReturnValue();
324     request->setSource(*this);
325     ++m_outstandingRequestCount;
326
327     return WTFMove(request);
328 }
329
330 void IDBCursor::setGetResult(IDBRequest& request, const IDBGetResult& getResult)
331 {
332     LOG(IndexedDB, "IDBCursor::setGetResult - current key %s", getResult.keyData().loggingString().substring(0, 100).utf8().data());
333     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
334
335     auto* context = request.scriptExecutionContext();
336     if (!context)
337         return;
338
339     auto* exec = context->execState();
340     if (!exec)
341         return;
342
343     if (!getResult.isDefined()) {
344         m_currentKey = { };
345         m_currentKeyData = { };
346         m_currentPrimaryKey = { };
347         m_currentPrimaryKeyData = { };
348         m_currentValue = { };
349
350         m_gotValue = false;
351         return;
352     }
353
354     auto& vm = context->vm();
355
356     m_currentKey = { vm, idbKeyDataToScriptValue(*exec, getResult.keyData()) };
357     m_currentKeyData = getResult.keyData();
358     m_currentPrimaryKey = { vm, idbKeyDataToScriptValue(*exec, getResult.primaryKeyData()) };
359     m_currentPrimaryKeyData = getResult.primaryKeyData();
360
361     if (isKeyCursorWithValue())
362         m_currentValue = { vm, deserializeIDBValueToJSValue(*exec, getResult.value()) };
363     else
364         m_currentValue = { };
365
366     m_gotValue = true;
367 }
368
369 const char* IDBCursor::activeDOMObjectName() const
370 {
371     return "IDBCursor";
372 }
373
374 bool IDBCursor::canSuspendForDocumentSuspension() const
375 {
376     return false;
377 }
378
379 bool IDBCursor::hasPendingActivity() const
380 {
381     return m_outstandingRequestCount;
382 }
383
384 void IDBCursor::decrementOutstandingRequestCount()
385 {
386     ASSERT(m_outstandingRequestCount);
387     --m_outstandingRequestCount;
388 }
389
390 } // namespace WebCore
391
392 #endif // ENABLE(INDEXED_DATABASE)