2458485185692fe910a407640bb12c9754a0c6eb
[WebKit-https.git] / WebCore / storage / Database.cpp
1 /*
2  * Copyright (C) 2007, 2008 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  *
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  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "config.h"
30 #include "Database.h"
31
32 #include "ChangeVersionWrapper.h"
33 #include "CString.h"
34 #include "DatabaseAuthorizer.h"
35 #include "DatabaseTask.h"
36 #include "DatabaseThread.h"
37 #include "DatabaseTracker.h"
38 #include "Document.h"
39 #include "ExceptionCode.h"
40 #include "FileSystem.h"
41 #include "Frame.h"
42 #include "InspectorController.h"
43 #include "Logging.h"
44 #include "NotImplemented.h"
45 #include "Page.h"
46 #include "SQLiteDatabase.h"
47 #include "SQLiteStatement.h"
48 #include "SQLResultSet.h"
49
50 namespace WebCore {
51         
52 #ifndef NDEBUG
53 class CurrentThreadSetter {
54 public:
55     CurrentThreadSetter(ThreadIdentifier& thread)
56         : threadIdentifierStorage(thread)
57     {
58         ASSERT(!thread);
59         thread = currentThread();
60     }
61
62     ~CurrentThreadSetter()
63     {
64         ASSERT(threadIdentifierStorage == currentThread());
65         threadIdentifierStorage = 0;
66     }
67
68 private:
69     ThreadIdentifier& threadIdentifierStorage;
70 };
71 #endif
72
73 Mutex& Database::globalCallbackMutex()
74 {
75     static Mutex mutex;
76     return mutex;
77 }
78
79 HashSet<RefPtr<Database> >& Database::globalCallbackSet()
80 {
81     static HashSet<RefPtr<Database> > set;
82     return set;
83 }
84
85 bool Database::s_globalCallbackScheduled = false;
86
87 Mutex& Database::guidMutex()
88 {
89     static Mutex mutex;
90     return mutex;
91 }
92
93 HashMap<int, String>& Database::guidToVersionMap()
94 {
95     static HashMap<int, String> map;
96     return map;
97 }
98
99 HashMap<int, HashSet<Database*>*>& Database::guidToDatabaseMap()
100 {
101     static HashMap<int, HashSet<Database*>*> map;
102     return map;
103 }
104
105 const String& Database::databaseInfoTableName()
106 {
107     static String name = "__WebKitDatabaseInfoTable__";
108     return name;
109 }
110
111 static const String& databaseVersionKey()
112 {
113     static String key = "WebKitDatabaseVersionKey";
114     return key;
115 }
116
117 PassRefPtr<Database> Database::openDatabase(Document* document, const String& name, const String& expectedVersion, const String& displayName, unsigned long estimatedSize, ExceptionCode& e)
118 {
119     if (!DatabaseTracker::tracker().canEstablishDatabase(document, name, displayName, estimatedSize)) {
120         // There should be an exception raised here in addition to returning a null Database object.  The question has been raised with the WHATWG
121         LOG(StorageAPI, "Database %s for origin %s not allowed to be established", name.ascii().data(), document->securityOrigin()->toString().ascii().data());
122         return 0;
123     }
124     
125     RefPtr<Database> database = new Database(document, name, expectedVersion);
126
127     if (!database->openAndVerifyVersion(e)) {
128        LOG(StorageAPI, "Failed to open and verify version (expected %s) of database %s", expectedVersion.ascii().data(), database->databaseDebugName().ascii().data());
129        return 0;
130     }
131     
132     DatabaseTracker::tracker().setDatabaseDetails(document->securityOrigin(), name, displayName, estimatedSize);
133
134     document->setHasOpenDatabases();
135
136     if (Page* page = document->frame()->page())
137         page->inspectorController()->didOpenDatabase(database.get(), document->domain(), name, expectedVersion);
138
139     return database;
140 }
141
142 Database::Database(Document* document, const String& name, const String& expectedVersion)
143     : m_document(document)
144     , m_name(name.copy())
145     , m_guid(0)
146     , m_expectedVersion(expectedVersion)
147     , m_databaseThread(0)
148 #ifndef NDEBUG
149     , m_transactionStepThread(0)
150 #endif
151 {
152     ASSERT(document);
153     m_securityOrigin = document->securityOrigin();
154
155     if (m_name.isNull())
156         m_name = "";
157
158     initializeThreading();
159
160     m_guid = guidForOriginAndName(m_securityOrigin->toString(), name);
161
162     {
163         MutexLocker locker(guidMutex());
164
165         HashSet<Database*>* hashSet = guidToDatabaseMap().get(m_guid);
166         if (!hashSet) {
167             hashSet = new HashSet<Database*>;
168             guidToDatabaseMap().set(m_guid, hashSet);
169         }
170
171         hashSet->add(this);
172     }
173
174     m_databaseThread = document->databaseThread();
175     ASSERT(m_databaseThread);
176
177     m_filename = DatabaseTracker::tracker().fullPathForDatabase(m_securityOrigin.get(), m_name);
178 }
179
180 Database::~Database()
181 {
182     MutexLocker locker(guidMutex());
183
184     HashSet<Database*>* hashSet = guidToDatabaseMap().get(m_guid);
185     ASSERT(hashSet);
186     ASSERT(hashSet->contains(this));
187     hashSet->remove(this);
188     if (hashSet->isEmpty()) {
189         guidToDatabaseMap().remove(m_guid);
190         delete hashSet;
191         guidToVersionMap().remove(m_guid);
192     }
193 }
194
195 bool Database::openAndVerifyVersion(ExceptionCode& e)
196 {
197     m_databaseAuthorizer = new DatabaseAuthorizer();
198
199     RefPtr<DatabaseOpenTask> task = new DatabaseOpenTask();
200
201     task->lockForSynchronousScheduling();
202     m_databaseThread->scheduleImmediateTask(this, task.get());
203     task->waitForSynchronousCompletion();
204
205     ASSERT(task->isComplete());
206     e = task->exceptionCode();
207     return task->openSuccessful();
208 }
209
210
211 static bool retrieveTextResultFromDatabase(SQLiteDatabase& db, const String& query, String& resultString)
212 {
213     SQLiteStatement statement(db, query);
214     int result = statement.prepare();
215
216     if (result != SQLResultOk) {
217         LOG_ERROR("Error (%i) preparing statement to read text result from database (%s)", result, query.ascii().data());
218         return false;
219     }
220
221     result = statement.step();
222     if (result == SQLResultRow) {
223         resultString = statement.getColumnText(0);
224         return true;
225     } else if (result == SQLResultDone) {
226         resultString = String();
227         return true;
228     } else {
229         LOG_ERROR("Error (%i) reading text result from database (%s)", result, query.ascii().data());
230         return false;
231     }
232 }
233
234 bool Database::getVersionFromDatabase(String& version)
235 {
236     static String getVersionQuery = "SELECT value FROM " + databaseInfoTableName() + " WHERE key = '" + databaseVersionKey() + "';";
237
238     m_databaseAuthorizer->disable();
239
240     bool result = retrieveTextResultFromDatabase(m_sqliteDatabase, getVersionQuery.copy(), version);
241     if (!result)
242         LOG_ERROR("Failed to retrieve version from database %s", databaseDebugName().ascii().data());
243
244     m_databaseAuthorizer->enable();
245
246     return result;
247 }
248
249 static bool setTextValueInDatabase(SQLiteDatabase& db, const String& query, const String& value)
250 {
251     SQLiteStatement statement(db, query);
252     int result = statement.prepare();
253
254     if (result != SQLResultOk) {
255         LOG_ERROR("Failed to prepare statement to set value in database (%s)", query.ascii().data());
256         return false;
257     }
258
259     statement.bindText(1, value);
260
261     result = statement.step();
262     if (result != SQLResultDone) {
263         LOG_ERROR("Failed to step statement to set value in database (%s)", query.ascii().data());
264         return false;
265     }
266
267     return true;
268 }
269
270 bool Database::setVersionInDatabase(const String& version)
271 {
272     static String setVersionQuery = "INSERT INTO " + databaseInfoTableName() + " (key, value) VALUES ('" + databaseVersionKey() + "', ?);";
273
274     m_databaseAuthorizer->disable();
275
276     bool result = setTextValueInDatabase(m_sqliteDatabase, setVersionQuery.copy(), version);
277     if (!result)
278         LOG_ERROR("Failed to set version %s in database (%s)", version.ascii().data(), setVersionQuery.ascii().data());
279
280     m_databaseAuthorizer->enable();
281
282     return result;
283 }
284
285 bool Database::versionMatchesExpected() const
286 {
287     if (!m_expectedVersion.isEmpty()) {
288         MutexLocker locker(guidMutex());
289         return m_expectedVersion == guidToVersionMap().get(m_guid);
290     }
291     
292     return true;
293 }
294
295 void Database::databaseThreadGoingAway()
296 {
297     // FIXME: We might not need this anymore
298
299     LOG(StorageAPI, "Database thread is going away for database %p\n", this);
300 }
301
302 void Database::disableAuthorizer()
303 {
304     ASSERT(m_databaseAuthorizer);
305     m_databaseAuthorizer->disable();
306 }
307
308 void Database::enableAuthorizer()
309 {
310     ASSERT(m_databaseAuthorizer);
311     m_databaseAuthorizer->enable();
312 }
313
314 int Database::guidForOriginAndName(const String& origin, const String& name)
315 {
316     static int currentNewGUID = 1;
317     static Mutex stringIdentifierMutex;
318     static HashMap<String, int> stringIdentifierToGUIDMap;
319
320     String stringID;
321     if (origin.endsWith("/"))
322         stringID = origin + name;
323     else
324         stringID = origin + "/" + name;
325
326     MutexLocker locker(stringIdentifierMutex);
327     int guid = stringIdentifierToGUIDMap.get(stringID);
328     if (!guid) {
329         guid = currentNewGUID++;
330         stringIdentifierToGUIDMap.set(stringID, guid);
331     }
332
333     return guid;
334 }
335
336 void Database::resetAuthorizer()
337 {
338     if (m_databaseAuthorizer)
339         m_databaseAuthorizer->reset();
340 }
341
342 void Database::performPolicyChecks()
343 {
344     // FIXME: Code similar to the following will need to be run to enforce the per-origin size limit the spec mandates.
345     // Additionally, we might need a way to pause the database thread while the UA prompts the user for permission to
346     // increase the size limit
347
348     /*
349     if (m_databaseAuthorizer->lastActionIncreasedSize())
350         DatabaseTracker::scheduleFileSizeCheckOnMainThread(this);
351     */
352
353     notImplemented();
354 }
355
356 bool Database::performOpenAndVerify(ExceptionCode& e)
357 {
358     if (!m_sqliteDatabase.open(m_filename)) {
359         LOG_ERROR("Unable to open database at path %s", m_filename.ascii().data());
360         e = INVALID_STATE_ERR;
361         return false;
362     }
363
364     ASSERT(m_databaseAuthorizer);
365     m_sqliteDatabase.setAuthorizer(m_databaseAuthorizer);
366
367     if (!m_sqliteDatabase.tableExists(databaseInfoTableName())) {
368         if (!m_sqliteDatabase.executeCommand("CREATE TABLE " + databaseInfoTableName() + " (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) {
369             LOG_ERROR("Unable to create table %s in database %s", databaseInfoTableName().ascii().data(), databaseDebugName().ascii().data());
370             e = INVALID_STATE_ERR;
371             return false;
372         }
373     }
374
375
376     String currentVersion;
377     {
378         MutexLocker locker(guidMutex());
379         currentVersion = guidToVersionMap().get(m_guid);
380
381         if (currentVersion.isNull())
382             LOG(StorageAPI, "Current cached version for guid %i is null", m_guid);
383         else
384             LOG(StorageAPI, "Current cached version for guid %i is %s", m_guid, currentVersion.ascii().data());
385
386         if (currentVersion.isNull()) {
387             if (!getVersionFromDatabase(currentVersion)) {
388                 LOG_ERROR("Failed to get current version from database %s", databaseDebugName().ascii().data());
389                 e = INVALID_STATE_ERR;
390                 return false;
391             }
392             if (currentVersion.length()) {
393                 LOG(StorageAPI, "Retrieved current version %s from database %s", currentVersion.ascii().data(), databaseDebugName().ascii().data());
394             } else {
395                 LOG(StorageAPI, "Setting version %s in database %s that was just created", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data());
396                 if (!setVersionInDatabase(m_expectedVersion)) {
397                     LOG_ERROR("Failed to set version %s in database %s", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data());
398                     e = INVALID_STATE_ERR;
399                     return false;
400                 }
401
402                 currentVersion = m_expectedVersion;
403             }
404
405             guidToVersionMap().set(m_guid, currentVersion.copy());
406         }
407     }
408
409     if (currentVersion.isNull()) {
410         LOG(StorageAPI, "Database %s does not have its version set", databaseDebugName().ascii().data());
411         currentVersion = "";
412     }
413
414     // FIXME: For now, the spec says that if the database has no version, it is valid for any "Expected version" string.  That seems silly and I think it should be
415     // changed, and here's where we would change it
416     if (m_expectedVersion.length()) {
417         if (currentVersion.length() && m_expectedVersion != currentVersion) {
418             LOG(StorageAPI, "page expects version %s from database %s, which actually has version name %s - openDatabase() call will fail", m_expectedVersion.ascii().data(),
419                 databaseDebugName().ascii().data(), currentVersion.ascii().data());
420             e = INVALID_STATE_ERR;
421             return false;
422         }
423     }
424
425     return true;
426 }
427
428 void Database::performTransactionStep()
429 {
430 #ifndef NDEBUG
431     CurrentThreadSetter threadSetter(m_transactionStepThread);
432 #endif
433
434     // Do not perform a transaction if a global callback is scheduled.
435     MutexLocker locker(globalCallbackMutex());
436     if (s_globalCallbackScheduled)
437         return;
438
439     {
440         MutexLocker locker(m_transactionMutex);
441
442         if (!m_currentTransaction) {
443             ASSERT(m_transactionQueue.size());
444             m_currentTransaction = m_transactionQueue.first();
445             m_transactionQueue.removeFirst();
446         }
447     }
448
449     // If this step completes the entire transaction, clear the working transaction
450     // and schedule the next one if necessary
451     if (!m_currentTransaction->performNextStep())
452         return;
453
454     {
455         MutexLocker locker(m_transactionMutex);
456         m_currentTransaction = 0;
457
458         if (m_transactionQueue.size())
459             m_databaseThread->scheduleTask(this, new DatabaseTransactionTask);
460     }
461 }
462
463 void Database::changeVersion(const String& oldVersion, const String& newVersion, 
464                              PassRefPtr<SQLTransactionCallback> callback, PassRefPtr<SQLTransactionErrorCallback> errorCallback,
465                              PassRefPtr<VoidCallback> successCallback)
466 {
467     scheduleTransaction(new SQLTransaction(this, callback, errorCallback, new ChangeVersionWrapper(oldVersion, newVersion)));
468 }
469
470 void Database::transaction(PassRefPtr<SQLTransactionCallback> callback, PassRefPtr<SQLTransactionErrorCallback> errorCallback,
471                            PassRefPtr<VoidCallback> successCallback)
472 {
473     scheduleTransaction(new SQLTransaction(this, callback, errorCallback, 0));
474 }
475
476 void Database::scheduleTransaction(PassRefPtr<SQLTransaction> transaction)
477 {   
478     MutexLocker locker(m_transactionMutex);
479     m_transactionQueue.append(transaction);
480     if (!m_currentTransaction)
481         m_databaseThread->scheduleTask(this, new DatabaseTransactionTask);
482 }
483
484 void Database::scheduleTransactionStep()
485 {
486     MutexLocker locker(m_transactionMutex);
487     m_databaseThread->scheduleTask(this, new DatabaseTransactionTask);
488 }
489
490 void Database::scheduleTransactionCallback(SQLTransaction* transaction)
491 {
492     ASSERT(m_transactionStepThread == currentThread());
493
494     MutexLocker locker(m_callbackMutex);
495
496     ASSERT(!m_transactionPendingCallback);
497     m_transactionPendingCallback = transaction;
498
499     globalCallbackSet().add(this);
500
501     if (!s_globalCallbackScheduled) {
502         callOnMainThread(deliverAllPendingCallbacks, 0);
503         s_globalCallbackScheduled = true;
504     }
505 }
506
507 Vector<String> Database::performGetTableNames()
508 {
509     disableAuthorizer();
510
511     SQLiteStatement statement(m_sqliteDatabase, "SELECT name FROM sqlite_master WHERE type='table';");
512     if (statement.prepare() != SQLResultOk) {
513         LOG_ERROR("Unable to retrieve list of tables for database %s", databaseDebugName().ascii().data());
514         enableAuthorizer();
515         return Vector<String>();
516     }
517
518     Vector<String> tableNames;
519     int result;
520     while ((result = statement.step()) == SQLResultRow) {
521         String name = statement.getColumnText(0);
522         if (name != databaseInfoTableName())
523             tableNames.append(name);
524     }
525
526     enableAuthorizer();
527
528     if (result != SQLResultDone) {
529         LOG_ERROR("Error getting tables for database %s", databaseDebugName().ascii().data());
530         return Vector<String>();
531     }
532
533     return tableNames;
534 }
535
536 String Database::version() const
537 {
538     MutexLocker locker(guidMutex());
539     return guidToVersionMap().get(m_guid).copy();
540 }
541     
542 void Database::deliverAllPendingCallbacks(void*)
543 {
544     Vector<RefPtr<Database> > databases;
545     {
546         MutexLocker locker(globalCallbackMutex());
547         copyToVector(globalCallbackSet(), databases);
548         globalCallbackSet().clear();
549         s_globalCallbackScheduled = false;
550     }
551
552     LOG(StorageAPI, "Having %zu databases deliver their pending callbacks", databases.size());
553     for (unsigned i = 0; i < databases.size(); ++i)
554         databases[i]->deliverPendingCallback();
555 }
556
557 void Database::deliverPendingCallback()
558 {
559     RefPtr<SQLTransaction> transaction;
560     {
561         MutexLocker locker(m_callbackMutex);
562         
563         ASSERT(m_transactionPendingCallback);
564         transaction = m_transactionPendingCallback.release();
565     }
566
567     transaction->performPendingCallback();
568 }
569
570 Vector<String> Database::tableNames()
571 {
572     RefPtr<DatabaseTableNamesTask> task = new DatabaseTableNamesTask();
573
574     task->lockForSynchronousScheduling();
575     m_databaseThread->scheduleImmediateTask(this, task.get());
576     task->waitForSynchronousCompletion();
577
578     return task->tableNames();
579 }
580
581 void Database::setExpectedVersion(const String& version)
582 {
583     m_expectedVersion = version.copy();
584 }
585
586 PassRefPtr<SecurityOrigin> Database::securityOriginCopy() const
587 {
588     return m_securityOrigin->copy();
589 }
590
591 String Database::stringIdentifier() const
592 {
593     // Return a deep copy for ref counting thread safety
594     return m_name.copy();
595 }
596
597 }