blob: 4448256c6941301ed2c6e3ee98a8774314a979fe [file] [log] [blame]
/*
* Copyright (C) 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 INC. OR
* 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 "third_party/blink/renderer/modules/webdatabase/database.h"
#include <memory>
#include "base/thread_annotations.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/public/platform/web_database_observer.h"
#include "third_party/blink/public/platform/web_security_origin.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/modules/webdatabase/change_version_data.h"
#include "third_party/blink/renderer/modules/webdatabase/change_version_wrapper.h"
#include "third_party/blink/renderer/modules/webdatabase/database_authorizer.h"
#include "third_party/blink/renderer/modules/webdatabase/database_context.h"
#include "third_party/blink/renderer/modules/webdatabase/database_manager.h"
#include "third_party/blink/renderer/modules/webdatabase/database_task.h"
#include "third_party/blink/renderer/modules/webdatabase/database_thread.h"
#include "third_party/blink/renderer/modules/webdatabase/database_tracker.h"
#include "third_party/blink/renderer/modules/webdatabase/sql_error.h"
#include "third_party/blink/renderer/modules/webdatabase/sql_transaction_backend.h"
#include "third_party/blink/renderer/modules/webdatabase/sql_transaction_client.h"
#include "third_party/blink/renderer/modules/webdatabase/sql_transaction_coordinator.h"
#include "third_party/blink/renderer/modules/webdatabase/sqlite/sqlite_statement.h"
#include "third_party/blink/renderer/modules/webdatabase/sqlite/sqlite_transaction.h"
#include "third_party/blink/renderer/modules/webdatabase/storage_log.h"
#include "third_party/blink/renderer/platform/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
#include "third_party/blink/renderer/platform/waitable_event.h"
// Registering "opened" databases with the DatabaseTracker
// =======================================================
// The DatabaseTracker maintains a list of databases that have been
// "opened" so that the client can call interrupt or delete on every database
// associated with a DatabaseContext.
//
// We will only call DatabaseTracker::addOpenDatabase() to add the database
// to the tracker as opened when we've succeeded in opening the database,
// and will set m_opened to true. Similarly, we only call
// DatabaseTracker::removeOpenDatabase() to remove the database from the
// tracker when we set m_opened to false in closeDatabase(). This sets up
// a simple symmetry between open and close operations, and a direct
// correlation to adding and removing databases from the tracker's list,
// thus ensuring that we have a correct list for the interrupt and
// delete operations to work on.
//
// The only databases instances not tracked by the tracker's open database
// list are the ones that have not been added yet, or the ones that we
// attempted an open on but failed to. Such instances only exist in the
// DatabaseServer's factory methods for creating database backends.
//
// The factory methods will either call openAndVerifyVersion() or
// performOpenAndVerify(). These methods will add the newly instantiated
// database backend if they succeed in opening the requested database.
// In the case of failure to open the database, the factory methods will
// simply discard the newly instantiated database backend when they return.
// The ref counting mechanims will automatically destruct the un-added
// (and un-returned) databases instances.
namespace blink {
namespace {
// Stores a cached version of each database, keyed by a unique integer obtained
// by providing an origin-name pair.
class DatabaseVersionCache {
public:
Mutex& GetMutex() const LOCK_RETURNED(mutex_) { return mutex_; }
// Registers a globally-unique integer using the string key (reusing it if it
// already exists), and returns the integer. Currently, these IDs live for the
// lifetime of the process.
DatabaseGuid RegisterOriginAndName(const String& origin, const String& name)
EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
mutex_.AssertAcquired();
String string_id = origin + "/" + name;
DCHECK(string_id.IsSafeToSendToAnotherThread());
DatabaseGuid guid = origin_name_to_guid_.at(string_id);
if (!guid) {
guid = next_guid_++;
origin_name_to_guid_.Set(string_id, guid);
}
count_.insert(guid);
return guid;
}
// Releases one use of this identifier (corresponding to a call to
// RegisterOriginAndName). If all uses are released, the cached version will
// be erased from memory.
void ReleaseGuid(DatabaseGuid guid) EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
mutex_.AssertAcquired();
DCHECK(count_.Contains(guid));
if (count_.erase(guid))
guid_to_version_.erase(guid);
}
// The null string is returned only if the cached version has not been set.
String GetVersion(DatabaseGuid guid) const EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
mutex_.AssertAcquired();
return guid_to_version_.at(guid).IsolatedCopy();
}
// Updates the cached version of a database.
// The null string is treated as the empty string.
void SetVersion(DatabaseGuid guid, const String& new_version)
EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
mutex_.AssertAcquired();
guid_to_version_.Set(guid, new_version.IsNull()
? g_empty_string
: new_version.IsolatedCopy());
}
private:
mutable Mutex mutex_;
HashMap<String, DatabaseGuid> origin_name_to_guid_ GUARDED_BY(mutex_);
HashCountedSet<DatabaseGuid> count_ GUARDED_BY(mutex_);
HashMap<DatabaseGuid, String> guid_to_version_ GUARDED_BY(mutex_);
DatabaseGuid next_guid_ GUARDED_BY(mutex_) = 1;
};
DatabaseVersionCache& GetDatabaseVersionCache() {
DEFINE_THREAD_SAFE_STATIC_LOCAL(DatabaseVersionCache, cache, ());
return cache;
}
} // namespace
static const char kVersionKey[] = "WebKitDatabaseVersionKey";
static const char kInfoTableName[] = "__WebKitDatabaseInfoTable__";
static String FormatErrorMessage(const char* message,
int sqlite_error_code,
const char* sqlite_error_message) {
return String::Format("%s (%d %s)", message, sqlite_error_code,
sqlite_error_message);
}
static bool RetrieveTextResultFromDatabase(SQLiteDatabase& db,
const String& query,
String& result_string) {
SQLiteStatement statement(db, query);
int result = statement.Prepare();
if (result != kSQLResultOk) {
DLOG(ERROR) << "Error (" << result
<< ") preparing statement to read text result from database ("
<< query << ")";
return false;
}
result = statement.Step();
if (result == kSQLResultRow) {
result_string = statement.GetColumnText(0);
return true;
}
if (result == kSQLResultDone) {
result_string = String();
return true;
}
DLOG(ERROR) << "Error (" << result << ") reading text result from database ("
<< query << ")";
return false;
}
static bool SetTextValueInDatabase(SQLiteDatabase& db,
const String& query,
const String& value) {
SQLiteStatement statement(db, query);
int result = statement.Prepare();
if (result != kSQLResultOk) {
DLOG(ERROR) << "Failed to prepare statement to set value in database ("
<< query << ")";
return false;
}
statement.BindText(1, value);
result = statement.Step();
if (result != kSQLResultDone) {
DLOG(ERROR) << "Failed to step statement to set value in database ("
<< query << ")";
return false;
}
return true;
}
Database::Database(DatabaseContext* database_context,
const String& name,
const String& expected_version,
const String& display_name,
unsigned estimated_size)
: database_context_(database_context),
name_(name.IsolatedCopy()),
expected_version_(expected_version.IsolatedCopy()),
display_name_(display_name.IsolatedCopy()),
estimated_size_(estimated_size),
guid_(0),
opened_(false),
new_(false),
transaction_in_progress_(false),
is_transaction_queue_enabled_(true) {
DCHECK(IsMainThread());
context_thread_security_origin_ =
database_context_->GetSecurityOrigin()->IsolatedCopy();
database_authorizer_ =
DatabaseAuthorizer::Create(database_context, kInfoTableName);
if (name_.IsNull())
name_ = "";
{
auto& cache = GetDatabaseVersionCache();
MutexLocker locker(cache.GetMutex());
guid_ = cache.RegisterOriginAndName(GetSecurityOrigin()->ToString(), name);
}
filename_ = DatabaseManager::Manager().FullPathForDatabase(
GetSecurityOrigin(), name_);
database_thread_security_origin_ =
context_thread_security_origin_->IsolatedCopy();
DCHECK(database_context_->GetDatabaseThread());
DCHECK(database_context_->IsContextThread());
database_task_runner_ =
GetExecutionContext()->GetTaskRunner(TaskType::kDatabaseAccess);
}
Database::~Database() {
// SQLite is "multi-thread safe", but each database handle can only be used
// on a single thread at a time.
//
// For Database, we open the SQLite database on the DatabaseThread, and
// hence we should also close it on that same thread. This means that the
// SQLite database need to be closed by another mechanism (see
// DatabaseContext::stopDatabases()). By the time we get here, the SQLite
// database should have already been closed.
DCHECK(!Opened());
}
void Database::Trace(blink::Visitor* visitor) {
visitor->Trace(database_context_);
visitor->Trace(sqlite_database_);
visitor->Trace(database_authorizer_);
ScriptWrappable::Trace(visitor);
}
bool Database::OpenAndVerifyVersion(bool set_version_in_new_database,
DatabaseError& error,
String& error_message,
V8DatabaseCallback* creation_callback) {
WaitableEvent event;
if (!GetDatabaseContext()->DatabaseThreadAvailable())
return false;
DatabaseTracker::Tracker().PrepareToOpenDatabase(this);
bool success = false;
std::unique_ptr<DatabaseOpenTask> task = DatabaseOpenTask::Create(
this, set_version_in_new_database, &event, error, error_message, success);
GetDatabaseContext()->GetDatabaseThread()->ScheduleTask(std::move(task));
event.Wait();
if (creation_callback) {
if (success && IsNew()) {
STORAGE_DVLOG(1)
<< "Scheduling DatabaseCreationCallbackTask for database " << this;
auto* v8persistent_callback =
ToV8PersistentCallbackFunction(creation_callback);
probe::AsyncTaskScheduled(GetExecutionContext(), "openDatabase",
v8persistent_callback);
GetExecutionContext()
->GetTaskRunner(TaskType::kDatabaseAccess)
->PostTask(
FROM_HERE,
WTF::Bind(&Database::RunCreationCallback, WrapPersistent(this),
WrapPersistent(v8persistent_callback)));
}
}
return success;
}
void Database::RunCreationCallback(
V8PersistentCallbackFunction<V8DatabaseCallback>* creation_callback) {
probe::AsyncTask async_task(GetExecutionContext(), creation_callback);
creation_callback->InvokeAndReportException(nullptr, this);
}
void Database::Close() {
DCHECK(GetDatabaseContext()->GetDatabaseThread());
DCHECK(GetDatabaseContext()->GetDatabaseThread()->IsDatabaseThread());
{
MutexLocker locker(transaction_in_progress_mutex_);
// Clean up transactions that have not been scheduled yet:
// Transaction phase 1 cleanup. See comment on "What happens if a
// transaction is interrupted?" at the top of SQLTransactionBackend.cpp.
SQLTransactionBackend* transaction = nullptr;
while (!transaction_queue_.IsEmpty()) {
transaction = transaction_queue_.TakeFirst();
transaction->NotifyDatabaseThreadIsShuttingDown();
}
is_transaction_queue_enabled_ = false;
transaction_in_progress_ = false;
}
CloseDatabase();
GetDatabaseContext()->GetDatabaseThread()->RecordDatabaseClosed(this);
}
SQLTransactionBackend* Database::RunTransaction(SQLTransaction* transaction,
bool read_only,
const ChangeVersionData* data) {
MutexLocker locker(transaction_in_progress_mutex_);
if (!is_transaction_queue_enabled_)
return nullptr;
SQLTransactionWrapper* wrapper = nullptr;
if (data)
wrapper =
ChangeVersionWrapper::Create(data->OldVersion(), data->NewVersion());
SQLTransactionBackend* transaction_backend =
SQLTransactionBackend::Create(this, transaction, wrapper, read_only);
transaction_queue_.push_back(transaction_backend);
if (!transaction_in_progress_)
ScheduleTransaction();
return transaction_backend;
}
void Database::InProgressTransactionCompleted() {
MutexLocker locker(transaction_in_progress_mutex_);
transaction_in_progress_ = false;
ScheduleTransaction();
}
void Database::ScheduleTransaction() {
transaction_in_progress_mutex_.AssertAcquired();
SQLTransactionBackend* transaction = nullptr;
if (is_transaction_queue_enabled_ && !transaction_queue_.IsEmpty())
transaction = transaction_queue_.TakeFirst();
if (transaction && GetDatabaseContext()->DatabaseThreadAvailable()) {
std::unique_ptr<DatabaseTransactionTask> task =
DatabaseTransactionTask::Create(transaction);
STORAGE_DVLOG(1) << "Scheduling DatabaseTransactionTask " << task.get()
<< " for transaction " << task->Transaction();
transaction_in_progress_ = true;
GetDatabaseContext()->GetDatabaseThread()->ScheduleTask(std::move(task));
} else {
transaction_in_progress_ = false;
}
}
void Database::ScheduleTransactionStep(SQLTransactionBackend* transaction) {
if (!GetDatabaseContext()->DatabaseThreadAvailable())
return;
std::unique_ptr<DatabaseTransactionTask> task =
DatabaseTransactionTask::Create(transaction);
STORAGE_DVLOG(1) << "Scheduling DatabaseTransactionTask " << task.get()
<< " for the transaction step";
GetDatabaseContext()->GetDatabaseThread()->ScheduleTask(std::move(task));
}
SQLTransactionClient* Database::TransactionClient() const {
return GetDatabaseContext()->GetDatabaseThread()->TransactionClient();
}
SQLTransactionCoordinator* Database::TransactionCoordinator() const {
return GetDatabaseContext()->GetDatabaseThread()->TransactionCoordinator();
}
// static
const char* Database::DatabaseInfoTableName() {
return kInfoTableName;
}
void Database::CloseDatabase() {
if (!opened_.load(std::memory_order_relaxed))
return;
opened_.store(false, std::memory_order_release);
sqlite_database_.Close();
// See comment at the top this file regarding calling removeOpenDatabase().
DatabaseTracker::Tracker().RemoveOpenDatabase(this);
{
auto& cache = GetDatabaseVersionCache();
MutexLocker locker(cache.GetMutex());
cache.ReleaseGuid(guid_);
}
}
String Database::version() const {
// Note: In multi-process browsers the cached value may be accurate, but we
// cannot read the actual version from the database without potentially
// inducing a deadlock.
// FIXME: Add an async version getter to the DatabaseAPI.
return GetCachedVersion();
}
class DoneCreatingDatabaseOnExitCaller {
STACK_ALLOCATED();
public:
DoneCreatingDatabaseOnExitCaller(Database* database)
: database_(database), open_succeeded_(false) {}
~DoneCreatingDatabaseOnExitCaller() {
if (!open_succeeded_)
DatabaseTracker::Tracker().FailedToOpenDatabase(database_);
}
void SetOpenSucceeded() { open_succeeded_ = true; }
private:
CrossThreadPersistent<Database> database_;
bool open_succeeded_;
};
bool Database::PerformOpenAndVerify(bool should_set_version_in_new_database,
DatabaseError& error,
String& error_message) {
DoneCreatingDatabaseOnExitCaller on_exit_caller(this);
DCHECK(error_message.IsEmpty());
DCHECK_EQ(error,
DatabaseError::kNone); // Better not have any errors already.
// Presumed failure. We'll clear it if we succeed below.
error = DatabaseError::kInvalidDatabaseState;
const int kMaxSqliteBusyWaitTime = 30000;
if (!sqlite_database_.Open(filename_)) {
ReportSqliteError(sqlite_database_.LastError());
error_message = FormatErrorMessage("unable to open database",
sqlite_database_.LastError(),
sqlite_database_.LastErrorMsg());
return false;
}
if (!sqlite_database_.TurnOnIncrementalAutoVacuum())
DLOG(ERROR) << "Unable to turn on incremental auto-vacuum ("
<< sqlite_database_.LastError() << " "
<< sqlite_database_.LastErrorMsg() << ")";
sqlite_database_.SetBusyTimeout(kMaxSqliteBusyWaitTime);
String current_version;
{
auto& cache = GetDatabaseVersionCache();
MutexLocker locker(cache.GetMutex());
current_version = cache.GetVersion(guid_);
if (!current_version.IsNull()) {
STORAGE_DVLOG(1) << "Current cached version for guid " << guid_ << " is "
<< current_version;
// Note: In multi-process browsers the cached value may be
// inaccurate, but we cannot read the actual version from the
// database without potentially inducing a form of deadlock, a
// busytimeout error when trying to access the database. So we'll
// use the cached value if we're unable to read the value from the
// database file without waiting.
// FIXME: Add an async openDatabase method to the DatabaseAPI.
const int kNoSqliteBusyWaitTime = 0;
sqlite_database_.SetBusyTimeout(kNoSqliteBusyWaitTime);
String version_from_database;
if (GetVersionFromDatabase(version_from_database, false)) {
current_version = version_from_database;
cache.SetVersion(guid_, current_version);
}
sqlite_database_.SetBusyTimeout(kMaxSqliteBusyWaitTime);
} else {
STORAGE_DVLOG(1) << "No cached version for guid " << guid_;
SQLiteTransaction transaction(sqlite_database_);
transaction.begin();
if (!transaction.InProgress()) {
ReportSqliteError(sqlite_database_.LastError());
error_message = FormatErrorMessage(
"unable to open database, failed to start transaction",
sqlite_database_.LastError(), sqlite_database_.LastErrorMsg());
sqlite_database_.Close();
return false;
}
String table_name(kInfoTableName);
if (!sqlite_database_.TableExists(table_name)) {
new_ = true;
if (!sqlite_database_.ExecuteCommand(
"CREATE TABLE " + table_name +
" (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT "
"REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) {
ReportSqliteError(sqlite_database_.LastError());
error_message = FormatErrorMessage(
"unable to open database, failed to create 'info' table",
sqlite_database_.LastError(), sqlite_database_.LastErrorMsg());
transaction.Rollback();
sqlite_database_.Close();
return false;
}
} else if (!GetVersionFromDatabase(current_version, false)) {
ReportSqliteError(sqlite_database_.LastError());
error_message = FormatErrorMessage(
"unable to open database, failed to read current version",
sqlite_database_.LastError(), sqlite_database_.LastErrorMsg());
transaction.Rollback();
sqlite_database_.Close();
return false;
}
if (current_version.length()) {
STORAGE_DVLOG(1) << "Retrieved current version " << current_version
<< " from database " << DatabaseDebugName();
} else if (!new_ || should_set_version_in_new_database) {
STORAGE_DVLOG(1) << "Setting version " << expected_version_
<< " in database " << DatabaseDebugName()
<< " that was just created";
if (!SetVersionInDatabase(expected_version_, false)) {
ReportSqliteError(sqlite_database_.LastError());
error_message = FormatErrorMessage(
"unable to open database, failed to write current version",
sqlite_database_.LastError(), sqlite_database_.LastErrorMsg());
transaction.Rollback();
sqlite_database_.Close();
return false;
}
current_version = expected_version_;
}
cache.SetVersion(guid_, current_version);
transaction.Commit();
}
}
if (current_version.IsNull()) {
STORAGE_DVLOG(1) << "Database " << DatabaseDebugName()
<< " does not have its version set";
current_version = "";
}
// If the expected version isn't the empty string, ensure that the current
// database version we have matches that version. Otherwise, set an
// exception.
// If the expected version is the empty string, then we always return with
// whatever version of the database we have.
if ((!new_ || should_set_version_in_new_database) &&
expected_version_.length() && expected_version_ != current_version) {
error_message =
"unable to open database, version mismatch, '" + expected_version_ +
"' does not match the currentVersion of '" + current_version + "'";
sqlite_database_.Close();
return false;
}
DCHECK(database_authorizer_);
sqlite_database_.SetAuthorizer(database_authorizer_.Get());
// See comment at the top this file regarding calling addOpenDatabase().
DatabaseTracker::Tracker().AddOpenDatabase(this);
opened_.store(true, std::memory_order_release);
// Declare success:
error = DatabaseError::kNone; // Clear the presumed error from above.
on_exit_caller.SetOpenSucceeded();
if (new_ && !should_set_version_in_new_database) {
// The caller provided a creationCallback which will set the expected
// version.
expected_version_ = "";
}
if (GetDatabaseContext()->GetDatabaseThread())
GetDatabaseContext()->GetDatabaseThread()->RecordDatabaseOpen(this);
return true;
}
String Database::StringIdentifier() const {
// Return a deep copy for ref counting thread safety
return name_.IsolatedCopy();
}
String Database::DisplayName() const {
// Return a deep copy for ref counting thread safety
return display_name_.IsolatedCopy();
}
unsigned Database::EstimatedSize() const {
return estimated_size_;
}
String Database::FileName() const {
// Return a deep copy for ref counting thread safety
return filename_.IsolatedCopy();
}
bool Database::GetVersionFromDatabase(String& version,
bool should_cache_version) {
String query(String("SELECT value FROM ") + kInfoTableName +
" WHERE key = '" + kVersionKey + "';");
database_authorizer_->Disable();
bool result =
RetrieveTextResultFromDatabase(sqlite_database_, query, version);
if (result) {
if (should_cache_version)
SetCachedVersion(version);
} else {
DLOG(ERROR) << "Failed to retrieve version from database "
<< DatabaseDebugName();
}
database_authorizer_->Enable();
return result;
}
bool Database::SetVersionInDatabase(const String& version,
bool should_cache_version) {
// The INSERT will replace an existing entry for the database with the new
// version number, due to the UNIQUE ON CONFLICT REPLACE clause in the
// CREATE statement (see Database::performOpenAndVerify()).
String query(String("INSERT INTO ") + kInfoTableName +
" (key, value) VALUES ('" + kVersionKey + "', ?);");
database_authorizer_->Disable();
bool result = SetTextValueInDatabase(sqlite_database_, query, version);
if (result) {
if (should_cache_version)
SetCachedVersion(version);
} else {
DLOG(ERROR) << "Failed to set version " << version << " in database ("
<< query << ")";
}
database_authorizer_->Enable();
return result;
}
void Database::SetExpectedVersion(const String& version) {
expected_version_ = version.IsolatedCopy();
}
String Database::GetCachedVersion() const {
auto& cache = GetDatabaseVersionCache();
MutexLocker locker(cache.GetMutex());
return cache.GetVersion(guid_);
}
void Database::SetCachedVersion(const String& actual_version) {
auto& cache = GetDatabaseVersionCache();
MutexLocker locker(cache.GetMutex());
cache.SetVersion(guid_, actual_version);
}
bool Database::GetActualVersionForTransaction(String& actual_version) {
DCHECK(sqlite_database_.TransactionInProgress());
// Note: In multi-process browsers the cached value may be inaccurate. So we
// retrieve the value from the database and update the cached value here.
return GetVersionFromDatabase(actual_version, true);
}
void Database::DisableAuthorizer() {
DCHECK(database_authorizer_);
database_authorizer_->Disable();
}
void Database::EnableAuthorizer() {
DCHECK(database_authorizer_);
database_authorizer_->Enable();
}
void Database::SetAuthorizerPermissions(int permissions) {
DCHECK(database_authorizer_);
database_authorizer_->SetPermissions(permissions);
}
bool Database::LastActionChangedDatabase() {
DCHECK(database_authorizer_);
return database_authorizer_->LastActionChangedDatabase();
}
bool Database::LastActionWasInsert() {
DCHECK(database_authorizer_);
return database_authorizer_->LastActionWasInsert();
}
void Database::ResetDeletes() {
DCHECK(database_authorizer_);
database_authorizer_->ResetDeletes();
}
bool Database::HadDeletes() {
DCHECK(database_authorizer_);
return database_authorizer_->HadDeletes();
}
void Database::ResetAuthorizer() {
if (database_authorizer_)
database_authorizer_->Reset();
}
uint64_t Database::MaximumSize() const {
return DatabaseTracker::Tracker().GetMaxSizeForDatabase(this);
}
void Database::IncrementalVacuumIfNeeded() {
int64_t free_space_size = sqlite_database_.FreeSpaceSize();
int64_t total_size = sqlite_database_.TotalSize();
if (total_size <= 10 * free_space_size) {
int result = sqlite_database_.RunIncrementalVacuumCommand();
if (result != kSQLResultOk) {
ReportSqliteError(result);
LogErrorMessage(FormatErrorMessage("error vacuuming database", result,
sqlite_database_.LastErrorMsg()));
}
}
}
void Database::ReportSqliteError(int sqlite_error_code) {
if (Platform::Current()->DatabaseObserver()) {
Platform::Current()->DatabaseObserver()->ReportSqliteError(
WebSecurityOrigin(GetSecurityOrigin()), StringIdentifier(),
sqlite_error_code);
}
}
void Database::LogErrorMessage(const String& message) {
GetExecutionContext()->AddConsoleMessage(ConsoleMessage::Create(
kStorageMessageSource, kErrorMessageLevel, message));
}
ExecutionContext* Database::GetExecutionContext() const {
return GetDatabaseContext()->GetExecutionContext();
}
void Database::CloseImmediately() {
DCHECK(GetExecutionContext()->IsContextThread());
if (GetDatabaseContext()->DatabaseThreadAvailable() && Opened()) {
LogErrorMessage("forcibly closing database");
GetDatabaseContext()->GetDatabaseThread()->ScheduleTask(
DatabaseCloseTask::Create(this, nullptr));
}
}
void Database::changeVersion(const String& old_version,
const String& new_version,
V8SQLTransactionCallback* callback,
V8SQLTransactionErrorCallback* error_callback,
V8VoidCallback* success_callback) {
ChangeVersionData data(old_version, new_version);
RunTransaction(SQLTransaction::OnProcessV8Impl::Create(callback),
SQLTransaction::OnErrorV8Impl::Create(error_callback),
SQLTransaction::OnSuccessV8Impl::Create(success_callback),
false, &data);
}
void Database::transaction(V8SQLTransactionCallback* callback,
V8SQLTransactionErrorCallback* error_callback,
V8VoidCallback* success_callback) {
RunTransaction(SQLTransaction::OnProcessV8Impl::Create(callback),
SQLTransaction::OnErrorV8Impl::Create(error_callback),
SQLTransaction::OnSuccessV8Impl::Create(success_callback),
false);
}
void Database::readTransaction(V8SQLTransactionCallback* callback,
V8SQLTransactionErrorCallback* error_callback,
V8VoidCallback* success_callback) {
RunTransaction(SQLTransaction::OnProcessV8Impl::Create(callback),
SQLTransaction::OnErrorV8Impl::Create(error_callback),
SQLTransaction::OnSuccessV8Impl::Create(success_callback),
true);
}
void Database::PerformTransaction(
SQLTransaction::OnProcessCallback* callback,
SQLTransaction::OnErrorCallback* error_callback,
SQLTransaction::OnSuccessCallback* success_callback) {
RunTransaction(callback, error_callback, success_callback, false);
}
static void CallTransactionErrorCallback(
SQLTransaction::OnErrorCallback* callback,
std::unique_ptr<SQLErrorData> error_data) {
callback->OnError(SQLError::Create(*error_data));
}
void Database::RunTransaction(
SQLTransaction::OnProcessCallback* callback,
SQLTransaction::OnErrorCallback* error_callback,
SQLTransaction::OnSuccessCallback* success_callback,
bool read_only,
const ChangeVersionData* change_version_data) {
if (!GetExecutionContext())
return;
DCHECK(GetExecutionContext()->IsContextThread());
// FIXME: Rather than passing errorCallback to SQLTransaction and then
// sometimes firing it ourselves, this code should probably be pushed down
// into Database so that we only create the SQLTransaction if we're
// actually going to run it.
#if DCHECK_IS_ON()
SQLTransaction::OnErrorCallback* original_error_callback = error_callback;
#endif
SQLTransaction* transaction = SQLTransaction::Create(
this, callback, success_callback, error_callback, read_only);
SQLTransactionBackend* transaction_backend =
RunTransaction(transaction, read_only, change_version_data);
if (!transaction_backend) {
SQLTransaction::OnErrorCallback* transaction_error_callback =
transaction->ReleaseErrorCallback();
#if DCHECK_IS_ON()
DCHECK_EQ(transaction_error_callback, original_error_callback);
#endif
if (transaction_error_callback) {
std::unique_ptr<SQLErrorData> error = SQLErrorData::Create(
SQLError::kUnknownErr, "database has been closed");
GetDatabaseTaskRunner()->PostTask(
FROM_HERE, WTF::Bind(&CallTransactionErrorCallback,
WrapPersistent(transaction_error_callback),
WTF::Passed(std::move(error))));
}
}
}
void Database::ScheduleTransactionCallback(SQLTransaction* transaction) {
// The task is constructed in a database thread, and destructed in the
// context thread.
PostCrossThreadTask(*GetDatabaseTaskRunner(), FROM_HERE,
CrossThreadBind(&SQLTransaction::PerformPendingCallback,
WrapCrossThreadPersistent(transaction)));
}
Vector<String> Database::PerformGetTableNames() {
DisableAuthorizer();
SQLiteStatement statement(
SqliteDatabase(), "SELECT name FROM sqlite_master WHERE type='table';");
if (statement.Prepare() != kSQLResultOk) {
DLOG(ERROR) << "Unable to retrieve list of tables for database "
<< DatabaseDebugName();
EnableAuthorizer();
return Vector<String>();
}
Vector<String> table_names;
int result;
while ((result = statement.Step()) == kSQLResultRow) {
String name = statement.GetColumnText(0);
if (name != DatabaseInfoTableName())
table_names.push_back(name);
}
EnableAuthorizer();
if (result != kSQLResultDone) {
DLOG(ERROR) << "Error getting tables for database " << DatabaseDebugName();
return Vector<String>();
}
return table_names;
}
Vector<String> Database::TableNames() {
// FIXME: Not using isolatedCopy on these strings looks ok since threads
// take strict turns in dealing with them. However, if the code changes,
// this may not be true anymore.
Vector<String> result;
WaitableEvent event;
if (!GetDatabaseContext()->DatabaseThreadAvailable())
return result;
std::unique_ptr<DatabaseTableNamesTask> task =
DatabaseTableNamesTask::Create(this, &event, result);
GetDatabaseContext()->GetDatabaseThread()->ScheduleTask(std::move(task));
event.Wait();
return result;
}
const SecurityOrigin* Database::GetSecurityOrigin() const {
if (!GetExecutionContext())
return nullptr;
if (GetExecutionContext()->IsContextThread())
return context_thread_security_origin_.get();
if (GetDatabaseContext()->GetDatabaseThread()->IsDatabaseThread())
return database_thread_security_origin_.get();
return nullptr;
}
base::SingleThreadTaskRunner* Database::GetDatabaseTaskRunner() const {
return database_task_runner_.get();
}
} // namespace blink