initial import
[vuplus_webkit] / Source / WebCore / storage / AbstractDatabase.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  * 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 "AbstractDatabase.h"
31
32 #if ENABLE(DATABASE)
33 #include "DatabaseAuthorizer.h"
34 #include "DatabaseTracker.h"
35 #include "Logging.h"
36 #include "SQLiteStatement.h"
37 #include "SQLiteTransaction.h"
38 #include "ScriptExecutionContext.h"
39 #include "SecurityOrigin.h"
40 #include <wtf/HashMap.h>
41 #include <wtf/HashSet.h>
42 #include <wtf/PassRefPtr.h>
43 #include <wtf/RefPtr.h>
44 #include <wtf/StdLibExtras.h>
45 #include <wtf/text/CString.h>
46 #include <wtf/text/StringHash.h>
47
48 namespace WebCore {
49
50 static bool retrieveTextResultFromDatabase(SQLiteDatabase& db, const String& query, String& resultString)
51 {
52     SQLiteStatement statement(db, query);
53     int result = statement.prepare();
54
55     if (result != SQLResultOk) {
56         LOG_ERROR("Error (%i) preparing statement to read text result from database (%s)", result, query.ascii().data());
57         return false;
58     }
59
60     result = statement.step();
61     if (result == SQLResultRow) {
62         resultString = statement.getColumnText(0);
63         return true;
64     }
65     if (result == SQLResultDone) {
66         resultString = String();
67         return true;
68     }
69
70     LOG_ERROR("Error (%i) reading text result from database (%s)", result, query.ascii().data());
71     return false;
72 }
73
74 static bool setTextValueInDatabase(SQLiteDatabase& db, const String& query, const String& value)
75 {
76     SQLiteStatement statement(db, query);
77     int result = statement.prepare();
78
79     if (result != SQLResultOk) {
80         LOG_ERROR("Failed to prepare statement to set value in database (%s)", query.ascii().data());
81         return false;
82     }
83
84     statement.bindText(1, value);
85
86     result = statement.step();
87     if (result != SQLResultDone) {
88         LOG_ERROR("Failed to step statement to set value in database (%s)", query.ascii().data());
89         return false;
90     }
91
92     return true;
93 }
94
95 // FIXME: move all guid-related functions to a DatabaseVersionTracker class.
96 static Mutex& guidMutex()
97 {
98     // Note: We don't have to use AtomicallyInitializedStatic here because
99     // this function is called once in the constructor on the main thread
100     // before any other threads that call this function are used.
101     DEFINE_STATIC_LOCAL(Mutex, mutex, ());
102     return mutex;
103 }
104
105 typedef HashMap<int, String> GuidVersionMap;
106 static GuidVersionMap& guidToVersionMap()
107 {
108     DEFINE_STATIC_LOCAL(GuidVersionMap, map, ());
109     return map;
110 }
111
112 // NOTE: Caller must lock guidMutex().
113 static inline void updateGuidVersionMap(int guid, String newVersion)
114 {
115     // Ensure the the mutex is locked.
116     ASSERT(!guidMutex().tryLock());
117
118     // Note: It is not safe to put an empty string into the guidToVersionMap() map.
119     // That's because the map is cross-thread, but empty strings are per-thread.
120     // The copy() function makes a version of the string you can use on the current
121     // thread, but we need a string we can keep in a cross-thread data structure.
122     // FIXME: This is a quite-awkward restriction to have to program with.
123
124     // Map null string to empty string (see comment above).
125     guidToVersionMap().set(guid, newVersion.isEmpty() ? String() : newVersion.threadsafeCopy());
126 }
127
128 typedef HashMap<int, HashSet<AbstractDatabase*>*> GuidDatabaseMap;
129 static GuidDatabaseMap& guidToDatabaseMap()
130 {
131     DEFINE_STATIC_LOCAL(GuidDatabaseMap, map, ());
132     return map;
133 }
134
135 static int guidForOriginAndName(const String& origin, const String& name)
136 {
137     String stringID = origin + "/" + name;
138
139     // Note: We don't have to use AtomicallyInitializedStatic here because
140     // this function is called once in the constructor on the main thread
141     // before any other threads that call this function are used.
142     DEFINE_STATIC_LOCAL(Mutex, stringIdentifierMutex, ());
143     MutexLocker locker(stringIdentifierMutex);
144     typedef HashMap<String, int> IDGuidMap;
145     DEFINE_STATIC_LOCAL(IDGuidMap, stringIdentifierToGUIDMap, ());
146     int guid = stringIdentifierToGUIDMap.get(stringID);
147     if (!guid) {
148         static int currentNewGUID = 1;
149         guid = currentNewGUID++;
150         stringIdentifierToGUIDMap.set(stringID, guid);
151     }
152
153     return guid;
154 }
155
156 static bool isDatabaseAvailable = true;
157
158 bool AbstractDatabase::isAvailable()
159 {
160     return isDatabaseAvailable;
161 }
162
163 void AbstractDatabase::setIsAvailable(bool available)
164 {
165     isDatabaseAvailable = available;
166 }
167
168 // static
169 const String& AbstractDatabase::databaseInfoTableName()
170 {
171     DEFINE_STATIC_LOCAL(String, name, ("__WebKitDatabaseInfoTable__"));
172     return name;
173 }
174
175 AbstractDatabase::AbstractDatabase(ScriptExecutionContext* context, const String& name, const String& expectedVersion,
176                                    const String& displayName, unsigned long estimatedSize)
177     : m_scriptExecutionContext(context)
178     , m_name(name.crossThreadString())
179     , m_expectedVersion(expectedVersion.crossThreadString())
180     , m_displayName(displayName.crossThreadString())
181     , m_estimatedSize(estimatedSize)
182     , m_guid(0)
183     , m_opened(false)
184     , m_new(false)
185 {
186     ASSERT(context->isContextThread());
187     m_contextThreadSecurityOrigin = m_scriptExecutionContext->securityOrigin();
188
189     m_databaseAuthorizer = DatabaseAuthorizer::create(databaseInfoTableName());
190
191     if (m_name.isNull())
192         m_name = "";
193
194     m_guid = guidForOriginAndName(securityOrigin()->toString(), name);
195     {
196         MutexLocker locker(guidMutex());
197
198         HashSet<AbstractDatabase*>* hashSet = guidToDatabaseMap().get(m_guid);
199         if (!hashSet) {
200             hashSet = new HashSet<AbstractDatabase*>;
201             guidToDatabaseMap().set(m_guid, hashSet);
202         }
203
204         hashSet->add(this);
205     }
206
207     m_filename = DatabaseTracker::tracker().fullPathForDatabase(securityOrigin(), m_name);
208     DatabaseTracker::tracker().addOpenDatabase(this);
209 }
210
211 AbstractDatabase::~AbstractDatabase()
212 {
213 }
214
215 void AbstractDatabase::closeDatabase()
216 {
217     if (!m_opened)
218         return;
219
220     m_sqliteDatabase.close();
221     m_opened = false;
222     {
223         MutexLocker locker(guidMutex());
224
225         HashSet<AbstractDatabase*>* hashSet = guidToDatabaseMap().get(m_guid);
226         ASSERT(hashSet);
227         ASSERT(hashSet->contains(this));
228         hashSet->remove(this);
229         if (hashSet->isEmpty()) {
230             guidToDatabaseMap().remove(m_guid);
231             delete hashSet;
232             guidToVersionMap().remove(m_guid);
233         }
234     }
235 }
236
237 String AbstractDatabase::version() const
238 {
239     // Note: In multi-process browsers the cached value may be accurate, but we cannot read the
240     // actual version from the database without potentially inducing a deadlock.
241     // FIXME: Add an async version getter to the DatabaseAPI.
242     return getCachedVersion();
243 }
244
245 bool AbstractDatabase::performOpenAndVerify(bool shouldSetVersionInNewDatabase, ExceptionCode& ec)
246 {
247     const int maxSqliteBusyWaitTime = 30000;
248
249     if (!m_sqliteDatabase.open(m_filename, true)) {
250         LOG_ERROR("Unable to open database at path %s", m_filename.ascii().data());
251         ec = INVALID_STATE_ERR;
252         return false;
253     }
254     if (!m_sqliteDatabase.turnOnIncrementalAutoVacuum())
255         LOG_ERROR("Unable to turn on incremental auto-vacuum for database %s", m_filename.ascii().data());
256
257     m_sqliteDatabase.setBusyTimeout(maxSqliteBusyWaitTime);
258
259     String currentVersion;
260     {
261         MutexLocker locker(guidMutex());
262
263         GuidVersionMap::iterator entry = guidToVersionMap().find(m_guid);
264         if (entry != guidToVersionMap().end()) {
265             // Map null string to empty string (see updateGuidVersionMap()).
266             currentVersion = entry->second.isNull() ? String("") : entry->second;
267             LOG(StorageAPI, "Current cached version for guid %i is %s", m_guid, currentVersion.ascii().data());
268
269 #if PLATFORM(CHROMIUM)
270             // Note: In multi-process browsers the cached value may be inaccurate, but
271             // we cannot read the actual version from the database without potentially
272             // inducing a form of deadlock, a busytimeout error when trying to
273             // access the database. So we'll use the cached value if we're able to read
274             // the value without waiting, and otherwise use the cached value (which may be off).
275             // FIXME: Add an async openDatabase method to the DatabaseAPI.
276             const int noSqliteBusyWaitTime = 0;
277             m_sqliteDatabase.setBusyTimeout(noSqliteBusyWaitTime);
278             String versionFromDatabase;
279             if (getVersionFromDatabase(versionFromDatabase, false)) {
280                 currentVersion = versionFromDatabase;
281                 updateGuidVersionMap(m_guid, currentVersion);
282             }
283             m_sqliteDatabase.setBusyTimeout(maxSqliteBusyWaitTime);
284 #endif
285         } else {
286             LOG(StorageAPI, "No cached version for guid %i", m_guid);
287
288             SQLiteTransaction transaction(m_sqliteDatabase);
289             transaction.begin();
290             if (!transaction.inProgress()) {
291                 LOG_ERROR("Unable to begin transaction while opening %s", databaseDebugName().ascii().data());
292                 ec = INVALID_STATE_ERR;
293                 m_sqliteDatabase.close();
294                 return false;
295             }
296
297             if (!m_sqliteDatabase.tableExists(databaseInfoTableName())) {
298                 m_new = true;
299
300                 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);")) {
301                     LOG_ERROR("Unable to create table %s in database %s", databaseInfoTableName().ascii().data(), databaseDebugName().ascii().data());
302                     ec = INVALID_STATE_ERR;
303                     transaction.rollback();
304                     m_sqliteDatabase.close();
305                     return false;
306                 }
307             } else if (!getVersionFromDatabase(currentVersion, false)) {
308                 LOG_ERROR("Failed to get current version from database %s", databaseDebugName().ascii().data());
309                 ec = INVALID_STATE_ERR;
310                 transaction.rollback();
311                 m_sqliteDatabase.close();
312                 return false;
313             }
314
315             if (currentVersion.length()) {
316                 LOG(StorageAPI, "Retrieved current version %s from database %s", currentVersion.ascii().data(), databaseDebugName().ascii().data());
317             } else if (!m_new || shouldSetVersionInNewDatabase) {
318                 LOG(StorageAPI, "Setting version %s in database %s that was just created", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data());
319                 if (!setVersionInDatabase(m_expectedVersion, false)) {
320                     LOG_ERROR("Failed to set version %s in database %s", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data());
321                     ec = INVALID_STATE_ERR;
322                     transaction.rollback();
323                     m_sqliteDatabase.close();
324                     return false;
325                 }
326                 currentVersion = m_expectedVersion;
327             }
328             updateGuidVersionMap(m_guid, currentVersion);
329             transaction.commit();
330         }
331     }
332
333     if (currentVersion.isNull()) {
334         LOG(StorageAPI, "Database %s does not have its version set", databaseDebugName().ascii().data());
335         currentVersion = "";
336     }
337
338     // If the expected version isn't the empty string, ensure that the current database version we have matches that version. Otherwise, set an exception.
339     // If the expected version is the empty string, then we always return with whatever version of the database we have.
340     if ((!m_new || shouldSetVersionInNewDatabase) && m_expectedVersion.length() && m_expectedVersion != currentVersion) {
341         LOG(StorageAPI, "page expects version %s from database %s, which actually has version name %s - openDatabase() call will fail", m_expectedVersion.ascii().data(),
342             databaseDebugName().ascii().data(), currentVersion.ascii().data());
343         ec = INVALID_STATE_ERR;
344         m_sqliteDatabase.close();
345         return false;
346     }
347
348     ASSERT(m_databaseAuthorizer);
349     m_sqliteDatabase.setAuthorizer(m_databaseAuthorizer);
350
351     m_opened = true;
352
353     if (m_new && !shouldSetVersionInNewDatabase)
354         m_expectedVersion = ""; // The caller provided a creationCallback which will set the expected version.
355
356     return true;
357 }
358
359 ScriptExecutionContext* AbstractDatabase::scriptExecutionContext() const
360 {
361     return m_scriptExecutionContext.get();
362 }
363
364 SecurityOrigin* AbstractDatabase::securityOrigin() const
365 {
366     return m_contextThreadSecurityOrigin.get();
367 }
368
369 String AbstractDatabase::stringIdentifier() const
370 {
371     // Return a deep copy for ref counting thread safety
372     return m_name.threadsafeCopy();
373 }
374
375 String AbstractDatabase::displayName() const
376 {
377     // Return a deep copy for ref counting thread safety
378     return m_displayName.threadsafeCopy();
379 }
380
381 unsigned long AbstractDatabase::estimatedSize() const
382 {
383     return m_estimatedSize;
384 }
385
386 String AbstractDatabase::fileName() const
387 {
388     // Return a deep copy for ref counting thread safety
389     return m_filename.threadsafeCopy();
390 }
391
392 // static
393 const String& AbstractDatabase::databaseVersionKey()
394 {
395     DEFINE_STATIC_LOCAL(String, key, ("WebKitDatabaseVersionKey"));
396     return key;
397 }
398
399 bool AbstractDatabase::getVersionFromDatabase(String& version, bool shouldCacheVersion)
400 {
401     DEFINE_STATIC_LOCAL(String, getVersionQuery, ("SELECT value FROM " + databaseInfoTableName() + " WHERE key = '" + databaseVersionKey() + "';"));
402
403     m_databaseAuthorizer->disable();
404
405     bool result = retrieveTextResultFromDatabase(m_sqliteDatabase, getVersionQuery.threadsafeCopy(), version);
406     if (result) {
407         if (shouldCacheVersion)
408             setCachedVersion(version);
409     } else
410         LOG_ERROR("Failed to retrieve version from database %s", databaseDebugName().ascii().data());
411
412     m_databaseAuthorizer->enable();
413
414     return result;
415 }
416
417 bool AbstractDatabase::setVersionInDatabase(const String& version, bool shouldCacheVersion)
418 {
419     // The INSERT will replace an existing entry for the database with the new version number, due to the UNIQUE ON CONFLICT REPLACE
420     // clause in the CREATE statement (see Database::performOpenAndVerify()).
421     DEFINE_STATIC_LOCAL(String, setVersionQuery, ("INSERT INTO " + databaseInfoTableName() + " (key, value) VALUES ('" + databaseVersionKey() + "', ?);"));
422
423     m_databaseAuthorizer->disable();
424
425     bool result = setTextValueInDatabase(m_sqliteDatabase, setVersionQuery.threadsafeCopy(), version);
426     if (result) {
427         if (shouldCacheVersion)
428             setCachedVersion(version);
429     } else
430         LOG_ERROR("Failed to set version %s in database (%s)", version.ascii().data(), setVersionQuery.ascii().data());
431
432     m_databaseAuthorizer->enable();
433
434     return result;
435 }
436
437 void AbstractDatabase::setExpectedVersion(const String& version)
438 {
439     m_expectedVersion = version.threadsafeCopy();
440 }
441
442 String AbstractDatabase::getCachedVersion() const
443 {
444     MutexLocker locker(guidMutex());
445     return guidToVersionMap().get(m_guid).threadsafeCopy();
446 }
447
448 void AbstractDatabase::setCachedVersion(const String& actualVersion)
449 {
450     // Update the in memory database version map.
451     MutexLocker locker(guidMutex());
452     updateGuidVersionMap(m_guid, actualVersion);
453 }
454
455 bool AbstractDatabase::getActualVersionForTransaction(String &actualVersion)
456 {
457     ASSERT(m_sqliteDatabase.transactionInProgress());
458 #if PLATFORM(CHROMIUM)
459     // Note: In multi-process browsers the cached value may be inaccurate.
460     // So we retrieve the value from the database and update the cached value here.
461     return getVersionFromDatabase(actualVersion, true);
462 #else
463     actualVersion = getCachedVersion();
464     return true;
465 #endif
466 }
467
468 void AbstractDatabase::disableAuthorizer()
469 {
470     ASSERT(m_databaseAuthorizer);
471     m_databaseAuthorizer->disable();
472 }
473
474 void AbstractDatabase::enableAuthorizer()
475 {
476     ASSERT(m_databaseAuthorizer);
477     m_databaseAuthorizer->enable();
478 }
479
480 void AbstractDatabase::setAuthorizerReadOnly()
481 {
482     ASSERT(m_databaseAuthorizer);
483     m_databaseAuthorizer->setReadOnly();
484 }
485
486 void AbstractDatabase::setAuthorizerPermissions(int permissions)
487 {
488     ASSERT(m_databaseAuthorizer);
489     m_databaseAuthorizer->setPermissions(permissions);
490 }
491
492 bool AbstractDatabase::lastActionChangedDatabase()
493 {
494     ASSERT(m_databaseAuthorizer);
495     return m_databaseAuthorizer->lastActionChangedDatabase();
496 }
497
498 bool AbstractDatabase::lastActionWasInsert()
499 {
500     ASSERT(m_databaseAuthorizer);
501     return m_databaseAuthorizer->lastActionWasInsert();
502 }
503
504 void AbstractDatabase::resetDeletes()
505 {
506     ASSERT(m_databaseAuthorizer);
507     m_databaseAuthorizer->resetDeletes();
508 }
509
510 bool AbstractDatabase::hadDeletes()
511 {
512     ASSERT(m_databaseAuthorizer);
513     return m_databaseAuthorizer->hadDeletes();
514 }
515
516 void AbstractDatabase::resetAuthorizer()
517 {
518     if (m_databaseAuthorizer)
519         m_databaseAuthorizer->reset();
520 }
521
522 unsigned long long AbstractDatabase::maximumSize() const
523 {
524     return DatabaseTracker::tracker().getMaxSizeForDatabase(this);
525 }
526
527 void AbstractDatabase::incrementalVacuumIfNeeded()
528 {
529     int64_t freeSpaceSize = m_sqliteDatabase.freeSpaceSize();
530     int64_t totalSize = m_sqliteDatabase.totalSize();
531     if (totalSize <= 10 * freeSpaceSize)
532         m_sqliteDatabase.runIncrementalVacuumCommand();
533 }
534
535 void AbstractDatabase::interrupt()
536 {
537     m_sqliteDatabase.interrupt();
538 }
539
540 bool AbstractDatabase::isInterrupted()
541 {
542     MutexLocker locker(m_sqliteDatabase.databaseMutex());
543     return m_sqliteDatabase.isInterrupted();
544 }
545
546 } // namespace WebCore
547
548 #endif // ENABLE(DATABASE)