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::PendingSetVersionCall : public RefCounted<PendingSetVersionCall> {
43 public:
create(const String & version,PassRefPtr<IDBCallbacks> callbacks,PassRefPtr<IDBDatabaseCallbacks> databaseCallbacks)44 static PassRefPtr<PendingSetVersionCall> create(const String& version, PassRefPtr<IDBCallbacks> callbacks, PassRefPtr<IDBDatabaseCallbacks> databaseCallbacks)
45 {
46 return adoptRef(new PendingSetVersionCall(version, callbacks, databaseCallbacks));
47 }
version()48 String version() { return m_version; }
callbacks()49 PassRefPtr<IDBCallbacks> callbacks() { return m_callbacks; }
databaseCallbacks()50 PassRefPtr<IDBDatabaseCallbacks> databaseCallbacks() { return m_databaseCallbacks; }
51
52 private:
PendingSetVersionCall(const String & version,PassRefPtr<IDBCallbacks> callbacks,PassRefPtr<IDBDatabaseCallbacks> databaseCallbacks)53 PendingSetVersionCall(const String& version, PassRefPtr<IDBCallbacks> callbacks, PassRefPtr<IDBDatabaseCallbacks> databaseCallbacks)
54 : m_version(version)
55 , m_callbacks(callbacks)
56 , m_databaseCallbacks(databaseCallbacks)
57 {
58 }
59 String m_version;
60 RefPtr<IDBCallbacks> m_callbacks;
61 RefPtr<IDBDatabaseCallbacks> m_databaseCallbacks;
62 };
63
IDBDatabaseBackendImpl(const String & name,IDBBackingStore * backingStore,IDBTransactionCoordinator * coordinator,IDBFactoryBackendImpl * factory,const String & uniqueIdentifier)64 IDBDatabaseBackendImpl::IDBDatabaseBackendImpl(const String& name, IDBBackingStore* backingStore, IDBTransactionCoordinator* coordinator, IDBFactoryBackendImpl* factory, const String& uniqueIdentifier)
65 : m_backingStore(backingStore)
66 , m_id(InvalidId)
67 , m_name(name)
68 , m_version("")
69 , m_identifier(uniqueIdentifier)
70 , m_factory(factory)
71 , m_transactionCoordinator(coordinator)
72 {
73 ASSERT(!m_name.isNull());
74
75 bool success = m_backingStore->extractIDBDatabaseMetaData(m_name, m_version, m_id);
76 ASSERT_UNUSED(success, success == (m_id != InvalidId));
77 if (!m_backingStore->setIDBDatabaseMetaData(m_name, m_version, m_id, m_id == InvalidId))
78 ASSERT_NOT_REACHED(); // FIXME: Need better error handling.
79 loadObjectStores();
80 }
81
~IDBDatabaseBackendImpl()82 IDBDatabaseBackendImpl::~IDBDatabaseBackendImpl()
83 {
84 m_factory->removeIDBDatabaseBackend(m_identifier);
85 }
86
backingStore() const87 PassRefPtr<IDBBackingStore> IDBDatabaseBackendImpl::backingStore() const
88 {
89 return m_backingStore;
90 }
91
objectStoreNames() const92 PassRefPtr<DOMStringList> IDBDatabaseBackendImpl::objectStoreNames() const
93 {
94 RefPtr<DOMStringList> objectStoreNames = DOMStringList::create();
95 for (ObjectStoreMap::const_iterator it = m_objectStores.begin(); it != m_objectStores.end(); ++it)
96 objectStoreNames->append(it->first);
97 return objectStoreNames.release();
98 }
99
createObjectStore(const String & name,const String & keyPath,bool autoIncrement,IDBTransactionBackendInterface * transactionPtr,ExceptionCode & ec)100 PassRefPtr<IDBObjectStoreBackendInterface> IDBDatabaseBackendImpl::createObjectStore(const String& name, const String& keyPath, bool autoIncrement, IDBTransactionBackendInterface* transactionPtr, ExceptionCode& ec)
101 {
102 ASSERT(transactionPtr->mode() == IDBTransaction::VERSION_CHANGE);
103
104 if (m_objectStores.contains(name)) {
105 ec = IDBDatabaseException::CONSTRAINT_ERR;
106 return 0;
107 }
108
109 RefPtr<IDBObjectStoreBackendImpl> objectStore = IDBObjectStoreBackendImpl::create(m_backingStore.get(), m_id, name, keyPath, autoIncrement);
110 ASSERT(objectStore->name() == name);
111
112 RefPtr<IDBDatabaseBackendImpl> database = this;
113 RefPtr<IDBTransactionBackendInterface> transaction = transactionPtr;
114 if (!transaction->scheduleTask(createCallbackTask(&IDBDatabaseBackendImpl::createObjectStoreInternal, database, objectStore, transaction),
115 createCallbackTask(&IDBDatabaseBackendImpl::removeObjectStoreFromMap, database, objectStore))) {
116 ec = IDBDatabaseException::NOT_ALLOWED_ERR;
117 return 0;
118 }
119
120 m_objectStores.set(name, objectStore);
121 return objectStore.release();
122 }
123
createObjectStoreInternal(ScriptExecutionContext *,PassRefPtr<IDBDatabaseBackendImpl> database,PassRefPtr<IDBObjectStoreBackendImpl> objectStore,PassRefPtr<IDBTransactionBackendInterface> transaction)124 void IDBDatabaseBackendImpl::createObjectStoreInternal(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl> database, PassRefPtr<IDBObjectStoreBackendImpl> objectStore, PassRefPtr<IDBTransactionBackendInterface> transaction)
125 {
126 int64_t objectStoreId;
127
128 if (!database->m_backingStore->createObjectStore(database->id(), objectStore->name(), objectStore->keyPath(), objectStore->autoIncrement(), objectStoreId)) {
129 transaction->abort();
130 return;
131 }
132
133 objectStore->setId(objectStoreId);
134 transaction->didCompleteTaskEvents();
135 }
136
objectStore(const String & name)137 PassRefPtr<IDBObjectStoreBackendInterface> IDBDatabaseBackendImpl::objectStore(const String& name)
138 {
139 return m_objectStores.get(name);
140 }
141
deleteObjectStore(const String & name,IDBTransactionBackendInterface * transactionPtr,ExceptionCode & ec)142 void IDBDatabaseBackendImpl::deleteObjectStore(const String& name, IDBTransactionBackendInterface* transactionPtr, ExceptionCode& ec)
143 {
144 RefPtr<IDBObjectStoreBackendImpl> objectStore = m_objectStores.get(name);
145 if (!objectStore) {
146 ec = IDBDatabaseException::NOT_FOUND_ERR;
147 return;
148 }
149 RefPtr<IDBDatabaseBackendImpl> database = this;
150 RefPtr<IDBTransactionBackendInterface> transaction = transactionPtr;
151 if (!transaction->scheduleTask(createCallbackTask(&IDBDatabaseBackendImpl::deleteObjectStoreInternal, database, objectStore, transaction),
152 createCallbackTask(&IDBDatabaseBackendImpl::addObjectStoreToMap, database, objectStore))) {
153 ec = IDBDatabaseException::NOT_ALLOWED_ERR;
154 return;
155 }
156 m_objectStores.remove(name);
157 }
158
deleteObjectStoreInternal(ScriptExecutionContext *,PassRefPtr<IDBDatabaseBackendImpl> database,PassRefPtr<IDBObjectStoreBackendImpl> objectStore,PassRefPtr<IDBTransactionBackendInterface> transaction)159 void IDBDatabaseBackendImpl::deleteObjectStoreInternal(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl> database, PassRefPtr<IDBObjectStoreBackendImpl> objectStore, PassRefPtr<IDBTransactionBackendInterface> transaction)
160 {
161 database->m_backingStore->deleteObjectStore(database->id(), objectStore->id());
162 transaction->didCompleteTaskEvents();
163 }
164
setVersion(const String & version,PassRefPtr<IDBCallbacks> prpCallbacks,PassRefPtr<IDBDatabaseCallbacks> prpDatabaseCallbacks,ExceptionCode & ec)165 void IDBDatabaseBackendImpl::setVersion(const String& version, PassRefPtr<IDBCallbacks> prpCallbacks, PassRefPtr<IDBDatabaseCallbacks> prpDatabaseCallbacks, ExceptionCode& ec)
166 {
167 RefPtr<IDBCallbacks> callbacks = prpCallbacks;
168 RefPtr<IDBDatabaseCallbacks> databaseCallbacks = prpDatabaseCallbacks;
169 if (!m_databaseCallbacksSet.contains(databaseCallbacks)) {
170 callbacks->onError(IDBDatabaseError::create(IDBDatabaseException::ABORT_ERR, "Connection was closed before set version transaction was created"));
171 return;
172 }
173 for (DatabaseCallbacksSet::const_iterator it = m_databaseCallbacksSet.begin(); it != m_databaseCallbacksSet.end(); ++it) {
174 if (*it != databaseCallbacks)
175 (*it)->onVersionChange(version);
176 }
177 if (m_databaseCallbacksSet.size() > 1) {
178 callbacks->onBlocked();
179 RefPtr<PendingSetVersionCall> pendingSetVersionCall = PendingSetVersionCall::create(version, callbacks, databaseCallbacks);
180 m_pendingSetVersionCalls.append(pendingSetVersionCall);
181 return;
182 }
183
184 RefPtr<DOMStringList> objectStoreNames = DOMStringList::create();
185 RefPtr<IDBDatabaseBackendImpl> database = this;
186 RefPtr<IDBTransactionBackendInterface> transaction = IDBTransactionBackendImpl::create(objectStoreNames.get(), IDBTransaction::VERSION_CHANGE, this);
187 if (!transaction->scheduleTask(createCallbackTask(&IDBDatabaseBackendImpl::setVersionInternal, database, version, callbacks, transaction),
188 createCallbackTask(&IDBDatabaseBackendImpl::resetVersion, database, m_version))) {
189 ec = IDBDatabaseException::NOT_ALLOWED_ERR;
190 }
191 }
192
setVersionInternal(ScriptExecutionContext *,PassRefPtr<IDBDatabaseBackendImpl> database,const String & version,PassRefPtr<IDBCallbacks> callbacks,PassRefPtr<IDBTransactionBackendInterface> transaction)193 void IDBDatabaseBackendImpl::setVersionInternal(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl> database, const String& version, PassRefPtr<IDBCallbacks> callbacks, PassRefPtr<IDBTransactionBackendInterface> transaction)
194 {
195 int64_t databaseId = database->id();
196 database->m_version = version;
197 if (!database->m_backingStore->setIDBDatabaseMetaData(database->m_name, database->m_version, databaseId, databaseId == InvalidId)) {
198 // FIXME: The Indexed Database specification does not have an error code dedicated to I/O errors.
199 callbacks->onError(IDBDatabaseError::create(IDBDatabaseException::UNKNOWN_ERR, "Error writing data to stable storage."));
200 transaction->abort();
201 return;
202 }
203 callbacks->onSuccess(transaction);
204 }
205
transaction(DOMStringList * objectStoreNames,unsigned short mode,ExceptionCode & ec)206 PassRefPtr<IDBTransactionBackendInterface> IDBDatabaseBackendImpl::transaction(DOMStringList* objectStoreNames, unsigned short mode, ExceptionCode& ec)
207 {
208 for (size_t i = 0; i < objectStoreNames->length(); ++i) {
209 if (!m_objectStores.contains(objectStoreNames->item(i))) {
210 ec = IDBDatabaseException::NOT_FOUND_ERR;
211 return 0;
212 }
213 }
214
215 // FIXME: Return not allowed err if close has been called.
216 return IDBTransactionBackendImpl::create(objectStoreNames, mode, this);
217 }
218
open(PassRefPtr<IDBDatabaseCallbacks> callbacks)219 void IDBDatabaseBackendImpl::open(PassRefPtr<IDBDatabaseCallbacks> callbacks)
220 {
221 m_databaseCallbacksSet.add(RefPtr<IDBDatabaseCallbacks>(callbacks));
222 }
223
close(PassRefPtr<IDBDatabaseCallbacks> prpCallbacks)224 void IDBDatabaseBackendImpl::close(PassRefPtr<IDBDatabaseCallbacks> prpCallbacks)
225 {
226 RefPtr<IDBDatabaseCallbacks> callbacks = prpCallbacks;
227 ASSERT(m_databaseCallbacksSet.contains(callbacks));
228 m_databaseCallbacksSet.remove(callbacks);
229 if (m_databaseCallbacksSet.size() > 1)
230 return;
231
232 while (!m_pendingSetVersionCalls.isEmpty()) {
233 ExceptionCode ec = 0;
234 RefPtr<PendingSetVersionCall> pendingSetVersionCall = m_pendingSetVersionCalls.takeFirst();
235 setVersion(pendingSetVersionCall->version(), pendingSetVersionCall->callbacks(), pendingSetVersionCall->databaseCallbacks(), ec);
236 ASSERT(!ec);
237 }
238 }
239
loadObjectStores()240 void IDBDatabaseBackendImpl::loadObjectStores()
241 {
242 Vector<int64_t> ids;
243 Vector<String> names;
244 Vector<String> keyPaths;
245 Vector<bool> autoIncrementFlags;
246 m_backingStore->getObjectStores(m_id, ids, names, keyPaths, autoIncrementFlags);
247
248 ASSERT(names.size() == ids.size());
249 ASSERT(keyPaths.size() == ids.size());
250 ASSERT(autoIncrementFlags.size() == ids.size());
251
252 for (size_t i = 0; i < ids.size(); i++)
253 m_objectStores.set(names[i], IDBObjectStoreBackendImpl::create(m_backingStore.get(), m_id, ids[i], names[i], keyPaths[i], autoIncrementFlags[i]));
254 }
255
removeObjectStoreFromMap(ScriptExecutionContext *,PassRefPtr<IDBDatabaseBackendImpl> database,PassRefPtr<IDBObjectStoreBackendImpl> objectStore)256 void IDBDatabaseBackendImpl::removeObjectStoreFromMap(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl> database, PassRefPtr<IDBObjectStoreBackendImpl> objectStore)
257 {
258 ASSERT(database->m_objectStores.contains(objectStore->name()));
259 database->m_objectStores.remove(objectStore->name());
260 }
261
addObjectStoreToMap(ScriptExecutionContext *,PassRefPtr<IDBDatabaseBackendImpl> database,PassRefPtr<IDBObjectStoreBackendImpl> objectStore)262 void IDBDatabaseBackendImpl::addObjectStoreToMap(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl> database, PassRefPtr<IDBObjectStoreBackendImpl> objectStore)
263 {
264 RefPtr<IDBObjectStoreBackendImpl> objectStorePtr = objectStore;
265 ASSERT(!database->m_objectStores.contains(objectStorePtr->name()));
266 database->m_objectStores.set(objectStorePtr->name(), objectStorePtr);
267 }
268
resetVersion(ScriptExecutionContext *,PassRefPtr<IDBDatabaseBackendImpl> database,const String & version)269 void IDBDatabaseBackendImpl::resetVersion(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl> database, const String& version)
270 {
271 database->m_version = version;
272 }
273
274
275 } // namespace WebCore
276
277 #endif // ENABLE(INDEXED_DATABASE)
278