IndexedDB: Move method precondition checks to front end objects
[WebKit-https.git] / Source / WebCore / Modules / indexeddb / IDBObjectStore.cpp
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 "IDBObjectStore.h"
28
29 #if ENABLE(INDEXED_DATABASE)
30
31 #include "DOMStringList.h"
32 #include "IDBAny.h"
33 #include "IDBBindingUtilities.h"
34 #include "IDBDatabase.h"
35 #include "IDBDatabaseException.h"
36 #include "IDBIndex.h"
37 #include "IDBKey.h"
38 #include "IDBKeyPath.h"
39 #include "IDBKeyRange.h"
40 #include "IDBTracing.h"
41 #include "IDBTransaction.h"
42 #include "SerializedScriptValue.h"
43 #include <wtf/UnusedParam.h>
44
45 namespace WebCore {
46
47 static const unsigned short defaultDirection = IDBCursor::NEXT;
48
49 IDBObjectStore::IDBObjectStore(const IDBObjectStoreMetadata& metadata, PassRefPtr<IDBObjectStoreBackendInterface> idbObjectStore, IDBTransaction* transaction)
50     : m_metadata(metadata)
51     , m_backend(idbObjectStore)
52     , m_transaction(transaction)
53     , m_deleted(false)
54 {
55     ASSERT(m_backend);
56     ASSERT(m_transaction);
57     // We pass a reference to this object before it can be adopted.
58     relaxAdoptionRequirement();
59 }
60
61 PassRefPtr<DOMStringList> IDBObjectStore::indexNames() const
62 {
63     IDB_TRACE("IDBObjectStore::indexNames");
64     RefPtr<DOMStringList> indexNames = DOMStringList::create();
65     for (IDBObjectStoreMetadata::IndexMap::const_iterator it = m_metadata.indexes.begin(); it != m_metadata.indexes.end(); ++it)
66         indexNames->append(it->first);
67     indexNames->sort();
68     return indexNames.release();
69 }
70
71 PassRefPtr<IDBRequest> IDBObjectStore::get(ScriptExecutionContext* context, PassRefPtr<IDBKeyRange> keyRange, ExceptionCode& ec)
72 {
73     IDB_TRACE("IDBObjectStore::get");
74     if (m_deleted) {
75         ec = IDBDatabaseException::IDB_INVALID_STATE_ERR;
76         return 0;
77     }
78     if (!keyRange) {
79         ec = IDBDatabaseException::DATA_ERR;
80         return 0;
81     }
82     if (!m_transaction->isActive()) {
83         ec = IDBDatabaseException::TRANSACTION_INACTIVE_ERR;
84         return 0;
85     }
86     RefPtr<IDBRequest> request = IDBRequest::create(context, IDBAny::create(this), m_transaction.get());
87     m_backend->get(keyRange, request, m_transaction->backend(), ec);
88     if (ec) {
89         request->markEarlyDeath();
90         return 0;
91     }
92     return request.release();
93 }
94
95 PassRefPtr<IDBRequest> IDBObjectStore::get(ScriptExecutionContext* context, PassRefPtr<IDBKey> key, ExceptionCode& ec)
96 {
97     IDB_TRACE("IDBObjectStore::get");
98     RefPtr<IDBKeyRange> keyRange = IDBKeyRange::only(key, ec);
99     if (ec)
100         return 0;
101     return get(context, keyRange.release(), ec);
102 }
103
104 PassRefPtr<IDBRequest> IDBObjectStore::add(ScriptExecutionContext* context, PassRefPtr<SerializedScriptValue> value, PassRefPtr<IDBKey> key, ExceptionCode& ec)
105 {
106     IDB_TRACE("IDBObjectStore::add");
107     return put(IDBObjectStoreBackendInterface::AddOnly, context, value, key, ec);
108 }
109
110 PassRefPtr<IDBRequest> IDBObjectStore::put(ScriptExecutionContext* context, PassRefPtr<SerializedScriptValue> value, PassRefPtr<IDBKey> key, ExceptionCode& ec)
111 {
112     IDB_TRACE("IDBObjectStore::put");
113     return put(IDBObjectStoreBackendInterface::AddOrUpdate, context, value, key, ec);
114 }
115
116 PassRefPtr<IDBRequest> IDBObjectStore::put(IDBObjectStoreBackendInterface::PutMode putMode, ScriptExecutionContext* context, PassRefPtr<SerializedScriptValue> prpValue, PassRefPtr<IDBKey> prpKey, ExceptionCode& ec)
117 {
118     IDB_TRACE("IDBObjectStore::put");
119     RefPtr<SerializedScriptValue> value = prpValue;
120     RefPtr<IDBKey> key = prpKey;
121     if (m_deleted) {
122         ec = IDBDatabaseException::IDB_INVALID_STATE_ERR;
123         return 0;
124     }
125     if (!m_transaction->isActive()) {
126         ec = IDBDatabaseException::TRANSACTION_INACTIVE_ERR;
127         return 0;
128     }
129     if (m_transaction->isReadOnly()) {
130         ec = IDBDatabaseException::READ_ONLY_ERR;
131         return 0;
132     }
133     if (value->blobURLs().size() > 0) {
134         // FIXME: Add Blob/File/FileList support
135         ec = IDBDatabaseException::IDB_DATA_CLONE_ERR;
136         return 0;
137     }
138
139     const IDBKeyPath& keyPath = m_metadata.keyPath;
140     const bool usesInLineKeys = !keyPath.isNull();
141     const bool hasKeyGenerator = autoIncrement();
142
143     if (usesInLineKeys && key) {
144         ec = IDBDatabaseException::DATA_ERR;
145         return 0;
146     }
147     if (!usesInLineKeys && !hasKeyGenerator && !key) {
148         ec = IDBDatabaseException::DATA_ERR;
149         return 0;
150     }
151     if (usesInLineKeys) {
152         RefPtr<IDBKey> keyPathKey = createIDBKeyFromSerializedValueAndKeyPath(value, keyPath);
153         if (keyPathKey && !keyPathKey->isValid()) {
154             ec = IDBDatabaseException::DATA_ERR;
155             return 0;
156         }
157         if (!hasKeyGenerator && !keyPathKey) {
158             ec = IDBDatabaseException::DATA_ERR;
159             return 0;
160         }
161         if (hasKeyGenerator && !keyPathKey) {
162             RefPtr<IDBKey> dummyKey = IDBKey::createNumber(-1);
163             RefPtr<SerializedScriptValue> valueAfterInjection = injectIDBKeyIntoSerializedValue(dummyKey, value, keyPath);
164             if (!valueAfterInjection) {
165                 ec = IDBDatabaseException::DATA_ERR;
166                 return 0;
167             }
168         }
169     }
170     if (key && !key->isValid()) {
171         ec = IDBDatabaseException::DATA_ERR;
172         return 0;
173     }
174
175     RefPtr<IDBRequest> request = IDBRequest::create(context, IDBAny::create(this), m_transaction.get());
176     // FIXME: Pass through keyPathKey as key to simplify back end implementation.
177     m_backend->put(value.release(), key.release(), putMode, request, m_transaction->backend(), ec);
178     if (ec) {
179         request->markEarlyDeath();
180         return 0;
181     }
182     return request.release();
183 }
184
185 PassRefPtr<IDBRequest> IDBObjectStore::deleteFunction(ScriptExecutionContext* context, PassRefPtr<IDBKeyRange> keyRange, ExceptionCode& ec)
186 {
187     IDB_TRACE("IDBObjectStore::delete");
188     if (m_deleted) {
189         ec = IDBDatabaseException::IDB_INVALID_STATE_ERR;
190         return 0;
191     }
192     if (!m_transaction->isActive()) {
193         ec = IDBDatabaseException::TRANSACTION_INACTIVE_ERR;
194         return 0;
195     }
196     if (m_transaction->isReadOnly()) {
197         ec = IDBDatabaseException::READ_ONLY_ERR;
198         return 0;
199     }
200     if (!keyRange) {
201         ec = IDBDatabaseException::DATA_ERR;
202         return 0;
203     }
204
205     RefPtr<IDBRequest> request = IDBRequest::create(context, IDBAny::create(this), m_transaction.get());
206     m_backend->deleteFunction(keyRange, request, m_transaction->backend(), ec);
207     if (ec) {
208         request->markEarlyDeath();
209         return 0;
210     }
211     return request.release();
212 }
213
214 PassRefPtr<IDBRequest> IDBObjectStore::deleteFunction(ScriptExecutionContext* context, PassRefPtr<IDBKey> key, ExceptionCode& ec)
215 {
216     IDB_TRACE("IDBObjectStore::delete");
217     RefPtr<IDBKeyRange> keyRange = IDBKeyRange::only(key, ec);
218     if (ec)
219         return 0;
220     return deleteFunction(context, keyRange.release(), ec);
221 }
222
223 PassRefPtr<IDBRequest> IDBObjectStore::clear(ScriptExecutionContext* context, ExceptionCode& ec)
224 {
225     IDB_TRACE("IDBObjectStore::clear");
226     if (m_deleted) {
227         ec = IDBDatabaseException::IDB_INVALID_STATE_ERR;
228         return 0;
229     }
230     if (!m_transaction->isActive()) {
231         ec = IDBDatabaseException::TRANSACTION_INACTIVE_ERR;
232         return 0;
233     }
234     if (m_transaction->isReadOnly()) {
235         ec = IDBDatabaseException::READ_ONLY_ERR;
236         return 0;
237     }
238
239     RefPtr<IDBRequest> request = IDBRequest::create(context, IDBAny::create(this), m_transaction.get());
240     m_backend->clear(request, m_transaction->backend(), ec);
241     if (ec) {
242         request->markEarlyDeath();
243         return 0;
244     }
245     return request.release();
246 }
247
248 PassRefPtr<IDBIndex> IDBObjectStore::createIndex(const String& name, const String& keyPath, const Dictionary& options, ExceptionCode& ec)
249 {
250     return createIndex(name, IDBKeyPath(keyPath), options, ec);
251 }
252
253 PassRefPtr<IDBIndex> IDBObjectStore::createIndex(const String& name, PassRefPtr<DOMStringList> keyPath, const Dictionary& options, ExceptionCode& ec)
254 {
255     // FIXME: Binding code for DOMString[] should not match null. http://webkit.org/b/84217
256     if (!keyPath)
257         return createIndex(name, IDBKeyPath("null"), options, ec);
258     return createIndex(name, IDBKeyPath(*keyPath), options, ec);
259 }
260
261
262 PassRefPtr<IDBIndex> IDBObjectStore::createIndex(const String& name, const IDBKeyPath& keyPath, const Dictionary& options, ExceptionCode& ec)
263 {
264     IDB_TRACE("IDBObjectStore::createIndex");
265     if (!m_transaction->isVersionChange() || m_deleted) {
266         ec = IDBDatabaseException::IDB_INVALID_STATE_ERR;
267         return 0;
268     }
269     if (!m_transaction->isActive()) {
270         ec = IDBDatabaseException::TRANSACTION_INACTIVE_ERR;
271         return 0;
272     }
273     if (!keyPath.isValid()) {
274         ec = IDBDatabaseException::IDB_SYNTAX_ERR;
275         return 0;
276     }
277     if (name.isNull()) {
278         ec = IDBDatabaseException::IDB_TYPE_ERR;
279         return 0;
280     }
281     if (m_metadata.indexes.contains(name)) {
282         ec = IDBDatabaseException::CONSTRAINT_ERR;
283         return 0;
284     }
285
286     bool unique = false;
287     options.get("unique", unique);
288
289     bool multiEntry = false;
290     options.get("multiEntry", multiEntry);
291
292     if (keyPath.type() == IDBKeyPath::ArrayType && multiEntry) {
293         ec = IDBDatabaseException::IDB_NOT_SUPPORTED_ERR;
294         return 0;
295     }
296
297     RefPtr<IDBIndexBackendInterface> indexBackend = m_backend->createIndex(name, keyPath, unique, multiEntry, m_transaction->backend(), ec);
298     ASSERT(!indexBackend != !ec); // If we didn't get an index, we should have gotten an exception code. And vice versa.
299     if (ec)
300         return 0;
301
302     IDBIndexMetadata metadata(name, keyPath, unique, multiEntry);
303     RefPtr<IDBIndex> index = IDBIndex::create(metadata, indexBackend.release(), this, m_transaction.get());
304     m_indexMap.set(name, index);
305     m_metadata.indexes.set(name, metadata);
306
307     return index.release();
308 }
309
310 PassRefPtr<IDBIndex> IDBObjectStore::index(const String& name, ExceptionCode& ec)
311 {
312     IDB_TRACE("IDBObjectStore::index");
313     if (m_deleted) {
314         ec = IDBDatabaseException::IDB_INVALID_STATE_ERR;
315         return 0;
316     }
317     if (m_transaction->isFinished()) {
318         ec = IDBDatabaseException::IDB_INVALID_STATE_ERR;
319         return 0;
320     }
321
322     IDBIndexMap::iterator it = m_indexMap.find(name);
323     if (it != m_indexMap.end())
324         return it->second;
325
326     RefPtr<IDBIndexBackendInterface> indexBackend = m_backend->index(name, ec);
327     ASSERT(!indexBackend != !ec); // If we didn't get an index, we should have gotten an exception code. And vice versa.
328     if (ec)
329         return 0;
330
331     IDBObjectStoreMetadata::IndexMap::const_iterator mdit = m_metadata.indexes.find(name);
332     ASSERT(mdit != m_metadata.indexes.end());
333
334     RefPtr<IDBIndex> index = IDBIndex::create(mdit->second, indexBackend.release(), this, m_transaction.get());
335     m_indexMap.set(name, index);
336     return index.release();
337 }
338
339 void IDBObjectStore::deleteIndex(const String& name, ExceptionCode& ec)
340 {
341     if (!m_transaction->isVersionChange() || m_deleted) {
342         ec = IDBDatabaseException::IDB_INVALID_STATE_ERR;
343         return;
344     }
345     if (!m_transaction->isActive()) {
346         ec = IDBDatabaseException::TRANSACTION_INACTIVE_ERR;
347         return;
348     }
349     if (!m_metadata.indexes.contains(name)) {
350         ec = IDBDatabaseException::IDB_NOT_FOUND_ERR;
351         return;
352     }
353
354     m_backend->deleteIndex(name, m_transaction->backend(), ec);
355     if (!ec) {
356         IDBIndexMap::iterator it = m_indexMap.find(name);
357         if (it != m_indexMap.end()) {
358             it->second->markDeleted();
359             m_indexMap.remove(name);
360         }
361
362         ASSERT(m_metadata.indexes.contains(name));
363         m_metadata.indexes.remove(name);
364     }
365 }
366
367 PassRefPtr<IDBRequest> IDBObjectStore::openCursor(ScriptExecutionContext* context, PassRefPtr<IDBKeyRange> range, const String& directionString, ExceptionCode& ec)
368 {
369     IDB_TRACE("IDBObjectStore::openCursor");
370     if (m_deleted) {
371         ec = IDBDatabaseException::IDB_INVALID_STATE_ERR;
372         return 0;
373     }
374     if (!m_transaction->isActive()) {
375         ec = IDBDatabaseException::TRANSACTION_INACTIVE_ERR;
376         return 0;
377     }
378     unsigned short direction = IDBCursor::stringToDirection(directionString, ec);
379     if (ec)
380         return 0;
381
382     RefPtr<IDBRequest> request = IDBRequest::create(context, IDBAny::create(this), m_transaction.get());
383     request->setCursorType(IDBCursorBackendInterface::ObjectStoreCursor);
384     m_backend->openCursor(range, direction, request, m_transaction->backend(), ec);
385     if (ec) {
386         request->markEarlyDeath();
387         return 0;
388     }
389     return request.release();
390 }
391
392 PassRefPtr<IDBRequest> IDBObjectStore::openCursor(ScriptExecutionContext* context, PassRefPtr<IDBKeyRange> range, unsigned short direction, ExceptionCode& ec)
393 {
394     IDB_TRACE("IDBObjectStore::openCursor");
395     DEFINE_STATIC_LOCAL(String, consoleMessage, ("Numeric direction values are deprecated in IDBObjectStore.openCursor. Use\"next\", \"nextunique\", \"prev\", or \"prevunique\"."));
396     context->addConsoleMessage(JSMessageSource, LogMessageType, WarningMessageLevel, consoleMessage);
397     const String& directionString = IDBCursor::directionToString(direction, ec);
398     if (ec)
399         return 0;
400     return openCursor(context, range, directionString, ec);
401 }
402
403 PassRefPtr<IDBRequest> IDBObjectStore::openCursor(ScriptExecutionContext* context, PassRefPtr<IDBKey> key, const String& direction, ExceptionCode& ec)
404 {
405     IDB_TRACE("IDBObjectStore::openCursor");
406     RefPtr<IDBKeyRange> keyRange = IDBKeyRange::only(key, ec);
407     if (ec)
408         return 0;
409     return openCursor(context, keyRange.release(), ec);
410 }
411
412 PassRefPtr<IDBRequest> IDBObjectStore::openCursor(ScriptExecutionContext* context, PassRefPtr<IDBKey> key, unsigned short direction, ExceptionCode& ec)
413 {
414     IDB_TRACE("IDBObjectStore::openCursor");
415     RefPtr<IDBKeyRange> keyRange = IDBKeyRange::only(key, ec);
416     if (ec)
417         return 0;
418     return openCursor(context, keyRange.release(), direction, ec);
419 }
420
421 PassRefPtr<IDBRequest> IDBObjectStore::count(ScriptExecutionContext* context, PassRefPtr<IDBKeyRange> range, ExceptionCode& ec)
422 {
423     IDB_TRACE("IDBObjectStore::count");
424     if (m_deleted) {
425         ec = IDBDatabaseException::IDB_INVALID_STATE_ERR;
426         return 0;
427     }
428     if (!m_transaction->isActive()) {
429         ec = IDBDatabaseException::TRANSACTION_INACTIVE_ERR;
430         return 0;
431     }
432     RefPtr<IDBRequest> request = IDBRequest::create(context, IDBAny::create(this), m_transaction.get());
433     m_backend->count(range, request, m_transaction->backend(), ec);
434     if (ec) {
435         request->markEarlyDeath();
436         return 0;
437     }
438     return request.release();
439 }
440
441 PassRefPtr<IDBRequest> IDBObjectStore::count(ScriptExecutionContext* context, PassRefPtr<IDBKey> key, ExceptionCode& ec)
442 {
443     IDB_TRACE("IDBObjectStore::count");
444     RefPtr<IDBKeyRange> keyRange = IDBKeyRange::only(key, ec);
445     if (ec)
446         return 0;
447     return count(context, keyRange.release(), ec);
448 }
449
450 void IDBObjectStore::transactionFinished()
451 {
452     ASSERT(m_transaction->isFinished());
453
454     // Break reference cycles.
455     m_indexMap.clear();
456 }
457
458
459 } // namespace WebCore
460
461 #endif // ENABLE(INDEXED_DATABASE)