IndexedDB: Implement custom bindings for parsing options
[WebKit-https.git] / Source / WebCore / Modules / indexeddb / IDBDatabase.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 "IDBDatabase.h"
28
29 #if ENABLE(INDEXED_DATABASE)
30
31 #include "DOMStringList.h"
32 #include "EventQueue.h"
33 #include "ExceptionCode.h"
34 #include "IDBAny.h"
35 #include "IDBDatabaseCallbacks.h"
36 #include "IDBDatabaseError.h"
37 #include "IDBDatabaseException.h"
38 #include "IDBEventDispatcher.h"
39 #include "IDBIndex.h"
40 #include "IDBKeyPath.h"
41 #include "IDBObjectStore.h"
42 #include "IDBTracing.h"
43 #include "IDBTransaction.h"
44 #include "IDBUpgradeNeededEvent.h"
45 #include "IDBVersionChangeEvent.h"
46 #include "IDBVersionChangeRequest.h"
47 #include "ScriptCallStack.h"
48 #include "ScriptExecutionContext.h"
49 #include <limits>
50 #include <wtf/Atomics.h>
51
52 namespace WebCore {
53
54 PassRefPtr<IDBDatabase> IDBDatabase::create(ScriptExecutionContext* context, PassRefPtr<IDBDatabaseBackendInterface> database, PassRefPtr<IDBDatabaseCallbacks> callbacks)
55 {
56     RefPtr<IDBDatabase> idbDatabase(adoptRef(new IDBDatabase(context, database, callbacks)));
57     idbDatabase->suspendIfNeeded();
58     return idbDatabase.release();
59 }
60
61 IDBDatabase::IDBDatabase(ScriptExecutionContext* context, PassRefPtr<IDBDatabaseBackendInterface> backend, PassRefPtr<IDBDatabaseCallbacks> callbacks)
62     : ActiveDOMObject(context, this)
63     , m_backend(backend)
64     , m_closePending(false)
65     , m_contextStopped(false)
66     , m_databaseCallbacks(callbacks)
67 {
68     // We pass a reference of this object before it can be adopted.
69     relaxAdoptionRequirement();
70 }
71
72 IDBDatabase::~IDBDatabase()
73 {
74     close();
75 }
76
77 int64_t IDBDatabase::nextTransactionId()
78 {
79     // Only keep a 32-bit counter to allow ports to use the other 32
80     // bits of the id.
81     AtomicallyInitializedStatic(int, currentTransactionId = 0);
82     return atomicIncrement(&currentTransactionId);
83 }
84
85 void IDBDatabase::transactionCreated(IDBTransaction* transaction)
86 {
87     ASSERT(transaction);
88     ASSERT(!m_transactions.contains(transaction->id()));
89     m_transactions.add(transaction->id(), transaction);
90
91     if (transaction->isVersionChange()) {
92         ASSERT(!m_versionChangeTransaction);
93         m_versionChangeTransaction = transaction;
94     }
95 }
96
97 void IDBDatabase::transactionFinished(IDBTransaction* transaction)
98 {
99     ASSERT(transaction);
100     ASSERT(m_transactions.contains(transaction->id()));
101     ASSERT(m_transactions.get(transaction->id()) == transaction);
102     m_transactions.remove(transaction->id());
103
104     if (transaction->isVersionChange()) {
105         ASSERT(m_versionChangeTransaction == transaction);
106         m_versionChangeTransaction = 0;
107     }
108
109     if (m_closePending && m_transactions.isEmpty())
110         closeConnection();
111 }
112
113 void IDBDatabase::onAbort(int64_t transactionId, PassRefPtr<IDBDatabaseError> error)
114 {
115     ASSERT(m_transactions.contains(transactionId));
116     m_transactions.get(transactionId)->onAbort(error);
117 }
118
119 void IDBDatabase::onComplete(int64_t transactionId)
120 {
121     ASSERT(m_transactions.contains(transactionId));
122     m_transactions.get(transactionId)->onComplete();
123 }
124
125 PassRefPtr<DOMStringList> IDBDatabase::objectStoreNames() const
126 {
127     RefPtr<DOMStringList> objectStoreNames = DOMStringList::create();
128     for (IDBDatabaseMetadata::ObjectStoreMap::const_iterator it = m_metadata.objectStores.begin(); it != m_metadata.objectStores.end(); ++it)
129         objectStoreNames->append(it->value.name);
130     objectStoreNames->sort();
131     return objectStoreNames.release();
132 }
133
134 PassRefPtr<IDBAny> IDBDatabase::version() const
135 {
136     int64_t intVersion = m_metadata.intVersion;
137     if (intVersion == IDBDatabaseMetadata::NoIntVersion)
138         return IDBAny::createString(m_metadata.version);
139     return IDBAny::create(intVersion);
140 }
141
142 PassRefPtr<IDBObjectStore> IDBDatabase::createObjectStore(const String& name, const Dictionary& options, ExceptionCode& ec)
143 {
144     IDBKeyPath keyPath;
145     bool autoIncrement = false;
146     if (!options.isUndefinedOrNull()) {
147         String keyPathString;
148         Vector<String> keyPathArray;
149         if (options.get("keyPath", keyPathArray))
150             keyPath = IDBKeyPath(keyPathArray);
151         else if (options.getWithUndefinedOrNullCheck("keyPath", keyPathString))
152             keyPath = IDBKeyPath(keyPathString);
153
154         options.get("autoIncrement", autoIncrement);
155     }
156
157     return createObjectStore(name, keyPath, autoIncrement, ec);
158 }
159
160 PassRefPtr<IDBObjectStore> IDBDatabase::createObjectStore(const String& name, const IDBKeyPath& keyPath, bool autoIncrement, ExceptionCode& ec)
161 {
162     if (!m_versionChangeTransaction) {
163         ec = IDBDatabaseException::InvalidStateError;
164         return 0;
165     }
166     if (!m_versionChangeTransaction->isActive()) {
167         ec = IDBDatabaseException::TransactionInactiveError;
168         return 0;
169     }
170
171     if (containsObjectStore(name)) {
172         ec = IDBDatabaseException::ConstraintError;
173         return 0;
174     }
175
176     if (!keyPath.isNull() && !keyPath.isValid()) {
177         ec = IDBDatabaseException::SyntaxError;
178         return 0;
179     }
180
181     if (autoIncrement && ((keyPath.type() == IDBKeyPath::StringType && keyPath.string().isEmpty()) || keyPath.type() == IDBKeyPath::ArrayType)) {
182         ec = IDBDatabaseException::InvalidAccessError;
183         return 0;
184     }
185
186     int64_t objectStoreId = m_metadata.maxObjectStoreId + 1;
187     RefPtr<IDBObjectStoreBackendInterface> objectStoreBackend = m_backend->createObjectStore(objectStoreId, name, keyPath, autoIncrement, m_versionChangeTransaction->backend(), ec);
188     if (!objectStoreBackend) {
189         ASSERT(ec);
190         return 0;
191     }
192
193     IDBObjectStoreMetadata metadata(name, objectStoreId, keyPath, autoIncrement, IDBObjectStoreBackendInterface::MinimumIndexId);
194     RefPtr<IDBObjectStore> objectStore = IDBObjectStore::create(metadata, objectStoreBackend.release(), m_versionChangeTransaction.get());
195     m_metadata.objectStores.set(metadata.id, metadata);
196     ++m_metadata.maxObjectStoreId;
197
198     m_versionChangeTransaction->objectStoreCreated(name, objectStore);
199     return objectStore.release();
200 }
201
202 void IDBDatabase::deleteObjectStore(const String& name, ExceptionCode& ec)
203 {
204     if (!m_versionChangeTransaction) {
205         ec = IDBDatabaseException::InvalidStateError;
206         return;
207     }
208     if (!m_versionChangeTransaction->isActive()) {
209         ec = IDBDatabaseException::TransactionInactiveError;
210         return;
211     }
212
213     int64_t objectStoreId = findObjectStoreId(name);
214     if (objectStoreId == IDBObjectStoreMetadata::InvalidId) {
215         ec = IDBDatabaseException::NotFoundError;
216         return;
217     }
218
219     m_backend->deleteObjectStore(objectStoreId, m_versionChangeTransaction->backend(), ec);
220     if (!ec) {
221         m_versionChangeTransaction->objectStoreDeleted(name);
222         m_metadata.objectStores.remove(objectStoreId);
223     }
224 }
225
226 PassRefPtr<IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext* context, const Vector<String>& scope, const String& modeString, ExceptionCode& ec)
227 {
228     if (!scope.size()) {
229         ec = IDBDatabaseException::InvalidAccessError;
230         return 0;
231     }
232
233     IDBTransaction::Mode mode = IDBTransaction::stringToMode(modeString, context, ec);
234     if (ec)
235         return 0;
236
237     if (m_versionChangeTransaction || m_closePending) {
238         ec = IDBDatabaseException::InvalidStateError;
239         return 0;
240     }
241
242     Vector<int64_t> objectStoreIds;
243     for (size_t i = 0; i < scope.size(); ++i) {
244         int64_t objectStoreId = findObjectStoreId(scope[i]);
245         if (objectStoreId == IDBObjectStoreMetadata::InvalidId) {
246             ec = IDBDatabaseException::NotFoundError;
247             return 0;
248         }
249         objectStoreIds.append(objectStoreId);
250     }
251
252     // We need to create a new transaction synchronously. Locks are acquired asynchronously. Operations
253     // can be queued against the transaction at any point. They will start executing as soon as the
254     // appropriate locks have been acquired.
255     // Also note that each backend object corresponds to exactly one IDBTransaction object.
256     int64_t transactionId = nextTransactionId();
257     RefPtr<IDBTransactionBackendInterface> transactionBackend = m_backend->createTransaction(transactionId, objectStoreIds, mode);
258     if (!transactionBackend) {
259         ASSERT(ec);
260         return 0;
261     }
262
263     RefPtr<IDBTransaction> transaction = IDBTransaction::create(context, transactionId, transactionBackend, scope, mode, this);
264     transactionBackend->setCallbacks(transaction.get());
265     return transaction.release();
266 }
267
268 PassRefPtr<IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext* context, const String& storeName, const String& mode, ExceptionCode& ec)
269 {
270     RefPtr<DOMStringList> storeNames = DOMStringList::create();
271     storeNames->append(storeName);
272     return transaction(context, storeNames, mode, ec);
273 }
274
275 void IDBDatabase::forceClose()
276 {
277     ExceptionCode ec = 0;
278     for (TransactionMap::const_iterator::Values it = m_transactions.begin().values(), end = m_transactions.end().values(); it != end; ++it)
279         (*it)->abort(ec);
280     this->close();
281 }
282
283 void IDBDatabase::close()
284 {
285     if (m_closePending)
286         return;
287
288     m_closePending = true;
289
290     if (m_transactions.isEmpty())
291         closeConnection();
292 }
293
294 void IDBDatabase::closeConnection()
295 {
296     ASSERT(m_closePending);
297     ASSERT(m_transactions.isEmpty());
298
299     m_backend->close(m_databaseCallbacks);
300
301     if (m_contextStopped || !scriptExecutionContext())
302         return;
303
304     EventQueue* eventQueue = scriptExecutionContext()->eventQueue();
305     // Remove any pending versionchange events scheduled to fire on this
306     // connection. They would have been scheduled by the backend when another
307     // connection called setVersion, but the frontend connection is being
308     // closed before they could fire.
309     for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) {
310         bool removed = eventQueue->cancelEvent(m_enqueuedEvents[i].get());
311         ASSERT_UNUSED(removed, removed);
312     }
313 }
314
315 void IDBDatabase::onVersionChange(int64_t oldVersion, int64_t newVersion)
316 {
317     if (m_contextStopped || !scriptExecutionContext())
318         return;
319
320     if (m_closePending)
321         return;
322
323     enqueueEvent(IDBUpgradeNeededEvent::create(oldVersion, newVersion, eventNames().versionchangeEvent));
324 }
325
326 void IDBDatabase::onVersionChange(const String& version)
327 {
328     if (m_contextStopped || !scriptExecutionContext())
329         return;
330
331     if (m_closePending)
332         return;
333
334     enqueueEvent(IDBVersionChangeEvent::create(version, eventNames().versionchangeEvent));
335 }
336
337 void IDBDatabase::enqueueEvent(PassRefPtr<Event> event)
338 {
339     ASSERT(!m_contextStopped);
340     ASSERT(scriptExecutionContext());
341     EventQueue* eventQueue = scriptExecutionContext()->eventQueue();
342     event->setTarget(this);
343     eventQueue->enqueueEvent(event.get());
344     m_enqueuedEvents.append(event);
345 }
346
347 bool IDBDatabase::dispatchEvent(PassRefPtr<Event> event)
348 {
349     IDB_TRACE("IDBDatabase::dispatchEvent");
350     ASSERT(event->type() == eventNames().versionchangeEvent);
351     for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) {
352         if (m_enqueuedEvents[i].get() == event.get())
353             m_enqueuedEvents.remove(i);
354     }
355     return EventTarget::dispatchEvent(event.get());
356 }
357
358 int64_t IDBDatabase::findObjectStoreId(const String& name) const
359 {
360     for (IDBDatabaseMetadata::ObjectStoreMap::const_iterator it = m_metadata.objectStores.begin(); it != m_metadata.objectStores.end(); ++it) {
361         if (it->value.name == name) {
362             ASSERT(it->key != IDBObjectStoreMetadata::InvalidId);
363             return it->key;
364         }
365     }
366     return IDBObjectStoreMetadata::InvalidId;
367 }
368
369 void IDBDatabase::stop()
370 {
371     ActiveDOMObject::stop();
372     // Stop fires at a deterministic time, so we need to call close in it.
373     close();
374
375     m_contextStopped = true;
376 }
377
378 const AtomicString& IDBDatabase::interfaceName() const
379 {
380     return eventNames().interfaceForIDBDatabase;
381 }
382
383 ScriptExecutionContext* IDBDatabase::scriptExecutionContext() const
384 {
385     return ActiveDOMObject::scriptExecutionContext();
386 }
387
388 EventTargetData* IDBDatabase::eventTargetData()
389 {
390     return &m_eventTargetData;
391 }
392
393 EventTargetData* IDBDatabase::ensureEventTargetData()
394 {
395     return &m_eventTargetData;
396 }
397
398 } // namespace WebCore
399
400 #endif // ENABLE(INDEXED_DATABASE)