2 * Copyright (C) 2010 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
30 #include "AbstractDatabase.h"
33 #include "DatabaseAuthorizer.h"
34 #include "DatabaseTracker.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>
50 static bool retrieveTextResultFromDatabase(SQLiteDatabase& db, const String& query, String& resultString)
52 SQLiteStatement statement(db, query);
53 int result = statement.prepare();
55 if (result != SQLResultOk) {
56 LOG_ERROR("Error (%i) preparing statement to read text result from database (%s)", result, query.ascii().data());
60 result = statement.step();
61 if (result == SQLResultRow) {
62 resultString = statement.getColumnText(0);
65 if (result == SQLResultDone) {
66 resultString = String();
70 LOG_ERROR("Error (%i) reading text result from database (%s)", result, query.ascii().data());
74 static bool setTextValueInDatabase(SQLiteDatabase& db, const String& query, const String& value)
76 SQLiteStatement statement(db, query);
77 int result = statement.prepare();
79 if (result != SQLResultOk) {
80 LOG_ERROR("Failed to prepare statement to set value in database (%s)", query.ascii().data());
84 statement.bindText(1, value);
86 result = statement.step();
87 if (result != SQLResultDone) {
88 LOG_ERROR("Failed to step statement to set value in database (%s)", query.ascii().data());
95 // FIXME: move all guid-related functions to a DatabaseVersionTracker class.
96 static Mutex& guidMutex()
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, ());
105 typedef HashMap<int, String> GuidVersionMap;
106 static GuidVersionMap& guidToVersionMap()
108 DEFINE_STATIC_LOCAL(GuidVersionMap, map, ());
112 // NOTE: Caller must lock guidMutex().
113 static inline void updateGuidVersionMap(int guid, String newVersion)
115 // Ensure the the mutex is locked.
116 ASSERT(!guidMutex().tryLock());
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.
124 // Map null string to empty string (see comment above).
125 guidToVersionMap().set(guid, newVersion.isEmpty() ? String() : newVersion.threadsafeCopy());
128 typedef HashMap<int, HashSet<AbstractDatabase*>*> GuidDatabaseMap;
129 static GuidDatabaseMap& guidToDatabaseMap()
131 DEFINE_STATIC_LOCAL(GuidDatabaseMap, map, ());
135 static int guidForOriginAndName(const String& origin, const String& name)
137 String stringID = origin + "/" + name;
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);
148 static int currentNewGUID = 1;
149 guid = currentNewGUID++;
150 stringIdentifierToGUIDMap.set(stringID, guid);
156 static bool isDatabaseAvailable = true;
158 bool AbstractDatabase::isAvailable()
160 return isDatabaseAvailable;
163 void AbstractDatabase::setIsAvailable(bool available)
165 isDatabaseAvailable = available;
169 const String& AbstractDatabase::databaseInfoTableName()
171 DEFINE_STATIC_LOCAL(String, name, ("__WebKitDatabaseInfoTable__"));
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)
186 ASSERT(context->isContextThread());
187 m_contextThreadSecurityOrigin = m_scriptExecutionContext->securityOrigin();
189 m_databaseAuthorizer = DatabaseAuthorizer::create(databaseInfoTableName());
194 m_guid = guidForOriginAndName(securityOrigin()->toString(), name);
196 MutexLocker locker(guidMutex());
198 HashSet<AbstractDatabase*>* hashSet = guidToDatabaseMap().get(m_guid);
200 hashSet = new HashSet<AbstractDatabase*>;
201 guidToDatabaseMap().set(m_guid, hashSet);
207 m_filename = DatabaseTracker::tracker().fullPathForDatabase(securityOrigin(), m_name);
208 DatabaseTracker::tracker().addOpenDatabase(this);
211 AbstractDatabase::~AbstractDatabase()
215 void AbstractDatabase::closeDatabase()
220 m_sqliteDatabase.close();
223 MutexLocker locker(guidMutex());
225 HashSet<AbstractDatabase*>* hashSet = guidToDatabaseMap().get(m_guid);
227 ASSERT(hashSet->contains(this));
228 hashSet->remove(this);
229 if (hashSet->isEmpty()) {
230 guidToDatabaseMap().remove(m_guid);
232 guidToVersionMap().remove(m_guid);
237 String AbstractDatabase::version() const
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();
245 bool AbstractDatabase::performOpenAndVerify(bool shouldSetVersionInNewDatabase, ExceptionCode& ec)
247 const int maxSqliteBusyWaitTime = 30000;
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;
254 if (!m_sqliteDatabase.turnOnIncrementalAutoVacuum())
255 LOG_ERROR("Unable to turn on incremental auto-vacuum for database %s", m_filename.ascii().data());
257 m_sqliteDatabase.setBusyTimeout(maxSqliteBusyWaitTime);
259 String currentVersion;
261 MutexLocker locker(guidMutex());
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());
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);
283 m_sqliteDatabase.setBusyTimeout(maxSqliteBusyWaitTime);
286 LOG(StorageAPI, "No cached version for guid %i", m_guid);
288 SQLiteTransaction transaction(m_sqliteDatabase);
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();
297 if (!m_sqliteDatabase.tableExists(databaseInfoTableName())) {
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();
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();
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();
326 currentVersion = m_expectedVersion;
328 updateGuidVersionMap(m_guid, currentVersion);
329 transaction.commit();
333 if (currentVersion.isNull()) {
334 LOG(StorageAPI, "Database %s does not have its version set", databaseDebugName().ascii().data());
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();
348 ASSERT(m_databaseAuthorizer);
349 m_sqliteDatabase.setAuthorizer(m_databaseAuthorizer);
353 if (m_new && !shouldSetVersionInNewDatabase)
354 m_expectedVersion = ""; // The caller provided a creationCallback which will set the expected version.
359 ScriptExecutionContext* AbstractDatabase::scriptExecutionContext() const
361 return m_scriptExecutionContext.get();
364 SecurityOrigin* AbstractDatabase::securityOrigin() const
366 return m_contextThreadSecurityOrigin.get();
369 String AbstractDatabase::stringIdentifier() const
371 // Return a deep copy for ref counting thread safety
372 return m_name.threadsafeCopy();
375 String AbstractDatabase::displayName() const
377 // Return a deep copy for ref counting thread safety
378 return m_displayName.threadsafeCopy();
381 unsigned long AbstractDatabase::estimatedSize() const
383 return m_estimatedSize;
386 String AbstractDatabase::fileName() const
388 // Return a deep copy for ref counting thread safety
389 return m_filename.threadsafeCopy();
393 const String& AbstractDatabase::databaseVersionKey()
395 DEFINE_STATIC_LOCAL(String, key, ("WebKitDatabaseVersionKey"));
399 bool AbstractDatabase::getVersionFromDatabase(String& version, bool shouldCacheVersion)
401 DEFINE_STATIC_LOCAL(String, getVersionQuery, ("SELECT value FROM " + databaseInfoTableName() + " WHERE key = '" + databaseVersionKey() + "';"));
403 m_databaseAuthorizer->disable();
405 bool result = retrieveTextResultFromDatabase(m_sqliteDatabase, getVersionQuery.threadsafeCopy(), version);
407 if (shouldCacheVersion)
408 setCachedVersion(version);
410 LOG_ERROR("Failed to retrieve version from database %s", databaseDebugName().ascii().data());
412 m_databaseAuthorizer->enable();
417 bool AbstractDatabase::setVersionInDatabase(const String& version, bool shouldCacheVersion)
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() + "', ?);"));
423 m_databaseAuthorizer->disable();
425 bool result = setTextValueInDatabase(m_sqliteDatabase, setVersionQuery.threadsafeCopy(), version);
427 if (shouldCacheVersion)
428 setCachedVersion(version);
430 LOG_ERROR("Failed to set version %s in database (%s)", version.ascii().data(), setVersionQuery.ascii().data());
432 m_databaseAuthorizer->enable();
437 void AbstractDatabase::setExpectedVersion(const String& version)
439 m_expectedVersion = version.threadsafeCopy();
442 String AbstractDatabase::getCachedVersion() const
444 MutexLocker locker(guidMutex());
445 return guidToVersionMap().get(m_guid).threadsafeCopy();
448 void AbstractDatabase::setCachedVersion(const String& actualVersion)
450 // Update the in memory database version map.
451 MutexLocker locker(guidMutex());
452 updateGuidVersionMap(m_guid, actualVersion);
455 bool AbstractDatabase::getActualVersionForTransaction(String &actualVersion)
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);
463 actualVersion = getCachedVersion();
468 void AbstractDatabase::disableAuthorizer()
470 ASSERT(m_databaseAuthorizer);
471 m_databaseAuthorizer->disable();
474 void AbstractDatabase::enableAuthorizer()
476 ASSERT(m_databaseAuthorizer);
477 m_databaseAuthorizer->enable();
480 void AbstractDatabase::setAuthorizerReadOnly()
482 ASSERT(m_databaseAuthorizer);
483 m_databaseAuthorizer->setReadOnly();
486 void AbstractDatabase::setAuthorizerPermissions(int permissions)
488 ASSERT(m_databaseAuthorizer);
489 m_databaseAuthorizer->setPermissions(permissions);
492 bool AbstractDatabase::lastActionChangedDatabase()
494 ASSERT(m_databaseAuthorizer);
495 return m_databaseAuthorizer->lastActionChangedDatabase();
498 bool AbstractDatabase::lastActionWasInsert()
500 ASSERT(m_databaseAuthorizer);
501 return m_databaseAuthorizer->lastActionWasInsert();
504 void AbstractDatabase::resetDeletes()
506 ASSERT(m_databaseAuthorizer);
507 m_databaseAuthorizer->resetDeletes();
510 bool AbstractDatabase::hadDeletes()
512 ASSERT(m_databaseAuthorizer);
513 return m_databaseAuthorizer->hadDeletes();
516 void AbstractDatabase::resetAuthorizer()
518 if (m_databaseAuthorizer)
519 m_databaseAuthorizer->reset();
522 unsigned long long AbstractDatabase::maximumSize() const
524 return DatabaseTracker::tracker().getMaxSizeForDatabase(this);
527 void AbstractDatabase::incrementalVacuumIfNeeded()
529 int64_t freeSpaceSize = m_sqliteDatabase.freeSpaceSize();
530 int64_t totalSize = m_sqliteDatabase.totalSize();
531 if (totalSize <= 10 * freeSpaceSize)
532 m_sqliteDatabase.runIncrementalVacuumCommand();
535 void AbstractDatabase::interrupt()
537 m_sqliteDatabase.interrupt();
540 bool AbstractDatabase::isInterrupted()
542 MutexLocker locker(m_sqliteDatabase.databaseMutex());
543 return m_sqliteDatabase.isInterrupted();
546 } // namespace WebCore
548 #endif // ENABLE(DATABASE)