Home | History | Annotate | Download | only in webdatabase
      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