1 /* 2 * Copyright (C) 2007 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 #include "config.h" 29 #include "SQLStatement.h" 30 31 #if ENABLE(DATABASE) 32 33 #include "Database.h" 34 #include "Logging.h" 35 #include "SQLError.h" 36 #include "SQLiteDatabase.h" 37 #include "SQLiteStatement.h" 38 #include "SQLStatementCallback.h" 39 #include "SQLStatementErrorCallback.h" 40 #include "SQLTransaction.h" 41 #include "SQLValue.h" 42 #include <wtf/text/CString.h> 43 44 namespace WebCore { 45 46 PassRefPtr<SQLStatement> SQLStatement::create(Database* database, const String& statement, const Vector<SQLValue>& arguments, PassRefPtr<SQLStatementCallback> callback, PassRefPtr<SQLStatementErrorCallback> errorCallback, int permissions) 47 { 48 return adoptRef(new SQLStatement(database, statement, arguments, callback, errorCallback, permissions)); 49 } 50 51 SQLStatement::SQLStatement(Database* database, const String& statement, const Vector<SQLValue>& arguments, PassRefPtr<SQLStatementCallback> callback, PassRefPtr<SQLStatementErrorCallback> errorCallback, int permissions) 52 : m_statement(statement.crossThreadString()) 53 , m_arguments(arguments) 54 , m_statementCallbackWrapper(callback, database->scriptExecutionContext()) 55 , m_statementErrorCallbackWrapper(errorCallback, database->scriptExecutionContext()) 56 , m_permissions(permissions) 57 { 58 } 59 60 bool SQLStatement::execute(Database* db) 61 { 62 ASSERT(!m_resultSet); 63 64 // If we're re-running this statement after a quota violation, we need to clear that error now 65 clearFailureDueToQuota(); 66 67 // This transaction might have been marked bad while it was being set up on the main thread, 68 // so if there is still an error, return false. 69 if (m_error) 70 return false; 71 72 db->setAuthorizerPermissions(m_permissions); 73 74 SQLiteDatabase* database = &db->sqliteDatabase(); 75 76 SQLiteStatement statement(*database, m_statement); 77 int result = statement.prepare(); 78 79 if (result != SQLResultOk) { 80 LOG(StorageAPI, "Unable to verify correctness of statement %s - error %i (%s)", m_statement.ascii().data(), result, database->lastErrorMsg()); 81 m_error = SQLError::create(result == SQLResultInterrupt ? SQLError::DATABASE_ERR : SQLError::SYNTAX_ERR, database->lastErrorMsg()); 82 return false; 83 } 84 85 // FIXME: If the statement uses the ?### syntax supported by sqlite, the bind parameter count is very likely off from the number of question marks. 86 // If this is the case, they might be trying to do something fishy or malicious 87 if (statement.bindParameterCount() != m_arguments.size()) { 88 LOG(StorageAPI, "Bind parameter count doesn't match number of question marks"); 89 m_error = SQLError::create(db->isInterrupted() ? SQLError::DATABASE_ERR : SQLError::SYNTAX_ERR, "number of '?'s in statement string does not match argument count"); 90 return false; 91 } 92 93 for (unsigned i = 0; i < m_arguments.size(); ++i) { 94 result = statement.bindValue(i + 1, m_arguments[i]); 95 if (result == SQLResultFull) { 96 setFailureDueToQuota(); 97 return false; 98 } 99 100 if (result != SQLResultOk) { 101 LOG(StorageAPI, "Failed to bind value index %i to statement for query '%s'", i + 1, m_statement.ascii().data()); 102 m_error = SQLError::create(SQLError::DATABASE_ERR, database->lastErrorMsg()); 103 return false; 104 } 105 } 106 107 RefPtr<SQLResultSet> resultSet = SQLResultSet::create(); 108 109 // Step so we can fetch the column names. 110 result = statement.step(); 111 if (result == SQLResultRow) { 112 int columnCount = statement.columnCount(); 113 SQLResultSetRowList* rows = resultSet->rows(); 114 115 for (int i = 0; i < columnCount; i++) 116 rows->addColumn(statement.getColumnName(i)); 117 118 do { 119 for (int i = 0; i < columnCount; i++) 120 rows->addResult(statement.getColumnValue(i)); 121 122 result = statement.step(); 123 } while (result == SQLResultRow); 124 125 if (result != SQLResultDone) { 126 m_error = SQLError::create(SQLError::DATABASE_ERR, database->lastErrorMsg()); 127 return false; 128 } 129 } else if (result == SQLResultDone) { 130 // Didn't find anything, or was an insert 131 if (db->lastActionWasInsert()) 132 resultSet->setInsertId(database->lastInsertRowID()); 133 } else if (result == SQLResultFull) { 134 // Return the Quota error - the delegate will be asked for more space and this statement might be re-run 135 setFailureDueToQuota(); 136 return false; 137 } else { 138 m_error = SQLError::create(SQLError::DATABASE_ERR, database->lastErrorMsg()); 139 return false; 140 } 141 142 // FIXME: If the spec allows triggers, and we want to be "accurate" in a different way, we'd use 143 // sqlite3_total_changes() here instead of sqlite3_changed, because that includes rows modified from within a trigger 144 // For now, this seems sufficient 145 resultSet->setRowsAffected(database->lastChanges()); 146 147 m_resultSet = resultSet; 148 return true; 149 } 150 151 void SQLStatement::setDatabaseDeletedError() 152 { 153 ASSERT(!m_error && !m_resultSet); 154 m_error = SQLError::create(SQLError::UNKNOWN_ERR, "unable to execute statement, because the user deleted the database"); 155 } 156 157 void SQLStatement::setVersionMismatchedError() 158 { 159 ASSERT(!m_error && !m_resultSet); 160 m_error = SQLError::create(SQLError::VERSION_ERR, "current version of the database and `oldVersion` argument do not match"); 161 } 162 163 bool SQLStatement::performCallback(SQLTransaction* transaction) 164 { 165 ASSERT(transaction); 166 167 bool callbackError = false; 168 169 RefPtr<SQLStatementCallback> callback = m_statementCallbackWrapper.unwrap(); 170 RefPtr<SQLStatementErrorCallback> errorCallback = m_statementErrorCallbackWrapper.unwrap(); 171 172 // Call the appropriate statement callback and track if it resulted in an error, 173 // because then we need to jump to the transaction error callback. 174 if (m_error) { 175 ASSERT(errorCallback); 176 callbackError = errorCallback->handleEvent(transaction, m_error.get()); 177 } else if (callback) 178 callbackError = !callback->handleEvent(transaction, m_resultSet.get()); 179 180 return callbackError; 181 } 182 183 void SQLStatement::setFailureDueToQuota() 184 { 185 ASSERT(!m_error && !m_resultSet); 186 m_error = SQLError::create(SQLError::QUOTA_ERR, "there was not enough remaining storage space, or the storage quota was reached and the user declined to allow more space"); 187 } 188 189 void SQLStatement::clearFailureDueToQuota() 190 { 191 if (lastExecutionFailedDueToQuota()) 192 m_error = 0; 193 } 194 195 bool SQLStatement::lastExecutionFailedDueToQuota() const 196 { 197 return m_error && m_error->code() == SQLError::QUOTA_ERR; 198 } 199 200 } // namespace WebCore 201 202 #endif // ENABLE(DATABASE) 203