| /* |
| * Copyright (C) 2007, 2013 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 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 |
| * its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "modules/webdatabase/SQLStatementBackend.h" |
| |
| #include "modules/webdatabase/Database.h" |
| #include "modules/webdatabase/SQLError.h" |
| #include "modules/webdatabase/SQLStatement.h" |
| #include "modules/webdatabase/StorageLog.h" |
| #include "modules/webdatabase/sqlite/SQLiteDatabase.h" |
| #include "modules/webdatabase/sqlite/SQLiteStatement.h" |
| #include "platform/wtf/text/CString.h" |
| |
| // The Life-Cycle of a SQLStatement i.e. Who's keeping the SQLStatement alive? |
| // ========================================================================== |
| // The RefPtr chain goes something like this: |
| // |
| // At birth (in SQLTransactionBackend::executeSQL()): |
| // ================================================= |
| // SQLTransactionBackend |
| // // HeapDeque<Member<SQLStatementBackend>> m_statementQueue |
| // // points to ... |
| // --> SQLStatementBackend |
| // // Member<SQLStatement> m_frontend points to ... |
| // --> SQLStatement |
| // |
| // After grabbing the statement for execution (in |
| // SQLTransactionBackend::getNextStatement()): |
| // ====================================================================== |
| // SQLTransactionBackend |
| // // Member<SQLStatementBackend> m_currentStatementBackend |
| // // points to ... |
| // --> SQLStatementBackend |
| // // Member<SQLStatement> m_frontend points to ... |
| // --> SQLStatement |
| // |
| // Then we execute the statement in |
| // SQLTransactionBackend::runCurrentStatementAndGetNextState(). |
| // And we callback to the script in |
| // SQLTransaction::deliverStatementCallback() if necessary. |
| // - Inside SQLTransaction::deliverStatementCallback(), we operate on a raw |
| // SQLStatement*. This pointer is valid because it is owned by |
| // SQLTransactionBackend's |
| // SQLTransactionBackend::m_currentStatementBackend. |
| // |
| // After we're done executing the statement (in |
| // SQLTransactionBackend::getNextStatement()): |
| // ====================================================================== |
| // When we're done executing, we'll grab the next statement. But before we |
| // do that, getNextStatement() nullify |
| // SQLTransactionBackend::m_currentStatementBackend. |
| // This will trigger the deletion of the SQLStatementBackend and |
| // SQLStatement. |
| // |
| // Note: unlike with SQLTransaction, there is no JS representation of |
| // SQLStatement. Hence, there is no GC dependency at play here. |
| |
| namespace blink { |
| |
| SQLStatementBackend* SQLStatementBackend::Create( |
| SQLStatement* frontend, |
| const String& statement, |
| const Vector<SQLValue>& arguments, |
| int permissions) { |
| return new SQLStatementBackend(frontend, statement, arguments, permissions); |
| } |
| |
| SQLStatementBackend::SQLStatementBackend(SQLStatement* frontend, |
| const String& statement, |
| const Vector<SQLValue>& arguments, |
| int permissions) |
| : frontend_(frontend), |
| statement_(statement.IsolatedCopy()), |
| arguments_(arguments), |
| has_callback_(frontend_->HasCallback()), |
| has_error_callback_(frontend_->HasErrorCallback()), |
| result_set_(SQLResultSet::Create()), |
| permissions_(permissions) { |
| DCHECK(IsMainThread()); |
| |
| frontend_->SetBackend(this); |
| } |
| |
| void SQLStatementBackend::Trace(blink::Visitor* visitor) { |
| visitor->Trace(frontend_); |
| visitor->Trace(result_set_); |
| } |
| |
| SQLStatement* SQLStatementBackend::GetFrontend() { |
| return frontend_.Get(); |
| } |
| |
| SQLErrorData* SQLStatementBackend::SqlError() const { |
| return error_.get(); |
| } |
| |
| SQLResultSet* SQLStatementBackend::SqlResultSet() const { |
| return result_set_->IsValid() ? result_set_.Get() : nullptr; |
| } |
| |
| bool SQLStatementBackend::Execute(Database* db) { |
| DCHECK(!result_set_->IsValid()); |
| |
| // If we're re-running this statement after a quota violation, we need to |
| // clear that error now |
| ClearFailureDueToQuota(); |
| |
| // This transaction might have been marked bad while it was being set up on |
| // the main thread, so if there is still an error, return false. |
| if (error_) |
| return false; |
| |
| db->SetAuthorizerPermissions(permissions_); |
| |
| SQLiteDatabase* database = &db->SqliteDatabase(); |
| |
| SQLiteStatement statement(*database, statement_); |
| int result = statement.Prepare(); |
| |
| if (result != kSQLResultOk) { |
| STORAGE_DVLOG(1) << "Unable to verify correctness of statement " |
| << statement_ << " - error " << result << " (" |
| << database->LastErrorMsg() << ")"; |
| if (result == kSQLResultInterrupt) |
| error_ = SQLErrorData::Create(SQLError::kDatabaseErr, |
| "could not prepare statement", result, |
| "interrupted"); |
| else |
| error_ = SQLErrorData::Create(SQLError::kSyntaxErr, |
| "could not prepare statement", result, |
| database->LastErrorMsg()); |
| db->ReportExecuteStatementResult(1, error_->Code(), result); |
| return false; |
| } |
| |
| // FIXME: If the statement uses the ?### syntax supported by sqlite, the bind |
| // parameter count is very likely off from the number of question marks. If |
| // this is the case, they might be trying to do something fishy or malicious |
| if (statement.BindParameterCount() != arguments_.size()) { |
| STORAGE_DVLOG(1) |
| << "Bind parameter count doesn't match number of question marks"; |
| error_ = SQLErrorData::Create( |
| SQLError::kSyntaxErr, |
| "number of '?'s in statement string does not match argument count"); |
| db->ReportExecuteStatementResult(2, error_->Code(), 0); |
| return false; |
| } |
| |
| for (unsigned i = 0; i < arguments_.size(); ++i) { |
| result = statement.BindValue(i + 1, arguments_[i]); |
| if (result == kSQLResultFull) { |
| SetFailureDueToQuota(db); |
| return false; |
| } |
| |
| if (result != kSQLResultOk) { |
| STORAGE_DVLOG(1) << "Failed to bind value index " << (i + 1) |
| << " to statement for query " << statement_; |
| db->ReportExecuteStatementResult(3, SQLError::kDatabaseErr, result); |
| error_ = |
| SQLErrorData::Create(SQLError::kDatabaseErr, "could not bind value", |
| result, database->LastErrorMsg()); |
| return false; |
| } |
| } |
| |
| // Step so we can fetch the column names. |
| result = statement.Step(); |
| if (result == kSQLResultRow) { |
| int column_count = statement.ColumnCount(); |
| SQLResultSetRowList* rows = result_set_->rows(); |
| |
| for (int i = 0; i < column_count; i++) |
| rows->AddColumn(statement.GetColumnName(i)); |
| |
| do { |
| for (int i = 0; i < column_count; i++) |
| rows->AddResult(statement.GetColumnValue(i)); |
| |
| result = statement.Step(); |
| } while (result == kSQLResultRow); |
| |
| if (result != kSQLResultDone) { |
| db->ReportExecuteStatementResult(4, SQLError::kDatabaseErr, result); |
| error_ = SQLErrorData::Create(SQLError::kDatabaseErr, |
| "could not iterate results", result, |
| database->LastErrorMsg()); |
| return false; |
| } |
| } else if (result == kSQLResultDone) { |
| // Didn't find anything, or was an insert |
| if (db->LastActionWasInsert()) |
| result_set_->SetInsertId(database->LastInsertRowID()); |
| } else if (result == kSQLResultFull) { |
| // Return the Quota error - the delegate will be asked for more space and |
| // this statement might be re-run. |
| SetFailureDueToQuota(db); |
| return false; |
| } else if (result == kSQLResultConstraint) { |
| db->ReportExecuteStatementResult(6, SQLError::kConstraintErr, result); |
| error_ = SQLErrorData::Create( |
| SQLError::kConstraintErr, |
| "could not execute statement due to a constraint failure", result, |
| database->LastErrorMsg()); |
| return false; |
| } else { |
| db->ReportExecuteStatementResult(5, SQLError::kDatabaseErr, result); |
| error_ = SQLErrorData::Create(SQLError::kDatabaseErr, |
| "could not execute statement", result, |
| database->LastErrorMsg()); |
| return false; |
| } |
| |
| // FIXME: If the spec allows triggers, and we want to be "accurate" in a |
| // different way, we'd use sqlite3_total_changes() here instead of |
| // sqlite3_changed, because that includes rows modified from within a trigger. |
| // For now, this seems sufficient. |
| result_set_->SetRowsAffected(database->LastChanges()); |
| |
| db->ReportExecuteStatementResult(0, -1, 0); // OK |
| return true; |
| } |
| |
| void SQLStatementBackend::SetVersionMismatchedError(Database* database) { |
| DCHECK(!error_); |
| DCHECK(!result_set_->IsValid()); |
| database->ReportExecuteStatementResult(7, SQLError::kVersionErr, 0); |
| error_ = SQLErrorData::Create( |
| SQLError::kVersionErr, |
| "current version of the database and `oldVersion` argument do not match"); |
| } |
| |
| void SQLStatementBackend::SetFailureDueToQuota(Database* database) { |
| DCHECK(!error_); |
| DCHECK(!result_set_->IsValid()); |
| database->ReportExecuteStatementResult(8, SQLError::kQuotaErr, 0); |
| error_ = SQLErrorData::Create(SQLError::kQuotaErr, |
| "there was not enough remaining storage " |
| "space, or the storage quota was reached and " |
| "the user declined to allow more space"); |
| } |
| |
| void SQLStatementBackend::ClearFailureDueToQuota() { |
| if (LastExecutionFailedDueToQuota()) |
| error_ = nullptr; |
| } |
| |
| bool SQLStatementBackend::LastExecutionFailedDueToQuota() const { |
| return error_ && error_->Code() == SQLError::kQuotaErr; |
| } |
| |
| } // namespace blink |