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 "IDBIndex.h"
28
29#if ENABLE(INDEXED_DATABASE)
30
31#include "IDBBindingUtilities.h"
32#include "IDBCursor.h"
33#include "IDBDatabase.h"
34#include "IDBKeyRangeData.h"
35#include "IDBObjectStore.h"
36#include "IDBRequest.h"
37#include "IDBTransaction.h"
38#include "Logging.h"
39#include <JavaScriptCore/HeapInlines.h>
40
41namespace WebCore {
42using namespace JSC;
43
44IDBIndex::IDBIndex(ScriptExecutionContext& context, const IDBIndexInfo& info, IDBObjectStore& objectStore)
45 : ActiveDOMObject(&context)
46 , m_info(info)
47 , m_originalInfo(info)
48 , m_objectStore(objectStore)
49{
50 ASSERT(&m_objectStore.transaction().database().originThread() == &Thread::current());
51
52 suspendIfNeeded();
53}
54
55IDBIndex::~IDBIndex()
56{
57 ASSERT(&m_objectStore.transaction().database().originThread() == &Thread::current());
58}
59
60const char* IDBIndex::activeDOMObjectName() const
61{
62 return "IDBIndex";
63}
64
65bool IDBIndex::canSuspendForDocumentSuspension() const
66{
67 return false;
68}
69
70bool IDBIndex::hasPendingActivity() const
71{
72 return m_objectStore.transaction().hasPendingActivity();
73}
74
75const String& IDBIndex::name() const
76{
77 ASSERT(&m_objectStore.transaction().database().originThread() == &Thread::current());
78 return m_info.name();
79}
80
81ExceptionOr<void> IDBIndex::setName(const String& name)
82{
83 ASSERT(&m_objectStore.transaction().database().originThread() == &Thread::current());
84
85 if (m_deleted)
86 return Exception { InvalidStateError, "Failed set property 'name' on 'IDBIndex': The index has been deleted."_s };
87
88 if (m_objectStore.isDeleted())
89 return Exception { InvalidStateError, "Failed set property 'name' on 'IDBIndex': The index's object store has been deleted."_s };
90
91 if (!m_objectStore.transaction().isVersionChange())
92 return Exception { InvalidStateError, "Failed set property 'name' on 'IDBIndex': The index's transaction is not a version change transaction."_s };
93
94 if (!m_objectStore.transaction().isActive())
95 return Exception { TransactionInactiveError, "Failed set property 'name' on 'IDBIndex': The index's transaction is not active."_s };
96
97 if (m_info.name() == name)
98 return { };
99
100 if (m_objectStore.info().hasIndex(name))
101 return Exception { ConstraintError, makeString("Failed set property 'name' on 'IDBIndex': The owning object store already has an index named '", name, "'.") };
102
103 m_objectStore.transaction().database().renameIndex(*this, name);
104 m_info.rename(name);
105
106 return { };
107}
108
109IDBObjectStore& IDBIndex::objectStore()
110{
111 ASSERT(&m_objectStore.transaction().database().originThread() == &Thread::current());
112 return m_objectStore;
113}
114
115const IDBKeyPath& IDBIndex::keyPath() const
116{
117 ASSERT(&m_objectStore.transaction().database().originThread() == &Thread::current());
118 return m_info.keyPath();
119}
120
121bool IDBIndex::unique() const
122{
123 ASSERT(&m_objectStore.transaction().database().originThread() == &Thread::current());
124 return m_info.unique();
125}
126
127bool IDBIndex::multiEntry() const
128{
129 ASSERT(&m_objectStore.transaction().database().originThread() == &Thread::current());
130 return m_info.multiEntry();
131}
132
133void IDBIndex::rollbackInfoForVersionChangeAbort()
134{
135 ASSERT(&m_objectStore.transaction().database().originThread() == &Thread::current());
136
137 // Only rollback to the original info if this index still exists in the rolled-back database info.
138 auto* objectStoreInfo = m_objectStore.transaction().database().info().infoForExistingObjectStore(m_objectStore.info().identifier());
139 if (!objectStoreInfo)
140 return;
141
142 if (!objectStoreInfo->hasIndex(m_info.identifier())) {
143 m_deleted = true;
144 return;
145 }
146
147 m_info = m_originalInfo;
148 m_deleted = false;
149}
150
151ExceptionOr<Ref<IDBRequest>> IDBIndex::doOpenCursor(ExecState& execState, IDBCursorDirection direction, WTF::Function<ExceptionOr<RefPtr<IDBKeyRange>>()>&& function)
152{
153 LOG(IndexedDB, "IDBIndex::openCursor");
154 ASSERT(&m_objectStore.transaction().database().originThread() == &Thread::current());
155
156 if (m_deleted || m_objectStore.isDeleted())
157 return Exception { InvalidStateError, "Failed to execute 'openCursor' on 'IDBIndex': The index or its object store has been deleted."_s };
158
159 if (!m_objectStore.transaction().isActive())
160 return Exception { TransactionInactiveError, "Failed to execute 'openCursor' on 'IDBIndex': The transaction is inactive or finished."_s };
161
162 auto keyRange = function();
163 if (keyRange.hasException())
164 return keyRange.releaseException();
165
166 IDBKeyRangeData rangeData = keyRange.returnValue() ? keyRange.releaseReturnValue().get() : nullptr;
167 if (rangeData.lowerKey.isNull())
168 rangeData.lowerKey = IDBKeyData::minimum();
169 if (rangeData.upperKey.isNull())
170 rangeData.upperKey = IDBKeyData::maximum();
171
172 auto info = IDBCursorInfo::indexCursor(m_objectStore.transaction(), m_objectStore.info().identifier(), m_info.identifier(), rangeData, direction, IndexedDB::CursorType::KeyAndValue);
173 return m_objectStore.transaction().requestOpenCursor(execState, *this, info);
174}
175
176ExceptionOr<Ref<IDBRequest>> IDBIndex::openCursor(ExecState& execState, RefPtr<IDBKeyRange>&& range, IDBCursorDirection direction)
177{
178 return doOpenCursor(execState, direction, [range=WTFMove(range)]() {
179 return range;
180 });
181}
182
183ExceptionOr<Ref<IDBRequest>> IDBIndex::openCursor(ExecState& execState, JSValue key, IDBCursorDirection direction)
184{
185 return doOpenCursor(execState, direction, [state=&execState, key]() {
186 auto onlyResult = IDBKeyRange::only(*state, key);
187 if (onlyResult.hasException())
188 return ExceptionOr<RefPtr<IDBKeyRange>>{ Exception(DataError, "Failed to execute 'openCursor' on 'IDBIndex': The parameter is not a valid key."_s) };
189
190 return ExceptionOr<RefPtr<IDBKeyRange>> { onlyResult.releaseReturnValue() };
191 });
192}
193
194ExceptionOr<Ref<IDBRequest>> IDBIndex::doOpenKeyCursor(ExecState& execState, IDBCursorDirection direction, WTF::Function<ExceptionOr<RefPtr<IDBKeyRange>>()>&& function)
195{
196 LOG(IndexedDB, "IDBIndex::openKeyCursor");
197 ASSERT(&m_objectStore.transaction().database().originThread() == &Thread::current());
198
199 if (m_deleted || m_objectStore.isDeleted())
200 return Exception { InvalidStateError, "Failed to execute 'openKeyCursor' on 'IDBIndex': The index or its object store has been deleted."_s };
201
202 if (!m_objectStore.transaction().isActive())
203 return Exception { TransactionInactiveError, "Failed to execute 'openKeyCursor' on 'IDBIndex': The transaction is inactive or finished."_s };
204
205 auto keyRange = function();
206 if (keyRange.hasException())
207 return keyRange.releaseException();
208
209 auto* keyRangePointer = keyRange.returnValue() ? keyRange.releaseReturnValue().get() : nullptr;
210 auto info = IDBCursorInfo::indexCursor(m_objectStore.transaction(), m_objectStore.info().identifier(), m_info.identifier(), keyRangePointer, direction, IndexedDB::CursorType::KeyOnly);
211 return m_objectStore.transaction().requestOpenCursor(execState, *this, info);
212}
213
214ExceptionOr<Ref<IDBRequest>> IDBIndex::openKeyCursor(ExecState& execState, RefPtr<IDBKeyRange>&& range, IDBCursorDirection direction)
215{
216 return doOpenKeyCursor(execState, direction, [range=WTFMove(range)]() {
217 return range;
218 });
219}
220
221ExceptionOr<Ref<IDBRequest>> IDBIndex::openKeyCursor(ExecState& execState, JSValue key, IDBCursorDirection direction)
222{
223 return doOpenKeyCursor(execState, direction, [state=&execState, key]() {
224 auto onlyResult = IDBKeyRange::only(*state, key);
225 if (onlyResult.hasException())
226 return ExceptionOr<RefPtr<IDBKeyRange>>{ Exception(DataError, "Failed to execute 'openKeyCursor' on 'IDBIndex': The parameter is not a valid key."_s) };
227
228 return ExceptionOr<RefPtr<IDBKeyRange>> { onlyResult.releaseReturnValue() };
229 });
230}
231
232ExceptionOr<Ref<IDBRequest>> IDBIndex::count(ExecState& execState, IDBKeyRange* range)
233{
234 LOG(IndexedDB, "IDBIndex::count");
235
236 return doCount(execState, range ? IDBKeyRangeData(range) : IDBKeyRangeData::allKeys());
237}
238
239ExceptionOr<Ref<IDBRequest>> IDBIndex::count(ExecState& execState, JSValue key)
240{
241 LOG(IndexedDB, "IDBIndex::count");
242
243 auto idbKey = scriptValueToIDBKey(execState, key);
244 auto* idbKeyPointer = idbKey->isValid() ? idbKey.ptr() : nullptr;
245
246 return doCount(execState, IDBKeyRangeData(idbKeyPointer));
247}
248
249ExceptionOr<Ref<IDBRequest>> IDBIndex::doCount(ExecState& execState, const IDBKeyRangeData& range)
250{
251 ASSERT(&m_objectStore.transaction().database().originThread() == &Thread::current());
252
253 if (m_deleted || m_objectStore.isDeleted())
254 return Exception { InvalidStateError, "Failed to execute 'count' on 'IDBIndex': The index or its object store has been deleted."_s };
255
256 auto& transaction = m_objectStore.transaction();
257 if (!transaction.isActive())
258 return Exception { TransactionInactiveError, "Failed to execute 'count' on 'IDBIndex': The transaction is inactive or finished."_s };
259
260 if (!range.isValid())
261 return Exception { DataError, "Failed to execute 'count' on 'IDBIndex': The parameter is not a valid key."_s };
262
263 return transaction.requestCount(execState, *this, range);
264}
265
266ExceptionOr<Ref<IDBRequest>> IDBIndex::get(ExecState& execState, IDBKeyRange* range)
267{
268 LOG(IndexedDB, "IDBIndex::get");
269
270 return doGet(execState, IDBKeyRangeData(range));
271}
272
273ExceptionOr<Ref<IDBRequest>> IDBIndex::get(ExecState& execState, JSValue key)
274{
275 LOG(IndexedDB, "IDBIndex::get");
276
277 auto idbKey = scriptValueToIDBKey(execState, key);
278 if (!idbKey->isValid())
279 return doGet(execState, Exception(DataError, "Failed to execute 'get' on 'IDBIndex': The parameter is not a valid key."_s));
280
281 return doGet(execState, IDBKeyRangeData(idbKey.ptr()));
282}
283
284ExceptionOr<Ref<IDBRequest>> IDBIndex::doGet(ExecState& execState, ExceptionOr<IDBKeyRangeData> range)
285{
286 ASSERT(&m_objectStore.transaction().database().originThread() == &Thread::current());
287
288 if (m_deleted || m_objectStore.isDeleted())
289 return Exception { InvalidStateError, "Failed to execute 'get' on 'IDBIndex': The index or its object store has been deleted."_s };
290
291 auto& transaction = m_objectStore.transaction();
292 if (!transaction.isActive())
293 return Exception { TransactionInactiveError, "Failed to execute 'get' on 'IDBIndex': The transaction is inactive or finished."_s };
294
295 if (range.hasException())
296 return range.releaseException();
297 auto keyRange = range.releaseReturnValue();
298
299 if (keyRange.isNull)
300 return Exception { DataError };
301
302 return transaction.requestGetValue(execState, *this, keyRange);
303}
304
305ExceptionOr<Ref<IDBRequest>> IDBIndex::getKey(ExecState& execState, IDBKeyRange* range)
306{
307 LOG(IndexedDB, "IDBIndex::getKey");
308
309 return doGetKey(execState, IDBKeyRangeData(range));
310}
311
312ExceptionOr<Ref<IDBRequest>> IDBIndex::getKey(ExecState& execState, JSValue key)
313{
314 LOG(IndexedDB, "IDBIndex::getKey");
315
316 auto idbKey = scriptValueToIDBKey(execState, key);
317 if (!idbKey->isValid())
318 return doGetKey(execState, Exception(DataError, "Failed to execute 'getKey' on 'IDBIndex': The parameter is not a valid key."_s));
319
320 return doGetKey(execState, IDBKeyRangeData(idbKey.ptr()));
321}
322
323ExceptionOr<Ref<IDBRequest>> IDBIndex::doGetKey(ExecState& execState, ExceptionOr<IDBKeyRangeData> range)
324{
325 ASSERT(&m_objectStore.transaction().database().originThread() == &Thread::current());
326
327 if (m_deleted || m_objectStore.isDeleted())
328 return Exception { InvalidStateError, "Failed to execute 'getKey' on 'IDBIndex': The index or its object store has been deleted."_s };
329
330 auto& transaction = m_objectStore.transaction();
331 if (!transaction.isActive())
332 return Exception { TransactionInactiveError, "Failed to execute 'getKey' on 'IDBIndex': The transaction is inactive or finished."_s };
333
334 if (range.hasException())
335 return range.releaseException();
336 auto keyRange = range.releaseReturnValue();
337
338 if (keyRange.isNull)
339 return Exception { DataError };
340
341 return transaction.requestGetKey(execState, *this, keyRange);
342}
343
344ExceptionOr<Ref<IDBRequest>> IDBIndex::doGetAll(ExecState& execState, Optional<uint32_t> count, WTF::Function<ExceptionOr<RefPtr<IDBKeyRange>>()>&& function)
345{
346 LOG(IndexedDB, "IDBIndex::getAll");
347 ASSERT(&m_objectStore.transaction().database().originThread() == &Thread::current());
348
349 if (m_deleted || m_objectStore.isDeleted())
350 return Exception { InvalidStateError, "Failed to execute 'getAll' on 'IDBIndex': The index or its object store has been deleted."_s };
351
352 if (!m_objectStore.transaction().isActive())
353 return Exception { TransactionInactiveError, "Failed to execute 'getAll' on 'IDBIndex': The transaction is inactive or finished."_s };
354
355 auto keyRange = function();
356 if (keyRange.hasException())
357 return keyRange.releaseException();
358
359 auto* keyRangePointer = keyRange.returnValue() ? keyRange.releaseReturnValue().get() : nullptr;
360 return m_objectStore.transaction().requestGetAllIndexRecords(execState, *this, keyRangePointer, IndexedDB::GetAllType::Values, count);
361}
362
363ExceptionOr<Ref<IDBRequest>> IDBIndex::getAll(ExecState& execState, RefPtr<IDBKeyRange>&& range, Optional<uint32_t> count)
364{
365 return doGetAll(execState, count, [range = WTFMove(range)]() {
366 return range;
367 });
368}
369
370ExceptionOr<Ref<IDBRequest>> IDBIndex::getAll(ExecState& execState, JSValue key, Optional<uint32_t> count)
371{
372 return doGetAll(execState, count, [state=&execState, key]() {
373 auto onlyResult = IDBKeyRange::only(*state, key);
374 if (onlyResult.hasException())
375 return ExceptionOr<RefPtr<IDBKeyRange>>{ Exception(DataError, "Failed to execute 'getAll' on 'IDBIndex': The parameter is not a valid key."_s) };
376
377 return ExceptionOr<RefPtr<IDBKeyRange>> { onlyResult.releaseReturnValue() };
378 });
379}
380
381ExceptionOr<Ref<IDBRequest>> IDBIndex::doGetAllKeys(ExecState& execState, Optional<uint32_t> count, WTF::Function<ExceptionOr<RefPtr<IDBKeyRange>>()>&& function)
382{
383 LOG(IndexedDB, "IDBIndex::getAllKeys");
384 ASSERT(&m_objectStore.transaction().database().originThread() == &Thread::current());
385
386 if (m_deleted || m_objectStore.isDeleted())
387 return Exception { InvalidStateError, "Failed to execute 'getAllKeys' on 'IDBIndex': The index or its object store has been deleted."_s };
388
389 if (!m_objectStore.transaction().isActive())
390 return Exception { TransactionInactiveError, "Failed to execute 'getAllKeys' on 'IDBIndex': The transaction is inactive or finished."_s };
391
392 auto keyRange = function();
393 if (keyRange.hasException())
394 return keyRange.releaseException();
395
396 auto* keyRangePointer = keyRange.returnValue() ? keyRange.releaseReturnValue().get() : nullptr;
397 return m_objectStore.transaction().requestGetAllIndexRecords(execState, *this, keyRangePointer, IndexedDB::GetAllType::Keys, count);
398}
399
400ExceptionOr<Ref<IDBRequest>> IDBIndex::getAllKeys(ExecState& execState, RefPtr<IDBKeyRange>&& range, Optional<uint32_t> count)
401{
402 return doGetAllKeys(execState, count, [range = WTFMove(range)]() {
403 return range;
404 });
405}
406
407ExceptionOr<Ref<IDBRequest>> IDBIndex::getAllKeys(ExecState& execState, JSValue key, Optional<uint32_t> count)
408{
409 return doGetAllKeys(execState, count, [state=&execState, key]() {
410 auto onlyResult = IDBKeyRange::only(*state, key);
411 if (onlyResult.hasException())
412 return ExceptionOr<RefPtr<IDBKeyRange>>{ Exception(DataError, "Failed to execute 'getAllKeys' on 'IDBIndex': The parameter is not a valid key."_s) };
413
414 return ExceptionOr<RefPtr<IDBKeyRange>> { onlyResult.releaseReturnValue() };
415 });
416}
417
418void IDBIndex::markAsDeleted()
419{
420 ASSERT(&m_objectStore.transaction().database().originThread() == &Thread::current());
421
422 ASSERT(!m_deleted);
423 m_deleted = true;
424}
425
426void IDBIndex::ref()
427{
428 m_objectStore.ref();
429}
430
431void IDBIndex::deref()
432{
433 m_objectStore.deref();
434}
435
436} // namespace WebCore
437
438#endif // ENABLE(INDEXED_DATABASE)
439