| /* | |
| * Copyright (C) 2013 Apple Inc. All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions | |
| * are met: | |
| * 1. Redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer. | |
| * 2. Redistributions in binary form must reproduce the above copyright | |
| * notice, this list of conditions and the following disclaimer in the | |
| * documentation and/or other materials provided with the distribution. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY | |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR | |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| */ | |
| #include "modules/webdatabase/Database.h" | |
| #include "core/dom/ExceptionCode.h" | |
| #include "core/dom/ExecutionContext.h" | |
| #include "core/dom/ExecutionContextTask.h" | |
| #include "core/dom/TaskRunnerHelper.h" | |
| #include "core/html/VoidCallback.h" | |
| #include "core/inspector/ConsoleMessage.h" | |
| #include "modules/webdatabase/ChangeVersionData.h" | |
| #include "modules/webdatabase/ChangeVersionWrapper.h" | |
| #include "modules/webdatabase/DatabaseAuthorizer.h" | |
| #include "modules/webdatabase/DatabaseContext.h" | |
| #include "modules/webdatabase/DatabaseManager.h" | |
| #include "modules/webdatabase/DatabaseTask.h" | |
| #include "modules/webdatabase/DatabaseThread.h" | |
| #include "modules/webdatabase/DatabaseTracker.h" | |
| #include "modules/webdatabase/SQLError.h" | |
| #include "modules/webdatabase/SQLTransaction.h" | |
| #include "modules/webdatabase/SQLTransactionBackend.h" | |
| #include "modules/webdatabase/SQLTransactionCallback.h" | |
| #include "modules/webdatabase/SQLTransactionClient.h" | |
| #include "modules/webdatabase/SQLTransactionCoordinator.h" | |
| #include "modules/webdatabase/SQLTransactionErrorCallback.h" | |
| #include "modules/webdatabase/StorageLog.h" | |
| #include "modules/webdatabase/sqlite/SQLiteStatement.h" | |
| #include "modules/webdatabase/sqlite/SQLiteTransaction.h" | |
| #include "platform/WaitableEvent.h" | |
| #include "platform/heap/SafePoint.h" | |
| #include "public/platform/Platform.h" | |
| #include "public/platform/WebDatabaseObserver.h" | |
| #include "public/platform/WebSecurityOrigin.h" | |
| #include "wtf/Atomics.h" | |
| #include "wtf/CurrentTime.h" | |
| #include <memory> | |
| // Registering "opened" databases with the DatabaseTracker | |
| // ======================================================= | |
| // The DatabaseTracker maintains a list of databases that have been | |
| // "opened" so that the client can call interrupt or delete on every database | |
| // associated with a DatabaseContext. | |
| // | |
| // We will only call DatabaseTracker::addOpenDatabase() to add the database | |
| // to the tracker as opened when we've succeeded in opening the database, | |
| // and will set m_opened to true. Similarly, we only call | |
| // DatabaseTracker::removeOpenDatabase() to remove the database from the | |
| // tracker when we set m_opened to false in closeDatabase(). This sets up | |
| // a simple symmetry between open and close operations, and a direct | |
| // correlation to adding and removing databases from the tracker's list, | |
| // thus ensuring that we have a correct list for the interrupt and | |
| // delete operations to work on. | |
| // | |
| // The only databases instances not tracked by the tracker's open database | |
| // list are the ones that have not been added yet, or the ones that we | |
| // attempted an open on but failed to. Such instances only exist in the | |
| // DatabaseServer's factory methods for creating database backends. | |
| // | |
| // The factory methods will either call openAndVerifyVersion() or | |
| // performOpenAndVerify(). These methods will add the newly instantiated | |
| // database backend if they succeed in opening the requested database. | |
| // In the case of failure to open the database, the factory methods will | |
| // simply discard the newly instantiated database backend when they return. | |
| // The ref counting mechanims will automatically destruct the un-added | |
| // (and un-returned) databases instances. | |
| namespace blink { | |
| // Defines static local variable after making sure that guid lock is held. | |
| // (We can't use DEFINE_STATIC_LOCAL for this because it asserts thread | |
| // safety, which is externally guaranteed by the guideMutex lock) | |
| #define DEFINE_STATIC_LOCAL_WITH_LOCK(type, name, arguments) \ | |
| ASSERT(guidMutex().locked()); \ | |
| static type& name = *new type arguments | |
| static const char versionKey[] = "WebKitDatabaseVersionKey"; | |
| static const char infoTableName[] = "__WebKitDatabaseInfoTable__"; | |
| static String formatErrorMessage(const char* message, | |
| int sqliteErrorCode, | |
| const char* sqliteErrorMessage) { | |
| return String::format("%s (%d %s)", message, sqliteErrorCode, | |
| sqliteErrorMessage); | |
| } | |
| static bool retrieveTextResultFromDatabase(SQLiteDatabase& db, | |
| const String& query, | |
| String& resultString) { | |
| SQLiteStatement statement(db, query); | |
| int result = statement.prepare(); | |
| if (result != SQLResultOk) { | |
| DLOG(ERROR) << "Error (" << result | |
| << ") preparing statement to read text result from database (" | |
| << query << ")"; | |
| return false; | |
| } | |
| result = statement.step(); | |
| if (result == SQLResultRow) { | |
| resultString = statement.getColumnText(0); | |
| return true; | |
| } | |
| if (result == SQLResultDone) { | |
| resultString = String(); | |
| return true; | |
| } | |
| DLOG(ERROR) << "Error (" << result << ") reading text result from database (" | |
| << query << ")"; | |
| return false; | |
| } | |
| static bool setTextValueInDatabase(SQLiteDatabase& db, | |
| const String& query, | |
| const String& value) { | |
| SQLiteStatement statement(db, query); | |
| int result = statement.prepare(); | |
| if (result != SQLResultOk) { | |
| DLOG(ERROR) << "Failed to prepare statement to set value in database (" | |
| << query << ")"; | |
| return false; | |
| } | |
| statement.bindText(1, value); | |
| result = statement.step(); | |
| if (result != SQLResultDone) { | |
| DLOG(ERROR) << "Failed to step statement to set value in database (" | |
| << query << ")"; | |
| return false; | |
| } | |
| return true; | |
| } | |
| // FIXME: move all guid-related functions to a DatabaseVersionTracker class. | |
| static RecursiveMutex& guidMutex() { | |
| DEFINE_THREAD_SAFE_STATIC_LOCAL(RecursiveMutex, mutex, new RecursiveMutex); | |
| return mutex; | |
| } | |
| typedef HashMap<DatabaseGuid, String> GuidVersionMap; | |
| static GuidVersionMap& guidToVersionMap() { | |
| DEFINE_STATIC_LOCAL_WITH_LOCK(GuidVersionMap, map, ()); | |
| return map; | |
| } | |
| // NOTE: Caller must lock guidMutex(). | |
| static inline void updateGuidVersionMap(DatabaseGuid guid, String newVersion) { | |
| // Ensure the the mutex is locked. | |
| ASSERT(guidMutex().locked()); | |
| // Note: It is not safe to put an empty string into the guidToVersionMap() | |
| // map. That's because the map is cross-thread, but empty strings are | |
| // per-thread. The copy() function makes a version of the string you can | |
| // use on the current thread, but we need a string we can keep in a | |
| // cross-thread data structure. | |
| // FIXME: This is a quite-awkward restriction to have to program with. | |
| // Map null string to empty string (see comment above). | |
| guidToVersionMap().set( | |
| guid, newVersion.isEmpty() ? String() : newVersion.isolatedCopy()); | |
| } | |
| static HashCountedSet<DatabaseGuid>& guidCount() { | |
| DEFINE_STATIC_LOCAL_WITH_LOCK(HashCountedSet<DatabaseGuid>, guidCount, ()); | |
| return guidCount; | |
| } | |
| static DatabaseGuid guidForOriginAndName(const String& origin, | |
| const String& name) { | |
| // Ensure the the mutex is locked. | |
| ASSERT(guidMutex().locked()); | |
| String stringID = origin + "/" + name; | |
| typedef HashMap<String, int> IDGuidMap; | |
| DEFINE_STATIC_LOCAL_WITH_LOCK(IDGuidMap, stringIdentifierToGUIDMap, ()); | |
| DatabaseGuid guid = stringIdentifierToGUIDMap.get(stringID); | |
| if (!guid) { | |
| static int currentNewGUID = 1; | |
| guid = currentNewGUID++; | |
| stringIdentifierToGUIDMap.set(stringID, guid); | |
| } | |
| return guid; | |
| } | |
| Database::Database(DatabaseContext* databaseContext, | |
| const String& name, | |
| const String& expectedVersion, | |
| const String& displayName, | |
| unsigned estimatedSize) | |
| : m_databaseContext(databaseContext), | |
| m_name(name.isolatedCopy()), | |
| m_expectedVersion(expectedVersion.isolatedCopy()), | |
| m_displayName(displayName.isolatedCopy()), | |
| m_estimatedSize(estimatedSize), | |
| m_guid(0), | |
| m_opened(0), | |
| m_new(false), | |
| m_transactionInProgress(false), | |
| m_isTransactionQueueEnabled(true) { | |
| DCHECK(isMainThread()); | |
| m_contextThreadSecurityOrigin = | |
| m_databaseContext->getSecurityOrigin()->isolatedCopy(); | |
| m_databaseAuthorizer = DatabaseAuthorizer::create(infoTableName); | |
| if (m_name.isNull()) | |
| m_name = ""; | |
| { | |
| SafePointAwareMutexLocker locker(guidMutex()); | |
| m_guid = guidForOriginAndName(getSecurityOrigin()->toString(), name); | |
| guidCount().add(m_guid); | |
| } | |
| m_filename = DatabaseManager::manager().fullPathForDatabase( | |
| getSecurityOrigin(), m_name); | |
| m_databaseThreadSecurityOrigin = | |
| m_contextThreadSecurityOrigin->isolatedCopy(); | |
| ASSERT(m_databaseContext->databaseThread()); | |
| ASSERT(m_databaseContext->isContextThread()); | |
| } | |
| Database::~Database() { | |
| // SQLite is "multi-thread safe", but each database handle can only be used | |
| // on a single thread at a time. | |
| // | |
| // For Database, we open the SQLite database on the DatabaseThread, and | |
| // hence we should also close it on that same thread. This means that the | |
| // SQLite database need to be closed by another mechanism (see | |
| // DatabaseContext::stopDatabases()). By the time we get here, the SQLite | |
| // database should have already been closed. | |
| ASSERT(!m_opened); | |
| } | |
| DEFINE_TRACE(Database) { | |
| visitor->trace(m_databaseContext); | |
| visitor->trace(m_sqliteDatabase); | |
| visitor->trace(m_databaseAuthorizer); | |
| } | |
| bool Database::openAndVerifyVersion(bool setVersionInNewDatabase, | |
| DatabaseError& error, | |
| String& errorMessage) { | |
| WaitableEvent event; | |
| if (!getDatabaseContext()->databaseThreadAvailable()) | |
| return false; | |
| DatabaseTracker::tracker().prepareToOpenDatabase(this); | |
| bool success = false; | |
| std::unique_ptr<DatabaseOpenTask> task = DatabaseOpenTask::create( | |
| this, setVersionInNewDatabase, &event, error, errorMessage, success); | |
| getDatabaseContext()->databaseThread()->scheduleTask(std::move(task)); | |
| event.wait(); | |
| return success; | |
| } | |
| void Database::close() { | |
| ASSERT(getDatabaseContext()->databaseThread()); | |
| ASSERT(getDatabaseContext()->databaseThread()->isDatabaseThread()); | |
| { | |
| MutexLocker locker(m_transactionInProgressMutex); | |
| // Clean up transactions that have not been scheduled yet: | |
| // Transaction phase 1 cleanup. See comment on "What happens if a | |
| // transaction is interrupted?" at the top of SQLTransactionBackend.cpp. | |
| SQLTransactionBackend* transaction = nullptr; | |
| while (!m_transactionQueue.isEmpty()) { | |
| transaction = m_transactionQueue.takeFirst(); | |
| transaction->notifyDatabaseThreadIsShuttingDown(); | |
| } | |
| m_isTransactionQueueEnabled = false; | |
| m_transactionInProgress = false; | |
| } | |
| closeDatabase(); | |
| getDatabaseContext()->databaseThread()->recordDatabaseClosed(this); | |
| } | |
| SQLTransactionBackend* Database::runTransaction(SQLTransaction* transaction, | |
| bool readOnly, | |
| const ChangeVersionData* data) { | |
| MutexLocker locker(m_transactionInProgressMutex); | |
| if (!m_isTransactionQueueEnabled) | |
| return nullptr; | |
| SQLTransactionWrapper* wrapper = nullptr; | |
| if (data) | |
| wrapper = | |
| ChangeVersionWrapper::create(data->oldVersion(), data->newVersion()); | |
| SQLTransactionBackend* transactionBackend = | |
| SQLTransactionBackend::create(this, transaction, wrapper, readOnly); | |
| m_transactionQueue.append(transactionBackend); | |
| if (!m_transactionInProgress) | |
| scheduleTransaction(); | |
| return transactionBackend; | |
| } | |
| void Database::inProgressTransactionCompleted() { | |
| MutexLocker locker(m_transactionInProgressMutex); | |
| m_transactionInProgress = false; | |
| scheduleTransaction(); | |
| } | |
| void Database::scheduleTransaction() { | |
| ASSERT(!m_transactionInProgressMutex.tryLock()); // Locked by caller. | |
| SQLTransactionBackend* transaction = nullptr; | |
| if (m_isTransactionQueueEnabled && !m_transactionQueue.isEmpty()) | |
| transaction = m_transactionQueue.takeFirst(); | |
| if (transaction && getDatabaseContext()->databaseThreadAvailable()) { | |
| std::unique_ptr<DatabaseTransactionTask> task = | |
| DatabaseTransactionTask::create(transaction); | |
| STORAGE_DVLOG(1) << "Scheduling DatabaseTransactionTask " << task.get() | |
| << " for transaction " << task->transaction(); | |
| m_transactionInProgress = true; | |
| getDatabaseContext()->databaseThread()->scheduleTask(std::move(task)); | |
| } else { | |
| m_transactionInProgress = false; | |
| } | |
| } | |
| void Database::scheduleTransactionStep(SQLTransactionBackend* transaction) { | |
| if (!getDatabaseContext()->databaseThreadAvailable()) | |
| return; | |
| std::unique_ptr<DatabaseTransactionTask> task = | |
| DatabaseTransactionTask::create(transaction); | |
| STORAGE_DVLOG(1) << "Scheduling DatabaseTransactionTask " << task.get() | |
| << " for the transaction step"; | |
| getDatabaseContext()->databaseThread()->scheduleTask(std::move(task)); | |
| } | |
| SQLTransactionClient* Database::transactionClient() const { | |
| return getDatabaseContext()->databaseThread()->transactionClient(); | |
| } | |
| SQLTransactionCoordinator* Database::transactionCoordinator() const { | |
| return getDatabaseContext()->databaseThread()->transactionCoordinator(); | |
| } | |
| // static | |
| const char* Database::databaseInfoTableName() { | |
| return infoTableName; | |
| } | |
| void Database::closeDatabase() { | |
| if (!m_opened) | |
| return; | |
| releaseStore(&m_opened, 0); | |
| m_sqliteDatabase.close(); | |
| // See comment at the top this file regarding calling removeOpenDatabase(). | |
| DatabaseTracker::tracker().removeOpenDatabase(this); | |
| { | |
| SafePointAwareMutexLocker locker(guidMutex()); | |
| ASSERT(guidCount().contains(m_guid)); | |
| if (guidCount().remove(m_guid)) { | |
| guidToVersionMap().remove(m_guid); | |
| } | |
| } | |
| } | |
| String Database::version() const { | |
| // Note: In multi-process browsers the cached value may be accurate, but we | |
| // cannot read the actual version from the database without potentially | |
| // inducing a deadlock. | |
| // FIXME: Add an async version getter to the DatabaseAPI. | |
| return getCachedVersion(); | |
| } | |
| class DoneCreatingDatabaseOnExitCaller { | |
| STACK_ALLOCATED(); | |
| public: | |
| DoneCreatingDatabaseOnExitCaller(Database* database) | |
| : m_database(database), m_openSucceeded(false) {} | |
| ~DoneCreatingDatabaseOnExitCaller() { | |
| if (!m_openSucceeded) | |
| DatabaseTracker::tracker().failedToOpenDatabase(m_database); | |
| } | |
| void setOpenSucceeded() { m_openSucceeded = true; } | |
| private: | |
| CrossThreadPersistent<Database> m_database; | |
| bool m_openSucceeded; | |
| }; | |
| bool Database::performOpenAndVerify(bool shouldSetVersionInNewDatabase, | |
| DatabaseError& error, | |
| String& errorMessage) { | |
| double callStartTime = WTF::monotonicallyIncreasingTime(); | |
| DoneCreatingDatabaseOnExitCaller onExitCaller(this); | |
| ASSERT(errorMessage.isEmpty()); | |
| ASSERT(error == DatabaseError::None); // Better not have any errors already. | |
| // Presumed failure. We'll clear it if we succeed below. | |
| error = DatabaseError::InvalidDatabaseState; | |
| const int maxSqliteBusyWaitTime = 30000; | |
| if (!m_sqliteDatabase.open(m_filename)) { | |
| reportOpenDatabaseResult( | |
| 1, InvalidStateError, m_sqliteDatabase.lastError(), | |
| WTF::monotonicallyIncreasingTime() - callStartTime); | |
| errorMessage = formatErrorMessage("unable to open database", | |
| m_sqliteDatabase.lastError(), | |
| m_sqliteDatabase.lastErrorMsg()); | |
| return false; | |
| } | |
| if (!m_sqliteDatabase.turnOnIncrementalAutoVacuum()) | |
| DLOG(ERROR) << "Unable to turn on incremental auto-vacuum (" | |
| << m_sqliteDatabase.lastError() << " " | |
| << m_sqliteDatabase.lastErrorMsg() << ")"; | |
| m_sqliteDatabase.setBusyTimeout(maxSqliteBusyWaitTime); | |
| String currentVersion; | |
| { | |
| SafePointAwareMutexLocker locker(guidMutex()); | |
| GuidVersionMap::iterator entry = guidToVersionMap().find(m_guid); | |
| if (entry != guidToVersionMap().end()) { | |
| // Map null string to empty string (see updateGuidVersionMap()). | |
| currentVersion = | |
| entry->value.isNull() ? emptyString() : entry->value.isolatedCopy(); | |
| STORAGE_DVLOG(1) << "Current cached version for guid " << m_guid << " is " | |
| << currentVersion; | |
| // Note: In multi-process browsers the cached value may be | |
| // inaccurate, but we cannot read the actual version from the | |
| // database without potentially inducing a form of deadlock, a | |
| // busytimeout error when trying to access the database. So we'll | |
| // use the cached value if we're unable to read the value from the | |
| // database file without waiting. | |
| // FIXME: Add an async openDatabase method to the DatabaseAPI. | |
| const int noSqliteBusyWaitTime = 0; | |
| m_sqliteDatabase.setBusyTimeout(noSqliteBusyWaitTime); | |
| String versionFromDatabase; | |
| if (getVersionFromDatabase(versionFromDatabase, false)) { | |
| currentVersion = versionFromDatabase; | |
| updateGuidVersionMap(m_guid, currentVersion); | |
| } | |
| m_sqliteDatabase.setBusyTimeout(maxSqliteBusyWaitTime); | |
| } else { | |
| STORAGE_DVLOG(1) << "No cached version for guid " << m_guid; | |
| SQLiteTransaction transaction(m_sqliteDatabase); | |
| transaction.begin(); | |
| if (!transaction.inProgress()) { | |
| reportOpenDatabaseResult( | |
| 2, InvalidStateError, m_sqliteDatabase.lastError(), | |
| WTF::monotonicallyIncreasingTime() - callStartTime); | |
| errorMessage = formatErrorMessage( | |
| "unable to open database, failed to start transaction", | |
| m_sqliteDatabase.lastError(), m_sqliteDatabase.lastErrorMsg()); | |
| m_sqliteDatabase.close(); | |
| return false; | |
| } | |
| String tableName(infoTableName); | |
| if (!m_sqliteDatabase.tableExists(tableName)) { | |
| m_new = true; | |
| if (!m_sqliteDatabase.executeCommand( | |
| "CREATE TABLE " + tableName + | |
| " (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT " | |
| "REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) { | |
| reportOpenDatabaseResult( | |
| 3, InvalidStateError, m_sqliteDatabase.lastError(), | |
| WTF::monotonicallyIncreasingTime() - callStartTime); | |
| errorMessage = formatErrorMessage( | |
| "unable to open database, failed to create 'info' table", | |
| m_sqliteDatabase.lastError(), m_sqliteDatabase.lastErrorMsg()); | |
| transaction.rollback(); | |
| m_sqliteDatabase.close(); | |
| return false; | |
| } | |
| } else if (!getVersionFromDatabase(currentVersion, false)) { | |
| reportOpenDatabaseResult( | |
| 4, InvalidStateError, m_sqliteDatabase.lastError(), | |
| WTF::monotonicallyIncreasingTime() - callStartTime); | |
| errorMessage = formatErrorMessage( | |
| "unable to open database, failed to read current version", | |
| m_sqliteDatabase.lastError(), m_sqliteDatabase.lastErrorMsg()); | |
| transaction.rollback(); | |
| m_sqliteDatabase.close(); | |
| return false; | |
| } | |
| if (currentVersion.length()) { | |
| STORAGE_DVLOG(1) << "Retrieved current version " << currentVersion | |
| << " from database " << databaseDebugName(); | |
| } else if (!m_new || shouldSetVersionInNewDatabase) { | |
| STORAGE_DVLOG(1) << "Setting version " << m_expectedVersion | |
| << " in database " << databaseDebugName() | |
| << " that was just created"; | |
| if (!setVersionInDatabase(m_expectedVersion, false)) { | |
| reportOpenDatabaseResult( | |
| 5, InvalidStateError, m_sqliteDatabase.lastError(), | |
| WTF::monotonicallyIncreasingTime() - callStartTime); | |
| errorMessage = formatErrorMessage( | |
| "unable to open database, failed to write current version", | |
| m_sqliteDatabase.lastError(), m_sqliteDatabase.lastErrorMsg()); | |
| transaction.rollback(); | |
| m_sqliteDatabase.close(); | |
| return false; | |
| } | |
| currentVersion = m_expectedVersion; | |
| } | |
| updateGuidVersionMap(m_guid, currentVersion); | |
| transaction.commit(); | |
| } | |
| } | |
| if (currentVersion.isNull()) { | |
| STORAGE_DVLOG(1) << "Database " << databaseDebugName() | |
| << " does not have its version set"; | |
| currentVersion = ""; | |
| } | |
| // If the expected version isn't the empty string, ensure that the current | |
| // database version we have matches that version. Otherwise, set an | |
| // exception. | |
| // If the expected version is the empty string, then we always return with | |
| // whatever version of the database we have. | |
| if ((!m_new || shouldSetVersionInNewDatabase) && m_expectedVersion.length() && | |
| m_expectedVersion != currentVersion) { | |
| reportOpenDatabaseResult( | |
| 6, InvalidStateError, 0, | |
| WTF::monotonicallyIncreasingTime() - callStartTime); | |
| errorMessage = | |
| "unable to open database, version mismatch, '" + m_expectedVersion + | |
| "' does not match the currentVersion of '" + currentVersion + "'"; | |
| m_sqliteDatabase.close(); | |
| return false; | |
| } | |
| ASSERT(m_databaseAuthorizer); | |
| m_sqliteDatabase.setAuthorizer(m_databaseAuthorizer.get()); | |
| // See comment at the top this file regarding calling addOpenDatabase(). | |
| DatabaseTracker::tracker().addOpenDatabase(this); | |
| m_opened = 1; | |
| // Declare success: | |
| error = DatabaseError::None; // Clear the presumed error from above. | |
| onExitCaller.setOpenSucceeded(); | |
| if (m_new && !shouldSetVersionInNewDatabase) { | |
| // The caller provided a creationCallback which will set the expected | |
| // version. | |
| m_expectedVersion = ""; | |
| } | |
| reportOpenDatabaseResult( | |
| 0, -1, 0, WTF::monotonicallyIncreasingTime() - callStartTime); // OK | |
| if (getDatabaseContext()->databaseThread()) | |
| getDatabaseContext()->databaseThread()->recordDatabaseOpen(this); | |
| return true; | |
| } | |
| String Database::stringIdentifier() const { | |
| // Return a deep copy for ref counting thread safety | |
| return m_name.isolatedCopy(); | |
| } | |
| String Database::displayName() const { | |
| // Return a deep copy for ref counting thread safety | |
| return m_displayName.isolatedCopy(); | |
| } | |
| unsigned Database::estimatedSize() const { | |
| return m_estimatedSize; | |
| } | |
| String Database::fileName() const { | |
| // Return a deep copy for ref counting thread safety | |
| return m_filename.isolatedCopy(); | |
| } | |
| bool Database::getVersionFromDatabase(String& version, | |
| bool shouldCacheVersion) { | |
| String query(String("SELECT value FROM ") + infoTableName + " WHERE key = '" + | |
| versionKey + "';"); | |
| m_databaseAuthorizer->disable(); | |
| bool result = | |
| retrieveTextResultFromDatabase(m_sqliteDatabase, query, version); | |
| if (result) { | |
| if (shouldCacheVersion) | |
| setCachedVersion(version); | |
| } else { | |
| DLOG(ERROR) << "Failed to retrieve version from database " | |
| << databaseDebugName(); | |
| } | |
| m_databaseAuthorizer->enable(); | |
| return result; | |
| } | |
| bool Database::setVersionInDatabase(const String& version, | |
| bool shouldCacheVersion) { | |
| // The INSERT will replace an existing entry for the database with the new | |
| // version number, due to the UNIQUE ON CONFLICT REPLACE clause in the | |
| // CREATE statement (see Database::performOpenAndVerify()). | |
| String query(String("INSERT INTO ") + infoTableName + | |
| " (key, value) VALUES ('" + versionKey + "', ?);"); | |
| m_databaseAuthorizer->disable(); | |
| bool result = setTextValueInDatabase(m_sqliteDatabase, query, version); | |
| if (result) { | |
| if (shouldCacheVersion) | |
| setCachedVersion(version); | |
| } else { | |
| DLOG(ERROR) << "Failed to set version " << version << " in database (" | |
| << query << ")"; | |
| } | |
| m_databaseAuthorizer->enable(); | |
| return result; | |
| } | |
| void Database::setExpectedVersion(const String& version) { | |
| m_expectedVersion = version.isolatedCopy(); | |
| } | |
| String Database::getCachedVersion() const { | |
| SafePointAwareMutexLocker locker(guidMutex()); | |
| return guidToVersionMap().get(m_guid).isolatedCopy(); | |
| } | |
| void Database::setCachedVersion(const String& actualVersion) { | |
| // Update the in memory database version map. | |
| SafePointAwareMutexLocker locker(guidMutex()); | |
| updateGuidVersionMap(m_guid, actualVersion); | |
| } | |
| bool Database::getActualVersionForTransaction(String& actualVersion) { | |
| ASSERT(m_sqliteDatabase.transactionInProgress()); | |
| // Note: In multi-process browsers the cached value may be inaccurate. So we | |
| // retrieve the value from the database and update the cached value here. | |
| return getVersionFromDatabase(actualVersion, true); | |
| } | |
| void Database::disableAuthorizer() { | |
| ASSERT(m_databaseAuthorizer); | |
| m_databaseAuthorizer->disable(); | |
| } | |
| void Database::enableAuthorizer() { | |
| ASSERT(m_databaseAuthorizer); | |
| m_databaseAuthorizer->enable(); | |
| } | |
| void Database::setAuthorizerPermissions(int permissions) { | |
| ASSERT(m_databaseAuthorizer); | |
| m_databaseAuthorizer->setPermissions(permissions); | |
| } | |
| bool Database::lastActionChangedDatabase() { | |
| ASSERT(m_databaseAuthorizer); | |
| return m_databaseAuthorizer->lastActionChangedDatabase(); | |
| } | |
| bool Database::lastActionWasInsert() { | |
| ASSERT(m_databaseAuthorizer); | |
| return m_databaseAuthorizer->lastActionWasInsert(); | |
| } | |
| void Database::resetDeletes() { | |
| ASSERT(m_databaseAuthorizer); | |
| m_databaseAuthorizer->resetDeletes(); | |
| } | |
| bool Database::hadDeletes() { | |
| ASSERT(m_databaseAuthorizer); | |
| return m_databaseAuthorizer->hadDeletes(); | |
| } | |
| void Database::resetAuthorizer() { | |
| if (m_databaseAuthorizer) | |
| m_databaseAuthorizer->reset(); | |
| } | |
| unsigned long long Database::maximumSize() const { | |
| return DatabaseTracker::tracker().getMaxSizeForDatabase(this); | |
| } | |
| void Database::incrementalVacuumIfNeeded() { | |
| int64_t freeSpaceSize = m_sqliteDatabase.freeSpaceSize(); | |
| int64_t totalSize = m_sqliteDatabase.totalSize(); | |
| if (totalSize <= 10 * freeSpaceSize) { | |
| int result = m_sqliteDatabase.runIncrementalVacuumCommand(); | |
| reportVacuumDatabaseResult(result); | |
| if (result != SQLResultOk) | |
| logErrorMessage(formatErrorMessage("error vacuuming database", result, | |
| m_sqliteDatabase.lastErrorMsg())); | |
| } | |
| } | |
| // These are used to generate histograms of errors seen with websql. | |
| // See about:histograms in chromium. | |
| void Database::reportOpenDatabaseResult(int errorSite, | |
| int webSqlErrorCode, | |
| int sqliteErrorCode, | |
| double duration) { | |
| if (Platform::current()->databaseObserver()) { | |
| Platform::current()->databaseObserver()->reportOpenDatabaseResult( | |
| WebSecurityOrigin(getSecurityOrigin()), stringIdentifier(), errorSite, | |
| webSqlErrorCode, sqliteErrorCode, duration); | |
| } | |
| } | |
| void Database::reportChangeVersionResult(int errorSite, | |
| int webSqlErrorCode, | |
| int sqliteErrorCode) { | |
| if (Platform::current()->databaseObserver()) { | |
| Platform::current()->databaseObserver()->reportChangeVersionResult( | |
| WebSecurityOrigin(getSecurityOrigin()), stringIdentifier(), errorSite, | |
| webSqlErrorCode, sqliteErrorCode); | |
| } | |
| } | |
| void Database::reportStartTransactionResult(int errorSite, | |
| int webSqlErrorCode, | |
| int sqliteErrorCode) { | |
| if (Platform::current()->databaseObserver()) { | |
| Platform::current()->databaseObserver()->reportStartTransactionResult( | |
| WebSecurityOrigin(getSecurityOrigin()), stringIdentifier(), errorSite, | |
| webSqlErrorCode, sqliteErrorCode); | |
| } | |
| } | |
| void Database::reportCommitTransactionResult(int errorSite, | |
| int webSqlErrorCode, | |
| int sqliteErrorCode) { | |
| if (Platform::current()->databaseObserver()) { | |
| Platform::current()->databaseObserver()->reportCommitTransactionResult( | |
| WebSecurityOrigin(getSecurityOrigin()), stringIdentifier(), errorSite, | |
| webSqlErrorCode, sqliteErrorCode); | |
| } | |
| } | |
| void Database::reportExecuteStatementResult(int errorSite, | |
| int webSqlErrorCode, | |
| int sqliteErrorCode) { | |
| if (Platform::current()->databaseObserver()) { | |
| Platform::current()->databaseObserver()->reportExecuteStatementResult( | |
| WebSecurityOrigin(getSecurityOrigin()), stringIdentifier(), errorSite, | |
| webSqlErrorCode, sqliteErrorCode); | |
| } | |
| } | |
| void Database::reportVacuumDatabaseResult(int sqliteErrorCode) { | |
| if (Platform::current()->databaseObserver()) { | |
| Platform::current()->databaseObserver()->reportVacuumDatabaseResult( | |
| WebSecurityOrigin(getSecurityOrigin()), stringIdentifier(), | |
| sqliteErrorCode); | |
| } | |
| } | |
| void Database::logErrorMessage(const String& message) { | |
| getExecutionContext()->addConsoleMessage( | |
| ConsoleMessage::create(StorageMessageSource, ErrorMessageLevel, message)); | |
| } | |
| ExecutionContext* Database::getExecutionContext() const { | |
| return getDatabaseContext()->getExecutionContext(); | |
| } | |
| void Database::closeImmediately() { | |
| ASSERT(getExecutionContext()->isContextThread()); | |
| if (getDatabaseContext()->databaseThreadAvailable() && opened()) { | |
| logErrorMessage("forcibly closing database"); | |
| getDatabaseContext()->databaseThread()->scheduleTask( | |
| DatabaseCloseTask::create(this, 0)); | |
| } | |
| } | |
| void Database::changeVersion(const String& oldVersion, | |
| const String& newVersion, | |
| SQLTransactionCallback* callback, | |
| SQLTransactionErrorCallback* errorCallback, | |
| VoidCallback* successCallback) { | |
| ChangeVersionData data(oldVersion, newVersion); | |
| runTransaction(callback, errorCallback, successCallback, false, &data); | |
| } | |
| void Database::transaction(SQLTransactionCallback* callback, | |
| SQLTransactionErrorCallback* errorCallback, | |
| VoidCallback* successCallback) { | |
| runTransaction(callback, errorCallback, successCallback, false); | |
| } | |
| void Database::readTransaction(SQLTransactionCallback* callback, | |
| SQLTransactionErrorCallback* errorCallback, | |
| VoidCallback* successCallback) { | |
| runTransaction(callback, errorCallback, successCallback, true); | |
| } | |
| static void callTransactionErrorCallback( | |
| SQLTransactionErrorCallback* callback, | |
| std::unique_ptr<SQLErrorData> errorData) { | |
| callback->handleEvent(SQLError::create(*errorData)); | |
| } | |
| void Database::runTransaction(SQLTransactionCallback* callback, | |
| SQLTransactionErrorCallback* errorCallback, | |
| VoidCallback* successCallback, | |
| bool readOnly, | |
| const ChangeVersionData* changeVersionData) { | |
| if (!getExecutionContext()) | |
| return; | |
| ASSERT(getExecutionContext()->isContextThread()); | |
| // FIXME: Rather than passing errorCallback to SQLTransaction and then | |
| // sometimes firing it ourselves, this code should probably be pushed down | |
| // into Database so that we only create the SQLTransaction if we're | |
| // actually going to run it. | |
| #if DCHECK_IS_ON() | |
| SQLTransactionErrorCallback* originalErrorCallback = errorCallback; | |
| #endif | |
| SQLTransaction* transaction = SQLTransaction::create( | |
| this, callback, successCallback, errorCallback, readOnly); | |
| SQLTransactionBackend* transactionBackend = | |
| runTransaction(transaction, readOnly, changeVersionData); | |
| if (!transactionBackend) { | |
| SQLTransactionErrorCallback* callback = transaction->releaseErrorCallback(); | |
| ASSERT(callback == originalErrorCallback); | |
| if (callback) { | |
| std::unique_ptr<SQLErrorData> error = SQLErrorData::create( | |
| SQLError::kUnknownErr, "database has been closed"); | |
| getExecutionContext()->postTask( | |
| TaskType::DatabaseAccess, BLINK_FROM_HERE, | |
| createSameThreadTask(&callTransactionErrorCallback, | |
| wrapPersistent(callback), | |
| WTF::passed(std::move(error)))); | |
| } | |
| } | |
| } | |
| void Database::scheduleTransactionCallback(SQLTransaction* transaction) { | |
| // The task is constructed in a database thread, and destructed in the | |
| // context thread. | |
| getExecutionContext()->postTask( | |
| TaskType::DatabaseAccess, BLINK_FROM_HERE, | |
| createCrossThreadTask(&SQLTransaction::performPendingCallback, | |
| wrapCrossThreadPersistent(transaction))); | |
| } | |
| Vector<String> Database::performGetTableNames() { | |
| disableAuthorizer(); | |
| SQLiteStatement statement( | |
| sqliteDatabase(), "SELECT name FROM sqlite_master WHERE type='table';"); | |
| if (statement.prepare() != SQLResultOk) { | |
| DLOG(ERROR) << "Unable to retrieve list of tables for database " | |
| << databaseDebugName(); | |
| enableAuthorizer(); | |
| return Vector<String>(); | |
| } | |
| Vector<String> tableNames; | |
| int result; | |
| while ((result = statement.step()) == SQLResultRow) { | |
| String name = statement.getColumnText(0); | |
| if (name != databaseInfoTableName()) | |
| tableNames.push_back(name); | |
| } | |
| enableAuthorizer(); | |
| if (result != SQLResultDone) { | |
| DLOG(ERROR) << "Error getting tables for database " << databaseDebugName(); | |
| return Vector<String>(); | |
| } | |
| return tableNames; | |
| } | |
| Vector<String> Database::tableNames() { | |
| // FIXME: Not using isolatedCopy on these strings looks ok since threads | |
| // take strict turns in dealing with them. However, if the code changes, | |
| // this may not be true anymore. | |
| Vector<String> result; | |
| WaitableEvent event; | |
| if (!getDatabaseContext()->databaseThreadAvailable()) | |
| return result; | |
| std::unique_ptr<DatabaseTableNamesTask> task = | |
| DatabaseTableNamesTask::create(this, &event, result); | |
| getDatabaseContext()->databaseThread()->scheduleTask(std::move(task)); | |
| event.wait(); | |
| return result; | |
| } | |
| SecurityOrigin* Database::getSecurityOrigin() const { | |
| if (!getExecutionContext()) | |
| return nullptr; | |
| if (getExecutionContext()->isContextThread()) | |
| return m_contextThreadSecurityOrigin.get(); | |
| if (getDatabaseContext()->databaseThread()->isDatabaseThread()) | |
| return m_databaseThreadSecurityOrigin.get(); | |
| return nullptr; | |
| } | |
| bool Database::opened() { | |
| return static_cast<bool>(acquireLoad(&m_opened)); | |
| } | |
| } // namespace blink |