diff options
Diffstat (limited to 'Source/WebCore/Modules/webdatabase/SQLTransactionBackend.cpp')
-rw-r--r-- | Source/WebCore/Modules/webdatabase/SQLTransactionBackend.cpp | 423 |
1 files changed, 44 insertions, 379 deletions
diff --git a/Source/WebCore/Modules/webdatabase/SQLTransactionBackend.cpp b/Source/WebCore/Modules/webdatabase/SQLTransactionBackend.cpp index 244f28566..2ea93ddf2 100644 --- a/Source/WebCore/Modules/webdatabase/SQLTransactionBackend.cpp +++ b/Source/WebCore/Modules/webdatabase/SQLTransactionBackend.cpp @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -29,23 +29,19 @@ #include "config.h" #include "SQLTransactionBackend.h" -#if ENABLE(SQL_DATABASE) - -#include "AbstractSQLTransaction.h" -#include "Database.h" // FIXME: Should only be used in the frontend. +#include "Database.h" #include "DatabaseAuthorizer.h" -#include "DatabaseBackend.h" -#include "DatabaseBackendContext.h" +#include "DatabaseContext.h" #include "DatabaseThread.h" #include "DatabaseTracker.h" -#include "ExceptionCode.h" #include "Logging.h" #include "OriginLock.h" #include "SQLError.h" -#include "SQLStatementBackend.h" -#include "SQLTransactionClient.h" +#include "SQLStatement.h" +#include "SQLStatementCallback.h" +#include "SQLStatementErrorCallback.h" +#include "SQLTransaction.h" #include "SQLTransactionCoordinator.h" -#include "SQLValue.h" #include "SQLiteTransaction.h" #include <wtf/StdLibExtras.h> #include <wtf/text/WTFString.h> @@ -260,7 +256,7 @@ // // When executing the transaction (in DatabaseThread::databaseThread()): // ==================================================================== -// OwnPtr<DatabaseTask> task; // points to ... +// std::unique_ptr<DatabaseTask> task; // points to ... // --> DatabaseTransactionTask // RefPtr<SQLTransactionBackend> m_transaction points to ... // --> SQLTransactionBackend // RefPtr<SQLTransaction> m_frontend; // --> SQLTransaction // RefPtr<SQLTransactionBackend> m_backend points to ... @@ -284,7 +280,7 @@ // However, there will still be a DatabaseTask pointing to the SQLTransactionBackend (see // the "When executing the transaction" chain above). This will keep the // SQLTransactionBackend alive until DatabaseThread::databaseThread() releases its -// task OwnPtr. +// task std::unique_ptr. // // What happens if a transaction is interrupted? // ============================================ @@ -344,61 +340,38 @@ namespace WebCore { -PassRefPtr<SQLTransactionBackend> SQLTransactionBackend::create(DatabaseBackend* db, - PassRefPtr<AbstractSQLTransaction> frontend, PassRefPtr<SQLTransactionWrapper> wrapper, bool readOnly) -{ - return adoptRef(new SQLTransactionBackend(db, frontend, wrapper, readOnly)); -} - -SQLTransactionBackend::SQLTransactionBackend(DatabaseBackend* db, - PassRefPtr<AbstractSQLTransaction> frontend, PassRefPtr<SQLTransactionWrapper> wrapper, bool readOnly) +SQLTransactionBackend::SQLTransactionBackend(SQLTransaction& frontend) : m_frontend(frontend) - , m_database(db) - , m_wrapper(wrapper) - , m_hasCallback(m_frontend->hasCallback()) - , m_hasSuccessCallback(m_frontend->hasSuccessCallback()) - , m_hasErrorCallback(m_frontend->hasErrorCallback()) - , m_shouldRetryCurrentStatement(false) - , m_modifiedDatabase(false) - , m_lockAcquired(false) - , m_readOnly(readOnly) - , m_hasVersionMismatch(false) { - ASSERT(m_database); - m_frontend->setBackend(this); m_requestedState = SQLTransactionState::AcquireLock; } SQLTransactionBackend::~SQLTransactionBackend() { - ASSERT(!m_sqliteTransaction); + ASSERT(!m_frontend.m_sqliteTransaction); } void SQLTransactionBackend::doCleanup() { - if (!m_frontend) - return; - m_frontend = 0; // Break the reference cycle. See comment about the life-cycle above. - - ASSERT(currentThread() == database()->databaseContext()->databaseThread()->getThreadID()); + ASSERT(currentThread() == m_frontend.database().databaseThread().getThreadID()); - releaseOriginLockIfNeeded(); + m_frontend.releaseOriginLockIfNeeded(); - MutexLocker locker(m_statementMutex); - m_statementQueue.clear(); + LockHolder locker(m_frontend.m_statementMutex); + m_frontend.m_statementQueue.clear(); - if (m_sqliteTransaction) { + if (m_frontend.m_sqliteTransaction) { // In the event we got here because of an interruption or error (i.e. if // the transaction is in progress), we should roll it back here. Clearing // m_sqliteTransaction invokes SQLiteTransaction's destructor which does // just that. We might as well do this unconditionally and free up its // resources because we're already terminating. - m_sqliteTransaction.clear(); + m_frontend.m_sqliteTransaction = nullptr; } // Release the lock on this database - if (m_lockAcquired) - m_database->transactionCoordinator()->releaseLock(this); + if (m_frontend.m_lockAcquired) + m_frontend.m_database->transactionCoordinator()->releaseLock(m_frontend); // Do some aggresive clean up here except for m_database. // @@ -423,23 +396,7 @@ void SQLTransactionBackend::doCleanup() // SQLTransactionBackend is guaranteed to not destruct until the frontend // is also destructing. - m_wrapper = 0; -} - -AbstractSQLStatement* SQLTransactionBackend::currentStatement() -{ - return m_currentStatementBackend->frontend(); -} - -PassRefPtr<SQLError> SQLTransactionBackend::transactionError() -{ - return m_transactionError; -} - -void SQLTransactionBackend::setShouldRetryCurrentStatement(bool shouldRetry) -{ - ASSERT(!m_shouldRetryCurrentStatement); - m_shouldRetryCurrentStatement = shouldRetry; + m_frontend.m_wrapper = nullptr; } SQLTransactionBackend::StateFunction SQLTransactionBackend::stateFunctionFor(SQLTransactionState state) @@ -450,14 +407,14 @@ SQLTransactionBackend::StateFunction SQLTransactionBackend::stateFunctionFor(SQL &SQLTransactionBackend::acquireLock, // 2. &SQLTransactionBackend::openTransactionAndPreflight, // 3. &SQLTransactionBackend::runStatements, // 4. - &SQLTransactionBackend::postflightAndCommit, // 5. + &SQLTransactionBackend::unreachableState, // 5. postflightAndCommit &SQLTransactionBackend::cleanupAndTerminate, // 6. &SQLTransactionBackend::cleanupAfterTransactionErrorCallback, // 7. - &SQLTransactionBackend::sendToFrontendState, // 8. deliverTransactionCallback - &SQLTransactionBackend::sendToFrontendState, // 9. deliverTransactionErrorCallback - &SQLTransactionBackend::sendToFrontendState, // 10. deliverStatementCallback - &SQLTransactionBackend::sendToFrontendState, // 11. deliverQuotaIncreaseCallback - &SQLTransactionBackend::sendToFrontendState // 12. deliverSuccessCallback + &SQLTransactionBackend::unreachableState, // 8. deliverTransactionCallback + &SQLTransactionBackend::unreachableState, // 9. deliverTransactionErrorCallback + &SQLTransactionBackend::unreachableState, // 10. deliverStatementCallback + &SQLTransactionBackend::unreachableState, // 11. deliverQuotaIncreaseCallback + &SQLTransactionBackend::unreachableState // 12. deliverSuccessCallback }; ASSERT(WTF_ARRAY_LENGTH(stateFunctions) == static_cast<int>(SQLTransactionState::NumberOfStates)); @@ -466,17 +423,11 @@ SQLTransactionBackend::StateFunction SQLTransactionBackend::stateFunctionFor(SQL return stateFunctions[static_cast<int>(state)]; } -void SQLTransactionBackend::enqueueStatementBackend(PassRefPtr<SQLStatementBackend> statementBackend) -{ - MutexLocker locker(m_statementMutex); - m_statementQueue.append(statementBackend); -} - void SQLTransactionBackend::computeNextStateAndCleanupIfNeeded() { // Only honor the requested state transition if we're not supposed to be // cleaning up and shutting down: - if (m_database->opened() && !m_database->isInterrupted()) { + if (m_frontend.m_database->opened()) { setStateToRequestedState(); ASSERT(m_nextState == SQLTransactionState::AcquireLock || m_nextState == SQLTransactionState::OpenTransactionAndPreflight @@ -498,49 +449,23 @@ void SQLTransactionBackend::computeNextStateAndCleanupIfNeeded() LOG(StorageAPI, "Database was stopped or interrupted - cancelling work for this transaction"); // The current SQLite transaction should be stopped, as well - if (m_sqliteTransaction) { - m_sqliteTransaction->stop(); - m_sqliteTransaction.clear(); + if (m_frontend.m_sqliteTransaction) { + m_frontend.m_sqliteTransaction->stop(); + m_frontend.m_sqliteTransaction = nullptr; } // Terminate the frontend state machine. This also gets the frontend to // call computeNextStateAndCleanupIfNeeded() and clear its wrappers // if needed. - m_frontend->requestTransitToState(SQLTransactionState::End); + m_frontend.requestTransitToState(SQLTransactionState::End); // Redirect to the end state to abort, clean up, and end the transaction. doCleanup(); } -void SQLTransactionBackend::performNextStep() -{ - computeNextStateAndCleanupIfNeeded(); - runStateMachine(); -} - -#if PLATFORM(IOS) -bool SQLTransactionBackend::shouldPerformWhilePaused() const -{ - // SQLTransactions should only run-while-paused if they have progressed passed the first transaction step. - return m_nextState != SQLTransactionState::AcquireLock; -} -#endif - -void SQLTransactionBackend::executeSQL(PassOwnPtr<AbstractSQLStatement> statement, - const String& sqlStatement, const Vector<SQLValue>& arguments, int permissions) -{ - RefPtr<SQLStatementBackend> statementBackend; - statementBackend = SQLStatementBackend::create(statement, sqlStatement, arguments, permissions); - - if (Database::from(m_database.get())->deleted()) - statementBackend->setDatabaseDeletedError(); - - enqueueStatementBackend(statementBackend); -} - void SQLTransactionBackend::notifyDatabaseThreadIsShuttingDown() { - ASSERT(currentThread() == database()->databaseContext()->databaseThread()->getThreadID()); + ASSERT(currentThread() == m_frontend.database().databaseThread().getThreadID()); // If the transaction is in progress, we should roll it back here, since this // is our last opportunity to do something related to this transaction on the @@ -550,264 +475,29 @@ void SQLTransactionBackend::notifyDatabaseThreadIsShuttingDown() doCleanup(); } -SQLTransactionState SQLTransactionBackend::acquireLock() -{ - m_database->transactionCoordinator()->acquireLock(this); - return SQLTransactionState::Idle; -} - -void SQLTransactionBackend::lockAcquired() -{ - m_lockAcquired = true; - requestTransitToState(SQLTransactionState::OpenTransactionAndPreflight); -} - -SQLTransactionState SQLTransactionBackend::openTransactionAndPreflight() -{ - ASSERT(!m_database->sqliteDatabase().transactionInProgress()); - ASSERT(m_lockAcquired); - - LOG(StorageAPI, "Opening and preflighting transaction %p", this); - - // If the database was deleted, jump to the error callback - if (Database::from(m_database.get())->deleted()) { - m_transactionError = SQLError::create(SQLError::UNKNOWN_ERR, "unable to open a transaction, because the user deleted the database"); - return nextStateForTransactionError(); - } - - // Set the maximum usage for this transaction if this transactions is not read-only - if (!m_readOnly) { - acquireOriginLock(); - m_database->sqliteDatabase().setMaximumSize(m_database->maximumSize()); - } - - ASSERT(!m_sqliteTransaction); - m_sqliteTransaction = adoptPtr(new SQLiteTransaction(m_database->sqliteDatabase(), m_readOnly)); - - m_database->resetDeletes(); - m_database->disableAuthorizer(); - m_sqliteTransaction->begin(); - m_database->enableAuthorizer(); - - // Spec 4.3.2.1+2: Open a transaction to the database, jumping to the error callback if that fails - if (!m_sqliteTransaction->inProgress()) { - ASSERT(!m_database->sqliteDatabase().transactionInProgress()); - m_transactionError = SQLError::create(SQLError::DATABASE_ERR, "unable to begin transaction", - m_database->sqliteDatabase().lastError(), m_database->sqliteDatabase().lastErrorMsg()); - m_sqliteTransaction.clear(); - return nextStateForTransactionError(); - } - - // Note: We intentionally retrieve the actual version even with an empty expected version. - // In multi-process browsers, we take this opportinutiy to update the cached value for - // the actual version. In single-process browsers, this is just a map lookup. - String actualVersion; - if (!m_database->getActualVersionForTransaction(actualVersion)) { - m_transactionError = SQLError::create(SQLError::DATABASE_ERR, "unable to read version", - m_database->sqliteDatabase().lastError(), m_database->sqliteDatabase().lastErrorMsg()); - m_database->disableAuthorizer(); - m_sqliteTransaction.clear(); - m_database->enableAuthorizer(); - return nextStateForTransactionError(); - } - m_hasVersionMismatch = !m_database->expectedVersion().isEmpty() && (m_database->expectedVersion() != actualVersion); - - // Spec 4.3.2.3: Perform preflight steps, jumping to the error callback if they fail - if (m_wrapper && !m_wrapper->performPreflight(this)) { - m_database->disableAuthorizer(); - m_sqliteTransaction.clear(); - m_database->enableAuthorizer(); - m_transactionError = m_wrapper->sqlError(); - if (!m_transactionError) - m_transactionError = SQLError::create(SQLError::UNKNOWN_ERR, "unknown error occurred during transaction preflight"); - return nextStateForTransactionError(); - } - - // Spec 4.3.2.4: Invoke the transaction callback with the new SQLTransaction object - if (m_hasCallback) - return SQLTransactionState::DeliverTransactionCallback; - - // If we have no callback to make, skip pass to the state after: - return SQLTransactionState::RunStatements; -} - -SQLTransactionState SQLTransactionBackend::runStatements() +void SQLTransactionBackend::acquireLock() { - ASSERT(m_lockAcquired); - SQLTransactionState nextState; - - // If there is a series of statements queued up that are all successful and have no associated - // SQLStatementCallback objects, then we can burn through the queue - do { - if (m_shouldRetryCurrentStatement && !m_sqliteTransaction->wasRolledBackBySqlite()) { - m_shouldRetryCurrentStatement = false; - // FIXME - Another place that needs fixing up after <rdar://problem/5628468> is addressed. - // See ::openTransactionAndPreflight() for discussion - - // Reset the maximum size here, as it was increased to allow us to retry this statement. - // m_shouldRetryCurrentStatement is set to true only when a statement exceeds - // the quota, which can happen only in a read-write transaction. Therefore, there - // is no need to check here if the transaction is read-write. - m_database->sqliteDatabase().setMaximumSize(m_database->maximumSize()); - } else { - // If the current statement has already been run, failed due to quota constraints, and we're not retrying it, - // that means it ended in an error. Handle it now - if (m_currentStatementBackend && m_currentStatementBackend->lastExecutionFailedDueToQuota()) { - return nextStateForCurrentStatementError(); - } - - // Otherwise, advance to the next statement - getNextStatement(); - } - nextState = runCurrentStatementAndGetNextState(); - } while (nextState == SQLTransactionState::RunStatements); - - return nextState; + m_frontend.acquireLock(); } -void SQLTransactionBackend::getNextStatement() +void SQLTransactionBackend::openTransactionAndPreflight() { - m_currentStatementBackend = 0; - - MutexLocker locker(m_statementMutex); - if (!m_statementQueue.isEmpty()) - m_currentStatementBackend = m_statementQueue.takeFirst(); -} - -SQLTransactionState SQLTransactionBackend::runCurrentStatementAndGetNextState() -{ - if (!m_currentStatementBackend) { - // No more statements to run. So move on to the next state. - return SQLTransactionState::PostflightAndCommit; - } - - m_database->resetAuthorizer(); - - if (m_hasVersionMismatch) - m_currentStatementBackend->setVersionMismatchedError(); - - if (m_currentStatementBackend->execute(m_database.get())) { - if (m_database->lastActionChangedDatabase()) { - // Flag this transaction as having changed the database for later delegate notification - m_modifiedDatabase = true; - } - - if (m_currentStatementBackend->hasStatementCallback()) { - return SQLTransactionState::DeliverStatementCallback; - } - - // If we get here, then the statement doesn't have a callback to invoke. - // We can move on to the next statement. Hence, stay in this state. - return SQLTransactionState::RunStatements; - } - - if (m_currentStatementBackend->lastExecutionFailedDueToQuota()) { - return SQLTransactionState::DeliverQuotaIncreaseCallback; - } - - return nextStateForCurrentStatementError(); -} - -SQLTransactionState SQLTransactionBackend::nextStateForCurrentStatementError() -{ - // Spec 4.3.2.6.6: error - Call the statement's error callback, but if there was no error callback, - // or the transaction was rolled back, jump to the transaction error callback - if (m_currentStatementBackend->hasStatementErrorCallback() && !m_sqliteTransaction->wasRolledBackBySqlite()) - return SQLTransactionState::DeliverStatementCallback; - - m_transactionError = m_currentStatementBackend->sqlError(); - if (!m_transactionError) - m_transactionError = SQLError::create(SQLError::DATABASE_ERR, "the statement failed to execute"); - return nextStateForTransactionError(); -} - -SQLTransactionState SQLTransactionBackend::postflightAndCommit() -{ - ASSERT(m_lockAcquired); - - // Spec 4.3.2.7: Perform postflight steps, jumping to the error callback if they fail. - if (m_wrapper && !m_wrapper->performPostflight(this)) { - m_transactionError = m_wrapper->sqlError(); - if (!m_transactionError) - m_transactionError = SQLError::create(SQLError::UNKNOWN_ERR, "unknown error occurred during transaction postflight"); - return nextStateForTransactionError(); - } - - // Spec 4.3.2.7: Commit the transaction, jumping to the error callback if that fails. - ASSERT(m_sqliteTransaction); - - m_database->disableAuthorizer(); - m_sqliteTransaction->commit(); - m_database->enableAuthorizer(); - - releaseOriginLockIfNeeded(); - - // If the commit failed, the transaction will still be marked as "in progress" - if (m_sqliteTransaction->inProgress()) { - if (m_wrapper) - m_wrapper->handleCommitFailedAfterPostflight(this); - m_transactionError = SQLError::create(SQLError::DATABASE_ERR, "unable to commit transaction", - m_database->sqliteDatabase().lastError(), m_database->sqliteDatabase().lastErrorMsg()); - return nextStateForTransactionError(); - } - - // Vacuum the database if anything was deleted. - if (m_database->hadDeletes()) - m_database->incrementalVacuumIfNeeded(); - - // The commit was successful. If the transaction modified this database, notify the delegates. - if (m_modifiedDatabase) - m_database->transactionClient()->didCommitWriteTransaction(database()); - - // Spec 4.3.2.8: Deliver success callback, if there is one. - return SQLTransactionState::DeliverSuccessCallback; + m_frontend.openTransactionAndPreflight(); } -SQLTransactionState SQLTransactionBackend::cleanupAndTerminate() +void SQLTransactionBackend::runStatements() { - ASSERT(m_lockAcquired); - - // Spec 4.3.2.9: End transaction steps. There is no next step. - LOG(StorageAPI, "Transaction %p is complete\n", this); - ASSERT(!m_database->sqliteDatabase().transactionInProgress()); - - // Phase 5 cleanup. See comment on the SQLTransaction life-cycle above. - doCleanup(); - m_database->inProgressTransactionCompleted(); - return SQLTransactionState::End; + m_frontend.runStatements(); } -SQLTransactionState SQLTransactionBackend::nextStateForTransactionError() +void SQLTransactionBackend::cleanupAndTerminate() { - ASSERT(m_transactionError); - if (m_hasErrorCallback) - return SQLTransactionState::DeliverTransactionErrorCallback; - - // No error callback, so fast-forward to the next state and rollback the - // transaction. - return SQLTransactionState::CleanupAfterTransactionErrorCallback; + m_frontend.cleanupAndTerminate(); } -SQLTransactionState SQLTransactionBackend::cleanupAfterTransactionErrorCallback() +void SQLTransactionBackend::cleanupAfterTransactionErrorCallback() { - ASSERT(m_lockAcquired); - - LOG(StorageAPI, "Transaction %p is complete with an error\n", this); - m_database->disableAuthorizer(); - if (m_sqliteTransaction) { - // Spec 4.3.2.10: Rollback the transaction. - m_sqliteTransaction->rollback(); - - ASSERT(!m_database->sqliteDatabase().transactionInProgress()); - m_sqliteTransaction.clear(); - } - m_database->enableAuthorizer(); - - releaseOriginLockIfNeeded(); - - ASSERT(!m_database->sqliteDatabase().transactionInProgress()); - - return SQLTransactionState::CleanupAndTerminate; + m_frontend.cleanupAfterTransactionErrorCallback(); } // requestTransitToState() can be called from the frontend. Hence, it should @@ -818,40 +508,15 @@ void SQLTransactionBackend::requestTransitToState(SQLTransactionState nextState) LOG(StorageAPI, "Scheduling %s for transaction %p\n", nameForSQLTransactionState(nextState), this); m_requestedState = nextState; ASSERT(m_requestedState != SQLTransactionState::End); - m_database->scheduleTransactionStep(this); + m_frontend.m_database->scheduleTransactionStep(m_frontend); } // This state function is used as a stub function to plug unimplemented states // in the state dispatch table. They are unimplemented because they should // never be reached in the course of correct execution. -SQLTransactionState SQLTransactionBackend::unreachableState() +void SQLTransactionBackend::unreachableState() { ASSERT_NOT_REACHED(); - return SQLTransactionState::End; -} - -SQLTransactionState SQLTransactionBackend::sendToFrontendState() -{ - ASSERT(m_nextState != SQLTransactionState::Idle); - m_frontend->requestTransitToState(m_nextState); - return SQLTransactionState::Idle; -} - -void SQLTransactionBackend::acquireOriginLock() -{ - ASSERT(!m_originLock); - m_originLock = DatabaseTracker::tracker().originLockFor(m_database->securityOrigin()); - m_originLock->lock(); -} - -void SQLTransactionBackend::releaseOriginLockIfNeeded() -{ - if (m_originLock) { - m_originLock->unlock(); - m_originLock.clear(); - } } } // namespace WebCore - -#endif // ENABLE(SQL_DATABASE) |