IndexedDB: Move method precondition checks to front end objects
[WebKit-https.git] / Source / WebCore / Modules / indexeddb / IDBDatabaseBackendImpl.cpp
1 /*
2  * Copyright (C) 2011 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 "IDBDatabaseBackendImpl.h"
28
29 #if ENABLE(INDEXED_DATABASE)
30
31 #include "CrossThreadTask.h"
32 #include "DOMStringList.h"
33 #include "IDBBackingStore.h"
34 #include "IDBDatabaseException.h"
35 #include "IDBFactoryBackendImpl.h"
36 #include "IDBObjectStoreBackendImpl.h"
37 #include "IDBTransactionBackendImpl.h"
38 #include "IDBTransactionCoordinator.h"
39
40 namespace WebCore {
41
42 class IDBDatabaseBackendImpl::PendingOpenCall : public RefCounted<PendingOpenCall> {
43 public:
44     static PassRefPtr<PendingOpenCall> create(PassRefPtr<IDBCallbacks> callbacks)
45     {
46         return adoptRef(new PendingOpenCall(callbacks));
47     }
48     PassRefPtr<IDBCallbacks> callbacks() { return m_callbacks; }
49
50 private:
51     PendingOpenCall(PassRefPtr<IDBCallbacks> callbacks)
52         : m_callbacks(callbacks)
53     {
54     }
55     RefPtr<IDBCallbacks> m_callbacks;
56 };
57
58 class IDBDatabaseBackendImpl::PendingDeleteCall : public RefCounted<PendingDeleteCall> {
59 public:
60     static PassRefPtr<PendingDeleteCall> create(PassRefPtr<IDBCallbacks> callbacks)
61     {
62         return adoptRef(new PendingDeleteCall(callbacks));
63     }
64     PassRefPtr<IDBCallbacks> callbacks() { return m_callbacks; }
65
66 private:
67     PendingDeleteCall(PassRefPtr<IDBCallbacks> callbacks)
68         : m_callbacks(callbacks)
69     {
70     }
71     RefPtr<IDBCallbacks> m_callbacks;
72 };
73
74 class IDBDatabaseBackendImpl::PendingSetVersionCall : public RefCounted<PendingSetVersionCall> {
75 public:
76     static PassRefPtr<PendingSetVersionCall> create(const String& version, PassRefPtr<IDBCallbacks> callbacks, PassRefPtr<IDBDatabaseCallbacks> databaseCallbacks)
77     {
78         return adoptRef(new PendingSetVersionCall(version, callbacks, databaseCallbacks));
79     }
80     String version() { return m_version; }
81     PassRefPtr<IDBCallbacks> callbacks() { return m_callbacks; }
82     PassRefPtr<IDBDatabaseCallbacks> databaseCallbacks() { return m_databaseCallbacks; }
83
84 private:
85     PendingSetVersionCall(const String& version, PassRefPtr<IDBCallbacks> callbacks, PassRefPtr<IDBDatabaseCallbacks> databaseCallbacks)
86         : m_version(version)
87         , m_callbacks(callbacks)
88         , m_databaseCallbacks(databaseCallbacks)
89     {
90     }
91     String m_version;
92     RefPtr<IDBCallbacks> m_callbacks;
93     RefPtr<IDBDatabaseCallbacks> m_databaseCallbacks;
94 };
95
96 PassRefPtr<IDBDatabaseBackendImpl> IDBDatabaseBackendImpl::create(const String& name, IDBBackingStore* database, IDBTransactionCoordinator* coordinator, IDBFactoryBackendImpl* factory, const String& uniqueIdentifier)
97 {
98     RefPtr<IDBDatabaseBackendImpl> backend = adoptRef(new IDBDatabaseBackendImpl(name, database, coordinator, factory, uniqueIdentifier));
99     if (!backend->openInternal())
100         return 0;
101     return backend.release();
102 }
103
104 IDBDatabaseBackendImpl::IDBDatabaseBackendImpl(const String& name, IDBBackingStore* backingStore, IDBTransactionCoordinator* coordinator, IDBFactoryBackendImpl* factory, const String& uniqueIdentifier)
105     : m_backingStore(backingStore)
106     , m_id(InvalidId)
107     , m_name(name)
108     , m_version("")
109     , m_identifier(uniqueIdentifier)
110     , m_factory(factory)
111     , m_transactionCoordinator(coordinator)
112     , m_pendingConnectionCount(0)
113 {
114     ASSERT(!m_name.isNull());
115 }
116
117 bool IDBDatabaseBackendImpl::openInternal()
118 {
119     bool success = m_backingStore->getIDBDatabaseMetaData(m_name, m_version, m_id);
120     ASSERT(success == (m_id != InvalidId));
121     if (success) {
122         loadObjectStores();
123         return true;
124     }
125     return m_backingStore->createIDBDatabaseMetaData(m_name, m_version, m_id);
126 }
127
128 IDBDatabaseBackendImpl::~IDBDatabaseBackendImpl()
129 {
130 }
131
132 PassRefPtr<IDBBackingStore> IDBDatabaseBackendImpl::backingStore() const
133 {
134     return m_backingStore;
135 }
136
137 IDBDatabaseMetadata IDBDatabaseBackendImpl::metadata() const
138 {
139     IDBDatabaseMetadata metadata(m_name, m_version);
140     for (ObjectStoreMap::const_iterator it = m_objectStores.begin(); it != m_objectStores.end(); ++it)
141         metadata.objectStores.set(it->first, it->second->metadata());
142     return metadata;
143 }
144
145 PassRefPtr<IDBObjectStoreBackendInterface> IDBDatabaseBackendImpl::createObjectStore(const String& name, const IDBKeyPath& keyPath, bool autoIncrement, IDBTransactionBackendInterface* transactionPtr, ExceptionCode& ec)
146 {
147     ASSERT(transactionPtr->mode() == IDBTransaction::VERSION_CHANGE);
148     ASSERT(!m_objectStores.contains(name));
149
150     RefPtr<IDBObjectStoreBackendImpl> objectStore = IDBObjectStoreBackendImpl::create(this, name, keyPath, autoIncrement);
151     ASSERT(objectStore->name() == name);
152
153     RefPtr<IDBDatabaseBackendImpl> database = this;
154     RefPtr<IDBTransactionBackendInterface> transaction = transactionPtr;
155     if (!transaction->scheduleTask(createCallbackTask(&IDBDatabaseBackendImpl::createObjectStoreInternal, database, objectStore, transaction),
156                                    createCallbackTask(&IDBDatabaseBackendImpl::removeObjectStoreFromMap, database, objectStore))) {
157         ec = IDBDatabaseException::TRANSACTION_INACTIVE_ERR;
158         return 0;
159     }
160
161     m_objectStores.set(name, objectStore);
162     return objectStore.release();
163 }
164
165 void IDBDatabaseBackendImpl::createObjectStoreInternal(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl> database, PassRefPtr<IDBObjectStoreBackendImpl> objectStore,  PassRefPtr<IDBTransactionBackendInterface> transaction)
166 {
167     int64_t objectStoreId;
168
169     if (!database->m_backingStore->createObjectStore(database->id(), objectStore->name(), objectStore->keyPath(), objectStore->autoIncrement(), objectStoreId)) {
170         transaction->abort();
171         return;
172     }
173
174     objectStore->setId(objectStoreId);
175     transaction->didCompleteTaskEvents();
176 }
177
178 PassRefPtr<IDBObjectStoreBackendInterface> IDBDatabaseBackendImpl::objectStore(const String& name)
179 {
180     return m_objectStores.get(name);
181 }
182
183 void IDBDatabaseBackendImpl::deleteObjectStore(const String& name, IDBTransactionBackendInterface* transactionPtr, ExceptionCode& ec)
184 {
185     ASSERT(transactionPtr->mode() == IDBTransaction::VERSION_CHANGE);
186     ASSERT(m_objectStores.contains(name));
187
188     RefPtr<IDBDatabaseBackendImpl> database = this;
189     RefPtr<IDBObjectStoreBackendImpl> objectStore = m_objectStores.get(name);
190     RefPtr<IDBTransactionBackendInterface> transaction = transactionPtr;
191     if (!transaction->scheduleTask(createCallbackTask(&IDBDatabaseBackendImpl::deleteObjectStoreInternal, database, objectStore, transaction),
192                                    createCallbackTask(&IDBDatabaseBackendImpl::addObjectStoreToMap, database, objectStore))) {
193         ec = IDBDatabaseException::TRANSACTION_INACTIVE_ERR;
194         return;
195     }
196     m_objectStores.remove(name);
197 }
198
199 void IDBDatabaseBackendImpl::deleteObjectStoreInternal(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl> database, PassRefPtr<IDBObjectStoreBackendImpl> objectStore, PassRefPtr<IDBTransactionBackendInterface> transaction)
200 {
201     database->m_backingStore->deleteObjectStore(database->id(), objectStore->id());
202     transaction->didCompleteTaskEvents();
203 }
204
205 void IDBDatabaseBackendImpl::setVersion(const String& version, PassRefPtr<IDBCallbacks> prpCallbacks, PassRefPtr<IDBDatabaseCallbacks> prpDatabaseCallbacks, ExceptionCode& ec)
206 {
207     RefPtr<IDBCallbacks> callbacks = prpCallbacks;
208     RefPtr<IDBDatabaseCallbacks> databaseCallbacks = prpDatabaseCallbacks;
209     if (!m_databaseCallbacksSet.contains(databaseCallbacks)) {
210         callbacks->onError(IDBDatabaseError::create(IDBDatabaseException::IDB_ABORT_ERR, "Connection was closed before set version transaction was created"));
211         return;
212     }
213     for (DatabaseCallbacksSet::const_iterator it = m_databaseCallbacksSet.begin(); it != m_databaseCallbacksSet.end(); ++it) {
214         if (*it != databaseCallbacks)
215             (*it)->onVersionChange(version);
216     }
217     // FIXME: Only fire onBlocked if there are open connections after the
218     // VersionChangeEvents are received, not just set up to fire.
219     // https://bugs.webkit.org/show_bug.cgi?id=71130
220     if (connectionCount() > 1) {
221         callbacks->onBlocked();
222         RefPtr<PendingSetVersionCall> pendingSetVersionCall = PendingSetVersionCall::create(version, callbacks, databaseCallbacks);
223         m_pendingSetVersionCalls.append(pendingSetVersionCall);
224         return;
225     }
226     if (m_runningVersionChangeTransaction) {
227         RefPtr<PendingSetVersionCall> pendingSetVersionCall = PendingSetVersionCall::create(version, callbacks, databaseCallbacks);
228         m_pendingSetVersionCalls.append(pendingSetVersionCall);
229         return;
230     }
231
232     RefPtr<DOMStringList> objectStoreNames = DOMStringList::create();
233     RefPtr<IDBTransactionBackendInterface> transaction = this->transaction(objectStoreNames.get(), IDBTransaction::VERSION_CHANGE, ec);
234     ASSERT(!ec);
235
236     RefPtr<IDBDatabaseBackendImpl> database = this;
237     if (!transaction->scheduleTask(createCallbackTask(&IDBDatabaseBackendImpl::setVersionInternal, database, version, callbacks, transaction),
238                                    createCallbackTask(&IDBDatabaseBackendImpl::resetVersion, database, m_version))) {
239         ec = IDBDatabaseException::TRANSACTION_INACTIVE_ERR;
240     }
241 }
242
243 void IDBDatabaseBackendImpl::setVersionInternal(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl> database, const String& version, PassRefPtr<IDBCallbacks> callbacks, PassRefPtr<IDBTransactionBackendInterface> transaction)
244 {
245     int64_t databaseId = database->id();
246     database->m_version = version;
247     if (!database->m_backingStore->updateIDBDatabaseMetaData(databaseId, database->m_version)) {
248         callbacks->onError(IDBDatabaseError::create(IDBDatabaseException::UNKNOWN_ERR, "Error writing data to stable storage."));
249         transaction->abort();
250         return;
251     }
252     callbacks->onSuccess(transaction);
253 }
254
255 void IDBDatabaseBackendImpl::transactionStarted(PassRefPtr<IDBTransactionBackendInterface> prpTransaction)
256 {
257     RefPtr<IDBTransactionBackendInterface> transaction = prpTransaction;
258     if (transaction->mode() == IDBTransaction::VERSION_CHANGE) {
259         ASSERT(!m_runningVersionChangeTransaction);
260         m_runningVersionChangeTransaction = transaction;
261     }
262 }
263
264 void IDBDatabaseBackendImpl::transactionFinished(PassRefPtr<IDBTransactionBackendInterface> prpTransaction)
265 {
266     RefPtr<IDBTransactionBackendInterface> transaction = prpTransaction;
267     ASSERT(m_transactions.contains(transaction.get()));
268     m_transactions.remove(transaction.get());
269     if (transaction->mode() == IDBTransaction::VERSION_CHANGE) {
270         ASSERT(transaction.get() == m_runningVersionChangeTransaction.get());
271         m_runningVersionChangeTransaction.clear();
272         processPendingCalls();
273     }
274 }
275
276 int32_t IDBDatabaseBackendImpl::connectionCount()
277 {
278     return m_databaseCallbacksSet.size() + m_pendingConnectionCount;
279 }
280
281 void IDBDatabaseBackendImpl::processPendingCalls()
282 {
283     ASSERT(connectionCount() <= 1);
284
285     // Pending calls may be requeued or aborted
286     Deque<RefPtr<PendingSetVersionCall> > pendingSetVersionCalls;
287     m_pendingSetVersionCalls.swap(pendingSetVersionCalls);
288     while (!pendingSetVersionCalls.isEmpty()) {
289         ExceptionCode ec = 0;
290         RefPtr<PendingSetVersionCall> pendingSetVersionCall = pendingSetVersionCalls.takeFirst();
291         setVersion(pendingSetVersionCall->version(), pendingSetVersionCall->callbacks(), pendingSetVersionCall->databaseCallbacks(), ec);
292         ASSERT(!ec);
293     }
294
295     // If there were any pending set version calls, we better have started one.
296     ASSERT(m_pendingSetVersionCalls.isEmpty() || m_runningVersionChangeTransaction);
297
298     // m_pendingSetVersionCalls is non-empty in two cases:
299     // 1) When two versionchange transactions are requested while another
300     //    version change transaction is running.
301     // 2) When three versionchange transactions are requested in a row, before
302     //    any of their event handlers are run.
303     // Note that this check is only an optimization to reduce queue-churn and
304     // not necessary for correctness; deleteDatabase and openConnection will
305     // requeue their calls if this condition is true.
306     if (m_runningVersionChangeTransaction || !m_pendingSetVersionCalls.isEmpty())
307         return;
308
309     // Pending calls may be requeued.
310     Deque<RefPtr<PendingDeleteCall> > pendingDeleteCalls;
311     m_pendingDeleteCalls.swap(pendingDeleteCalls);
312     while (!pendingDeleteCalls.isEmpty()) {
313         RefPtr<PendingDeleteCall> pendingDeleteCall = pendingDeleteCalls.takeFirst();
314         deleteDatabase(pendingDeleteCall->callbacks());
315     }
316
317     // This check is also not really needed, openConnection would just requeue its calls.
318     if (m_runningVersionChangeTransaction || !m_pendingSetVersionCalls.isEmpty() || !m_pendingDeleteCalls.isEmpty())
319         return;
320
321     // Given the check above, it appears that calls cannot be requeued by
322     // openConnection, but use a different queue for iteration to be safe.
323     Deque<RefPtr<PendingOpenCall> > pendingOpenCalls;
324     m_pendingOpenCalls.swap(pendingOpenCalls);
325     while (!pendingOpenCalls.isEmpty()) {
326         RefPtr<PendingOpenCall> pendingOpenCall = pendingOpenCalls.takeFirst();
327         openConnection(pendingOpenCall->callbacks());
328     }
329     ASSERT(m_pendingOpenCalls.isEmpty());
330 }
331
332 PassRefPtr<IDBTransactionBackendInterface> IDBDatabaseBackendImpl::transaction(DOMStringList* objectStoreNames, unsigned short mode, ExceptionCode& ec)
333 {
334     for (size_t i = 0; i < objectStoreNames->length(); ++i) {
335         if (!m_objectStores.contains(objectStoreNames->item(i))) {
336             ec = IDBDatabaseException::IDB_NOT_FOUND_ERR;
337             return 0;
338         }
339     }
340
341     RefPtr<IDBTransactionBackendInterface> transaction = IDBTransactionBackendImpl::create(objectStoreNames, mode, this);
342     m_transactions.add(transaction.get());
343     return transaction.release();
344 }
345
346 void IDBDatabaseBackendImpl::registerFrontendCallbacks(PassRefPtr<IDBDatabaseCallbacks> callbacks)
347 {
348     ASSERT(m_backingStore.get());
349     ASSERT(m_pendingConnectionCount);
350     --m_pendingConnectionCount;
351     m_databaseCallbacksSet.add(RefPtr<IDBDatabaseCallbacks>(callbacks));
352 }
353
354 void IDBDatabaseBackendImpl::openConnection(PassRefPtr<IDBCallbacks> callbacks)
355 {
356     ASSERT(m_backingStore.get());
357     if (!m_pendingDeleteCalls.isEmpty() || m_runningVersionChangeTransaction || !m_pendingSetVersionCalls.isEmpty())
358         m_pendingOpenCalls.append(PendingOpenCall::create(callbacks));
359     else {
360         if (m_id == InvalidId && !openInternal())
361             callbacks->onError(IDBDatabaseError::create(IDBDatabaseException::UNKNOWN_ERR, "Internal error."));
362         else {
363             ++m_pendingConnectionCount;
364             callbacks->onSuccess(this);
365         }
366     }
367 }
368
369 void IDBDatabaseBackendImpl::deleteDatabase(PassRefPtr<IDBCallbacks> prpCallbacks)
370 {
371     if (m_runningVersionChangeTransaction || !m_pendingSetVersionCalls.isEmpty()) {
372         m_pendingDeleteCalls.append(PendingDeleteCall::create(prpCallbacks));
373         return;
374     }
375     RefPtr<IDBCallbacks> callbacks = prpCallbacks;
376     // FIXME: Only fire onVersionChange if there the connection isn't in
377     // the process of closing.
378     // https://bugs.webkit.org/show_bug.cgi?id=71129
379     for (DatabaseCallbacksSet::const_iterator it = m_databaseCallbacksSet.begin(); it != m_databaseCallbacksSet.end(); ++it)
380         (*it)->onVersionChange("");
381     // FIXME: Only fire onBlocked if there are open connections after the
382     // VersionChangeEvents are received, not just set up to fire.
383     // https://bugs.webkit.org/show_bug.cgi?id=71130
384     if (!m_databaseCallbacksSet.isEmpty()) {
385         m_pendingDeleteCalls.append(PendingDeleteCall::create(callbacks));
386         callbacks->onBlocked();
387         return;
388     }
389     if (!m_backingStore->deleteDatabase(m_name)) {
390         callbacks->onError(IDBDatabaseError::create(IDBDatabaseException::UNKNOWN_ERR, "Internal error."));
391         return;
392     }
393     m_version = "";
394     m_id = InvalidId;
395     m_objectStores.clear();
396     callbacks->onSuccess(SerializedScriptValue::nullValue());
397 }
398
399 void IDBDatabaseBackendImpl::close(PassRefPtr<IDBDatabaseCallbacks> prpCallbacks)
400 {
401     RefPtr<IDBDatabaseCallbacks> callbacks = prpCallbacks;
402     ASSERT(m_databaseCallbacksSet.contains(callbacks));
403     m_databaseCallbacksSet.remove(callbacks);
404     if (connectionCount() > 1)
405         return;
406
407     processPendingCalls();
408
409     if (!connectionCount()) {
410         TransactionSet transactions(m_transactions);
411         for (TransactionSet::const_iterator it = transactions.begin(); it != transactions.end(); ++it)
412             (*it)->abort();
413         ASSERT(m_transactions.isEmpty());
414
415         m_backingStore.clear();
416         // This check should only be false in tests.
417         if (m_factory)
418             m_factory->removeIDBDatabaseBackend(m_identifier);
419     }
420 }
421
422 void IDBDatabaseBackendImpl::loadObjectStores()
423 {
424     Vector<int64_t> ids;
425     Vector<String> names;
426     Vector<IDBKeyPath> keyPaths;
427     Vector<bool> autoIncrementFlags;
428     m_backingStore->getObjectStores(m_id, ids, names, keyPaths, autoIncrementFlags);
429
430     ASSERT(names.size() == ids.size());
431     ASSERT(keyPaths.size() == ids.size());
432     ASSERT(autoIncrementFlags.size() == ids.size());
433
434     for (size_t i = 0; i < ids.size(); i++)
435         m_objectStores.set(names[i], IDBObjectStoreBackendImpl::create(this, ids[i], names[i], keyPaths[i], autoIncrementFlags[i]));
436 }
437
438 void IDBDatabaseBackendImpl::removeObjectStoreFromMap(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl> database, PassRefPtr<IDBObjectStoreBackendImpl> prpObjectStore)
439 {
440     RefPtr<IDBObjectStoreBackendImpl> objectStore = prpObjectStore;
441     ASSERT(database->m_objectStores.contains(objectStore->name()));
442     database->m_objectStores.remove(objectStore->name());
443 }
444
445 void IDBDatabaseBackendImpl::addObjectStoreToMap(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl> database, PassRefPtr<IDBObjectStoreBackendImpl> objectStore)
446 {
447     RefPtr<IDBObjectStoreBackendImpl> objectStorePtr = objectStore;
448     ASSERT(!database->m_objectStores.contains(objectStorePtr->name()));
449     database->m_objectStores.set(objectStorePtr->name(), objectStorePtr);
450 }
451
452 void IDBDatabaseBackendImpl::resetVersion(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl> database, const String& version)
453 {
454     database->m_version = version;
455 }
456
457
458 } // namespace WebCore
459
460 #endif // ENABLE(INDEXED_DATABASE)