1/* 2 * Copyright (C) 2010 Google Inc. All rights reserved. 3 * Copyright (C) 2013 Apple Inc. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * * Redistributions in binary form must reproduce the above 12 * copyright notice, this list of conditions and the following disclaimer 13 * in the documentation and/or other materials provided with the 14 * distribution. 15 * * Neither the name of Google Inc. nor the names of its 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32#include "config.h" 33#include "SQLTransactionBackendSync.h" 34 35#if ENABLE(SQL_DATABASE) 36 37#include "DatabaseAuthorizer.h" 38#include "DatabaseBackendContext.h" 39#include "DatabaseSync.h" 40#include "SQLException.h" 41#include "SQLResultSet.h" 42#include "SQLStatementSync.h" 43#include "SQLTransactionClient.h" 44#include "SQLTransactionSync.h" 45#include "SQLTransactionSyncCallback.h" 46#include "SQLValue.h" 47#include "SQLiteTransaction.h" 48#include "ScriptExecutionContext.h" 49#include <wtf/PassRefPtr.h> 50#include <wtf/RefPtr.h> 51#include <wtf/text/WTFString.h> 52 53namespace WebCore { 54 55SQLTransactionBackendSync::SQLTransactionBackendSync(DatabaseSync* db, PassRefPtr<SQLTransactionSyncCallback> callback, bool readOnly) 56 : m_database(db) 57 , m_callback(callback) 58 , m_readOnly(readOnly) 59 , m_hasVersionMismatch(false) 60 , m_modifiedDatabase(false) 61 , m_transactionClient(std::make_unique<SQLTransactionClient>()) 62{ 63 ASSERT(m_database->scriptExecutionContext()->isContextThread()); 64} 65 66SQLTransactionBackendSync::~SQLTransactionBackendSync() 67{ 68 ASSERT(m_database->scriptExecutionContext()->isContextThread()); 69 if (m_sqliteTransaction && m_sqliteTransaction->inProgress()) 70 rollback(); 71} 72 73PassRefPtr<SQLResultSet> SQLTransactionBackendSync::executeSQL(const String& sqlStatement, const Vector<SQLValue>& arguments, ExceptionCode& ec) 74{ 75 ASSERT(m_database->scriptExecutionContext()->isContextThread()); 76 77 m_database->setLastErrorMessage(""); 78 79 if (!m_database->opened()) { 80 m_database->setLastErrorMessage("cannot executeSQL because the database is not open"); 81 ec = SQLException::UNKNOWN_ERR; 82 return 0; 83 } 84 85 if (m_hasVersionMismatch) { 86 m_database->setLastErrorMessage("cannot executeSQL because there is a version mismatch"); 87 ec = SQLException::VERSION_ERR; 88 return 0; 89 } 90 91 if (sqlStatement.isEmpty()) 92 return 0; 93 94 int permissions = DatabaseAuthorizer::ReadWriteMask; 95 if (!m_database->databaseContext()->allowDatabaseAccess()) 96 permissions |= DatabaseAuthorizer::NoAccessMask; 97 else if (m_readOnly) 98 permissions |= DatabaseAuthorizer::ReadOnlyMask; 99 100 SQLStatementSync statement(sqlStatement, arguments, permissions); 101 102 m_database->resetAuthorizer(); 103 bool retryStatement = true; 104 RefPtr<SQLResultSet> resultSet; 105 while (retryStatement) { 106 retryStatement = false; 107 resultSet = statement.execute(m_database.get(), ec); 108 if (!resultSet) { 109 if (m_sqliteTransaction->wasRolledBackBySqlite()) 110 return 0; 111 112 if (ec == SQLException::QUOTA_ERR) { 113 if (m_transactionClient->didExceedQuota(database())) { 114 ec = 0; 115 retryStatement = true; 116 } else { 117 m_database->setLastErrorMessage("there was not enough remaining storage space"); 118 return 0; 119 } 120 } 121 } 122 } 123 124 if (m_database->lastActionChangedDatabase()) 125 m_modifiedDatabase = true; 126 127 return resultSet.release(); 128} 129 130ExceptionCode SQLTransactionBackendSync::begin() 131{ 132 ASSERT(m_database->scriptExecutionContext()->isContextThread()); 133 if (!m_database->opened()) { 134 m_database->setLastErrorMessage("cannot begin transaction because the database is not open"); 135 return SQLException::UNKNOWN_ERR; 136 } 137 138 ASSERT(!m_database->sqliteDatabase().transactionInProgress()); 139 140 // Set the maximum usage for this transaction if this transactions is not read-only. 141 if (!m_readOnly) 142 m_database->sqliteDatabase().setMaximumSize(m_database->maximumSize()); 143 144 ASSERT(!m_sqliteTransaction); 145 m_sqliteTransaction = std::make_unique<SQLiteTransaction>(m_database->sqliteDatabase(), m_readOnly); 146 147 m_database->resetDeletes(); 148 m_database->disableAuthorizer(); 149 m_sqliteTransaction->begin(); 150 m_database->enableAuthorizer(); 151 152 // Check if begin() succeeded. 153 if (!m_sqliteTransaction->inProgress()) { 154 ASSERT(!m_database->sqliteDatabase().transactionInProgress()); 155 m_database->setLastErrorMessage("unable to begin transaction", 156 m_database->sqliteDatabase().lastError(), m_database->sqliteDatabase().lastErrorMsg()); 157 m_sqliteTransaction = nullptr; 158 return SQLException::DATABASE_ERR; 159 } 160 161 // Note: We intentionally retrieve the actual version even with an empty expected version. 162 // In multi-process browsers, we take this opportinutiy to update the cached value for 163 // the actual version. In single-process browsers, this is just a map lookup. 164 String actualVersion; 165 if (!m_database->getActualVersionForTransaction(actualVersion)) { 166 m_database->setLastErrorMessage("unable to read version", 167 m_database->sqliteDatabase().lastError(), m_database->sqliteDatabase().lastErrorMsg()); 168 rollback(); 169 return SQLException::DATABASE_ERR; 170 } 171 m_hasVersionMismatch = !m_database->expectedVersion().isEmpty() && (m_database->expectedVersion() != actualVersion); 172 return 0; 173} 174 175ExceptionCode SQLTransactionBackendSync::execute() 176{ 177 ASSERT(m_database->scriptExecutionContext()->isContextThread()); 178 if (!m_database->opened() || (m_callback && !m_callback->handleEvent(SQLTransactionSync::from(this)))) { 179 if (m_database->lastErrorMessage().isEmpty()) 180 m_database->setLastErrorMessage("failed to execute transaction callback"); 181 m_callback = 0; 182 return SQLException::UNKNOWN_ERR; 183 } 184 185 m_callback = 0; 186 return 0; 187} 188 189ExceptionCode SQLTransactionBackendSync::commit() 190{ 191 ASSERT(m_database->scriptExecutionContext()->isContextThread()); 192 if (!m_database->opened()) { 193 m_database->setLastErrorMessage("unable to commit transaction because the database is not open."); 194 return SQLException::UNKNOWN_ERR; 195 } 196 197 ASSERT(m_sqliteTransaction); 198 199 m_database->disableAuthorizer(); 200 m_sqliteTransaction->commit(); 201 m_database->enableAuthorizer(); 202 203 // If the commit failed, the transaction will still be marked as "in progress" 204 if (m_sqliteTransaction->inProgress()) { 205 m_database->setLastErrorMessage("unable to commit transaction", 206 m_database->sqliteDatabase().lastError(), m_database->sqliteDatabase().lastErrorMsg()); 207 return SQLException::DATABASE_ERR; 208 } 209 210 m_sqliteTransaction = nullptr; 211 212 // Vacuum the database if anything was deleted. 213 if (m_database->hadDeletes()) 214 m_database->incrementalVacuumIfNeeded(); 215 216 // The commit was successful. If the transaction modified this database, notify the delegates. 217 if (m_modifiedDatabase) 218 m_transactionClient->didCommitWriteTransaction(database()); 219 return 0; 220} 221 222void SQLTransactionBackendSync::rollback() 223{ 224 ASSERT(m_database->scriptExecutionContext()->isContextThread()); 225 m_database->disableAuthorizer(); 226 if (m_sqliteTransaction) { 227 m_sqliteTransaction->rollback(); 228 m_sqliteTransaction = nullptr; 229 } 230 m_database->enableAuthorizer(); 231 232 ASSERT(!m_database->sqliteDatabase().transactionInProgress()); 233} 234 235} // namespace WebCore 236 237#endif // ENABLE(SQL_DATABASE) 238