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