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 "modules/webdatabase/SQLTransactionBackendSync.h" 34 35 #include "bindings/v8/ExceptionState.h" 36 #include "core/dom/ExceptionCode.h" 37 #include "core/dom/ExecutionContext.h" 38 #include "modules/webdatabase/sqlite/SQLValue.h" 39 #include "modules/webdatabase/sqlite/SQLiteTransaction.h" 40 #include "modules/webdatabase/DatabaseAuthorizer.h" 41 #include "modules/webdatabase/DatabaseContext.h" 42 #include "modules/webdatabase/DatabaseSync.h" 43 #include "modules/webdatabase/SQLError.h" 44 #include "modules/webdatabase/SQLResultSet.h" 45 #include "modules/webdatabase/SQLStatementSync.h" 46 #include "modules/webdatabase/SQLTransactionClient.h" 47 #include "modules/webdatabase/SQLTransactionSync.h" 48 #include "modules/webdatabase/SQLTransactionSyncCallback.h" 49 #include "wtf/PassRefPtr.h" 50 #include "wtf/RefPtr.h" 51 52 namespace WebCore { 53 54 SQLTransactionBackendSync::SQLTransactionBackendSync(DatabaseSync* db, PassOwnPtr<SQLTransactionSyncCallback> callback, bool readOnly) 55 : m_database(db) 56 , m_callback(callback) 57 , m_readOnly(readOnly) 58 , m_hasVersionMismatch(false) 59 , m_modifiedDatabase(false) 60 , m_transactionClient(adoptPtr(new SQLTransactionClient())) 61 { 62 ASSERT(m_database->executionContext()->isContextThread()); 63 } 64 65 SQLTransactionBackendSync::~SQLTransactionBackendSync() 66 { 67 ASSERT(m_database->executionContext()->isContextThread()); 68 if (m_sqliteTransaction && m_sqliteTransaction->inProgress()) 69 rollback(); 70 } 71 72 PassRefPtr<SQLResultSet> SQLTransactionBackendSync::executeSQL(const String& sqlStatement, const Vector<SQLValue>& arguments, ExceptionState& exceptionState) 73 { 74 ASSERT(m_database->executionContext()->isContextThread()); 75 76 m_database->setLastErrorMessage(""); 77 78 if (!m_database->opened()) { 79 m_database->setLastErrorMessage("cannot executeSQL because the database is not open"); 80 exceptionState.throwDOMException(UnknownError, SQLError::unknownErrorMessage); 81 return 0; 82 } 83 84 if (m_hasVersionMismatch) { 85 m_database->setLastErrorMessage("cannot executeSQL because there is a version mismatch"); 86 exceptionState.throwDOMException(VersionError, SQLError::versionErrorMessage); 87 return 0; 88 } 89 90 if (sqlStatement.isEmpty()) 91 return 0; 92 93 int permissions = DatabaseAuthorizer::ReadWriteMask; 94 if (!m_database->databaseContext()->allowDatabaseAccess()) 95 permissions |= DatabaseAuthorizer::NoAccessMask; 96 else if (m_readOnly) 97 permissions |= DatabaseAuthorizer::ReadOnlyMask; 98 99 SQLStatementSync statement(sqlStatement, arguments, permissions); 100 101 m_database->resetAuthorizer(); 102 bool retryStatement = true; 103 RefPtr<SQLResultSet> resultSet; 104 while (retryStatement) { 105 retryStatement = false; 106 resultSet = statement.execute(m_database.get(), exceptionState); 107 if (!resultSet) { 108 if (m_sqliteTransaction->wasRolledBackBySqlite()) 109 return 0; 110 111 if (exceptionState.code() == QuotaExceededError) { 112 if (m_transactionClient->didExceedQuota(database())) { 113 exceptionState.clearException(); 114 retryStatement = true; 115 } else { 116 m_database->setLastErrorMessage("there was not enough remaining storage space"); 117 return 0; 118 } 119 } 120 } 121 } 122 123 if (m_database->lastActionChangedDatabase()) 124 m_modifiedDatabase = true; 125 126 return resultSet.release(); 127 } 128 129 void SQLTransactionBackendSync::begin(ExceptionState& exceptionState) 130 { 131 ASSERT(m_database->executionContext()->isContextThread()); 132 if (!m_database->opened()) { 133 m_database->reportStartTransactionResult(1, SQLError::UNKNOWN_ERR, 0); 134 m_database->setLastErrorMessage("cannot begin transaction because the database is not open"); 135 exceptionState.throwDOMException(UnknownError, SQLError::unknownErrorMessage); 136 return; 137 } 138 139 ASSERT(!m_database->sqliteDatabase().transactionInProgress()); 140 141 // Set the maximum usage for this transaction if this transactions is not read-only. 142 if (!m_readOnly) 143 m_database->sqliteDatabase().setMaximumSize(m_database->maximumSize()); 144 145 ASSERT(!m_sqliteTransaction); 146 m_sqliteTransaction = adoptPtr(new SQLiteTransaction(m_database->sqliteDatabase(), m_readOnly)); 147 148 m_database->resetDeletes(); 149 m_database->disableAuthorizer(); 150 m_sqliteTransaction->begin(); 151 m_database->enableAuthorizer(); 152 153 // Check if begin() succeeded. 154 if (!m_sqliteTransaction->inProgress()) { 155 ASSERT(!m_database->sqliteDatabase().transactionInProgress()); 156 m_database->reportStartTransactionResult(2, SQLError::DATABASE_ERR, m_database->sqliteDatabase().lastError()); 157 m_database->setLastErrorMessage("unable to begin transaction", 158 m_database->sqliteDatabase().lastError(), m_database->sqliteDatabase().lastErrorMsg()); 159 m_sqliteTransaction.clear(); 160 exceptionState.throwUninformativeAndGenericDOMException(SQLDatabaseError); 161 return; 162 } 163 164 // Note: We intentionally retrieve the actual version even with an empty expected version. 165 // In multi-process browsers, we take this opportinutiy to update the cached value for 166 // the actual version. In single-process browsers, this is just a map lookup. 167 String actualVersion; 168 if (!m_database->getActualVersionForTransaction(actualVersion)) { 169 m_database->reportStartTransactionResult(3, SQLError::DATABASE_ERR, m_database->sqliteDatabase().lastError()); 170 m_database->setLastErrorMessage("unable to read version", 171 m_database->sqliteDatabase().lastError(), m_database->sqliteDatabase().lastErrorMsg()); 172 rollback(); 173 exceptionState.throwUninformativeAndGenericDOMException(SQLDatabaseError); 174 return; 175 } 176 m_hasVersionMismatch = !m_database->expectedVersion().isEmpty() && (m_database->expectedVersion() != actualVersion); 177 m_database->reportStartTransactionResult(0, -1, 0); // OK 178 } 179 180 void SQLTransactionBackendSync::execute(ExceptionState& exceptionState) 181 { 182 ASSERT(m_database->executionContext()->isContextThread()); 183 if (!m_database->opened() || (m_callback && !m_callback->handleEvent(SQLTransactionSync::from(this)))) { 184 if (m_database->lastErrorMessage().isEmpty()) 185 m_database->setLastErrorMessage("failed to execute transaction callback"); 186 m_callback.clear(); 187 exceptionState.throwDOMException(UnknownError, SQLError::unknownErrorMessage); 188 return; 189 } 190 191 m_callback.clear(); 192 } 193 194 void SQLTransactionBackendSync::commit(ExceptionState& exceptionState) 195 { 196 ASSERT(m_database->executionContext()->isContextThread()); 197 if (!m_database->opened()) { 198 m_database->reportCommitTransactionResult(1, SQLError::UNKNOWN_ERR, 0); 199 m_database->setLastErrorMessage("unable to commit transaction because the database is not open."); 200 exceptionState.throwDOMException(UnknownError, SQLError::unknownErrorMessage); 201 return; 202 } 203 204 ASSERT(m_sqliteTransaction); 205 206 m_database->disableAuthorizer(); 207 m_sqliteTransaction->commit(); 208 m_database->enableAuthorizer(); 209 210 // If the commit failed, the transaction will still be marked as "in progress" 211 if (m_sqliteTransaction->inProgress()) { 212 m_database->reportCommitTransactionResult(2, SQLError::DATABASE_ERR, m_database->sqliteDatabase().lastError()); 213 m_database->setLastErrorMessage("unable to commit transaction", 214 m_database->sqliteDatabase().lastError(), m_database->sqliteDatabase().lastErrorMsg()); 215 exceptionState.throwUninformativeAndGenericDOMException(SQLDatabaseError); 216 return; 217 } 218 219 m_sqliteTransaction.clear(); 220 221 // Vacuum the database if anything was deleted. 222 if (m_database->hadDeletes()) 223 m_database->incrementalVacuumIfNeeded(); 224 225 // The commit was successful. If the transaction modified this database, notify the delegates. 226 if (m_modifiedDatabase) 227 m_transactionClient->didCommitWriteTransaction(database()); 228 229 m_database->reportCommitTransactionResult(0, -1, 0); // OK 230 } 231 232 void SQLTransactionBackendSync::rollback() 233 { 234 ASSERT(m_database->executionContext()->isContextThread()); 235 m_database->disableAuthorizer(); 236 if (m_sqliteTransaction) { 237 m_sqliteTransaction->rollback(); 238 m_sqliteTransaction.clear(); 239 } 240 m_database->enableAuthorizer(); 241 242 ASSERT(!m_database->sqliteDatabase().transactionInProgress()); 243 } 244 245 } // namespace WebCore 246