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 "IDBBindingUtilities.h" |
32 | #include "IDBDatabase.h" |
33 | #include "IDBGetResult.h" |
34 | #include "IDBIndex.h" |
35 | #include "IDBIterateCursorData.h" |
36 | #include "IDBObjectStore.h" |
37 | #include "IDBRequest.h" |
38 | #include "IDBTransaction.h" |
39 | #include "Logging.h" |
40 | #include "ScriptExecutionContext.h" |
41 | #include <JavaScriptCore/HeapInlines.h> |
42 | #include <JavaScriptCore/JSCJSValueInlines.h> |
43 | #include <JavaScriptCore/StrongInlines.h> |
44 | #include <wtf/IsoMallocInlines.h> |
45 | |
46 | namespace WebCore { |
47 | using namespace JSC; |
48 | |
49 | WTF_MAKE_ISO_ALLOCATED_IMPL(IDBCursor); |
50 | |
51 | Ref<IDBCursor> IDBCursor::create(IDBObjectStore& objectStore, const IDBCursorInfo& info) |
52 | { |
53 | return adoptRef(*new IDBCursor(objectStore, info)); |
54 | } |
55 | |
56 | Ref<IDBCursor> IDBCursor::create(IDBIndex& index, const IDBCursorInfo& info) |
57 | { |
58 | return adoptRef(*new IDBCursor(index, info)); |
59 | } |
60 | |
61 | IDBCursor::IDBCursor(IDBObjectStore& objectStore, const IDBCursorInfo& info) |
62 | : m_info(info) |
63 | , m_source(&objectStore) |
64 | { |
65 | ASSERT(&effectiveObjectStore().transaction().database().originThread() == &Thread::current()); |
66 | } |
67 | |
68 | IDBCursor::IDBCursor(IDBIndex& index, const IDBCursorInfo& info) |
69 | : m_info(info) |
70 | , m_source(&index) |
71 | { |
72 | ASSERT(&effectiveObjectStore().transaction().database().originThread() == &Thread::current()); |
73 | } |
74 | |
75 | IDBCursor::~IDBCursor() |
76 | { |
77 | ASSERT(&effectiveObjectStore().transaction().database().originThread() == &Thread::current()); |
78 | } |
79 | |
80 | bool IDBCursor::sourcesDeleted() const |
81 | { |
82 | ASSERT(&effectiveObjectStore().transaction().database().originThread() == &Thread::current()); |
83 | |
84 | return WTF::switchOn(m_source, |
85 | [] (const RefPtr<IDBObjectStore>& objectStore) { return objectStore->isDeleted(); }, |
86 | [] (const RefPtr<IDBIndex>& index) { return index->isDeleted() || index->objectStore().isDeleted(); } |
87 | ); |
88 | } |
89 | |
90 | IDBObjectStore& IDBCursor::effectiveObjectStore() const |
91 | { |
92 | return WTF::switchOn(m_source, |
93 | [] (const RefPtr<IDBObjectStore>& objectStore) -> IDBObjectStore& { return *objectStore; }, |
94 | [] (const RefPtr<IDBIndex>& index) -> IDBObjectStore& { return index->objectStore(); } |
95 | ); |
96 | } |
97 | |
98 | IDBTransaction& IDBCursor::transaction() const |
99 | { |
100 | ASSERT(&effectiveObjectStore().transaction().database().originThread() == &Thread::current()); |
101 | return effectiveObjectStore().transaction(); |
102 | } |
103 | |
104 | ExceptionOr<Ref<IDBRequest>> IDBCursor::update(ExecState& state, JSValue value) |
105 | { |
106 | LOG(IndexedDB, "IDBCursor::update" ); |
107 | ASSERT(&effectiveObjectStore().transaction().database().originThread() == &Thread::current()); |
108 | |
109 | if (sourcesDeleted()) |
110 | return Exception { InvalidStateError, "Failed to execute 'update' on 'IDBCursor': The cursor's source or effective object store has been deleted."_s }; |
111 | |
112 | if (!transaction().isActive()) |
113 | return Exception { TransactionInactiveError, "Failed to execute 'update' on 'IDBCursor': The transaction is inactive or finished."_s }; |
114 | |
115 | if (transaction().isReadOnly()) |
116 | return Exception { ReadonlyError, "Failed to execute 'update' on 'IDBCursor': The record may not be updated inside a read-only transaction."_s }; |
117 | |
118 | if (!m_gotValue) |
119 | return Exception { InvalidStateError, "Failed to execute 'update' on 'IDBCursor': The cursor is being iterated or has iterated past its end."_s }; |
120 | |
121 | if (!isKeyCursorWithValue()) |
122 | return Exception { InvalidStateError, "Failed to execute 'update' on 'IDBCursor': The cursor is a key cursor."_s }; |
123 | |
124 | auto& objectStore = effectiveObjectStore(); |
125 | auto& optionalKeyPath = objectStore.info().keyPath(); |
126 | const bool usesInLineKeys = !!optionalKeyPath; |
127 | if (usesInLineKeys) { |
128 | RefPtr<IDBKey> keyPathKey = maybeCreateIDBKeyFromScriptValueAndKeyPath(state, value, optionalKeyPath.value()); |
129 | IDBKeyData keyPathKeyData(keyPathKey.get()); |
130 | if (!keyPathKey || keyPathKeyData != m_primaryKeyData) |
131 | return Exception { DataError, "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."_s }; |
132 | } |
133 | |
134 | auto putResult = effectiveObjectStore().putForCursorUpdate(state, value, m_primaryKey.copyRef()); |
135 | if (putResult.hasException()) |
136 | return putResult.releaseException(); |
137 | |
138 | auto request = putResult.releaseReturnValue(); |
139 | request->setSource(*this); |
140 | |
141 | return request; |
142 | } |
143 | |
144 | ExceptionOr<void> IDBCursor::advance(unsigned count) |
145 | { |
146 | LOG(IndexedDB, "IDBCursor::advance" ); |
147 | ASSERT(&effectiveObjectStore().transaction().database().originThread() == &Thread::current()); |
148 | |
149 | if (!m_request) |
150 | return Exception { InvalidStateError }; |
151 | |
152 | if (!count) |
153 | return Exception { TypeError, "Failed to execute 'advance' on 'IDBCursor': A count argument with value 0 (zero) was supplied, must be greater than 0."_s }; |
154 | |
155 | if (!transaction().isActive()) |
156 | return Exception { TransactionInactiveError, "Failed to execute 'advance' on 'IDBCursor': The transaction is inactive or finished."_s }; |
157 | |
158 | if (sourcesDeleted()) |
159 | return Exception { InvalidStateError, "Failed to execute 'advance' on 'IDBCursor': The cursor's source or effective object store has been deleted."_s }; |
160 | |
161 | if (!m_gotValue) |
162 | return Exception { InvalidStateError, "Failed to execute 'advance' on 'IDBCursor': The cursor is being iterated or has iterated past its end."_s }; |
163 | |
164 | m_gotValue = false; |
165 | |
166 | uncheckedIterateCursor(IDBKeyData(), count); |
167 | |
168 | return { }; |
169 | } |
170 | |
171 | ExceptionOr<void> IDBCursor::continuePrimaryKey(ExecState& state, JSValue keyValue, JSValue primaryKeyValue) |
172 | { |
173 | if (!m_request) |
174 | return Exception { InvalidStateError }; |
175 | |
176 | if (!transaction().isActive()) |
177 | return Exception { TransactionInactiveError, "Failed to execute 'continuePrimaryKey' on 'IDBCursor': The transaction is inactive or finished."_s }; |
178 | |
179 | if (sourcesDeleted()) |
180 | return Exception { InvalidStateError, "Failed to execute 'continuePrimaryKey' on 'IDBCursor': The cursor's source or effective object store has been deleted."_s }; |
181 | |
182 | if (!WTF::holds_alternative<RefPtr<IDBIndex>>(m_source)) |
183 | return Exception { InvalidAccessError, "Failed to execute 'continuePrimaryKey' on 'IDBCursor': The cursor's source is not an index."_s }; |
184 | |
185 | auto direction = m_info.cursorDirection(); |
186 | if (direction != IndexedDB::CursorDirection::Next && direction != IndexedDB::CursorDirection::Prev) |
187 | return Exception { InvalidAccessError, "Failed to execute 'continuePrimaryKey' on 'IDBCursor': The cursor's direction must be either \"next\" or \"prev\"."_s }; |
188 | |
189 | if (!m_gotValue) |
190 | return Exception { InvalidStateError, "Failed to execute 'continuePrimaryKey' on 'IDBCursor': The cursor is being iterated or has iterated past its end."_s }; |
191 | |
192 | RefPtr<IDBKey> key = scriptValueToIDBKey(state, keyValue); |
193 | if (!key->isValid()) |
194 | return Exception { DataError, "Failed to execute 'continuePrimaryKey' on 'IDBCursor': The first parameter is not a valid key."_s }; |
195 | |
196 | RefPtr<IDBKey> primaryKey = scriptValueToIDBKey(state, primaryKeyValue); |
197 | if (!primaryKey->isValid()) |
198 | return Exception { DataError, "Failed to execute 'continuePrimaryKey' on 'IDBCursor': The second parameter is not a valid key."_s }; |
199 | |
200 | IDBKeyData keyData = { key.get() }; |
201 | IDBKeyData primaryKeyData = { primaryKey.get() }; |
202 | |
203 | if (keyData < m_keyData && direction == IndexedDB::CursorDirection::Next) |
204 | return Exception { DataError, "Failed to execute 'continuePrimaryKey' on 'IDBCursor': The first parameter is less than this cursor's position and this cursor's direction is \"next\"."_s }; |
205 | |
206 | if (keyData > m_keyData && direction == IndexedDB::CursorDirection::Prev) |
207 | return Exception { DataError, "Failed to execute 'continuePrimaryKey' on 'IDBCursor': The first parameter is greater than this cursor's position and this cursor's direction is \"prev\"."_s }; |
208 | |
209 | if (keyData == m_keyData) { |
210 | if (primaryKeyData <= m_primaryKeyData && direction == IndexedDB::CursorDirection::Next) |
211 | return Exception { DataError, "Failed to execute 'continuePrimaryKey' on 'IDBCursor': The key parameters represent a position less-than-or-equal-to this cursor's position and this cursor's direction is \"next\"."_s }; |
212 | if (primaryKeyData >= m_primaryKeyData && direction == IndexedDB::CursorDirection::Prev) |
213 | return Exception { DataError, "Failed to execute 'continuePrimaryKey' on 'IDBCursor': The key parameters represent a position greater-than-or-equal-to this cursor's position and this cursor's direction is \"prev\"."_s }; |
214 | } |
215 | |
216 | m_gotValue = false; |
217 | |
218 | uncheckedIterateCursor(keyData, primaryKeyData); |
219 | |
220 | return { }; |
221 | } |
222 | |
223 | ExceptionOr<void> IDBCursor::continueFunction(ExecState& execState, JSValue keyValue) |
224 | { |
225 | RefPtr<IDBKey> key; |
226 | if (!keyValue.isUndefined()) |
227 | key = scriptValueToIDBKey(execState, keyValue); |
228 | |
229 | return continueFunction(key.get()); |
230 | } |
231 | |
232 | ExceptionOr<void> IDBCursor::continueFunction(const IDBKeyData& key) |
233 | { |
234 | LOG(IndexedDB, "IDBCursor::continueFunction (to key %s)" , key.loggingString().utf8().data()); |
235 | ASSERT(&effectiveObjectStore().transaction().database().originThread() == &Thread::current()); |
236 | |
237 | if (!m_request) |
238 | return Exception { InvalidStateError }; |
239 | |
240 | if (!transaction().isActive()) |
241 | return Exception { TransactionInactiveError, "Failed to execute 'continue' on 'IDBCursor': The transaction is inactive or finished."_s }; |
242 | |
243 | if (sourcesDeleted()) |
244 | return Exception { InvalidStateError, "Failed to execute 'continue' on 'IDBCursor': The cursor's source or effective object store has been deleted."_s }; |
245 | |
246 | if (!m_gotValue) |
247 | return Exception { InvalidStateError, "Failed to execute 'continue' on 'IDBCursor': The cursor is being iterated or has iterated past its end."_s }; |
248 | |
249 | if (!key.isNull() && !key.isValid()) |
250 | return Exception { DataError, "Failed to execute 'continue' on 'IDBCursor': The parameter is not a valid key."_s }; |
251 | |
252 | if (m_info.isDirectionForward()) { |
253 | if (!key.isNull() && key.compare(m_keyData) <= 0) |
254 | return Exception { DataError, "Failed to execute 'continue' on 'IDBCursor': The parameter is less than or equal to this cursor's position."_s }; |
255 | } else { |
256 | if (!key.isNull() && key.compare(m_keyData) >= 0) |
257 | return Exception { DataError, "Failed to execute 'continue' on 'IDBCursor': The parameter is greater than or equal to this cursor's position."_s }; |
258 | } |
259 | |
260 | m_gotValue = false; |
261 | |
262 | uncheckedIterateCursor(key, 0); |
263 | |
264 | return { }; |
265 | } |
266 | |
267 | void IDBCursor::uncheckedIterateCursor(const IDBKeyData& key, unsigned count) |
268 | { |
269 | ASSERT(m_request); |
270 | ASSERT(&effectiveObjectStore().transaction().database().originThread() == &Thread::current()); |
271 | |
272 | m_request->willIterateCursor(*this); |
273 | transaction().iterateCursor(*this, { key, { }, count }); |
274 | } |
275 | |
276 | void IDBCursor::uncheckedIterateCursor(const IDBKeyData& key, const IDBKeyData& primaryKey) |
277 | { |
278 | ASSERT(m_request); |
279 | ASSERT(&effectiveObjectStore().transaction().database().originThread() == &Thread::current()); |
280 | |
281 | m_request->willIterateCursor(*this); |
282 | transaction().iterateCursor(*this, { key, primaryKey, 0 }); |
283 | } |
284 | |
285 | ExceptionOr<Ref<WebCore::IDBRequest>> IDBCursor::deleteFunction(ExecState& state) |
286 | { |
287 | LOG(IndexedDB, "IDBCursor::deleteFunction" ); |
288 | ASSERT(&effectiveObjectStore().transaction().database().originThread() == &Thread::current()); |
289 | |
290 | if (sourcesDeleted()) |
291 | return Exception { InvalidStateError, "Failed to execute 'delete' on 'IDBCursor': The cursor's source or effective object store has been deleted."_s }; |
292 | |
293 | if (!transaction().isActive()) |
294 | return Exception { TransactionInactiveError, "Failed to execute 'delete' on 'IDBCursor': The transaction is inactive or finished."_s }; |
295 | |
296 | if (transaction().isReadOnly()) |
297 | return Exception { ReadonlyError, "Failed to execute 'delete' on 'IDBCursor': The record may not be deleted inside a read-only transaction."_s }; |
298 | |
299 | if (!m_gotValue) |
300 | return Exception { InvalidStateError, "Failed to execute 'delete' on 'IDBCursor': The cursor is being iterated or has iterated past its end."_s }; |
301 | |
302 | if (!isKeyCursorWithValue()) |
303 | return Exception { InvalidStateError, "Failed to execute 'delete' on 'IDBCursor': The cursor is a key cursor."_s }; |
304 | |
305 | auto result = effectiveObjectStore().deleteFunction(state, IDBKeyRange::create(m_primaryKey.copyRef()).ptr()); |
306 | if (result.hasException()) |
307 | return result.releaseException(); |
308 | |
309 | auto request = result.releaseReturnValue(); |
310 | request->setSource(*this); |
311 | |
312 | return request; |
313 | } |
314 | |
315 | bool IDBCursor::setGetResult(IDBRequest& request, const IDBGetResult& getResult) |
316 | { |
317 | LOG(IndexedDB, "IDBCursor::setGetResult - current key %s" , getResult.keyData().loggingString().substring(0, 100).utf8().data()); |
318 | ASSERT(&effectiveObjectStore().transaction().database().originThread() == &Thread::current()); |
319 | |
320 | auto* context = request.scriptExecutionContext(); |
321 | if (!context) |
322 | return false; |
323 | |
324 | VM& vm = context->vm(); |
325 | JSLockHolder lock(vm); |
326 | |
327 | m_keyWrapper = { }; |
328 | m_primaryKeyWrapper = { }; |
329 | m_valueWrapper = { }; |
330 | |
331 | if (!getResult.isDefined()) { |
332 | m_keyData = { }; |
333 | m_key = nullptr; |
334 | m_primaryKeyData = { }; |
335 | m_primaryKey = nullptr; |
336 | m_value = { }; |
337 | |
338 | m_gotValue = false; |
339 | return false; |
340 | } |
341 | |
342 | m_keyData = getResult.keyData(); |
343 | m_key = m_keyData.maybeCreateIDBKey(); |
344 | m_primaryKeyData = getResult.primaryKeyData(); |
345 | m_primaryKey = m_primaryKeyData.maybeCreateIDBKey(); |
346 | |
347 | if (isKeyCursorWithValue()) { |
348 | m_value = getResult.value(); |
349 | m_keyPath = getResult.keyPath(); |
350 | } |
351 | |
352 | m_gotValue = true; |
353 | return true; |
354 | } |
355 | |
356 | void IDBCursor::clearWrappers() |
357 | { |
358 | m_keyWrapper.clear(); |
359 | m_primaryKeyWrapper.clear(); |
360 | m_valueWrapper.clear(); |
361 | } |
362 | |
363 | } // namespace WebCore |
364 | |
365 | #endif // ENABLE(INDEXED_DATABASE) |
366 | |