diff options
author | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
---|---|---|
committer | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
commit | 679147eead574d186ebf3069647b4c23e8ccace6 (patch) | |
tree | fc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/sql | |
download | qtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz |
Initial import.
Diffstat (limited to 'chromium/sql')
-rw-r--r-- | chromium/sql/DEPS | 3 | ||||
-rw-r--r-- | chromium/sql/OWNERS | 3 | ||||
-rw-r--r-- | chromium/sql/connection.cc | 1060 | ||||
-rw-r--r-- | chromium/sql/connection.h | 576 | ||||
-rw-r--r-- | chromium/sql/connection_unittest.cc | 845 | ||||
-rw-r--r-- | chromium/sql/error_delegate_util.cc | 80 | ||||
-rw-r--r-- | chromium/sql/error_delegate_util.h | 18 | ||||
-rw-r--r-- | chromium/sql/init_status.h | 23 | ||||
-rw-r--r-- | chromium/sql/meta_table.cc | 155 | ||||
-rw-r--r-- | chromium/sql/meta_table.h | 87 | ||||
-rw-r--r-- | chromium/sql/recovery.cc | 207 | ||||
-rw-r--r-- | chromium/sql/recovery.h | 106 | ||||
-rw-r--r-- | chromium/sql/recovery_unittest.cc | 425 | ||||
-rw-r--r-- | chromium/sql/run_all_unittests.cc | 9 | ||||
-rw-r--r-- | chromium/sql/sql.gyp | 136 | ||||
-rw-r--r-- | chromium/sql/sql_export.h | 29 | ||||
-rw-r--r-- | chromium/sql/sqlite_features_unittest.cc | 86 | ||||
-rw-r--r-- | chromium/sql/statement.cc | 316 | ||||
-rw-r--r-- | chromium/sql/statement.h | 189 | ||||
-rw-r--r-- | chromium/sql/statement_unittest.cc | 130 | ||||
-rw-r--r-- | chromium/sql/transaction.cc | 51 | ||||
-rw-r--r-- | chromium/sql/transaction.h | 60 | ||||
-rw-r--r-- | chromium/sql/transaction_unittest.cc | 133 |
23 files changed, 4727 insertions, 0 deletions
diff --git a/chromium/sql/DEPS b/chromium/sql/DEPS new file mode 100644 index 00000000000..0f76c3a31e7 --- /dev/null +++ b/chromium/sql/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+third_party/sqlite", +] diff --git a/chromium/sql/OWNERS b/chromium/sql/OWNERS new file mode 100644 index 00000000000..1ea5750da92 --- /dev/null +++ b/chromium/sql/OWNERS @@ -0,0 +1,3 @@ +erikwright@chromium.org +shess@chromium.org +gbillock@chromium.org diff --git a/chromium/sql/connection.cc b/chromium/sql/connection.cc new file mode 100644 index 00000000000..3bc25458546 --- /dev/null +++ b/chromium/sql/connection.cc @@ -0,0 +1,1060 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sql/connection.h" + +#include <string.h> + +#include "base/files/file_path.h" +#include "base/file_util.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/metrics/sparse_histogram.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/synchronization/lock.h" +#include "sql/statement.h" +#include "third_party/sqlite/sqlite3.h" + +#if defined(OS_IOS) && defined(USE_SYSTEM_SQLITE) +#include "third_party/sqlite/src/ext/icu/sqliteicu.h" +#endif + +namespace { + +// Spin for up to a second waiting for the lock to clear when setting +// up the database. +// TODO(shess): Better story on this. http://crbug.com/56559 +const int kBusyTimeoutSeconds = 1; + +class ScopedBusyTimeout { + public: + explicit ScopedBusyTimeout(sqlite3* db) + : db_(db) { + } + ~ScopedBusyTimeout() { + sqlite3_busy_timeout(db_, 0); + } + + int SetTimeout(base::TimeDelta timeout) { + DCHECK_LT(timeout.InMilliseconds(), INT_MAX); + return sqlite3_busy_timeout(db_, + static_cast<int>(timeout.InMilliseconds())); + } + + private: + sqlite3* db_; +}; + +// Helper to "safely" enable writable_schema. No error checking +// because it is reasonable to just forge ahead in case of an error. +// If turning it on fails, then most likely nothing will work, whereas +// if turning it off fails, it only matters if some code attempts to +// continue working with the database and tries to modify the +// sqlite_master table (none of our code does this). +class ScopedWritableSchema { + public: + explicit ScopedWritableSchema(sqlite3* db) + : db_(db) { + sqlite3_exec(db_, "PRAGMA writable_schema=1", NULL, NULL, NULL); + } + ~ScopedWritableSchema() { + sqlite3_exec(db_, "PRAGMA writable_schema=0", NULL, NULL, NULL); + } + + private: + sqlite3* db_; +}; + +// Helper to wrap the sqlite3_backup_*() step of Raze(). Return +// SQLite error code from running the backup step. +int BackupDatabase(sqlite3* src, sqlite3* dst, const char* db_name) { + DCHECK_NE(src, dst); + sqlite3_backup* backup = sqlite3_backup_init(dst, db_name, src, db_name); + if (!backup) { + // Since this call only sets things up, this indicates a gross + // error in SQLite. + DLOG(FATAL) << "Unable to start sqlite3_backup(): " << sqlite3_errmsg(dst); + return sqlite3_errcode(dst); + } + + // -1 backs up the entire database. + int rc = sqlite3_backup_step(backup, -1); + int pages = sqlite3_backup_pagecount(backup); + sqlite3_backup_finish(backup); + + // If successful, exactly one page should have been backed up. If + // this breaks, check this function to make sure assumptions aren't + // being broken. + if (rc == SQLITE_DONE) + DCHECK_EQ(pages, 1); + + return rc; +} + +// Be very strict on attachment point. SQLite can handle a much wider +// character set with appropriate quoting, but Chromium code should +// just use clean names to start with. +bool ValidAttachmentPoint(const char* attachment_point) { + for (size_t i = 0; attachment_point[i]; ++i) { + if (!((attachment_point[i] >= '0' && attachment_point[i] <= '9') || + (attachment_point[i] >= 'a' && attachment_point[i] <= 'z') || + (attachment_point[i] >= 'A' && attachment_point[i] <= 'Z') || + attachment_point[i] == '_')) { + return false; + } + } + return true; +} + +// SQLite automatically calls sqlite3_initialize() lazily, but +// sqlite3_initialize() uses double-checked locking and thus can have +// data races. +// +// TODO(shess): Another alternative would be to have +// sqlite3_initialize() called as part of process bring-up. If this +// is changed, remove the dynamic_annotations dependency in sql.gyp. +base::LazyInstance<base::Lock>::Leaky + g_sqlite_init_lock = LAZY_INSTANCE_INITIALIZER; +void InitializeSqlite() { + base::AutoLock lock(g_sqlite_init_lock.Get()); + sqlite3_initialize(); +} + +} // namespace + +namespace sql { + +// static +Connection::ErrorIgnorerCallback* Connection::current_ignorer_cb_ = NULL; + +// static +bool Connection::ShouldIgnore(int error) { + if (!current_ignorer_cb_) + return false; + return current_ignorer_cb_->Run(error); +} + +// static +void Connection::SetErrorIgnorer(Connection::ErrorIgnorerCallback* cb) { + CHECK(current_ignorer_cb_ == NULL); + current_ignorer_cb_ = cb; +} + +// static +void Connection::ResetErrorIgnorer() { + CHECK(current_ignorer_cb_); + current_ignorer_cb_ = NULL; +} + +bool StatementID::operator<(const StatementID& other) const { + if (number_ != other.number_) + return number_ < other.number_; + return strcmp(str_, other.str_) < 0; +} + +Connection::StatementRef::StatementRef(Connection* connection, + sqlite3_stmt* stmt, + bool was_valid) + : connection_(connection), + stmt_(stmt), + was_valid_(was_valid) { + if (connection) + connection_->StatementRefCreated(this); +} + +Connection::StatementRef::~StatementRef() { + if (connection_) + connection_->StatementRefDeleted(this); + Close(false); +} + +void Connection::StatementRef::Close(bool forced) { + if (stmt_) { + // Call to AssertIOAllowed() cannot go at the beginning of the function + // because Close() is called unconditionally from destructor to clean + // connection_. And if this is inactive statement this won't cause any + // disk access and destructor most probably will be called on thread + // not allowing disk access. + // TODO(paivanof@gmail.com): This should move to the beginning + // of the function. http://crbug.com/136655. + AssertIOAllowed(); + sqlite3_finalize(stmt_); + stmt_ = NULL; + } + connection_ = NULL; // The connection may be getting deleted. + + // Forced close is expected to happen from a statement error + // handler. In that case maintain the sense of |was_valid_| which + // previously held for this ref. + was_valid_ = was_valid_ && forced; +} + +Connection::Connection() + : db_(NULL), + page_size_(0), + cache_size_(0), + exclusive_locking_(false), + restrict_to_user_(false), + transaction_nesting_(0), + needs_rollback_(false), + in_memory_(false), + poisoned_(false) { +} + +Connection::~Connection() { + Close(); +} + +bool Connection::Open(const base::FilePath& path) { + if (!histogram_tag_.empty()) { + int64 size_64 = 0; + if (file_util::GetFileSize(path, &size_64)) { + size_t sample = static_cast<size_t>(size_64 / 1024); + std::string full_histogram_name = "Sqlite.SizeKB." + histogram_tag_; + base::HistogramBase* histogram = + base::Histogram::FactoryGet( + full_histogram_name, 1, 1000000, 50, + base::HistogramBase::kUmaTargetedHistogramFlag); + if (histogram) + histogram->Add(sample); + } + } + +#if defined(OS_WIN) + return OpenInternal(WideToUTF8(path.value()), RETRY_ON_POISON); +#elif defined(OS_POSIX) + return OpenInternal(path.value(), RETRY_ON_POISON); +#endif +} + +bool Connection::OpenInMemory() { + in_memory_ = true; + return OpenInternal(":memory:", NO_RETRY); +} + +bool Connection::OpenTemporary() { + return OpenInternal("", NO_RETRY); +} + +void Connection::CloseInternal(bool forced) { + // TODO(shess): Calling "PRAGMA journal_mode = DELETE" at this point + // will delete the -journal file. For ChromiumOS or other more + // embedded systems, this is probably not appropriate, whereas on + // desktop it might make some sense. + + // sqlite3_close() needs all prepared statements to be finalized. + + // Release cached statements. + statement_cache_.clear(); + + // With cached statements released, in-use statements will remain. + // Closing the database while statements are in use is an API + // violation, except for forced close (which happens from within a + // statement's error handler). + DCHECK(forced || open_statements_.empty()); + + // Deactivate any outstanding statements so sqlite3_close() works. + for (StatementRefSet::iterator i = open_statements_.begin(); + i != open_statements_.end(); ++i) + (*i)->Close(forced); + open_statements_.clear(); + + if (db_) { + // Call to AssertIOAllowed() cannot go at the beginning of the function + // because Close() must be called from destructor to clean + // statement_cache_, it won't cause any disk access and it most probably + // will happen on thread not allowing disk access. + // TODO(paivanof@gmail.com): This should move to the beginning + // of the function. http://crbug.com/136655. + AssertIOAllowed(); + + int rc = sqlite3_close(db_); + if (rc != SQLITE_OK) { + UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.CloseFailure", rc); + DLOG(FATAL) << "sqlite3_close failed: " << GetErrorMessage(); + } + } + db_ = NULL; +} + +void Connection::Close() { + // If the database was already closed by RazeAndClose(), then no + // need to close again. Clear the |poisoned_| bit so that incorrect + // API calls are caught. + if (poisoned_) { + poisoned_ = false; + return; + } + + CloseInternal(false); +} + +void Connection::Preload() { + AssertIOAllowed(); + + if (!db_) { + DLOG_IF(FATAL, !poisoned_) << "Cannot preload null db"; + return; + } + + // A statement must be open for the preload command to work. If the meta + // table doesn't exist, it probably means this is a new database and there + // is nothing to preload (so it's OK we do nothing). + if (!DoesTableExist("meta")) + return; + Statement dummy(GetUniqueStatement("SELECT * FROM meta")); + if (!dummy.Step()) + return; + +#if !defined(USE_SYSTEM_SQLITE) + // This function is only defined in Chromium's version of sqlite. + // Do not call it when using system sqlite. + sqlite3_preload(db_); +#endif +} + +void Connection::TrimMemory(bool aggressively) { + if (!db_) + return; + + // TODO(shess): investigate using sqlite3_db_release_memory() when possible. + int original_cache_size; + { + Statement sql_get_original(GetUniqueStatement("PRAGMA cache_size")); + if (!sql_get_original.Step()) { + DLOG(WARNING) << "Could not get cache size " << GetErrorMessage(); + return; + } + original_cache_size = sql_get_original.ColumnInt(0); + } + int shrink_cache_size = aggressively ? 1 : (original_cache_size / 2); + + // Force sqlite to try to reduce page cache usage. + const std::string sql_shrink = + base::StringPrintf("PRAGMA cache_size=%d", shrink_cache_size); + if (!Execute(sql_shrink.c_str())) + DLOG(WARNING) << "Could not shrink cache size: " << GetErrorMessage(); + + // Restore cache size. + const std::string sql_restore = + base::StringPrintf("PRAGMA cache_size=%d", original_cache_size); + if (!Execute(sql_restore.c_str())) + DLOG(WARNING) << "Could not restore cache size: " << GetErrorMessage(); +} + +// Create an in-memory database with the existing database's page +// size, then backup that database over the existing database. +bool Connection::Raze() { + AssertIOAllowed(); + + if (!db_) { + DLOG_IF(FATAL, !poisoned_) << "Cannot raze null db"; + return false; + } + + if (transaction_nesting_ > 0) { + DLOG(FATAL) << "Cannot raze within a transaction"; + return false; + } + + sql::Connection null_db; + if (!null_db.OpenInMemory()) { + DLOG(FATAL) << "Unable to open in-memory database."; + return false; + } + + if (page_size_) { + // Enforce SQLite restrictions on |page_size_|. + DCHECK(!(page_size_ & (page_size_ - 1))) + << " page_size_ " << page_size_ << " is not a power of two."; + const int kSqliteMaxPageSize = 32768; // from sqliteLimit.h + DCHECK_LE(page_size_, kSqliteMaxPageSize); + const std::string sql = + base::StringPrintf("PRAGMA page_size=%d", page_size_); + if (!null_db.Execute(sql.c_str())) + return false; + } + +#if defined(OS_ANDROID) + // Android compiles with SQLITE_DEFAULT_AUTOVACUUM. Unfortunately, + // in-memory databases do not respect this define. + // TODO(shess): Figure out a way to set this without using platform + // specific code. AFAICT from sqlite3.c, the only way to do it + // would be to create an actual filesystem database, which is + // unfortunate. + if (!null_db.Execute("PRAGMA auto_vacuum = 1")) + return false; +#endif + + // The page size doesn't take effect until a database has pages, and + // at this point the null database has none. Changing the schema + // version will create the first page. This will not affect the + // schema version in the resulting database, as SQLite's backup + // implementation propagates the schema version from the original + // connection to the new version of the database, incremented by one + // so that other readers see the schema change and act accordingly. + if (!null_db.Execute("PRAGMA schema_version = 1")) + return false; + + // SQLite tracks the expected number of database pages in the first + // page, and if it does not match the total retrieved from a + // filesystem call, treats the database as corrupt. This situation + // breaks almost all SQLite calls. "PRAGMA writable_schema" can be + // used to hint to SQLite to soldier on in that case, specifically + // for purposes of recovery. [See SQLITE_CORRUPT_BKPT case in + // sqlite3.c lockBtree().] + // TODO(shess): With this, "PRAGMA auto_vacuum" and "PRAGMA + // page_size" can be used to query such a database. + ScopedWritableSchema writable_schema(db_); + + const char* kMain = "main"; + int rc = BackupDatabase(null_db.db_, db_, kMain); + UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RazeDatabase",rc); + + // The destination database was locked. + if (rc == SQLITE_BUSY) { + return false; + } + + // SQLITE_NOTADB can happen if page 1 of db_ exists, but is not + // formatted correctly. SQLITE_IOERR_SHORT_READ can happen if db_ + // isn't even big enough for one page. Either way, reach in and + // truncate it before trying again. + // TODO(shess): Maybe it would be worthwhile to just truncate from + // the get-go? + if (rc == SQLITE_NOTADB || rc == SQLITE_IOERR_SHORT_READ) { + sqlite3_file* file = NULL; + rc = sqlite3_file_control(db_, "main", SQLITE_FCNTL_FILE_POINTER, &file); + if (rc != SQLITE_OK) { + DLOG(FATAL) << "Failure getting file handle."; + return false; + } else if (!file) { + DLOG(FATAL) << "File handle is empty."; + return false; + } + + rc = file->pMethods->xTruncate(file, 0); + if (rc != SQLITE_OK) { + UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RazeDatabaseTruncate",rc); + DLOG(FATAL) << "Failed to truncate file."; + return false; + } + + rc = BackupDatabase(null_db.db_, db_, kMain); + UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RazeDatabase2",rc); + + if (rc != SQLITE_DONE) { + DLOG(FATAL) << "Failed retrying Raze()."; + } + } + + // The entire database should have been backed up. + if (rc != SQLITE_DONE) { + // TODO(shess): Figure out which other cases can happen. + DLOG(FATAL) << "Unable to copy entire null database."; + return false; + } + + return true; +} + +bool Connection::RazeWithTimout(base::TimeDelta timeout) { + if (!db_) { + DLOG_IF(FATAL, !poisoned_) << "Cannot raze null db"; + return false; + } + + ScopedBusyTimeout busy_timeout(db_); + busy_timeout.SetTimeout(timeout); + return Raze(); +} + +bool Connection::RazeAndClose() { + if (!db_) { + DLOG_IF(FATAL, !poisoned_) << "Cannot raze null db"; + return false; + } + + // Raze() cannot run in a transaction. + RollbackAllTransactions(); + + bool result = Raze(); + + CloseInternal(true); + + // Mark the database so that future API calls fail appropriately, + // but don't DCHECK (because after calling this function they are + // expected to fail). + poisoned_ = true; + + return result; +} + +void Connection::Poison() { + if (!db_) { + DLOG_IF(FATAL, !poisoned_) << "Cannot poison null db"; + return; + } + + RollbackAllTransactions(); + CloseInternal(true); + + // Mark the database so that future API calls fail appropriately, + // but don't DCHECK (because after calling this function they are + // expected to fail). + poisoned_ = true; +} + +// TODO(shess): To the extent possible, figure out the optimal +// ordering for these deletes which will prevent other connections +// from seeing odd behavior. For instance, it may be necessary to +// manually lock the main database file in a SQLite-compatible fashion +// (to prevent other processes from opening it), then delete the +// journal files, then delete the main database file. Another option +// might be to lock the main database file and poison the header with +// junk to prevent other processes from opening it successfully (like +// Gears "SQLite poison 3" trick). +// +// static +bool Connection::Delete(const base::FilePath& path) { + base::ThreadRestrictions::AssertIOAllowed(); + + base::FilePath journal_path(path.value() + FILE_PATH_LITERAL("-journal")); + base::FilePath wal_path(path.value() + FILE_PATH_LITERAL("-wal")); + + base::DeleteFile(journal_path, false); + base::DeleteFile(wal_path, false); + base::DeleteFile(path, false); + + return !base::PathExists(journal_path) && + !base::PathExists(wal_path) && + !base::PathExists(path); +} + +bool Connection::BeginTransaction() { + if (needs_rollback_) { + DCHECK_GT(transaction_nesting_, 0); + + // When we're going to rollback, fail on this begin and don't actually + // mark us as entering the nested transaction. + return false; + } + + bool success = true; + if (!transaction_nesting_) { + needs_rollback_ = false; + + Statement begin(GetCachedStatement(SQL_FROM_HERE, "BEGIN TRANSACTION")); + if (!begin.Run()) + return false; + } + transaction_nesting_++; + return success; +} + +void Connection::RollbackTransaction() { + if (!transaction_nesting_) { + DLOG_IF(FATAL, !poisoned_) << "Rolling back a nonexistent transaction"; + return; + } + + transaction_nesting_--; + + if (transaction_nesting_ > 0) { + // Mark the outermost transaction as needing rollback. + needs_rollback_ = true; + return; + } + + DoRollback(); +} + +bool Connection::CommitTransaction() { + if (!transaction_nesting_) { + DLOG_IF(FATAL, !poisoned_) << "Rolling back a nonexistent transaction"; + return false; + } + transaction_nesting_--; + + if (transaction_nesting_ > 0) { + // Mark any nested transactions as failing after we've already got one. + return !needs_rollback_; + } + + if (needs_rollback_) { + DoRollback(); + return false; + } + + Statement commit(GetCachedStatement(SQL_FROM_HERE, "COMMIT")); + return commit.Run(); +} + +void Connection::RollbackAllTransactions() { + if (transaction_nesting_ > 0) { + transaction_nesting_ = 0; + DoRollback(); + } +} + +bool Connection::AttachDatabase(const base::FilePath& other_db_path, + const char* attachment_point) { + DCHECK(ValidAttachmentPoint(attachment_point)); + + Statement s(GetUniqueStatement("ATTACH DATABASE ? AS ?")); +#if OS_WIN + s.BindString16(0, other_db_path.value()); +#else + s.BindString(0, other_db_path.value()); +#endif + s.BindString(1, attachment_point); + return s.Run(); +} + +bool Connection::DetachDatabase(const char* attachment_point) { + DCHECK(ValidAttachmentPoint(attachment_point)); + + Statement s(GetUniqueStatement("DETACH DATABASE ?")); + s.BindString(0, attachment_point); + return s.Run(); +} + +int Connection::ExecuteAndReturnErrorCode(const char* sql) { + AssertIOAllowed(); + if (!db_) { + DLOG_IF(FATAL, !poisoned_) << "Illegal use of connection without a db"; + return SQLITE_ERROR; + } + return sqlite3_exec(db_, sql, NULL, NULL, NULL); +} + +bool Connection::Execute(const char* sql) { + if (!db_) { + DLOG_IF(FATAL, !poisoned_) << "Illegal use of connection without a db"; + return false; + } + + int error = ExecuteAndReturnErrorCode(sql); + if (error != SQLITE_OK) + error = OnSqliteError(error, NULL); + + // This needs to be a FATAL log because the error case of arriving here is + // that there's a malformed SQL statement. This can arise in development if + // a change alters the schema but not all queries adjust. This can happen + // in production if the schema is corrupted. + if (error == SQLITE_ERROR) + DLOG(FATAL) << "SQL Error in " << sql << ", " << GetErrorMessage(); + return error == SQLITE_OK; +} + +bool Connection::ExecuteWithTimeout(const char* sql, base::TimeDelta timeout) { + if (!db_) { + DLOG_IF(FATAL, !poisoned_) << "Illegal use of connection without a db"; + return false; + } + + ScopedBusyTimeout busy_timeout(db_); + busy_timeout.SetTimeout(timeout); + return Execute(sql); +} + +bool Connection::HasCachedStatement(const StatementID& id) const { + return statement_cache_.find(id) != statement_cache_.end(); +} + +scoped_refptr<Connection::StatementRef> Connection::GetCachedStatement( + const StatementID& id, + const char* sql) { + CachedStatementMap::iterator i = statement_cache_.find(id); + if (i != statement_cache_.end()) { + // Statement is in the cache. It should still be active (we're the only + // one invalidating cached statements, and we'll remove it from the cache + // if we do that. Make sure we reset it before giving out the cached one in + // case it still has some stuff bound. + DCHECK(i->second->is_valid()); + sqlite3_reset(i->second->stmt()); + return i->second; + } + + scoped_refptr<StatementRef> statement = GetUniqueStatement(sql); + if (statement->is_valid()) + statement_cache_[id] = statement; // Only cache valid statements. + return statement; +} + +scoped_refptr<Connection::StatementRef> Connection::GetUniqueStatement( + const char* sql) { + AssertIOAllowed(); + + // Return inactive statement. + if (!db_) + return new StatementRef(NULL, NULL, poisoned_); + + sqlite3_stmt* stmt = NULL; + int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + // This is evidence of a syntax error in the incoming SQL. + DLOG(FATAL) << "SQL compile error " << GetErrorMessage(); + + // It could also be database corruption. + OnSqliteError(rc, NULL); + return new StatementRef(NULL, NULL, false); + } + return new StatementRef(this, stmt, true); +} + +scoped_refptr<Connection::StatementRef> Connection::GetUntrackedStatement( + const char* sql) const { + // Return inactive statement. + if (!db_) + return new StatementRef(NULL, NULL, poisoned_); + + sqlite3_stmt* stmt = NULL; + int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + // This is evidence of a syntax error in the incoming SQL. + DLOG(FATAL) << "SQL compile error " << GetErrorMessage(); + return new StatementRef(NULL, NULL, false); + } + return new StatementRef(NULL, stmt, true); +} + +bool Connection::IsSQLValid(const char* sql) { + AssertIOAllowed(); + if (!db_) { + DLOG_IF(FATAL, !poisoned_) << "Illegal use of connection without a db"; + return false; + } + + sqlite3_stmt* stmt = NULL; + if (sqlite3_prepare_v2(db_, sql, -1, &stmt, NULL) != SQLITE_OK) + return false; + + sqlite3_finalize(stmt); + return true; +} + +bool Connection::DoesTableExist(const char* table_name) const { + return DoesTableOrIndexExist(table_name, "table"); +} + +bool Connection::DoesIndexExist(const char* index_name) const { + return DoesTableOrIndexExist(index_name, "index"); +} + +bool Connection::DoesTableOrIndexExist( + const char* name, const char* type) const { + const char* kSql = "SELECT name FROM sqlite_master WHERE type=? AND name=?"; + Statement statement(GetUntrackedStatement(kSql)); + statement.BindString(0, type); + statement.BindString(1, name); + + return statement.Step(); // Table exists if any row was returned. +} + +bool Connection::DoesColumnExist(const char* table_name, + const char* column_name) const { + std::string sql("PRAGMA TABLE_INFO("); + sql.append(table_name); + sql.append(")"); + + Statement statement(GetUntrackedStatement(sql.c_str())); + while (statement.Step()) { + if (!statement.ColumnString(1).compare(column_name)) + return true; + } + return false; +} + +int64 Connection::GetLastInsertRowId() const { + if (!db_) { + DLOG_IF(FATAL, !poisoned_) << "Illegal use of connection without a db"; + return 0; + } + return sqlite3_last_insert_rowid(db_); +} + +int Connection::GetLastChangeCount() const { + if (!db_) { + DLOG_IF(FATAL, !poisoned_) << "Illegal use of connection without a db"; + return 0; + } + return sqlite3_changes(db_); +} + +int Connection::GetErrorCode() const { + if (!db_) + return SQLITE_ERROR; + return sqlite3_errcode(db_); +} + +int Connection::GetLastErrno() const { + if (!db_) + return -1; + + int err = 0; + if (SQLITE_OK != sqlite3_file_control(db_, NULL, SQLITE_LAST_ERRNO, &err)) + return -2; + + return err; +} + +const char* Connection::GetErrorMessage() const { + if (!db_) + return "sql::Connection has no connection."; + return sqlite3_errmsg(db_); +} + +bool Connection::OpenInternal(const std::string& file_name, + Connection::Retry retry_flag) { + AssertIOAllowed(); + + if (db_) { + DLOG(FATAL) << "sql::Connection is already open."; + return false; + } + + // Make sure sqlite3_initialize() is called before anything else. + InitializeSqlite(); + + // If |poisoned_| is set, it means an error handler called + // RazeAndClose(). Until regular Close() is called, the caller + // should be treating the database as open, but is_open() currently + // only considers the sqlite3 handle's state. + // TODO(shess): Revise is_open() to consider poisoned_, and review + // to see if any non-testing code even depends on it. + DLOG_IF(FATAL, poisoned_) << "sql::Connection is already open."; + poisoned_ = false; + + int err = sqlite3_open(file_name.c_str(), &db_); + if (err != SQLITE_OK) { + // Extended error codes cannot be enabled until a handle is + // available, fetch manually. + err = sqlite3_extended_errcode(db_); + + // Histogram failures specific to initial open for debugging + // purposes. + UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.OpenFailure", err); + + OnSqliteError(err, NULL); + bool was_poisoned = poisoned_; + Close(); + + if (was_poisoned && retry_flag == RETRY_ON_POISON) + return OpenInternal(file_name, NO_RETRY); + return false; + } + + // TODO(shess): OS_WIN support? +#if defined(OS_POSIX) + if (restrict_to_user_) { + DCHECK_NE(file_name, std::string(":memory")); + base::FilePath file_path(file_name); + int mode = 0; + // TODO(shess): Arguably, failure to retrieve and change + // permissions should be fatal if the file exists. + if (file_util::GetPosixFilePermissions(file_path, &mode)) { + mode &= file_util::FILE_PERMISSION_USER_MASK; + file_util::SetPosixFilePermissions(file_path, mode); + + // SQLite sets the permissions on these files from the main + // database on create. Set them here in case they already exist + // at this point. Failure to set these permissions should not + // be fatal unless the file doesn't exist. + base::FilePath journal_path(file_name + FILE_PATH_LITERAL("-journal")); + base::FilePath wal_path(file_name + FILE_PATH_LITERAL("-wal")); + file_util::SetPosixFilePermissions(journal_path, mode); + file_util::SetPosixFilePermissions(wal_path, mode); + } + } +#endif // defined(OS_POSIX) + + // SQLite uses a lookaside buffer to improve performance of small mallocs. + // Chromium already depends on small mallocs being efficient, so we disable + // this to avoid the extra memory overhead. + // This must be called immediatly after opening the database before any SQL + // statements are run. + sqlite3_db_config(db_, SQLITE_DBCONFIG_LOOKASIDE, NULL, 0, 0); + + // Enable extended result codes to provide more color on I/O errors. + // Not having extended result codes is not a fatal problem, as + // Chromium code does not attempt to handle I/O errors anyhow. The + // current implementation always returns SQLITE_OK, the DCHECK is to + // quickly notify someone if SQLite changes. + err = sqlite3_extended_result_codes(db_, 1); + DCHECK_EQ(err, SQLITE_OK) << "Could not enable extended result codes"; + + // sqlite3_open() does not actually read the database file (unless a + // hot journal is found). Successfully executing this pragma on an + // existing database requires a valid header on page 1. + // TODO(shess): For now, just probing to see what the lay of the + // land is. If it's mostly SQLITE_NOTADB, then the database should + // be razed. + err = ExecuteAndReturnErrorCode("PRAGMA auto_vacuum"); + if (err != SQLITE_OK) + UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.OpenProbeFailure", err); + +#if defined(OS_IOS) && defined(USE_SYSTEM_SQLITE) + // The version of SQLite shipped with iOS doesn't enable ICU, which includes + // REGEXP support. Add it in dynamically. + err = sqlite3IcuInit(db_); + DCHECK_EQ(err, SQLITE_OK) << "Could not enable ICU support"; +#endif // OS_IOS && USE_SYSTEM_SQLITE + + // If indicated, lock up the database before doing anything else, so + // that the following code doesn't have to deal with locking. + // TODO(shess): This code is brittle. Find the cases where code + // doesn't request |exclusive_locking_| and audit that it does the + // right thing with SQLITE_BUSY, and that it doesn't make + // assumptions about who might change things in the database. + // http://crbug.com/56559 + if (exclusive_locking_) { + // TODO(shess): This should probably be a failure. Code which + // requests exclusive locking but doesn't get it is almost certain + // to be ill-tested. + ignore_result(Execute("PRAGMA locking_mode=EXCLUSIVE")); + } + + // http://www.sqlite.org/pragma.html#pragma_journal_mode + // DELETE (default) - delete -journal file to commit. + // TRUNCATE - truncate -journal file to commit. + // PERSIST - zero out header of -journal file to commit. + // journal_size_limit provides size to trim to in PERSIST. + // TODO(shess): Figure out if PERSIST and journal_size_limit really + // matter. In theory, it keeps pages pre-allocated, so if + // transactions usually fit, it should be faster. + ignore_result(Execute("PRAGMA journal_mode = PERSIST")); + ignore_result(Execute("PRAGMA journal_size_limit = 16384")); + + const base::TimeDelta kBusyTimeout = + base::TimeDelta::FromSeconds(kBusyTimeoutSeconds); + + if (page_size_ != 0) { + // Enforce SQLite restrictions on |page_size_|. + DCHECK(!(page_size_ & (page_size_ - 1))) + << " page_size_ " << page_size_ << " is not a power of two."; + const int kSqliteMaxPageSize = 32768; // from sqliteLimit.h + DCHECK_LE(page_size_, kSqliteMaxPageSize); + const std::string sql = + base::StringPrintf("PRAGMA page_size=%d", page_size_); + ignore_result(ExecuteWithTimeout(sql.c_str(), kBusyTimeout)); + } + + if (cache_size_ != 0) { + const std::string sql = + base::StringPrintf("PRAGMA cache_size=%d", cache_size_); + ignore_result(ExecuteWithTimeout(sql.c_str(), kBusyTimeout)); + } + + if (!ExecuteWithTimeout("PRAGMA secure_delete=ON", kBusyTimeout)) { + bool was_poisoned = poisoned_; + Close(); + if (was_poisoned && retry_flag == RETRY_ON_POISON) + return OpenInternal(file_name, NO_RETRY); + return false; + } + + return true; +} + +void Connection::DoRollback() { + Statement rollback(GetCachedStatement(SQL_FROM_HERE, "ROLLBACK")); + rollback.Run(); + needs_rollback_ = false; +} + +void Connection::StatementRefCreated(StatementRef* ref) { + DCHECK(open_statements_.find(ref) == open_statements_.end()); + open_statements_.insert(ref); +} + +void Connection::StatementRefDeleted(StatementRef* ref) { + StatementRefSet::iterator i = open_statements_.find(ref); + if (i == open_statements_.end()) + DLOG(FATAL) << "Could not find statement"; + else + open_statements_.erase(i); +} + +void Connection::AddTaggedHistogram(const std::string& name, + size_t sample) const { + if (histogram_tag_.empty()) + return; + + // TODO(shess): The histogram macros create a bit of static storage + // for caching the histogram object. This code shouldn't execute + // often enough for such caching to be crucial. If it becomes an + // issue, the object could be cached alongside histogram_prefix_. + std::string full_histogram_name = name + "." + histogram_tag_; + base::HistogramBase* histogram = + base::SparseHistogram::FactoryGet( + full_histogram_name, + base::HistogramBase::kUmaTargetedHistogramFlag); + if (histogram) + histogram->Add(sample); +} + +int Connection::OnSqliteError(int err, sql::Statement *stmt) { + UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.Error", err); + AddTaggedHistogram("Sqlite.Error", err); + + // Always log the error. + LOG(ERROR) << "sqlite error " << err + << ", errno " << GetLastErrno() + << ": " << GetErrorMessage(); + + if (!error_callback_.is_null()) { + // Fire from a copy of the callback in case of reentry into + // re/set_error_callback(). + // TODO(shess): <http://crbug.com/254584> + ErrorCallback(error_callback_).Run(err, stmt); + return err; + } + + // The default handling is to assert on debug and to ignore on release. + if (!ShouldIgnore(err)) + DLOG(FATAL) << GetErrorMessage(); + return err; +} + +// TODO(shess): Allow specifying integrity_check versus quick_check. +// TODO(shess): Allow specifying maximum results (default 100 lines). +bool Connection::IntegrityCheck(std::vector<std::string>* messages) { + messages->clear(); + + // This has the side effect of setting SQLITE_RecoveryMode, which + // allows SQLite to process through certain cases of corruption. + // Failing to set this pragma probably means that the database is + // beyond recovery. + const char kWritableSchema[] = "PRAGMA writable_schema = ON"; + if (!Execute(kWritableSchema)) + return false; + + bool ret = false; + { + const char kSql[] = "PRAGMA integrity_check"; + sql::Statement stmt(GetUniqueStatement(kSql)); + + // The pragma appears to return all results (up to 100 by default) + // as a single string. This doesn't appear to be an API contract, + // it could return separate lines, so loop _and_ split. + while (stmt.Step()) { + std::string result(stmt.ColumnString(0)); + base::SplitString(result, '\n', messages); + } + ret = stmt.Succeeded(); + } + + // Best effort to put things back as they were before. + const char kNoWritableSchema[] = "PRAGMA writable_schema = OFF"; + ignore_result(Execute(kNoWritableSchema)); + + return ret; +} + +} // namespace sql diff --git a/chromium/sql/connection.h b/chromium/sql/connection.h new file mode 100644 index 00000000000..24f06deaedc --- /dev/null +++ b/chromium/sql/connection.h @@ -0,0 +1,576 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SQL_CONNECTION_H_ +#define SQL_CONNECTION_H_ + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread_restrictions.h" +#include "base/time/time.h" +#include "sql/sql_export.h" + +struct sqlite3; +struct sqlite3_stmt; + +namespace base { +class FilePath; +} + +namespace sql { + +class Recovery; +class Statement; + +// Uniquely identifies a statement. There are two modes of operation: +// +// - In the most common mode, you will use the source file and line number to +// identify your statement. This is a convienient way to get uniqueness for +// a statement that is only used in one place. Use the SQL_FROM_HERE macro +// to generate a StatementID. +// +// - In the "custom" mode you may use the statement from different places or +// need to manage it yourself for whatever reason. In this case, you should +// make up your own unique name and pass it to the StatementID. This name +// must be a static string, since this object only deals with pointers and +// assumes the underlying string doesn't change or get deleted. +// +// This object is copyable and assignable using the compiler-generated +// operator= and copy constructor. +class StatementID { + public: + // Creates a uniquely named statement with the given file ane line number. + // Normally you will use SQL_FROM_HERE instead of calling yourself. + StatementID(const char* file, int line) + : number_(line), + str_(file) { + } + + // Creates a uniquely named statement with the given user-defined name. + explicit StatementID(const char* unique_name) + : number_(-1), + str_(unique_name) { + } + + // This constructor is unimplemented and will generate a linker error if + // called. It is intended to try to catch people dynamically generating + // a statement name that will be deallocated and will cause a crash later. + // All strings must be static and unchanging! + explicit StatementID(const std::string& dont_ever_do_this); + + // We need this to insert into our map. + bool operator<(const StatementID& other) const; + + private: + int number_; + const char* str_; +}; + +#define SQL_FROM_HERE sql::StatementID(__FILE__, __LINE__) + +class Connection; + +class SQL_EXPORT Connection { + private: + class StatementRef; // Forward declaration, see real one below. + + public: + // The database is opened by calling Open[InMemory](). Any uncommitted + // transactions will be rolled back when this object is deleted. + Connection(); + ~Connection(); + + // Pre-init configuration ---------------------------------------------------- + + // Sets the page size that will be used when creating a new database. This + // must be called before Init(), and will only have an effect on new + // databases. + // + // From sqlite.org: "The page size must be a power of two greater than or + // equal to 512 and less than or equal to SQLITE_MAX_PAGE_SIZE. The maximum + // value for SQLITE_MAX_PAGE_SIZE is 32768." + void set_page_size(int page_size) { page_size_ = page_size; } + + // Sets the number of pages that will be cached in memory by sqlite. The + // total cache size in bytes will be page_size * cache_size. This must be + // called before Open() to have an effect. + void set_cache_size(int cache_size) { cache_size_ = cache_size; } + + // Call to put the database in exclusive locking mode. There is no "back to + // normal" flag because of some additional requirements sqlite puts on this + // transaition (requires another access to the DB) and because we don't + // actually need it. + // + // Exclusive mode means that the database is not unlocked at the end of each + // transaction, which means there may be less time spent initializing the + // next transaction because it doesn't have to re-aquire locks. + // + // This must be called before Open() to have an effect. + void set_exclusive_locking() { exclusive_locking_ = true; } + + // Call to cause Open() to restrict access permissions of the + // database file to only the owner. + // TODO(shess): Currently only supported on OS_POSIX, is a noop on + // other platforms. + void set_restrict_to_user() { restrict_to_user_ = true; } + + // Set an error-handling callback. On errors, the error number (and + // statement, if available) will be passed to the callback. + // + // If no callback is set, the default action is to crash in debug + // mode or return failure in release mode. + typedef base::Callback<void(int, Statement*)> ErrorCallback; + void set_error_callback(const ErrorCallback& callback) { + error_callback_ = callback; + } + bool has_error_callback() const { + return !error_callback_.is_null(); + } + void reset_error_callback() { + error_callback_.Reset(); + } + + // Set this tag to enable additional connection-type histogramming + // for SQLite error codes and database version numbers. + void set_histogram_tag(const std::string& tag) { + histogram_tag_ = tag; + } + + // Record a sparse UMA histogram sample under + // |name|+"."+|histogram_tag_|. If |histogram_tag_| is empty, no + // histogram is recorded. + void AddTaggedHistogram(const std::string& name, size_t sample) const; + + // Run "PRAGMA integrity_check" and post each line of results into + // |messages|. Returns the success of running the statement - per + // the SQLite documentation, if no errors are found the call should + // succeed, and a single value "ok" should be in messages. + bool IntegrityCheck(std::vector<std::string>* messages); + + // Initialization ------------------------------------------------------------ + + // Initializes the SQL connection for the given file, returning true if the + // file could be opened. You can call this or OpenInMemory. + bool Open(const base::FilePath& path) WARN_UNUSED_RESULT; + + // Initializes the SQL connection for a temporary in-memory database. There + // will be no associated file on disk, and the initial database will be + // empty. You can call this or Open. + bool OpenInMemory() WARN_UNUSED_RESULT; + + // Create a temporary on-disk database. The database will be + // deleted after close. This kind of database is similar to + // OpenInMemory() for small databases, but can page to disk if the + // database becomes large. + bool OpenTemporary() WARN_UNUSED_RESULT; + + // Returns true if the database has been successfully opened. + bool is_open() const { return !!db_; } + + // Closes the database. This is automatically performed on destruction for + // you, but this allows you to close the database early. You must not call + // any other functions after closing it. It is permissable to call Close on + // an uninitialized or already-closed database. + void Close(); + + // Pre-loads the first <cache-size> pages into the cache from the file. + // If you expect to soon use a substantial portion of the database, this + // is much more efficient than allowing the pages to be populated organically + // since there is no per-page hard drive seeking. If the file is larger than + // the cache, the last part that doesn't fit in the cache will be brought in + // organically. + // + // This function assumes your class is using a meta table on the current + // database, as it openes a transaction on the meta table to force the + // database to be initialized. You should feel free to initialize the meta + // table after calling preload since the meta table will already be in the + // database if it exists, and if it doesn't exist, the database won't + // generally exist either. + void Preload(); + + // Try to trim the cache memory used by the database. If |aggressively| is + // true, this function will try to free all of the cache memory it can. If + // |aggressively| is false, this function will try to cut cache memory + // usage by half. + void TrimMemory(bool aggressively); + + // Raze the database to the ground. This approximates creating a + // fresh database from scratch, within the constraints of SQLite's + // locking protocol (locks and open handles can make doing this with + // filesystem operations problematic). Returns true if the database + // was razed. + // + // false is returned if the database is locked by some other + // process. RazeWithTimeout() may be used if appropriate. + // + // NOTE(shess): Raze() will DCHECK in the following situations: + // - database is not open. + // - the connection has a transaction open. + // - a SQLite issue occurs which is structural in nature (like the + // statements used are broken). + // Since Raze() is expected to be called in unexpected situations, + // these all return false, since it is unlikely that the caller + // could fix them. + // + // The database's page size is taken from |page_size_|. The + // existing database's |auto_vacuum| setting is lost (the + // possibility of corruption makes it unreliable to pull it from the + // existing database). To re-enable on the empty database requires + // running "PRAGMA auto_vacuum = 1;" then "VACUUM". + // + // NOTE(shess): For Android, SQLITE_DEFAULT_AUTOVACUUM is set to 1, + // so Raze() sets auto_vacuum to 1. + // + // TODO(shess): Raze() needs a connection so cannot clear SQLITE_NOTADB. + // TODO(shess): Bake auto_vacuum into Connection's API so it can + // just pick up the default. + bool Raze(); + bool RazeWithTimout(base::TimeDelta timeout); + + // Breaks all outstanding transactions (as initiated by + // BeginTransaction()), closes the SQLite database, and poisons the + // object so that all future operations against the Connection (or + // its Statements) fail safely, without side effects. + // + // This is intended as an alternative to Close() in error callbacks. + // Close() should still be called at some point. + void Poison(); + + // Raze() the database and Poison() the handle. Returns the return + // value from Raze(). + // TODO(shess): Rename to RazeAndPoison(). + bool RazeAndClose(); + + // Delete the underlying database files associated with |path|. + // This should be used on a database which has no existing + // connections. If any other connections are open to the same + // database, this could cause odd results or corruption (for + // instance if a hot journal is deleted but the associated database + // is not). + // + // Returns true if the database file and associated journals no + // longer exist, false otherwise. If the database has never + // existed, this will return true. + static bool Delete(const base::FilePath& path); + + // Transactions -------------------------------------------------------------- + + // Transaction management. We maintain a virtual transaction stack to emulate + // nested transactions since sqlite can't do nested transactions. The + // limitation is you can't roll back a sub transaction: if any transaction + // fails, all transactions open will also be rolled back. Any nested + // transactions after one has rolled back will return fail for Begin(). If + // Begin() fails, you must not call Commit or Rollback(). + // + // Normally you should use sql::Transaction to manage a transaction, which + // will scope it to a C++ context. + bool BeginTransaction(); + void RollbackTransaction(); + bool CommitTransaction(); + + // Rollback all outstanding transactions. Use with care, there may + // be scoped transactions on the stack. + void RollbackAllTransactions(); + + // Returns the current transaction nesting, which will be 0 if there are + // no open transactions. + int transaction_nesting() const { return transaction_nesting_; } + + // Attached databases--------------------------------------------------------- + + // SQLite supports attaching multiple database files to a single + // handle. Attach the database in |other_db_path| to the current + // handle under |attachment_point|. |attachment_point| should only + // contain characters from [a-zA-Z0-9_]. + // + // Note that calling attach or detach with an open transaction is an + // error. + bool AttachDatabase(const base::FilePath& other_db_path, + const char* attachment_point); + bool DetachDatabase(const char* attachment_point); + + // Statements ---------------------------------------------------------------- + + // Executes the given SQL string, returning true on success. This is + // normally used for simple, 1-off statements that don't take any bound + // parameters and don't return any data (e.g. CREATE TABLE). + // + // This will DCHECK if the |sql| contains errors. + // + // Do not use ignore_result() to ignore all errors. Use + // ExecuteAndReturnErrorCode() and ignore only specific errors. + bool Execute(const char* sql) WARN_UNUSED_RESULT; + + // Like Execute(), but returns the error code given by SQLite. + int ExecuteAndReturnErrorCode(const char* sql) WARN_UNUSED_RESULT; + + // Returns true if we have a statement with the given identifier already + // cached. This is normally not necessary to call, but can be useful if the + // caller has to dynamically build up SQL to avoid doing so if it's already + // cached. + bool HasCachedStatement(const StatementID& id) const; + + // Returns a statement for the given SQL using the statement cache. It can + // take a nontrivial amount of work to parse and compile a statement, so + // keeping commonly-used ones around for future use is important for + // performance. + // + // If the |sql| has an error, an invalid, inert StatementRef is returned (and + // the code will crash in debug). The caller must deal with this eventuality, + // either by checking validity of the |sql| before calling, by correctly + // handling the return of an inert statement, or both. + // + // The StatementID and the SQL must always correspond to one-another. The + // ID is the lookup into the cache, so crazy things will happen if you use + // different SQL with the same ID. + // + // You will normally use the SQL_FROM_HERE macro to generate a statement + // ID associated with the current line of code. This gives uniqueness without + // you having to manage unique names. See StatementID above for more. + // + // Example: + // sql::Statement stmt(connection_.GetCachedStatement( + // SQL_FROM_HERE, "SELECT * FROM foo")); + // if (!stmt) + // return false; // Error creating statement. + scoped_refptr<StatementRef> GetCachedStatement(const StatementID& id, + const char* sql); + + // Used to check a |sql| statement for syntactic validity. If the statement is + // valid SQL, returns true. + bool IsSQLValid(const char* sql); + + // Returns a non-cached statement for the given SQL. Use this for SQL that + // is only executed once or only rarely (there is overhead associated with + // keeping a statement cached). + // + // See GetCachedStatement above for examples and error information. + scoped_refptr<StatementRef> GetUniqueStatement(const char* sql); + + // Info querying ------------------------------------------------------------- + + // Returns true if the given table exists. + bool DoesTableExist(const char* table_name) const; + + // Returns true if the given index exists. + bool DoesIndexExist(const char* index_name) const; + + // Returns true if a column with the given name exists in the given table. + bool DoesColumnExist(const char* table_name, const char* column_name) const; + + // Returns sqlite's internal ID for the last inserted row. Valid only + // immediately after an insert. + int64 GetLastInsertRowId() const; + + // Returns sqlite's count of the number of rows modified by the last + // statement executed. Will be 0 if no statement has executed or the database + // is closed. + int GetLastChangeCount() const; + + // Errors -------------------------------------------------------------------- + + // Returns the error code associated with the last sqlite operation. + int GetErrorCode() const; + + // Returns the errno associated with GetErrorCode(). See + // SQLITE_LAST_ERRNO in SQLite documentation. + int GetLastErrno() const; + + // Returns a pointer to a statically allocated string associated with the + // last sqlite operation. + const char* GetErrorMessage() const; + + private: + // For recovery module. + friend class Recovery; + + // Allow test-support code to set/reset error ignorer. + friend class ScopedErrorIgnorer; + + // Statement accesses StatementRef which we don't want to expose to everybody + // (they should go through Statement). + friend class Statement; + + // Internal initialize function used by both Init and InitInMemory. The file + // name is always 8 bits since we want to use the 8-bit version of + // sqlite3_open. The string can also be sqlite's special ":memory:" string. + // + // |retry_flag| controls retrying the open if the error callback + // addressed errors using RazeAndClose(). + enum Retry { + NO_RETRY = 0, + RETRY_ON_POISON + }; + bool OpenInternal(const std::string& file_name, Retry retry_flag); + + // Internal close function used by Close() and RazeAndClose(). + // |forced| indicates that orderly-shutdown checks should not apply. + void CloseInternal(bool forced); + + // Check whether the current thread is allowed to make IO calls, but only + // if database wasn't open in memory. Function is inlined to be a no-op in + // official build. + void AssertIOAllowed() { + if (!in_memory_) + base::ThreadRestrictions::AssertIOAllowed(); + } + + // Internal helper for DoesTableExist and DoesIndexExist. + bool DoesTableOrIndexExist(const char* name, const char* type) const; + + // Accessors for global error-ignorer, for injecting behavior during tests. + // See test/scoped_error_ignorer.h. + typedef base::Callback<bool(int)> ErrorIgnorerCallback; + static ErrorIgnorerCallback* current_ignorer_cb_; + static bool ShouldIgnore(int error); + static void SetErrorIgnorer(ErrorIgnorerCallback* ignorer); + static void ResetErrorIgnorer(); + + // A StatementRef is a refcounted wrapper around a sqlite statement pointer. + // Refcounting allows us to give these statements out to sql::Statement + // objects while also optionally maintaining a cache of compiled statements + // by just keeping a refptr to these objects. + // + // A statement ref can be valid, in which case it can be used, or invalid to + // indicate that the statement hasn't been created yet, has an error, or has + // been destroyed. + // + // The Connection may revoke a StatementRef in some error cases, so callers + // should always check validity before using. + class SQL_EXPORT StatementRef : public base::RefCounted<StatementRef> { + public: + // |connection| is the sql::Connection instance associated with + // the statement, and is used for tracking outstanding statements + // and for error handling. Set to NULL for invalid or untracked + // refs. |stmt| is the actual statement, and should only be NULL + // to create an invalid ref. |was_valid| indicates whether the + // statement should be considered valid for diagnistic purposes. + // |was_valid| can be true for NULL |stmt| if the connection has + // been forcibly closed by an error handler. + StatementRef(Connection* connection, sqlite3_stmt* stmt, bool was_valid); + + // When true, the statement can be used. + bool is_valid() const { return !!stmt_; } + + // When true, the statement is either currently valid, or was + // previously valid but the connection was forcibly closed. Used + // for diagnostic checks. + bool was_valid() const { return was_valid_; } + + // If we've not been linked to a connection, this will be NULL. + // TODO(shess): connection_ can be NULL in case of GetUntrackedStatement(), + // which prevents Statement::OnError() from forwarding errors. + Connection* connection() const { return connection_; } + + // Returns the sqlite statement if any. If the statement is not active, + // this will return NULL. + sqlite3_stmt* stmt() const { return stmt_; } + + // Destroys the compiled statement and marks it NULL. The statement will + // no longer be active. |forced| is used to indicate if orderly-shutdown + // checks should apply (see Connection::RazeAndClose()). + void Close(bool forced); + + // Check whether the current thread is allowed to make IO calls, but only + // if database wasn't open in memory. + void AssertIOAllowed() { if (connection_) connection_->AssertIOAllowed(); } + + private: + friend class base::RefCounted<StatementRef>; + + ~StatementRef(); + + Connection* connection_; + sqlite3_stmt* stmt_; + bool was_valid_; + + DISALLOW_COPY_AND_ASSIGN(StatementRef); + }; + friend class StatementRef; + + // Executes a rollback statement, ignoring all transaction state. Used + // internally in the transaction management code. + void DoRollback(); + + // Called by a StatementRef when it's being created or destroyed. See + // open_statements_ below. + void StatementRefCreated(StatementRef* ref); + void StatementRefDeleted(StatementRef* ref); + + // Called by Statement objects when an sqlite function returns an error. + // The return value is the error code reflected back to client code. + int OnSqliteError(int err, Statement* stmt); + + // Like |Execute()|, but retries if the database is locked. + bool ExecuteWithTimeout(const char* sql, base::TimeDelta ms_timeout) + WARN_UNUSED_RESULT; + + // Internal helper for const functions. Like GetUniqueStatement(), + // except the statement is not entered into open_statements_, + // allowing this function to be const. Open statements can block + // closing the database, so only use in cases where the last ref is + // released before close could be called (which should always be the + // case for const functions). + scoped_refptr<StatementRef> GetUntrackedStatement(const char* sql) const; + + // The actual sqlite database. Will be NULL before Init has been called or if + // Init resulted in an error. + sqlite3* db_; + + // Parameters we'll configure in sqlite before doing anything else. Zero means + // use the default value. + int page_size_; + int cache_size_; + bool exclusive_locking_; + bool restrict_to_user_; + + // All cached statements. Keeping a reference to these statements means that + // they'll remain active. + typedef std::map<StatementID, scoped_refptr<StatementRef> > + CachedStatementMap; + CachedStatementMap statement_cache_; + + // A list of all StatementRefs we've given out. Each ref must register with + // us when it's created or destroyed. This allows us to potentially close + // any open statements when we encounter an error. + typedef std::set<StatementRef*> StatementRefSet; + StatementRefSet open_statements_; + + // Number of currently-nested transactions. + int transaction_nesting_; + + // True if any of the currently nested transactions have been rolled back. + // When we get to the outermost transaction, this will determine if we do + // a rollback instead of a commit. + bool needs_rollback_; + + // True if database is open with OpenInMemory(), False if database is open + // with Open(). + bool in_memory_; + + // |true| if the connection was closed using RazeAndClose(). Used + // to enable diagnostics to distinguish calls to never-opened + // databases (incorrect use of the API) from calls to once-valid + // databases. + bool poisoned_; + + ErrorCallback error_callback_; + + // Tag for auxiliary histograms. + std::string histogram_tag_; + + DISALLOW_COPY_AND_ASSIGN(Connection); +}; + +} // namespace sql + +#endif // SQL_CONNECTION_H_ diff --git a/chromium/sql/connection_unittest.cc b/chromium/sql/connection_unittest.cc new file mode 100644 index 00000000000..09a47fb47fd --- /dev/null +++ b/chromium/sql/connection_unittest.cc @@ -0,0 +1,845 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "sql/connection.h" +#include "sql/meta_table.h" +#include "sql/statement.h" +#include "sql/test/error_callback_support.h" +#include "sql/test/scoped_error_ignorer.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/sqlite/sqlite3.h" + +namespace { + +// Helper to return the count of items in sqlite_master. Return -1 in +// case of error. +int SqliteMasterCount(sql::Connection* db) { + const char* kMasterCount = "SELECT COUNT(*) FROM sqlite_master"; + sql::Statement s(db->GetUniqueStatement(kMasterCount)); + return s.Step() ? s.ColumnInt(0) : -1; +} + +// Track the number of valid references which share the same pointer. +// This is used to allow testing an implicitly use-after-free case by +// explicitly having the ref count live longer than the object. +class RefCounter { + public: + RefCounter(size_t* counter) + : counter_(counter) { + (*counter_)++; + } + RefCounter(const RefCounter& other) + : counter_(other.counter_) { + (*counter_)++; + } + ~RefCounter() { + (*counter_)--; + } + + private: + size_t* counter_; + + DISALLOW_ASSIGN(RefCounter); +}; + +// Empty callback for implementation of ErrorCallbackSetHelper(). +void IgnoreErrorCallback(int error, sql::Statement* stmt) { +} + +void ErrorCallbackSetHelper(sql::Connection* db, + size_t* counter, + const RefCounter& r, + int error, sql::Statement* stmt) { + // The ref count should not go to zero when changing the callback. + EXPECT_GT(*counter, 0u); + db->set_error_callback(base::Bind(&IgnoreErrorCallback)); + EXPECT_GT(*counter, 0u); +} + +void ErrorCallbackResetHelper(sql::Connection* db, + size_t* counter, + const RefCounter& r, + int error, sql::Statement* stmt) { + // The ref count should not go to zero when clearing the callback. + EXPECT_GT(*counter, 0u); + db->reset_error_callback(); + EXPECT_GT(*counter, 0u); +} + +#if defined(OS_POSIX) +// Set a umask and restore the old mask on destruction. Cribbed from +// shared_memory_unittest.cc. Used by POSIX-only UserPermission test. +class ScopedUmaskSetter { + public: + explicit ScopedUmaskSetter(mode_t target_mask) { + old_umask_ = umask(target_mask); + } + ~ScopedUmaskSetter() { umask(old_umask_); } + private: + mode_t old_umask_; + DISALLOW_IMPLICIT_CONSTRUCTORS(ScopedUmaskSetter); +}; +#endif + +class SQLConnectionTest : public testing::Test { + public: + SQLConnectionTest() {} + + virtual void SetUp() { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + ASSERT_TRUE(db_.Open(db_path())); + } + + virtual void TearDown() { + db_.Close(); + } + + sql::Connection& db() { return db_; } + + base::FilePath db_path() { + return temp_dir_.path().AppendASCII("SQLConnectionTest.db"); + } + + // Handle errors by blowing away the database. + void RazeErrorCallback(int expected_error, int error, sql::Statement* stmt) { + EXPECT_EQ(expected_error, error); + db_.RazeAndClose(); + } + + private: + base::ScopedTempDir temp_dir_; + sql::Connection db_; +}; + +TEST_F(SQLConnectionTest, Execute) { + // Valid statement should return true. + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + EXPECT_EQ(SQLITE_OK, db().GetErrorCode()); + + // Invalid statement should fail. + ASSERT_EQ(SQLITE_ERROR, + db().ExecuteAndReturnErrorCode("CREATE TAB foo (a, b")); + EXPECT_EQ(SQLITE_ERROR, db().GetErrorCode()); +} + +TEST_F(SQLConnectionTest, ExecuteWithErrorCode) { + ASSERT_EQ(SQLITE_OK, + db().ExecuteAndReturnErrorCode("CREATE TABLE foo (a, b)")); + ASSERT_EQ(SQLITE_ERROR, + db().ExecuteAndReturnErrorCode("CREATE TABLE TABLE")); + ASSERT_EQ(SQLITE_ERROR, + db().ExecuteAndReturnErrorCode( + "INSERT INTO foo(a, b) VALUES (1, 2, 3, 4)")); +} + +TEST_F(SQLConnectionTest, CachedStatement) { + sql::StatementID id1("foo", 12); + + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + ASSERT_TRUE(db().Execute("INSERT INTO foo(a, b) VALUES (12, 13)")); + + // Create a new cached statement. + { + sql::Statement s(db().GetCachedStatement(id1, "SELECT a FROM foo")); + ASSERT_TRUE(s.is_valid()); + + ASSERT_TRUE(s.Step()); + EXPECT_EQ(12, s.ColumnInt(0)); + } + + // The statement should be cached still. + EXPECT_TRUE(db().HasCachedStatement(id1)); + + { + // Get the same statement using different SQL. This should ignore our + // SQL and use the cached one (so it will be valid). + sql::Statement s(db().GetCachedStatement(id1, "something invalid(")); + ASSERT_TRUE(s.is_valid()); + + ASSERT_TRUE(s.Step()); + EXPECT_EQ(12, s.ColumnInt(0)); + } + + // Make sure other statements aren't marked as cached. + EXPECT_FALSE(db().HasCachedStatement(SQL_FROM_HERE)); +} + +TEST_F(SQLConnectionTest, IsSQLValidTest) { + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + ASSERT_TRUE(db().IsSQLValid("SELECT a FROM foo")); + ASSERT_FALSE(db().IsSQLValid("SELECT no_exist FROM foo")); +} + +TEST_F(SQLConnectionTest, DoesStuffExist) { + // Test DoesTableExist. + EXPECT_FALSE(db().DoesTableExist("foo")); + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + EXPECT_TRUE(db().DoesTableExist("foo")); + + // Should be case sensitive. + EXPECT_FALSE(db().DoesTableExist("FOO")); + + // Test DoesColumnExist. + EXPECT_FALSE(db().DoesColumnExist("foo", "bar")); + EXPECT_TRUE(db().DoesColumnExist("foo", "a")); + + // Testing for a column on a nonexistent table. + EXPECT_FALSE(db().DoesColumnExist("bar", "b")); +} + +TEST_F(SQLConnectionTest, GetLastInsertRowId) { + ASSERT_TRUE(db().Execute("CREATE TABLE foo (id INTEGER PRIMARY KEY, value)")); + + ASSERT_TRUE(db().Execute("INSERT INTO foo (value) VALUES (12)")); + + // Last insert row ID should be valid. + int64 row = db().GetLastInsertRowId(); + EXPECT_LT(0, row); + + // It should be the primary key of the row we just inserted. + sql::Statement s(db().GetUniqueStatement("SELECT value FROM foo WHERE id=?")); + s.BindInt64(0, row); + ASSERT_TRUE(s.Step()); + EXPECT_EQ(12, s.ColumnInt(0)); +} + +TEST_F(SQLConnectionTest, Rollback) { + ASSERT_TRUE(db().BeginTransaction()); + ASSERT_TRUE(db().BeginTransaction()); + EXPECT_EQ(2, db().transaction_nesting()); + db().RollbackTransaction(); + EXPECT_FALSE(db().CommitTransaction()); + EXPECT_TRUE(db().BeginTransaction()); +} + +// Test the scoped error ignorer by attempting to insert a duplicate +// value into an index. +TEST_F(SQLConnectionTest, ScopedIgnoreError) { + const char* kCreateSql = "CREATE TABLE foo (id INTEGER UNIQUE)"; + ASSERT_TRUE(db().Execute(kCreateSql)); + ASSERT_TRUE(db().Execute("INSERT INTO foo (id) VALUES (12)")); + + sql::ScopedErrorIgnorer ignore_errors; + ignore_errors.IgnoreError(SQLITE_CONSTRAINT); + ASSERT_FALSE(db().Execute("INSERT INTO foo (id) VALUES (12)")); + ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); +} + +TEST_F(SQLConnectionTest, ErrorCallback) { + const char* kCreateSql = "CREATE TABLE foo (id INTEGER UNIQUE)"; + ASSERT_TRUE(db().Execute(kCreateSql)); + ASSERT_TRUE(db().Execute("INSERT INTO foo (id) VALUES (12)")); + + int error = SQLITE_OK; + { + sql::ScopedErrorCallback sec( + &db(), base::Bind(&sql::CaptureErrorCallback, &error)); + EXPECT_FALSE(db().Execute("INSERT INTO foo (id) VALUES (12)")); + EXPECT_EQ(SQLITE_CONSTRAINT, error); + } + + // Callback is no longer in force due to reset. + { + error = SQLITE_OK; + sql::ScopedErrorIgnorer ignore_errors; + ignore_errors.IgnoreError(SQLITE_CONSTRAINT); + ASSERT_FALSE(db().Execute("INSERT INTO foo (id) VALUES (12)")); + ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); + EXPECT_EQ(SQLITE_OK, error); + } + + // base::Bind() can curry arguments to be passed by const reference + // to the callback function. If the callback function calls + // re/set_error_callback(), the storage for those arguments can be + // deleted while the callback function is still executing. + // + // RefCounter() counts how many objects are live using an external + // count. The same counter is passed to the callback, so that it + // can check directly even if the RefCounter object is no longer + // live. + { + size_t count = 0; + sql::ScopedErrorCallback sec( + &db(), base::Bind(&ErrorCallbackSetHelper, + &db(), &count, RefCounter(&count))); + + EXPECT_FALSE(db().Execute("INSERT INTO foo (id) VALUES (12)")); + } + + // Same test, but reset_error_callback() case. + { + size_t count = 0; + sql::ScopedErrorCallback sec( + &db(), base::Bind(&ErrorCallbackResetHelper, + &db(), &count, RefCounter(&count))); + + EXPECT_FALSE(db().Execute("INSERT INTO foo (id) VALUES (12)")); + } +} + +// Test that sql::Connection::Raze() results in a database without the +// tables from the original database. +TEST_F(SQLConnectionTest, Raze) { + const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)"; + ASSERT_TRUE(db().Execute(kCreateSql)); + ASSERT_TRUE(db().Execute("INSERT INTO foo (value) VALUES (12)")); + + int pragma_auto_vacuum = 0; + { + sql::Statement s(db().GetUniqueStatement("PRAGMA auto_vacuum")); + ASSERT_TRUE(s.Step()); + pragma_auto_vacuum = s.ColumnInt(0); + ASSERT_TRUE(pragma_auto_vacuum == 0 || pragma_auto_vacuum == 1); + } + + // If auto_vacuum is set, there's an extra page to maintain a freelist. + const int kExpectedPageCount = 2 + pragma_auto_vacuum; + + { + sql::Statement s(db().GetUniqueStatement("PRAGMA page_count")); + ASSERT_TRUE(s.Step()); + EXPECT_EQ(kExpectedPageCount, s.ColumnInt(0)); + } + + { + sql::Statement s(db().GetUniqueStatement("SELECT * FROM sqlite_master")); + ASSERT_TRUE(s.Step()); + EXPECT_EQ("table", s.ColumnString(0)); + EXPECT_EQ("foo", s.ColumnString(1)); + EXPECT_EQ("foo", s.ColumnString(2)); + // Table "foo" is stored in the last page of the file. + EXPECT_EQ(kExpectedPageCount, s.ColumnInt(3)); + EXPECT_EQ(kCreateSql, s.ColumnString(4)); + } + + ASSERT_TRUE(db().Raze()); + + { + sql::Statement s(db().GetUniqueStatement("PRAGMA page_count")); + ASSERT_TRUE(s.Step()); + EXPECT_EQ(1, s.ColumnInt(0)); + } + + ASSERT_EQ(0, SqliteMasterCount(&db())); + + { + sql::Statement s(db().GetUniqueStatement("PRAGMA auto_vacuum")); + ASSERT_TRUE(s.Step()); + // The new database has the same auto_vacuum as a fresh database. + EXPECT_EQ(pragma_auto_vacuum, s.ColumnInt(0)); + } +} + +// Test that Raze() maintains page_size. +TEST_F(SQLConnectionTest, RazePageSize) { + // Fetch the default page size and double it for use in this test. + // Scoped to release statement before Close(). + int default_page_size = 0; + { + sql::Statement s(db().GetUniqueStatement("PRAGMA page_size")); + ASSERT_TRUE(s.Step()); + default_page_size = s.ColumnInt(0); + } + ASSERT_GT(default_page_size, 0); + const int kPageSize = 2 * default_page_size; + + // Re-open the database to allow setting the page size. + db().Close(); + db().set_page_size(kPageSize); + ASSERT_TRUE(db().Open(db_path())); + + // page_size should match the indicated value. + sql::Statement s(db().GetUniqueStatement("PRAGMA page_size")); + ASSERT_TRUE(s.Step()); + ASSERT_EQ(kPageSize, s.ColumnInt(0)); + + // After raze, page_size should still match the indicated value. + ASSERT_TRUE(db().Raze()); + s.Reset(true); + ASSERT_TRUE(s.Step()); + ASSERT_EQ(kPageSize, s.ColumnInt(0)); +} + +// Test that Raze() results are seen in other connections. +TEST_F(SQLConnectionTest, RazeMultiple) { + const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)"; + ASSERT_TRUE(db().Execute(kCreateSql)); + + sql::Connection other_db; + ASSERT_TRUE(other_db.Open(db_path())); + + // Check that the second connection sees the table. + ASSERT_EQ(1, SqliteMasterCount(&other_db)); + + ASSERT_TRUE(db().Raze()); + + // The second connection sees the updated database. + ASSERT_EQ(0, SqliteMasterCount(&other_db)); +} + +TEST_F(SQLConnectionTest, RazeLocked) { + const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)"; + ASSERT_TRUE(db().Execute(kCreateSql)); + + // Open a transaction and write some data in a second connection. + // This will acquire a PENDING or EXCLUSIVE transaction, which will + // cause the raze to fail. + sql::Connection other_db; + ASSERT_TRUE(other_db.Open(db_path())); + ASSERT_TRUE(other_db.BeginTransaction()); + const char* kInsertSql = "INSERT INTO foo VALUES (1, 'data')"; + ASSERT_TRUE(other_db.Execute(kInsertSql)); + + ASSERT_FALSE(db().Raze()); + + // Works after COMMIT. + ASSERT_TRUE(other_db.CommitTransaction()); + ASSERT_TRUE(db().Raze()); + + // Re-create the database. + ASSERT_TRUE(db().Execute(kCreateSql)); + ASSERT_TRUE(db().Execute(kInsertSql)); + + // An unfinished read transaction in the other connection also + // blocks raze. + const char *kQuery = "SELECT COUNT(*) FROM foo"; + sql::Statement s(other_db.GetUniqueStatement(kQuery)); + ASSERT_TRUE(s.Step()); + ASSERT_FALSE(db().Raze()); + + // Complete the statement unlocks the database. + ASSERT_FALSE(s.Step()); + ASSERT_TRUE(db().Raze()); +} + +// Verify that Raze() can handle an empty file. SQLite should treat +// this as an empty database. +TEST_F(SQLConnectionTest, RazeEmptyDB) { + const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)"; + ASSERT_TRUE(db().Execute(kCreateSql)); + db().Close(); + + { + file_util::ScopedFILE file(file_util::OpenFile(db_path(), "rb+")); + ASSERT_TRUE(file.get() != NULL); + ASSERT_EQ(0, fseek(file.get(), 0, SEEK_SET)); + ASSERT_TRUE(file_util::TruncateFile(file.get())); + } + + ASSERT_TRUE(db().Open(db_path())); + ASSERT_TRUE(db().Raze()); + EXPECT_EQ(0, SqliteMasterCount(&db())); +} + +// Verify that Raze() can handle a file of junk. +TEST_F(SQLConnectionTest, RazeNOTADB) { + db().Close(); + sql::Connection::Delete(db_path()); + ASSERT_FALSE(base::PathExists(db_path())); + + { + file_util::ScopedFILE file(file_util::OpenFile(db_path(), "wb")); + ASSERT_TRUE(file.get() != NULL); + + const char* kJunk = "This is the hour of our discontent."; + fputs(kJunk, file.get()); + } + ASSERT_TRUE(base::PathExists(db_path())); + + // SQLite will successfully open the handle, but will fail with + // SQLITE_IOERR_SHORT_READ on pragma statemenets which read the + // header. + { + sql::ScopedErrorIgnorer ignore_errors; + ignore_errors.IgnoreError(SQLITE_IOERR_SHORT_READ); + EXPECT_TRUE(db().Open(db_path())); + ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); + } + EXPECT_TRUE(db().Raze()); + db().Close(); + + // Now empty, the open should open an empty database. + EXPECT_TRUE(db().Open(db_path())); + EXPECT_EQ(0, SqliteMasterCount(&db())); +} + +// Verify that Raze() can handle a database overwritten with garbage. +TEST_F(SQLConnectionTest, RazeNOTADB2) { + const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)"; + ASSERT_TRUE(db().Execute(kCreateSql)); + ASSERT_EQ(1, SqliteMasterCount(&db())); + db().Close(); + + { + file_util::ScopedFILE file(file_util::OpenFile(db_path(), "rb+")); + ASSERT_TRUE(file.get() != NULL); + ASSERT_EQ(0, fseek(file.get(), 0, SEEK_SET)); + + const char* kJunk = "This is the hour of our discontent."; + fputs(kJunk, file.get()); + } + + // SQLite will successfully open the handle, but will fail with + // SQLITE_NOTADB on pragma statemenets which attempt to read the + // corrupted header. + { + sql::ScopedErrorIgnorer ignore_errors; + ignore_errors.IgnoreError(SQLITE_NOTADB); + EXPECT_TRUE(db().Open(db_path())); + ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); + } + EXPECT_TRUE(db().Raze()); + db().Close(); + + // Now empty, the open should succeed with an empty database. + EXPECT_TRUE(db().Open(db_path())); + EXPECT_EQ(0, SqliteMasterCount(&db())); +} + +// Test that a callback from Open() can raze the database. This is +// essential for cases where the Open() can fail entirely, so the +// Raze() cannot happen later. Additionally test that when the +// callback does this during Open(), the open is retried and succeeds. +// +// Most corruptions seen in the wild seem to happen when two pages in +// the database were not written transactionally (the transaction +// changed both, but one wasn't successfully written for some reason). +// A special case of that is when the header indicates that the +// database contains more pages than are in the file. This breaks +// things at a very basic level, verify that Raze() can handle it. +TEST_F(SQLConnectionTest, RazeCallbackReopen) { + const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)"; + ASSERT_TRUE(db().Execute(kCreateSql)); + ASSERT_EQ(1, SqliteMasterCount(&db())); + int page_size = 0; + { + sql::Statement s(db().GetUniqueStatement("PRAGMA page_size")); + ASSERT_TRUE(s.Step()); + page_size = s.ColumnInt(0); + } + db().Close(); + + // Trim a single page from the end of the file. + { + file_util::ScopedFILE file(file_util::OpenFile(db_path(), "rb+")); + ASSERT_TRUE(file.get() != NULL); + ASSERT_EQ(0, fseek(file.get(), -page_size, SEEK_END)); + ASSERT_TRUE(file_util::TruncateFile(file.get())); + } + + // Open() will succeed, even though the PRAGMA calls within will + // fail with SQLITE_CORRUPT, as will this PRAGMA. + { + sql::ScopedErrorIgnorer ignore_errors; + ignore_errors.IgnoreError(SQLITE_CORRUPT); + ASSERT_TRUE(db().Open(db_path())); + ASSERT_FALSE(db().Execute("PRAGMA auto_vacuum")); + db().Close(); + ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); + } + + db().set_error_callback(base::Bind(&SQLConnectionTest::RazeErrorCallback, + base::Unretained(this), + SQLITE_CORRUPT)); + + // When the PRAGMA calls in Open() raise SQLITE_CORRUPT, the error + // callback will call RazeAndClose(). Open() will then fail and be + // retried. The second Open() on the empty database will succeed + // cleanly. + ASSERT_TRUE(db().Open(db_path())); + ASSERT_TRUE(db().Execute("PRAGMA auto_vacuum")); + EXPECT_EQ(0, SqliteMasterCount(&db())); +} + +// Basic test of RazeAndClose() operation. +TEST_F(SQLConnectionTest, RazeAndClose) { + const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)"; + const char* kPopulateSql = "INSERT INTO foo (value) VALUES (12)"; + + // Test that RazeAndClose() closes the database, and that the + // database is empty when re-opened. + ASSERT_TRUE(db().Execute(kCreateSql)); + ASSERT_TRUE(db().Execute(kPopulateSql)); + ASSERT_TRUE(db().RazeAndClose()); + ASSERT_FALSE(db().is_open()); + db().Close(); + ASSERT_TRUE(db().Open(db_path())); + ASSERT_EQ(0, SqliteMasterCount(&db())); + + // Test that RazeAndClose() can break transactions. + ASSERT_TRUE(db().Execute(kCreateSql)); + ASSERT_TRUE(db().Execute(kPopulateSql)); + ASSERT_TRUE(db().BeginTransaction()); + ASSERT_TRUE(db().RazeAndClose()); + ASSERT_FALSE(db().is_open()); + ASSERT_FALSE(db().CommitTransaction()); + db().Close(); + ASSERT_TRUE(db().Open(db_path())); + ASSERT_EQ(0, SqliteMasterCount(&db())); +} + +// Test that various operations fail without crashing after +// RazeAndClose(). +TEST_F(SQLConnectionTest, RazeAndCloseDiagnostics) { + const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)"; + const char* kPopulateSql = "INSERT INTO foo (value) VALUES (12)"; + const char* kSimpleSql = "SELECT 1"; + + ASSERT_TRUE(db().Execute(kCreateSql)); + ASSERT_TRUE(db().Execute(kPopulateSql)); + + // Test baseline expectations. + db().Preload(); + ASSERT_TRUE(db().DoesTableExist("foo")); + ASSERT_TRUE(db().IsSQLValid(kSimpleSql)); + ASSERT_EQ(SQLITE_OK, db().ExecuteAndReturnErrorCode(kSimpleSql)); + ASSERT_TRUE(db().Execute(kSimpleSql)); + ASSERT_TRUE(db().is_open()); + { + sql::Statement s(db().GetUniqueStatement(kSimpleSql)); + ASSERT_TRUE(s.Step()); + } + { + sql::Statement s(db().GetCachedStatement(SQL_FROM_HERE, kSimpleSql)); + ASSERT_TRUE(s.Step()); + } + ASSERT_TRUE(db().BeginTransaction()); + ASSERT_TRUE(db().CommitTransaction()); + ASSERT_TRUE(db().BeginTransaction()); + db().RollbackTransaction(); + + ASSERT_TRUE(db().RazeAndClose()); + + // At this point, they should all fail, but not crash. + db().Preload(); + ASSERT_FALSE(db().DoesTableExist("foo")); + ASSERT_FALSE(db().IsSQLValid(kSimpleSql)); + ASSERT_EQ(SQLITE_ERROR, db().ExecuteAndReturnErrorCode(kSimpleSql)); + ASSERT_FALSE(db().Execute(kSimpleSql)); + ASSERT_FALSE(db().is_open()); + { + sql::Statement s(db().GetUniqueStatement(kSimpleSql)); + ASSERT_FALSE(s.Step()); + } + { + sql::Statement s(db().GetCachedStatement(SQL_FROM_HERE, kSimpleSql)); + ASSERT_FALSE(s.Step()); + } + ASSERT_FALSE(db().BeginTransaction()); + ASSERT_FALSE(db().CommitTransaction()); + ASSERT_FALSE(db().BeginTransaction()); + db().RollbackTransaction(); + + // Close normally to reset the poisoned flag. + db().Close(); + + // DEATH tests not supported on Android or iOS. +#if !defined(OS_ANDROID) && !defined(OS_IOS) + // Once the real Close() has been called, various calls enforce API + // usage by becoming fatal in debug mode. Since DEATH tests are + // expensive, just test one of them. + if (DLOG_IS_ON(FATAL)) { + ASSERT_DEATH({ + db().IsSQLValid(kSimpleSql); + }, "Illegal use of connection without a db"); + } +#endif +} + +// TODO(shess): Spin up a background thread to hold other_db, to more +// closely match real life. That would also allow testing +// RazeWithTimeout(). + +#if defined(OS_ANDROID) +TEST_F(SQLConnectionTest, SetTempDirForSQL) { + + sql::MetaTable meta_table; + // Below call needs a temporary directory in sqlite3 + // On Android, it can pass only when the temporary directory is set. + // Otherwise, sqlite3 doesn't find the correct directory to store + // temporary files and will report the error 'unable to open + // database file'. + ASSERT_TRUE(meta_table.Init(&db(), 4, 4)); +} +#endif + +TEST_F(SQLConnectionTest, Delete) { + EXPECT_TRUE(db().Execute("CREATE TABLE x (x)")); + db().Close(); + + // Should have both a main database file and a journal file because + // of journal_mode PERSIST. + base::FilePath journal(db_path().value() + FILE_PATH_LITERAL("-journal")); + ASSERT_TRUE(base::PathExists(db_path())); + ASSERT_TRUE(base::PathExists(journal)); + + sql::Connection::Delete(db_path()); + EXPECT_FALSE(base::PathExists(db_path())); + EXPECT_FALSE(base::PathExists(journal)); +} + +#if defined(OS_POSIX) +// Test that set_restrict_to_user() trims database permissions so that +// only the owner (and root) can read. +TEST_F(SQLConnectionTest, UserPermission) { + // If the bots all had a restrictive umask setting such that + // databases are always created with only the owner able to read + // them, then the code could break without breaking the tests. + // Temporarily provide a more permissive umask. + db().Close(); + sql::Connection::Delete(db_path()); + ASSERT_FALSE(base::PathExists(db_path())); + ScopedUmaskSetter permissive_umask(S_IWGRP | S_IWOTH); + ASSERT_TRUE(db().Open(db_path())); + + // Cause the journal file to be created. If the default + // journal_mode is changed back to DELETE, then parts of this test + // will need to be updated. + EXPECT_TRUE(db().Execute("CREATE TABLE x (x)")); + + base::FilePath journal(db_path().value() + FILE_PATH_LITERAL("-journal")); + int mode; + + // Given a permissive umask, the database is created with permissive + // read access for the database and journal. + ASSERT_TRUE(base::PathExists(db_path())); + ASSERT_TRUE(base::PathExists(journal)); + mode = file_util::FILE_PERMISSION_MASK; + EXPECT_TRUE(file_util::GetPosixFilePermissions(db_path(), &mode)); + ASSERT_NE((mode & file_util::FILE_PERMISSION_USER_MASK), mode); + mode = file_util::FILE_PERMISSION_MASK; + EXPECT_TRUE(file_util::GetPosixFilePermissions(journal, &mode)); + ASSERT_NE((mode & file_util::FILE_PERMISSION_USER_MASK), mode); + + // Re-open with restricted permissions and verify that the modes + // changed for both the main database and the journal. + db().Close(); + db().set_restrict_to_user(); + ASSERT_TRUE(db().Open(db_path())); + ASSERT_TRUE(base::PathExists(db_path())); + ASSERT_TRUE(base::PathExists(journal)); + mode = file_util::FILE_PERMISSION_MASK; + EXPECT_TRUE(file_util::GetPosixFilePermissions(db_path(), &mode)); + ASSERT_EQ((mode & file_util::FILE_PERMISSION_USER_MASK), mode); + mode = file_util::FILE_PERMISSION_MASK; + EXPECT_TRUE(file_util::GetPosixFilePermissions(journal, &mode)); + ASSERT_EQ((mode & file_util::FILE_PERMISSION_USER_MASK), mode); + + // Delete and re-create the database, the restriction should still apply. + db().Close(); + sql::Connection::Delete(db_path()); + ASSERT_TRUE(db().Open(db_path())); + ASSERT_TRUE(base::PathExists(db_path())); + ASSERT_FALSE(base::PathExists(journal)); + mode = file_util::FILE_PERMISSION_MASK; + EXPECT_TRUE(file_util::GetPosixFilePermissions(db_path(), &mode)); + ASSERT_EQ((mode & file_util::FILE_PERMISSION_USER_MASK), mode); + + // Verify that journal creation inherits the restriction. + EXPECT_TRUE(db().Execute("CREATE TABLE x (x)")); + ASSERT_TRUE(base::PathExists(journal)); + mode = file_util::FILE_PERMISSION_MASK; + EXPECT_TRUE(file_util::GetPosixFilePermissions(journal, &mode)); + ASSERT_EQ((mode & file_util::FILE_PERMISSION_USER_MASK), mode); +} +#endif // defined(OS_POSIX) + +// Test that errors start happening once Poison() is called. +TEST_F(SQLConnectionTest, Poison) { + EXPECT_TRUE(db().Execute("CREATE TABLE x (x)")); + + // Before the Poison() call, things generally work. + EXPECT_TRUE(db().IsSQLValid("INSERT INTO x VALUES ('x')")); + EXPECT_TRUE(db().Execute("INSERT INTO x VALUES ('x')")); + { + sql::Statement s(db().GetUniqueStatement("SELECT COUNT(*) FROM x")); + ASSERT_TRUE(s.is_valid()); + ASSERT_TRUE(s.Step()); + } + + // Get a statement which is valid before and will exist across Poison(). + sql::Statement valid_statement( + db().GetUniqueStatement("SELECT COUNT(*) FROM sqlite_master")); + ASSERT_TRUE(valid_statement.is_valid()); + ASSERT_TRUE(valid_statement.Step()); + valid_statement.Reset(true); + + db().Poison(); + + // After the Poison() call, things fail. + EXPECT_FALSE(db().IsSQLValid("INSERT INTO x VALUES ('x')")); + EXPECT_FALSE(db().Execute("INSERT INTO x VALUES ('x')")); + { + sql::Statement s(db().GetUniqueStatement("SELECT COUNT(*) FROM x")); + ASSERT_FALSE(s.is_valid()); + ASSERT_FALSE(s.Step()); + } + + // The existing statement has become invalid. + ASSERT_FALSE(valid_statement.is_valid()); + ASSERT_FALSE(valid_statement.Step()); +} + +// Test attaching and detaching databases from the connection. +TEST_F(SQLConnectionTest, Attach) { + EXPECT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + + // Create a database to attach to. + base::FilePath attach_path = + db_path().DirName().AppendASCII("SQLConnectionAttach.db"); + const char kAttachmentPoint[] = "other"; + { + sql::Connection other_db; + ASSERT_TRUE(other_db.Open(attach_path)); + EXPECT_TRUE(other_db.Execute("CREATE TABLE bar (a, b)")); + EXPECT_TRUE(other_db.Execute("INSERT INTO bar VALUES ('hello', 'world')")); + } + + // Cannot see the attached database, yet. + EXPECT_FALSE(db().IsSQLValid("SELECT count(*) from other.bar")); + + // Attach fails in a transaction. + EXPECT_TRUE(db().BeginTransaction()); + { + sql::ScopedErrorIgnorer ignore_errors; + ignore_errors.IgnoreError(SQLITE_ERROR); + EXPECT_FALSE(db().AttachDatabase(attach_path, kAttachmentPoint)); + ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); + } + + // Attach succeeds when the transaction is closed. + db().RollbackTransaction(); + EXPECT_TRUE(db().AttachDatabase(attach_path, kAttachmentPoint)); + EXPECT_TRUE(db().IsSQLValid("SELECT count(*) from other.bar")); + + // Queries can touch both databases. + EXPECT_TRUE(db().Execute("INSERT INTO foo SELECT a, b FROM other.bar")); + { + sql::Statement s(db().GetUniqueStatement("SELECT COUNT(*) FROM foo")); + ASSERT_TRUE(s.Step()); + EXPECT_EQ(1, s.ColumnInt(0)); + } + + // Detach also fails in a transaction. + EXPECT_TRUE(db().BeginTransaction()); + { + sql::ScopedErrorIgnorer ignore_errors; + ignore_errors.IgnoreError(SQLITE_ERROR); + EXPECT_FALSE(db().DetachDatabase(kAttachmentPoint)); + EXPECT_TRUE(db().IsSQLValid("SELECT count(*) from other.bar")); + ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); + } + + // Detach succeeds outside of a transaction. + db().RollbackTransaction(); + EXPECT_TRUE(db().DetachDatabase(kAttachmentPoint)); + + EXPECT_FALSE(db().IsSQLValid("SELECT count(*) from other.bar")); +} + +} // namespace diff --git a/chromium/sql/error_delegate_util.cc b/chromium/sql/error_delegate_util.cc new file mode 100644 index 00000000000..37fe006947e --- /dev/null +++ b/chromium/sql/error_delegate_util.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sql/error_delegate_util.h" + +#include "third_party/sqlite/sqlite3.h" + +namespace sql { + +bool IsErrorCatastrophic(int error) { + switch (error) { + case SQLITE_DONE: + case SQLITE_OK: + // Theoretically, the wrapped delegate might have resolved the error, and + // we would end up here. + return false; + + case SQLITE_CORRUPT: + case SQLITE_NOTADB: + // Highly unlikely we would ever recover from these. + return true; + + case SQLITE_CANTOPEN: + // TODO(erikwright): Figure out what this means. + return false; + + case SQLITE_IOERR: + // This could be broken blocks, in which case deleting the DB would be a + // good idea. But it might also be transient. + // TODO(erikwright): Figure out if we can distinguish between the two, + // or determine through metrics analysis to what extent these failures are + // transient. + return false; + + case SQLITE_BUSY: + // Presumably transient. + return false; + + case SQLITE_TOOBIG: + case SQLITE_FULL: + case SQLITE_NOMEM: + // Not a problem with the database. + return false; + + case SQLITE_READONLY: + // Presumably either transient or we don't have the privileges to + // move/delete the file anyway. + return false; + + case SQLITE_CONSTRAINT: + case SQLITE_ERROR: + // These probgably indicate a programming error or a migration failure + // that we prefer not to mask. + return false; + + case SQLITE_LOCKED: + case SQLITE_INTERNAL: + case SQLITE_PERM: + case SQLITE_ABORT: + case SQLITE_INTERRUPT: + case SQLITE_NOTFOUND: + case SQLITE_PROTOCOL: + case SQLITE_EMPTY: + case SQLITE_SCHEMA: + case SQLITE_MISMATCH: + case SQLITE_MISUSE: + case SQLITE_NOLFS: + case SQLITE_AUTH: + case SQLITE_FORMAT: + case SQLITE_RANGE: + case SQLITE_ROW: + // None of these appear in error reports, so for now let's not try to + // guess at how to handle them. + return false; + } + return false; +} + +} // namespace sql diff --git a/chromium/sql/error_delegate_util.h b/chromium/sql/error_delegate_util.h new file mode 100644 index 00000000000..0c67c07ec78 --- /dev/null +++ b/chromium/sql/error_delegate_util.h @@ -0,0 +1,18 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SQL_ERROR_DELEGATE_UTIL_H_ +#define SQL_ERROR_DELEGATE_UTIL_H_ + +#include "sql/sql_export.h" + +namespace sql { + +// Returns true if it is highly unlikely that the database can recover from +// |error|. +SQL_EXPORT bool IsErrorCatastrophic(int error); + +} // namespace sql + +#endif // SQL_ERROR_DELEGATE_UTIL_H_ diff --git a/chromium/sql/init_status.h b/chromium/sql/init_status.h new file mode 100644 index 00000000000..8002b4353b3 --- /dev/null +++ b/chromium/sql/init_status.h @@ -0,0 +1,23 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef APP_SQL_INIT_STATUS_H_ +#define APP_SQL_INIT_STATUS_H_ + +namespace sql { + +// Used as the return value for some databases' init functions. +enum InitStatus { + INIT_OK, + + // Some error, usually I/O related opening the file. + INIT_FAILURE, + + // The database is from a future version of the app and cannot be read. + INIT_TOO_NEW, +}; + +} // namespace sql + +#endif // APP_SQL_INIT_STATUS_H_ diff --git a/chromium/sql/meta_table.cc b/chromium/sql/meta_table.cc new file mode 100644 index 00000000000..c7e803c1cc1 --- /dev/null +++ b/chromium/sql/meta_table.cc @@ -0,0 +1,155 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sql/meta_table.h" + +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "sql/connection.h" +#include "sql/statement.h" +#include "sql/transaction.h" + +namespace sql { + +// Key used in our meta table for version numbers. +static const char kVersionKey[] = "version"; +static const char kCompatibleVersionKey[] = "last_compatible_version"; + +MetaTable::MetaTable() : db_(NULL) { +} + +MetaTable::~MetaTable() { +} + +// static +bool MetaTable::DoesTableExist(sql::Connection* db) { + DCHECK(db); + return db->DoesTableExist("meta"); +} + +bool MetaTable::Init(Connection* db, int version, int compatible_version) { + DCHECK(!db_ && db); + db_ = db; + + // If values stored are null or missing entirely, 0 will be reported. + // Require new clients to start with a greater initial version. + DCHECK_GT(version, 0); + DCHECK_GT(compatible_version, 0); + + // Make sure the table is created an populated atomically. + sql::Transaction transaction(db_); + if (!transaction.Begin()) + return false; + + if (!DoesTableExist(db)) { + if (!db_->Execute("CREATE TABLE meta" + "(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY, value LONGVARCHAR)")) + return false; + + // Note: there is no index over the meta table. We currently only have a + // couple of keys, so it doesn't matter. If we start storing more stuff in + // there, we should create an index. + SetVersionNumber(version); + SetCompatibleVersionNumber(compatible_version); + } else { + db_->AddTaggedHistogram("Sqlite.Version", GetVersionNumber()); + } + return transaction.Commit(); +} + +void MetaTable::Reset() { + db_ = NULL; +} + +void MetaTable::SetVersionNumber(int version) { + DCHECK_GT(version, 0); + SetValue(kVersionKey, version); +} + +int MetaTable::GetVersionNumber() { + int version = 0; + return GetValue(kVersionKey, &version) ? version : 0; +} + +void MetaTable::SetCompatibleVersionNumber(int version) { + DCHECK_GT(version, 0); + SetValue(kCompatibleVersionKey, version); +} + +int MetaTable::GetCompatibleVersionNumber() { + int version = 0; + return GetValue(kCompatibleVersionKey, &version) ? version : 0; +} + +bool MetaTable::SetValue(const char* key, const std::string& value) { + Statement s; + PrepareSetStatement(&s, key); + s.BindString(1, value); + return s.Run(); +} + +bool MetaTable::SetValue(const char* key, int value) { + Statement s; + PrepareSetStatement(&s, key); + s.BindInt(1, value); + return s.Run(); +} + +bool MetaTable::SetValue(const char* key, int64 value) { + Statement s; + PrepareSetStatement(&s, key); + s.BindInt64(1, value); + return s.Run(); +} + +bool MetaTable::GetValue(const char* key, std::string* value) { + Statement s; + if (!PrepareGetStatement(&s, key)) + return false; + + *value = s.ColumnString(0); + return true; +} + +bool MetaTable::GetValue(const char* key, int* value) { + Statement s; + if (!PrepareGetStatement(&s, key)) + return false; + + *value = s.ColumnInt(0); + return true; +} + +bool MetaTable::GetValue(const char* key, int64* value) { + Statement s; + if (!PrepareGetStatement(&s, key)) + return false; + + *value = s.ColumnInt64(0); + return true; +} + +bool MetaTable::DeleteKey(const char* key) { + DCHECK(db_); + Statement s(db_->GetUniqueStatement("DELETE FROM meta WHERE key=?")); + s.BindCString(0, key); + return s.Run(); +} + +void MetaTable::PrepareSetStatement(Statement* statement, const char* key) { + DCHECK(db_ && statement); + statement->Assign(db_->GetCachedStatement(SQL_FROM_HERE, + "INSERT OR REPLACE INTO meta (key,value) VALUES (?,?)")); + statement->BindCString(0, key); +} + +bool MetaTable::PrepareGetStatement(Statement* statement, const char* key) { + DCHECK(db_ && statement); + statement->Assign(db_->GetCachedStatement(SQL_FROM_HERE, + "SELECT value FROM meta WHERE key=?")); + statement->BindCString(0, key); + return statement->Step(); +} + +} // namespace sql diff --git a/chromium/sql/meta_table.h b/chromium/sql/meta_table.h new file mode 100644 index 00000000000..0f4ee72c3f9 --- /dev/null +++ b/chromium/sql/meta_table.h @@ -0,0 +1,87 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SQL_META_TABLE_H_ +#define SQL_META_TABLE_H_ + +#include <string> + +#include "base/basictypes.h" +#include "sql/sql_export.h" + +namespace sql { + +class Connection; +class Statement; + +class SQL_EXPORT MetaTable { + public: + MetaTable(); + ~MetaTable(); + + // Returns true if the 'meta' table exists. + static bool DoesTableExist(Connection* db); + + // Initializes the MetaTableHelper, creating the meta table if necessary. For + // new tables, it will initialize the version number to |version| and the + // compatible version number to |compatible_version|. Versions must be + // greater than 0 to distinguish missing versions (see GetVersionNumber()). + bool Init(Connection* db, int version, int compatible_version); + + // Resets this MetaTable object, making another call to Init() possible. + void Reset(); + + // The version number of the database. This should be the version number of + // the creator of the file. The version number will be 0 if there is no + // previously set version number. + // + // See also Get/SetCompatibleVersionNumber(). + void SetVersionNumber(int version); + int GetVersionNumber(); + + // The compatible version number is the lowest version of the code that this + // database can be read by. If there are minor changes or additions, old + // versions of the code can still work with the database without failing. + // + // For example, if an optional column is added to a table in version 3, the + // new code will set the version to 3, and the compatible version to 2, since + // the code expecting version 2 databases can still read and write the table. + // + // Rule of thumb: check the version number when you're upgrading, but check + // the compatible version number to see if you can read the file at all. If + // it's larger than you code is expecting, fail. + // + // The compatible version number will be 0 if there is no previously set + // compatible version number. + void SetCompatibleVersionNumber(int version); + int GetCompatibleVersionNumber(); + + // Set the given arbitrary key with the given data. Returns true on success. + bool SetValue(const char* key, const std::string& value); + bool SetValue(const char* key, int value); + bool SetValue(const char* key, int64 value); + + // Retrieves the value associated with the given key. This will use sqlite's + // type conversion rules. It will return true on success. + bool GetValue(const char* key, std::string* value); + bool GetValue(const char* key, int* value); + bool GetValue(const char* key, int64* value); + + // Deletes the key from the table. + bool DeleteKey(const char* key); + + private: + // Conveniences to prepare the two types of statements used by + // MetaTableHelper. + void PrepareSetStatement(Statement* statement, const char* key); + bool PrepareGetStatement(Statement* statement, const char* key); + + Connection* db_; + + DISALLOW_COPY_AND_ASSIGN(MetaTable); +}; + +} // namespace sql + +#endif // SQL_META_TABLE_H_ diff --git a/chromium/sql/recovery.cc b/chromium/sql/recovery.cc new file mode 100644 index 00000000000..9016f8a7eff --- /dev/null +++ b/chromium/sql/recovery.cc @@ -0,0 +1,207 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sql/recovery.h" + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/metrics/sparse_histogram.h" +#include "sql/connection.h" +#include "third_party/sqlite/sqlite3.h" + +namespace sql { + +// static +scoped_ptr<Recovery> Recovery::Begin( + Connection* connection, + const base::FilePath& db_path) { + scoped_ptr<Recovery> r(new Recovery(connection)); + if (!r->Init(db_path)) { + // TODO(shess): Should Init() failure result in Raze()? + r->Shutdown(POISON); + return scoped_ptr<Recovery>(); + } + + return r.Pass(); +} + +// static +bool Recovery::Recovered(scoped_ptr<Recovery> r) { + return r->Backup(); +} + +// static +void Recovery::Unrecoverable(scoped_ptr<Recovery> r) { + CHECK(r->db_); + // ~Recovery() will RAZE_AND_POISON. +} + +Recovery::Recovery(Connection* connection) + : db_(connection), + recover_db_() { + // Result should keep the page size specified earlier. + if (db_->page_size_) + recover_db_.set_page_size(db_->page_size_); + + // TODO(shess): This may not handle cases where the default page + // size is used, but the default has changed. I do not think this + // has ever happened. This could be handled by using "PRAGMA + // page_size", at the cost of potential additional failure cases. +} + +Recovery::~Recovery() { + Shutdown(RAZE_AND_POISON); +} + +bool Recovery::Init(const base::FilePath& db_path) { + // Prevent the possibility of re-entering this code due to errors + // which happen while executing this code. + DCHECK(!db_->has_error_callback()); + + // Break any outstanding transactions on the original database to + // prevent deadlocks reading through the attached version. + // TODO(shess): A client may legitimately wish to recover from + // within the transaction context, because it would potentially + // preserve any in-flight changes. Unfortunately, any attach-based + // system could not handle that. A system which manually queried + // one database and stored to the other possibly could, but would be + // more complicated. + db_->RollbackAllTransactions(); + + if (!recover_db_.OpenTemporary()) + return false; + + // TODO(shess): Figure out a story for USE_SYSTEM_SQLITE. The + // virtual table implementation relies on SQLite internals for some + // types and functions, which could be copied inline to make it + // standalone. Or an alternate implementation could try to read + // through errors entirely at the SQLite level. + // + // For now, defer to the caller. The setup will succeed, but the + // later CREATE VIRTUAL TABLE call will fail, at which point the + // caller can fire Unrecoverable(). +#if !defined(USE_SYSTEM_SQLITE) + int rc = recoverVtableInit(recover_db_.db_); + if (rc != SQLITE_OK) { + LOG(ERROR) << "Failed to initialize recover module: " + << recover_db_.GetErrorMessage(); + return false; + } +#endif + + // Turn on |SQLITE_RecoveryMode| for the handle, which allows + // reading certain broken databases. + if (!recover_db_.Execute("PRAGMA writable_schema=1")) + return false; + + if (!recover_db_.AttachDatabase(db_path, "corrupt")) + return false; + + return true; +} + +bool Recovery::Backup() { + CHECK(db_); + CHECK(recover_db_.is_open()); + + // TODO(shess): Some of the failure cases here may need further + // exploration. Just as elsewhere, persistent problems probably + // need to be razed, while anything which might succeed on a future + // run probably should be allowed to try. But since Raze() uses the + // same approach, even that wouldn't work when this code fails. + // + // The documentation for the backup system indicate a relatively + // small number of errors are expected: + // SQLITE_BUSY - cannot lock the destination database. This should + // only happen if someone has another handle to the + // database, Chromium generally doesn't do that. + // SQLITE_LOCKED - someone locked the source database. Should be + // impossible (perhaps anti-virus could?). + // SQLITE_READONLY - destination is read-only. + // SQLITE_IOERR - since source database is temporary, probably + // indicates that the destination contains blocks + // throwing errors, or gross filesystem errors. + // SQLITE_NOMEM - out of memory, should be transient. + // + // AFAICT, SQLITE_BUSY and SQLITE_NOMEM could perhaps be considered + // transient, with SQLITE_LOCKED being unclear. + // + // SQLITE_READONLY and SQLITE_IOERR are probably persistent, with a + // strong chance that Raze() would not resolve them. If Delete() + // deletes the database file, the code could then re-open the file + // and attempt the backup again. + // + // For now, this code attempts a best effort and records histograms + // to inform future development. + + // Backup the original db from the recovered db. + const char* kMain = "main"; + sqlite3_backup* backup = sqlite3_backup_init(db_->db_, kMain, + recover_db_.db_, kMain); + if (!backup) { + // Error code is in the destination database handle. + int err = sqlite3_errcode(db_->db_); + UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryHandle", err); + LOG(ERROR) << "sqlite3_backup_init() failed: " + << sqlite3_errmsg(db_->db_); + return false; + } + + // -1 backs up the entire database. + int rc = sqlite3_backup_step(backup, -1); + int pages = sqlite3_backup_pagecount(backup); + // TODO(shess): sqlite3_backup_finish() appears to allow returning a + // different value from sqlite3_backup_step(). Circle back and + // figure out if that can usefully inform the decision of whether to + // retry or not. + sqlite3_backup_finish(backup); + DCHECK_GT(pages, 0); + + if (rc != SQLITE_DONE) { + UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryStep", rc); + LOG(ERROR) << "sqlite3_backup_step() failed: " + << sqlite3_errmsg(db_->db_); + } + + // The destination database was locked. Give up, but leave the data + // in place. Maybe it won't be locked next time. + if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) { + Shutdown(POISON); + return false; + } + + // Running out of memory should be transient, retry later. + if (rc == SQLITE_NOMEM) { + Shutdown(POISON); + return false; + } + + // TODO(shess): For now, leave the original database alone, pending + // results from Sqlite.RecoveryStep. Some errors should probably + // route to RAZE_AND_POISON. + if (rc != SQLITE_DONE) { + Shutdown(POISON); + return false; + } + + // Clean up the recovery db, and terminate the main database + // connection. + Shutdown(POISON); + return true; +} + +void Recovery::Shutdown(Recovery::Disposition raze) { + if (!db_) + return; + + recover_db_.Close(); + if (raze == RAZE_AND_POISON) { + db_->RazeAndClose(); + } else if (raze == POISON) { + db_->Poison(); + } + db_ = NULL; +} + +} // namespace sql diff --git a/chromium/sql/recovery.h b/chromium/sql/recovery.h new file mode 100644 index 00000000000..c0bb6da236d --- /dev/null +++ b/chromium/sql/recovery.h @@ -0,0 +1,106 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SQL_RECOVERY_H_ +#define SQL_RECOVERY_H_ + +#include "base/basictypes.h" + +#include "sql/connection.h" + +namespace base { +class FilePath; +} + +namespace sql { + +// Recovery module for sql/. The basic idea is to create a fresh +// database and populate it with the recovered contents of the +// original database. If recovery is successful, the recovered +// database is backed up over the original database. If recovery is +// not successful, the original database is razed. In either case, +// the original handle is poisoned so that operations on the stack do +// not accidentally disrupt the restored data. +// +// { +// scoped_ptr<sql::Recovery> r = +// sql::Recovery::Begin(orig_db, orig_db_path); +// if (r) { +// if (r.db()->Execute(kCreateSchemaSql) && +// r.db()->Execute(kCopyDataFromOrigSql)) { +// sql::Recovery::Recovered(r.Pass()); +// } +// } +// } +// +// If Recovered() is not called, then RazeAndClose() is called on +// orig_db. + +class SQL_EXPORT Recovery { + public: + ~Recovery(); + + // Begin the recovery process by opening a temporary database handle + // and attach the existing database to it at "corrupt". To prevent + // deadlock, all transactions on |connection| are rolled back. + // + // Returns NULL in case of failure, with no cleanup done on the + // original connection (except for breaking the transactions). The + // caller should Raze() or otherwise cleanup as appropriate. + // + // TODO(shess): Later versions of SQLite allow extracting the path + // from the connection. + // TODO(shess): Allow specifying the connection point? + static scoped_ptr<Recovery> Begin( + Connection* connection, + const base::FilePath& db_path) WARN_UNUSED_RESULT; + + // Mark recovery completed by replicating the recovery database over + // the original database, then closing the recovery database. The + // original database handle is poisoned, causing future calls + // against it to fail. + // + // If Recovered() is not called, the destructor will call + // Unrecoverable(). + // + // TODO(shess): At this time, this function an fail while leaving + // the original database intact. Figure out which failure cases + // should go to RazeAndClose() instead. + static bool Recovered(scoped_ptr<Recovery> r) WARN_UNUSED_RESULT; + + // Indicate that the database is unrecoverable. The original + // database is razed, and the handle poisoned. + static void Unrecoverable(scoped_ptr<Recovery> r); + + // Handle to the temporary recovery database. + sql::Connection* db() { return &recover_db_; } + + private: + explicit Recovery(Connection* connection); + + // Setup the recovery database handle for Begin(). Returns false in + // case anything failed. + bool Init(const base::FilePath& db_path) WARN_UNUSED_RESULT; + + // Copy the recovered database over the original database. + bool Backup() WARN_UNUSED_RESULT; + + // Close the recovery database, and poison the original handle. + // |raze| controls whether the original database is razed or just + // poisoned. + enum Disposition { + RAZE_AND_POISON, + POISON, + }; + void Shutdown(Disposition raze); + + Connection* db_; // Original database connection. + Connection recover_db_; // Recovery connection. + + DISALLOW_COPY_AND_ASSIGN(Recovery); +}; + +} // namespace sql + +#endif // SQL_RECOVERY_H_ diff --git a/chromium/sql/recovery_unittest.cc b/chromium/sql/recovery_unittest.cc new file mode 100644 index 00000000000..fc7c2f2dc5b --- /dev/null +++ b/chromium/sql/recovery_unittest.cc @@ -0,0 +1,425 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "sql/connection.h" +#include "sql/meta_table.h" +#include "sql/recovery.h" +#include "sql/statement.h" +#include "sql/test/scoped_error_ignorer.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/sqlite/sqlite3.h" + +namespace { + +// Execute |sql|, and stringify the results with |column_sep| between +// columns and |row_sep| between rows. +// TODO(shess): Promote this to a central testing helper. +std::string ExecuteWithResults(sql::Connection* db, + const char* sql, + const char* column_sep, + const char* row_sep) { + sql::Statement s(db->GetUniqueStatement(sql)); + std::string ret; + while (s.Step()) { + if (!ret.empty()) + ret += row_sep; + for (int i = 0; i < s.ColumnCount(); ++i) { + if (i > 0) + ret += column_sep; + ret += s.ColumnString(i); + } + } + return ret; +} + +// Dump consistent human-readable representation of the database +// schema. For tables or indices, this will contain the sql command +// to create the table or index. For certain automatic SQLite +// structures with no sql, the name is used. +std::string GetSchema(sql::Connection* db) { + const char kSql[] = + "SELECT COALESCE(sql, name) FROM sqlite_master ORDER BY 1"; + return ExecuteWithResults(db, kSql, "|", "\n"); +} + +int GetPageSize(sql::Connection* db) { + sql::Statement s(db->GetUniqueStatement("PRAGMA page_size")); + EXPECT_TRUE(s.Step()); + return s.ColumnInt(0); +} + +// Get |name|'s root page number in the database. +int GetRootPage(sql::Connection* db, const char* name) { + const char kPageSql[] = "SELECT rootpage FROM sqlite_master WHERE name = ?"; + sql::Statement s(db->GetUniqueStatement(kPageSql)); + s.BindString(0, name); + EXPECT_TRUE(s.Step()); + return s.ColumnInt(0); +} + +// Helper to read a SQLite page into a buffer. |page_no| is 1-based +// per SQLite usage. +bool ReadPage(const base::FilePath& path, size_t page_no, + char* buf, size_t page_size) { + file_util::ScopedFILE file(file_util::OpenFile(path, "rb")); + if (!file.get()) + return false; + if (0 != fseek(file.get(), (page_no - 1) * page_size, SEEK_SET)) + return false; + if (1u != fread(buf, page_size, 1, file.get())) + return false; + return true; +} + +// Helper to write a SQLite page into a buffer. |page_no| is 1-based +// per SQLite usage. +bool WritePage(const base::FilePath& path, size_t page_no, + const char* buf, size_t page_size) { + file_util::ScopedFILE file(file_util::OpenFile(path, "rb+")); + if (!file.get()) + return false; + if (0 != fseek(file.get(), (page_no - 1) * page_size, SEEK_SET)) + return false; + if (1u != fwrite(buf, page_size, 1, file.get())) + return false; + return true; +} + +class SQLRecoveryTest : public testing::Test { + public: + SQLRecoveryTest() {} + + virtual void SetUp() { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + ASSERT_TRUE(db_.Open(db_path())); + } + + virtual void TearDown() { + db_.Close(); + } + + sql::Connection& db() { return db_; } + + base::FilePath db_path() { + return temp_dir_.path().AppendASCII("SQLRecoveryTest.db"); + } + + bool Reopen() { + db_.Close(); + return db_.Open(db_path()); + } + + private: + base::ScopedTempDir temp_dir_; + sql::Connection db_; +}; + +TEST_F(SQLRecoveryTest, RecoverBasic) { + const char kCreateSql[] = "CREATE TABLE x (t TEXT)"; + const char kInsertSql[] = "INSERT INTO x VALUES ('This is a test')"; + ASSERT_TRUE(db().Execute(kCreateSql)); + ASSERT_TRUE(db().Execute(kInsertSql)); + ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db())); + + // If the Recovery handle goes out of scope without being + // Recovered(), the database is razed. + { + scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); + ASSERT_TRUE(recovery.get()); + } + EXPECT_FALSE(db().is_open()); + ASSERT_TRUE(Reopen()); + EXPECT_TRUE(db().is_open()); + ASSERT_EQ("", GetSchema(&db())); + + // Recreate the database. + ASSERT_TRUE(db().Execute(kCreateSql)); + ASSERT_TRUE(db().Execute(kInsertSql)); + ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db())); + + // Unrecoverable() also razes. + { + scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); + ASSERT_TRUE(recovery.get()); + sql::Recovery::Unrecoverable(recovery.Pass()); + + // TODO(shess): Test that calls to recover.db() start failing. + } + EXPECT_FALSE(db().is_open()); + ASSERT_TRUE(Reopen()); + EXPECT_TRUE(db().is_open()); + ASSERT_EQ("", GetSchema(&db())); + + // Recreate the database. + ASSERT_TRUE(db().Execute(kCreateSql)); + ASSERT_TRUE(db().Execute(kInsertSql)); + ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db())); + + // Recovered() replaces the original with the "recovered" version. + { + scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); + ASSERT_TRUE(recovery.get()); + + // Create the new version of the table. + ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); + + // Insert different data to distinguish from original database. + const char kAltInsertSql[] = "INSERT INTO x VALUES ('That was a test')"; + ASSERT_TRUE(recovery->db()->Execute(kAltInsertSql)); + + // Successfully recovered. + ASSERT_TRUE(sql::Recovery::Recovered(recovery.Pass())); + } + EXPECT_FALSE(db().is_open()); + ASSERT_TRUE(Reopen()); + EXPECT_TRUE(db().is_open()); + ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db())); + + const char* kXSql = "SELECT * FROM x ORDER BY 1"; + ASSERT_EQ("That was a test", + ExecuteWithResults(&db(), kXSql, "|", "\n")); +} + +// The recovery virtual table is only supported for Chromium's SQLite. +#if !defined(USE_SYSTEM_SQLITE) + +// Run recovery through its paces on a valid database. +TEST_F(SQLRecoveryTest, VirtualTable) { + const char kCreateSql[] = "CREATE TABLE x (t TEXT)"; + ASSERT_TRUE(db().Execute(kCreateSql)); + ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('This is a test')")); + ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('That was a test')")); + + // Successfully recover the database. + { + scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); + + // Tables to recover original DB, now at [corrupt]. + const char kRecoveryCreateSql[] = + "CREATE VIRTUAL TABLE temp.recover_x using recover(" + " corrupt.x," + " t TEXT STRICT" + ")"; + ASSERT_TRUE(recovery->db()->Execute(kRecoveryCreateSql)); + + // Re-create the original schema. + ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); + + // Copy the data from the recovery tables to the new database. + const char kRecoveryCopySql[] = + "INSERT INTO x SELECT t FROM recover_x"; + ASSERT_TRUE(recovery->db()->Execute(kRecoveryCopySql)); + + // Successfully recovered. + ASSERT_TRUE(sql::Recovery::Recovered(recovery.Pass())); + } + + // Since the database was not corrupt, the entire schema and all + // data should be recovered. + ASSERT_TRUE(Reopen()); + ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db())); + + const char* kXSql = "SELECT * FROM x ORDER BY 1"; + ASSERT_EQ("That was a test\nThis is a test", + ExecuteWithResults(&db(), kXSql, "|", "\n")); +} + +void RecoveryCallback(sql::Connection* db, const base::FilePath& db_path, + int* record_error, int error, sql::Statement* stmt) { + *record_error = error; + + // Clear the error callback to prevent reentrancy. + db->reset_error_callback(); + + scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(db, db_path); + ASSERT_TRUE(recovery.get()); + + const char kRecoveryCreateSql[] = + "CREATE VIRTUAL TABLE temp.recover_x using recover(" + " corrupt.x," + " id INTEGER STRICT," + " v INTEGER STRICT" + ")"; + const char kCreateTable[] = "CREATE TABLE x (id INTEGER, v INTEGER)"; + const char kCreateIndex[] = "CREATE UNIQUE INDEX x_id ON x (id)"; + + // Replicate data over. + const char kRecoveryCopySql[] = + "INSERT OR REPLACE INTO x SELECT id, v FROM recover_x"; + + ASSERT_TRUE(recovery->db()->Execute(kRecoveryCreateSql)); + ASSERT_TRUE(recovery->db()->Execute(kCreateTable)); + ASSERT_TRUE(recovery->db()->Execute(kCreateIndex)); + ASSERT_TRUE(recovery->db()->Execute(kRecoveryCopySql)); + + ASSERT_TRUE(sql::Recovery::Recovered(recovery.Pass())); +} + +// Build a database, corrupt it by making an index reference to +// deleted row, then recover when a query selects that row. +TEST_F(SQLRecoveryTest, RecoverCorruptIndex) { + const char kCreateTable[] = "CREATE TABLE x (id INTEGER, v INTEGER)"; + const char kCreateIndex[] = "CREATE UNIQUE INDEX x_id ON x (id)"; + ASSERT_TRUE(db().Execute(kCreateTable)); + ASSERT_TRUE(db().Execute(kCreateIndex)); + + // Insert a bit of data. + { + ASSERT_TRUE(db().BeginTransaction()); + + const char kInsertSql[] = "INSERT INTO x (id, v) VALUES (?, ?)"; + sql::Statement s(db().GetUniqueStatement(kInsertSql)); + for (int i = 0; i < 10; ++i) { + s.Reset(true); + s.BindInt(0, i); + s.BindInt(1, i); + EXPECT_FALSE(s.Step()); + EXPECT_TRUE(s.Succeeded()); + } + + ASSERT_TRUE(db().CommitTransaction()); + } + + + // Capture the index's root page into |buf|. + int index_page = GetRootPage(&db(), "x_id"); + int page_size = GetPageSize(&db()); + scoped_ptr<char[]> buf(new char[page_size]); + ASSERT_TRUE(ReadPage(db_path(), index_page, buf.get(), page_size)); + + // Delete the row from the table and index. + ASSERT_TRUE(db().Execute("DELETE FROM x WHERE id = 0")); + + // Close to clear any cached data. + db().Close(); + + // Put the stale index page back. + ASSERT_TRUE(WritePage(db_path(), index_page, buf.get(), page_size)); + + // At this point, the index references a value not in the table. + + ASSERT_TRUE(Reopen()); + + int error = SQLITE_OK; + db().set_error_callback(base::Bind(&RecoveryCallback, + &db(), db_path(), &error)); + + // This works before the callback is called. + const char kTrivialSql[] = "SELECT COUNT(*) FROM sqlite_master"; + EXPECT_TRUE(db().IsSQLValid(kTrivialSql)); + + // TODO(shess): Could this be delete? Anything which fails should work. + const char kSelectSql[] = "SELECT v FROM x WHERE id = 0"; + ASSERT_FALSE(db().Execute(kSelectSql)); + EXPECT_EQ(SQLITE_CORRUPT, error); + + // Database handle has been poisoned. + EXPECT_FALSE(db().IsSQLValid(kTrivialSql)); + + ASSERT_TRUE(Reopen()); + + // The recovered table should reflect the deletion. + const char kSelectAllSql[] = "SELECT v FROM x ORDER BY id"; + EXPECT_EQ("1,2,3,4,5,6,7,8,9", + ExecuteWithResults(&db(), kSelectAllSql, "|", ",")); + + // The failing statement should now succeed, with no results. + EXPECT_EQ("", ExecuteWithResults(&db(), kSelectSql, "|", ",")); +} + +// Build a database, corrupt it by making a table contain a row not +// referenced by the index, then recover the database. +TEST_F(SQLRecoveryTest, RecoverCorruptTable) { + const char kCreateTable[] = "CREATE TABLE x (id INTEGER, v INTEGER)"; + const char kCreateIndex[] = "CREATE UNIQUE INDEX x_id ON x (id)"; + ASSERT_TRUE(db().Execute(kCreateTable)); + ASSERT_TRUE(db().Execute(kCreateIndex)); + + // Insert a bit of data. + { + ASSERT_TRUE(db().BeginTransaction()); + + const char kInsertSql[] = "INSERT INTO x (id, v) VALUES (?, ?)"; + sql::Statement s(db().GetUniqueStatement(kInsertSql)); + for (int i = 0; i < 10; ++i) { + s.Reset(true); + s.BindInt(0, i); + s.BindInt(1, i); + EXPECT_FALSE(s.Step()); + EXPECT_TRUE(s.Succeeded()); + } + + ASSERT_TRUE(db().CommitTransaction()); + } + + // Capture the table's root page into |buf|. + // Find the page the table is stored on. + const int table_page = GetRootPage(&db(), "x"); + const int page_size = GetPageSize(&db()); + scoped_ptr<char[]> buf(new char[page_size]); + ASSERT_TRUE(ReadPage(db_path(), table_page, buf.get(), page_size)); + + // Delete the row from the table and index. + ASSERT_TRUE(db().Execute("DELETE FROM x WHERE id = 0")); + + // Close to clear any cached data. + db().Close(); + + // Put the stale table page back. + ASSERT_TRUE(WritePage(db_path(), table_page, buf.get(), page_size)); + + // At this point, the table contains a value not referenced by the + // index. + // TODO(shess): Figure out a query which causes SQLite to notice + // this organically. Meanwhile, just handle it manually. + + ASSERT_TRUE(Reopen()); + + // Index shows one less than originally inserted. + const char kCountSql[] = "SELECT COUNT (*) FROM x"; + EXPECT_EQ("9", ExecuteWithResults(&db(), kCountSql, "|", ",")); + + // A full table scan shows all of the original data. + const char kDistinctSql[] = "SELECT DISTINCT COUNT (id) FROM x"; + EXPECT_EQ("10", ExecuteWithResults(&db(), kDistinctSql, "|", ",")); + + // Insert id 0 again. Since it is not in the index, the insert + // succeeds, but results in a duplicate value in the table. + const char kInsertSql[] = "INSERT INTO x (id, v) VALUES (0, 100)"; + ASSERT_TRUE(db().Execute(kInsertSql)); + + // Duplication is visible. + EXPECT_EQ("10", ExecuteWithResults(&db(), kCountSql, "|", ",")); + EXPECT_EQ("11", ExecuteWithResults(&db(), kDistinctSql, "|", ",")); + + // This works before the callback is called. + const char kTrivialSql[] = "SELECT COUNT(*) FROM sqlite_master"; + EXPECT_TRUE(db().IsSQLValid(kTrivialSql)); + + // Call the recovery callback manually. + int error = SQLITE_OK; + RecoveryCallback(&db(), db_path(), &error, SQLITE_CORRUPT, NULL); + EXPECT_EQ(SQLITE_CORRUPT, error); + + // Database handle has been poisoned. + EXPECT_FALSE(db().IsSQLValid(kTrivialSql)); + + ASSERT_TRUE(Reopen()); + + // The recovered table has consistency between the index and the table. + EXPECT_EQ("10", ExecuteWithResults(&db(), kCountSql, "|", ",")); + EXPECT_EQ("10", ExecuteWithResults(&db(), kDistinctSql, "|", ",")); + + // The expected value was retained. + const char kSelectSql[] = "SELECT v FROM x WHERE id = 0"; + EXPECT_EQ("100", ExecuteWithResults(&db(), kSelectSql, "|", ",")); +} +#endif // !defined(USE_SYSTEM_SQLITE) + +} // namespace diff --git a/chromium/sql/run_all_unittests.cc b/chromium/sql/run_all_unittests.cc new file mode 100644 index 00000000000..2c8d29c6d00 --- /dev/null +++ b/chromium/sql/run_all_unittests.cc @@ -0,0 +1,9 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_suite.h" + +int main(int argc, char** argv) { + return base::TestSuite(argc, argv).Run(); +} diff --git a/chromium/sql/sql.gyp b/chromium/sql/sql.gyp new file mode 100644 index 00000000000..49ace016eb3 --- /dev/null +++ b/chromium/sql/sql.gyp @@ -0,0 +1,136 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'variables': { + 'chromium_code': 1, + }, + 'targets': [ + { + 'target_name': 'sql', + 'type': '<(component)', + 'dependencies': [ + '../base/base.gyp:base', + '../third_party/sqlite/sqlite.gyp:sqlite', + '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + ], + 'export_dependent_settings': [ + '../base/base.gyp:base', + ], + 'defines': [ 'SQL_IMPLEMENTATION' ], + 'sources': [ + 'connection.cc', + 'connection.h', + 'error_delegate_util.cc', + 'error_delegate_util.h', + 'init_status.h', + 'meta_table.cc', + 'meta_table.h', + 'recovery.cc', + 'recovery.h', + 'statement.cc', + 'statement.h', + 'transaction.cc', + 'transaction.h', + ], + 'include_dirs': [ + '..', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '..', + ], + }, + # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. + 'msvs_disabled_warnings': [4267, ], + }, + { + 'target_name': 'test_support_sql', + 'type': 'static_library', + 'dependencies': [ + 'sql', + '../base/base.gyp:base', + '../testing/gtest.gyp:gtest', + ], + 'export_dependent_settings': [ + 'sql', + '../base/base.gyp:base', + ], + 'sources': [ + 'test/error_callback_support.cc', + 'test/error_callback_support.h', + 'test/scoped_error_ignorer.cc', + 'test/scoped_error_ignorer.h', + ], + 'include_dirs': [ + '..', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '..', + ], + }, + }, + { + 'target_name': 'sql_unittests', + 'type': '<(gtest_target_type)', + 'dependencies': [ + 'sql', + 'test_support_sql', + '../base/base.gyp:test_support_base', + '../testing/gtest.gyp:gtest', + '../third_party/sqlite/sqlite.gyp:sqlite', + ], + 'sources': [ + 'run_all_unittests.cc', + 'connection_unittest.cc', + 'recovery_unittest.cc', + 'sqlite_features_unittest.cc', + 'statement_unittest.cc', + 'transaction_unittest.cc', + ], + 'include_dirs': [ + '..', + ], + 'conditions': [ + ['os_posix==1 and OS!="mac" and OS!="ios"', { + 'conditions': [ + ['linux_use_tcmalloc==1', { + 'dependencies': [ + '../base/allocator/allocator.gyp:allocator', + ], + }], + ], + }], + ['OS == "android" and gtest_target_type == "shared_library"', { + 'dependencies': [ + '../testing/android/native_test.gyp:native_test_native_code', + ], + }], + ], + # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. + 'msvs_disabled_warnings': [4267, ], + }, + ], + 'conditions': [ + # Special target to wrap a gtest_target_type==shared_library + # sql_unittests into an android apk for execution. + ['OS == "android" and gtest_target_type == "shared_library"', { + 'targets': [ + { + 'target_name': 'sql_unittests_apk', + 'type': 'none', + 'dependencies': [ + 'sql_unittests', + ], + 'variables': { + 'test_suite_name': 'sql_unittests', + 'input_shlib_path': '<(SHARED_LIB_DIR)/<(SHARED_LIB_PREFIX)sql_unittests<(SHARED_LIB_SUFFIX)', + }, + 'includes': [ '../build/apk_test.gypi' ], + }, + ], + }], + ], +} diff --git a/chromium/sql/sql_export.h b/chromium/sql/sql_export.h new file mode 100644 index 00000000000..7ab33558499 --- /dev/null +++ b/chromium/sql/sql_export.h @@ -0,0 +1,29 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SQL_EXPORT_H_ +#define SQL_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(SQL_IMPLEMENTATION) +#define SQL_EXPORT __declspec(dllexport) +#else +#define SQL_EXPORT __declspec(dllimport) +#endif // defined(SQL_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(SQL_IMPLEMENTATION) +#define SQL_EXPORT __attribute__((visibility("default"))) +#else +#define SQL_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define SQL_EXPORT +#endif + +#endif // SQL_EXPORT_H_ diff --git a/chromium/sql/sqlite_features_unittest.cc b/chromium/sql/sqlite_features_unittest.cc new file mode 100644 index 00000000000..4fc730c2b81 --- /dev/null +++ b/chromium/sql/sqlite_features_unittest.cc @@ -0,0 +1,86 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "sql/connection.h" +#include "sql/statement.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/sqlite/sqlite3.h" + +// Test that certain features are/are-not enabled in our SQLite. + +namespace { + +void CaptureErrorCallback(int* error_pointer, std::string* sql_text, + int error, sql::Statement* stmt) { + *error_pointer = error; + const char* text = stmt ? stmt->GetSQLStatement() : NULL; + *sql_text = text ? text : "no statement available"; +} + +class SQLiteFeaturesTest : public testing::Test { + public: + SQLiteFeaturesTest() : error_(SQLITE_OK) {} + + virtual void SetUp() { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + ASSERT_TRUE(db_.Open(temp_dir_.path().AppendASCII("SQLStatementTest.db"))); + + // The error delegate will set |error_| and |sql_text_| when any sqlite + // statement operation returns an error code. + db_.set_error_callback(base::Bind(&CaptureErrorCallback, + &error_, &sql_text_)); + } + + virtual void TearDown() { + // If any error happened the original sql statement can be found in + // |sql_text_|. + EXPECT_EQ(SQLITE_OK, error_); + db_.Close(); + } + + sql::Connection& db() { return db_; } + + int sqlite_error() const { + return error_; + } + + private: + base::ScopedTempDir temp_dir_; + sql::Connection db_; + + // The error code of the most recent error. + int error_; + // Original statement which has caused the error. + std::string sql_text_; +}; + +// Do not include fts1 support, it is not useful, and nobody is +// looking at it. +TEST_F(SQLiteFeaturesTest, NoFTS1) { + ASSERT_EQ(SQLITE_ERROR, db().ExecuteAndReturnErrorCode( + "CREATE VIRTUAL TABLE foo USING fts1(x)")); +} + +#if !defined(OS_IOS) +// fts2 is used for older history files, so we're signed on for keeping our +// version up-to-date. iOS does not include fts2, so this test does not run on +// iOS. +// TODO(shess): Think up a crazy way to get out from having to support +// this forever. +TEST_F(SQLiteFeaturesTest, FTS2) { + ASSERT_TRUE(db().Execute("CREATE VIRTUAL TABLE foo USING fts2(x)")); +} +#endif + +// fts3 is used for current history files, and also for WebDatabase. +TEST_F(SQLiteFeaturesTest, FTS3) { + ASSERT_TRUE(db().Execute("CREATE VIRTUAL TABLE foo USING fts3(x)")); +} + +} // namespace diff --git a/chromium/sql/statement.cc b/chromium/sql/statement.cc new file mode 100644 index 00000000000..da2c58fd69d --- /dev/null +++ b/chromium/sql/statement.cc @@ -0,0 +1,316 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sql/statement.h" + +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "third_party/sqlite/sqlite3.h" + +namespace sql { + +// This empty constructor initializes our reference with an empty one so that +// we don't have to NULL-check the ref_ to see if the statement is valid: we +// only have to check the ref's validity bit. +Statement::Statement() + : ref_(new Connection::StatementRef(NULL, NULL, false)), + succeeded_(false) { +} + +Statement::Statement(scoped_refptr<Connection::StatementRef> ref) + : ref_(ref), + succeeded_(false) { +} + +Statement::~Statement() { + // Free the resources associated with this statement. We assume there's only + // one statement active for a given sqlite3_stmt at any time, so this won't + // mess with anything. + Reset(true); +} + +void Statement::Assign(scoped_refptr<Connection::StatementRef> ref) { + Reset(true); + ref_ = ref; +} + +void Statement::Clear() { + Assign(new Connection::StatementRef(NULL, NULL, false)); + succeeded_ = false; +} + +bool Statement::CheckValid() const { + // Allow operations to fail silently if a statement was invalidated + // because the database was closed by an error handler. + DLOG_IF(FATAL, !ref_->was_valid()) + << "Cannot call mutating statements on an invalid statement."; + return is_valid(); +} + +bool Statement::Run() { + ref_->AssertIOAllowed(); + if (!CheckValid()) + return false; + + return CheckError(sqlite3_step(ref_->stmt())) == SQLITE_DONE; +} + +bool Statement::Step() { + ref_->AssertIOAllowed(); + if (!CheckValid()) + return false; + + return CheckError(sqlite3_step(ref_->stmt())) == SQLITE_ROW; +} + +void Statement::Reset(bool clear_bound_vars) { + ref_->AssertIOAllowed(); + if (is_valid()) { + // We don't call CheckError() here because sqlite3_reset() returns + // the last error that Step() caused thereby generating a second + // spurious error callback. + if (clear_bound_vars) + sqlite3_clear_bindings(ref_->stmt()); + sqlite3_reset(ref_->stmt()); + } + + succeeded_ = false; +} + +bool Statement::Succeeded() const { + if (!is_valid()) + return false; + + return succeeded_; +} + +bool Statement::BindNull(int col) { + if (!is_valid()) + return false; + + return CheckOk(sqlite3_bind_null(ref_->stmt(), col + 1)); +} + +bool Statement::BindBool(int col, bool val) { + return BindInt(col, val ? 1 : 0); +} + +bool Statement::BindInt(int col, int val) { + if (!is_valid()) + return false; + + return CheckOk(sqlite3_bind_int(ref_->stmt(), col + 1, val)); +} + +bool Statement::BindInt64(int col, int64 val) { + if (!is_valid()) + return false; + + return CheckOk(sqlite3_bind_int64(ref_->stmt(), col + 1, val)); +} + +bool Statement::BindDouble(int col, double val) { + if (!is_valid()) + return false; + + return CheckOk(sqlite3_bind_double(ref_->stmt(), col + 1, val)); +} + +bool Statement::BindCString(int col, const char* val) { + if (!is_valid()) + return false; + + return CheckOk( + sqlite3_bind_text(ref_->stmt(), col + 1, val, -1, SQLITE_TRANSIENT)); +} + +bool Statement::BindString(int col, const std::string& val) { + if (!is_valid()) + return false; + + return CheckOk(sqlite3_bind_text(ref_->stmt(), + col + 1, + val.data(), + val.size(), + SQLITE_TRANSIENT)); +} + +bool Statement::BindString16(int col, const string16& value) { + return BindString(col, UTF16ToUTF8(value)); +} + +bool Statement::BindBlob(int col, const void* val, int val_len) { + if (!is_valid()) + return false; + + return CheckOk( + sqlite3_bind_blob(ref_->stmt(), col + 1, val, val_len, SQLITE_TRANSIENT)); +} + +int Statement::ColumnCount() const { + if (!is_valid()) + return 0; + + return sqlite3_column_count(ref_->stmt()); +} + +ColType Statement::ColumnType(int col) const { + // Verify that our enum matches sqlite's values. + COMPILE_ASSERT(COLUMN_TYPE_INTEGER == SQLITE_INTEGER, integer_no_match); + COMPILE_ASSERT(COLUMN_TYPE_FLOAT == SQLITE_FLOAT, float_no_match); + COMPILE_ASSERT(COLUMN_TYPE_TEXT == SQLITE_TEXT, integer_no_match); + COMPILE_ASSERT(COLUMN_TYPE_BLOB == SQLITE_BLOB, blob_no_match); + COMPILE_ASSERT(COLUMN_TYPE_NULL == SQLITE_NULL, null_no_match); + + return static_cast<ColType>(sqlite3_column_type(ref_->stmt(), col)); +} + +ColType Statement::DeclaredColumnType(int col) const { + std::string column_type(sqlite3_column_decltype(ref_->stmt(), col)); + StringToLowerASCII(&column_type); + + if (column_type == "integer") + return COLUMN_TYPE_INTEGER; + else if (column_type == "float") + return COLUMN_TYPE_FLOAT; + else if (column_type == "text") + return COLUMN_TYPE_TEXT; + else if (column_type == "blob") + return COLUMN_TYPE_BLOB; + + return COLUMN_TYPE_NULL; +} + +bool Statement::ColumnBool(int col) const { + return !!ColumnInt(col); +} + +int Statement::ColumnInt(int col) const { + if (!CheckValid()) + return 0; + + return sqlite3_column_int(ref_->stmt(), col); +} + +int64 Statement::ColumnInt64(int col) const { + if (!CheckValid()) + return 0; + + return sqlite3_column_int64(ref_->stmt(), col); +} + +double Statement::ColumnDouble(int col) const { + if (!CheckValid()) + return 0; + + return sqlite3_column_double(ref_->stmt(), col); +} + +std::string Statement::ColumnString(int col) const { + if (!CheckValid()) + return std::string(); + + const char* str = reinterpret_cast<const char*>( + sqlite3_column_text(ref_->stmt(), col)); + int len = sqlite3_column_bytes(ref_->stmt(), col); + + std::string result; + if (str && len > 0) + result.assign(str, len); + return result; +} + +string16 Statement::ColumnString16(int col) const { + if (!CheckValid()) + return string16(); + + std::string s = ColumnString(col); + return !s.empty() ? UTF8ToUTF16(s) : string16(); +} + +int Statement::ColumnByteLength(int col) const { + if (!CheckValid()) + return 0; + + return sqlite3_column_bytes(ref_->stmt(), col); +} + +const void* Statement::ColumnBlob(int col) const { + if (!CheckValid()) + return NULL; + + return sqlite3_column_blob(ref_->stmt(), col); +} + +bool Statement::ColumnBlobAsString(int col, std::string* blob) { + if (!CheckValid()) + return false; + + const void* p = ColumnBlob(col); + size_t len = ColumnByteLength(col); + blob->resize(len); + if (blob->size() != len) { + return false; + } + blob->assign(reinterpret_cast<const char*>(p), len); + return true; +} + +bool Statement::ColumnBlobAsString16(int col, string16* val) const { + if (!CheckValid()) + return false; + + const void* data = ColumnBlob(col); + size_t len = ColumnByteLength(col) / sizeof(char16); + val->resize(len); + if (val->size() != len) + return false; + val->assign(reinterpret_cast<const char16*>(data), len); + return true; +} + +bool Statement::ColumnBlobAsVector(int col, std::vector<char>* val) const { + val->clear(); + + if (!CheckValid()) + return false; + + const void* data = sqlite3_column_blob(ref_->stmt(), col); + int len = sqlite3_column_bytes(ref_->stmt(), col); + if (data && len > 0) { + val->resize(len); + memcpy(&(*val)[0], data, len); + } + return true; +} + +bool Statement::ColumnBlobAsVector( + int col, + std::vector<unsigned char>* val) const { + return ColumnBlobAsVector(col, reinterpret_cast< std::vector<char>* >(val)); +} + +const char* Statement::GetSQLStatement() { + return sqlite3_sql(ref_->stmt()); +} + +bool Statement::CheckOk(int err) const { + // Binding to a non-existent variable is evidence of a serious error. + // TODO(gbillock,shess): make this invalidate the statement so it + // can't wreak havoc. + if (err == SQLITE_RANGE) + DLOG(FATAL) << "Bind value out of range"; + return err == SQLITE_OK; +} + +int Statement::CheckError(int err) { + // Please don't add DCHECKs here, OnSqliteError() already has them. + succeeded_ = (err == SQLITE_OK || err == SQLITE_ROW || err == SQLITE_DONE); + if (!succeeded_ && ref_.get() && ref_->connection()) + return ref_->connection()->OnSqliteError(err, this); + return err; +} + +} // namespace sql diff --git a/chromium/sql/statement.h b/chromium/sql/statement.h new file mode 100644 index 00000000000..5fedc53a09b --- /dev/null +++ b/chromium/sql/statement.h @@ -0,0 +1,189 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SQL_STATEMENT_H_ +#define SQL_STATEMENT_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/strings/string16.h" +#include "sql/connection.h" +#include "sql/sql_export.h" + +namespace sql { + +// Possible return values from ColumnType in a statement. These should match +// the values in sqlite3.h. +enum ColType { + COLUMN_TYPE_INTEGER = 1, + COLUMN_TYPE_FLOAT = 2, + COLUMN_TYPE_TEXT = 3, + COLUMN_TYPE_BLOB = 4, + COLUMN_TYPE_NULL = 5, +}; + +// Normal usage: +// sql::Statement s(connection_.GetUniqueStatement(...)); +// s.BindInt(0, a); +// if (s.Step()) +// return s.ColumnString(0); +// +// If there are errors getting the statement, the statement will be inert; no +// mutating or database-access methods will work. If you need to check for +// validity, use: +// if (!s.is_valid()) +// return false; +// +// Step() and Run() just return true to signal success. If you want to handle +// specific errors such as database corruption, install an error handler in +// in the connection object using set_error_delegate(). +class SQL_EXPORT Statement { + public: + // Creates an uninitialized statement. The statement will be invalid until + // you initialize it via Assign. + Statement(); + + explicit Statement(scoped_refptr<Connection::StatementRef> ref); + ~Statement(); + + // Initializes this object with the given statement, which may or may not + // be valid. Use is_valid() to check if it's OK. + void Assign(scoped_refptr<Connection::StatementRef> ref); + + // Resets the statement to an uninitialized state corrosponding to + // the default constructor, releasing the StatementRef. + void Clear(); + + // Returns true if the statement can be executed. All functions can still + // be used if the statement is invalid, but they will return failure or some + // default value. This is because the statement can become invalid in the + // middle of executing a command if there is a serious error and the database + // has to be reset. + bool is_valid() const { return ref_->is_valid(); } + + // Running ------------------------------------------------------------------- + + // Executes the statement, returning true on success. This is like Step but + // for when there is no output, like an INSERT statement. + bool Run(); + + // Executes the statement, returning true if there is a row of data returned. + // You can keep calling Step() until it returns false to iterate through all + // the rows in your result set. + // + // When Step returns false, the result is either that there is no more data + // or there is an error. This makes it most convenient for loop usage. If you + // need to disambiguate these cases, use Succeeded(). + // + // Typical example: + // while (s.Step()) { + // ... + // } + // return s.Succeeded(); + bool Step(); + + // Resets the statement to its initial condition. This includes any current + // result row, and also the bound variables if the |clear_bound_vars| is true. + void Reset(bool clear_bound_vars); + + // Returns true if the last executed thing in this statement succeeded. If + // there was no last executed thing or the statement is invalid, this will + // return false. + bool Succeeded() const; + + // Binding ------------------------------------------------------------------- + + // These all take a 0-based argument index and return true on success. You + // may not always care about the return value (they'll DCHECK if they fail). + // The main thing you may want to check is when binding large blobs or + // strings there may be out of memory. + bool BindNull(int col); + bool BindBool(int col, bool val); + bool BindInt(int col, int val); + bool BindInt64(int col, int64 val); + bool BindDouble(int col, double val); + bool BindCString(int col, const char* val); + bool BindString(int col, const std::string& val); + bool BindString16(int col, const string16& value); + bool BindBlob(int col, const void* value, int value_len); + + // Retrieving ---------------------------------------------------------------- + + // Returns the number of output columns in the result. + int ColumnCount() const; + + // Returns the type associated with the given column. + // + // Watch out: the type may be undefined if you've done something to cause a + // "type conversion." This means requesting the value of a column of a type + // where that type is not the native type. For safety, call ColumnType only + // on a column before getting the value out in any way. + ColType ColumnType(int col) const; + ColType DeclaredColumnType(int col) const; + + // These all take a 0-based argument index. + bool ColumnBool(int col) const; + int ColumnInt(int col) const; + int64 ColumnInt64(int col) const; + double ColumnDouble(int col) const; + std::string ColumnString(int col) const; + string16 ColumnString16(int col) const; + + // When reading a blob, you can get a raw pointer to the underlying data, + // along with the length, or you can just ask us to copy the blob into a + // vector. Danger! ColumnBlob may return NULL if there is no data! + int ColumnByteLength(int col) const; + const void* ColumnBlob(int col) const; + bool ColumnBlobAsString(int col, std::string* blob); + bool ColumnBlobAsString16(int col, string16* val) const; + bool ColumnBlobAsVector(int col, std::vector<char>* val) const; + bool ColumnBlobAsVector(int col, std::vector<unsigned char>* val) const; + + // Diagnostics -------------------------------------------------------------- + + // Returns the original text of sql statement. Do not keep a pointer to it. + const char* GetSQLStatement(); + + private: + // This is intended to check for serious errors and report them to the + // connection object. It takes a sqlite error code, and returns the same + // code. Currently this function just updates the succeeded flag, but will be + // enhanced in the future to do the notification. + int CheckError(int err); + + // Contraction for checking an error code against SQLITE_OK. Does not set the + // succeeded flag. + bool CheckOk(int err) const; + + // Should be called by all mutating methods to check that the statement is + // valid. Returns true if the statement is valid. DCHECKS and returns false + // if it is not. + // The reason for this is to handle two specific cases in which a Statement + // may be invalid. The first case is that the programmer made an SQL error. + // Those cases need to be DCHECKed so that we are guaranteed to find them + // before release. The second case is that the computer has an error (probably + // out of disk space) which is prohibiting the correct operation of the + // database. Our testing apparatus should not exhibit this defect, but release + // situations may. Therefore, the code is handling disjoint situations in + // release and test. In test, we're ensuring correct SQL. In release, we're + // ensuring that contracts are honored in error edge cases. + bool CheckValid() const; + + // The actual sqlite statement. This may be unique to us, or it may be cached + // by the connection, which is why it's refcounted. This pointer is + // guaranteed non-NULL. + scoped_refptr<Connection::StatementRef> ref_; + + // See Succeeded() for what this holds. + bool succeeded_; + + DISALLOW_COPY_AND_ASSIGN(Statement); +}; + +} // namespace sql + +#endif // SQL_STATEMENT_H_ diff --git a/chromium/sql/statement_unittest.cc b/chromium/sql/statement_unittest.cc new file mode 100644 index 00000000000..c5217aa6c99 --- /dev/null +++ b/chromium/sql/statement_unittest.cc @@ -0,0 +1,130 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "sql/connection.h" +#include "sql/statement.h" +#include "sql/test/error_callback_support.h" +#include "sql/test/scoped_error_ignorer.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/sqlite/sqlite3.h" + +namespace { + +class SQLStatementTest : public testing::Test { + public: + virtual void SetUp() { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + ASSERT_TRUE(db_.Open(temp_dir_.path().AppendASCII("SQLStatementTest.db"))); + } + + virtual void TearDown() { + db_.Close(); + } + + sql::Connection& db() { return db_; } + + private: + base::ScopedTempDir temp_dir_; + sql::Connection db_; +}; + +} // namespace + +TEST_F(SQLStatementTest, Assign) { + sql::Statement s; + EXPECT_FALSE(s.is_valid()); + + s.Assign(db().GetUniqueStatement("CREATE TABLE foo (a, b)")); + EXPECT_TRUE(s.is_valid()); +} + +TEST_F(SQLStatementTest, Run) { + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)")); + + sql::Statement s(db().GetUniqueStatement("SELECT b FROM foo WHERE a=?")); + EXPECT_FALSE(s.Succeeded()); + + // Stepping it won't work since we haven't bound the value. + EXPECT_FALSE(s.Step()); + + // Run should fail since this produces output, and we should use Step(). This + // gets a bit wonky since sqlite says this is OK so succeeded is set. + s.Reset(true); + s.BindInt(0, 3); + EXPECT_FALSE(s.Run()); + EXPECT_EQ(SQLITE_ROW, db().GetErrorCode()); + EXPECT_TRUE(s.Succeeded()); + + // Resetting it should put it back to the previous state (not runnable). + s.Reset(true); + EXPECT_FALSE(s.Succeeded()); + + // Binding and stepping should produce one row. + s.BindInt(0, 3); + EXPECT_TRUE(s.Step()); + EXPECT_TRUE(s.Succeeded()); + EXPECT_EQ(12, s.ColumnInt(0)); + EXPECT_FALSE(s.Step()); + EXPECT_TRUE(s.Succeeded()); +} + +// Error callback called for error running a statement. +TEST_F(SQLStatementTest, ErrorCallback) { + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a INTEGER PRIMARY KEY, b)")); + + int error = SQLITE_OK; + sql::ScopedErrorCallback sec( + &db(), base::Bind(&sql::CaptureErrorCallback, &error)); + + // Insert in the foo table the primary key. It is an error to insert + // something other than an number. This error causes the error callback + // handler to be called with SQLITE_MISMATCH as error code. + sql::Statement s(db().GetUniqueStatement("INSERT INTO foo (a) VALUES (?)")); + EXPECT_TRUE(s.is_valid()); + s.BindCString(0, "bad bad"); + EXPECT_FALSE(s.Run()); + EXPECT_EQ(SQLITE_MISMATCH, error); +} + +// Error ignorer works for error running a statement. +TEST_F(SQLStatementTest, ScopedIgnoreError) { + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a INTEGER PRIMARY KEY, b)")); + + sql::Statement s(db().GetUniqueStatement("INSERT INTO foo (a) VALUES (?)")); + EXPECT_TRUE(s.is_valid()); + + sql::ScopedErrorIgnorer ignore_errors; + ignore_errors.IgnoreError(SQLITE_MISMATCH); + s.BindCString(0, "bad bad"); + ASSERT_FALSE(s.Run()); + ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); +} + +TEST_F(SQLStatementTest, Reset) { + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)")); + ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (4, 13)")); + + sql::Statement s(db().GetUniqueStatement( + "SELECT b FROM foo WHERE a = ? ")); + s.BindInt(0, 3); + ASSERT_TRUE(s.Step()); + EXPECT_EQ(12, s.ColumnInt(0)); + ASSERT_FALSE(s.Step()); + + s.Reset(false); + // Verify that we can get all rows again. + ASSERT_TRUE(s.Step()); + EXPECT_EQ(12, s.ColumnInt(0)); + EXPECT_FALSE(s.Step()); + + s.Reset(true); + ASSERT_FALSE(s.Step()); +} diff --git a/chromium/sql/transaction.cc b/chromium/sql/transaction.cc new file mode 100644 index 00000000000..06bcbebebdd --- /dev/null +++ b/chromium/sql/transaction.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sql/transaction.h" + +#include "base/logging.h" +#include "sql/connection.h" + +namespace sql { + +Transaction::Transaction(Connection* connection) + : connection_(connection), + is_open_(false) { +} + +Transaction::~Transaction() { + if (is_open_) + connection_->RollbackTransaction(); +} + +bool Transaction::Begin() { + if (is_open_) { + NOTREACHED() << "Beginning a transaction twice!"; + return false; + } + is_open_ = connection_->BeginTransaction(); + return is_open_; +} + +void Transaction::Rollback() { + if (!is_open_) { + NOTREACHED() << "Attempting to roll back a nonexistent transaction. " + << "Did you remember to call Begin() and check its return?"; + return; + } + is_open_ = false; + connection_->RollbackTransaction(); +} + +bool Transaction::Commit() { + if (!is_open_) { + NOTREACHED() << "Attempting to commit a nonexistent transaction. " + << "Did you remember to call Begin() and check its return?"; + return false; + } + is_open_ = false; + return connection_->CommitTransaction(); +} + +} // namespace sql diff --git a/chromium/sql/transaction.h b/chromium/sql/transaction.h new file mode 100644 index 00000000000..788a002f831 --- /dev/null +++ b/chromium/sql/transaction.h @@ -0,0 +1,60 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SQL_TRANSACTION_H_ +#define SQL_TRANSACTION_H_ + +#include "base/basictypes.h" +#include "sql/sql_export.h" + +namespace sql { + +class Connection; + +class SQL_EXPORT Transaction { + public: + // Creates the scoped transaction object. You MUST call Begin() to begin the + // transaction. If you have begun a transaction and not committed it, the + // constructor will roll back the transaction. If you want to commit, you + // need to manually call Commit before this goes out of scope. + // + // Nested transactions are supported. See sql::Connection::BeginTransaction + // for details. + explicit Transaction(Connection* connection); + ~Transaction(); + + // Returns true when there is a transaction that has been successfully begun. + bool is_open() const { return is_open_; } + + // Begins the transaction. This uses the default sqlite "deferred" transaction + // type, which means that the DB lock is lazily acquired the next time the + // database is accessed, not in the begin transaction command. + // + // Returns false on failure. Note that if this fails, you shouldn't do + // anything you expect to be actually transactional, because it won't be! + bool Begin(); + + // Rolls back the transaction. This will happen automatically if you do + // nothing when the transaction goes out of scope. + void Rollback(); + + // Commits the transaction, returning true on success. This will return + // false if sqlite could not commit it, or if another transaction in the + // same outermost transaction has been rolled back (which necessitates a + // rollback of all transactions in that outermost one). + bool Commit(); + + private: + Connection* connection_; + + // True when the transaction is open, false when it's already been committed + // or rolled back. + bool is_open_; + + DISALLOW_COPY_AND_ASSIGN(Transaction); +}; + +} // namespace sql + +#endif // SQL_TRANSACTION_H_ diff --git a/chromium/sql/transaction_unittest.cc b/chromium/sql/transaction_unittest.cc new file mode 100644 index 00000000000..ceaa4dbd7de --- /dev/null +++ b/chromium/sql/transaction_unittest.cc @@ -0,0 +1,133 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "sql/connection.h" +#include "sql/statement.h" +#include "sql/transaction.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/sqlite/sqlite3.h" + +class SQLTransactionTest : public testing::Test { + public: + SQLTransactionTest() {} + + virtual void SetUp() { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + ASSERT_TRUE(db_.Open( + temp_dir_.path().AppendASCII("SQLTransactionTest.db"))); + + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + } + + virtual void TearDown() { + db_.Close(); + } + + sql::Connection& db() { return db_; } + + // Returns the number of rows in table "foo". + int CountFoo() { + sql::Statement count(db().GetUniqueStatement("SELECT count(*) FROM foo")); + count.Step(); + return count.ColumnInt(0); + } + + private: + base::ScopedTempDir temp_dir_; + sql::Connection db_; +}; + +TEST_F(SQLTransactionTest, Commit) { + { + sql::Transaction t(&db()); + EXPECT_FALSE(t.is_open()); + EXPECT_TRUE(t.Begin()); + EXPECT_TRUE(t.is_open()); + + EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); + + t.Commit(); + EXPECT_FALSE(t.is_open()); + } + + EXPECT_EQ(1, CountFoo()); +} + +TEST_F(SQLTransactionTest, Rollback) { + // Test some basic initialization, and that rollback runs when you exit the + // scope. + { + sql::Transaction t(&db()); + EXPECT_FALSE(t.is_open()); + EXPECT_TRUE(t.Begin()); + EXPECT_TRUE(t.is_open()); + + EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); + } + + // Nothing should have been committed since it was implicitly rolled back. + EXPECT_EQ(0, CountFoo()); + + // Test explicit rollback. + sql::Transaction t2(&db()); + EXPECT_FALSE(t2.is_open()); + EXPECT_TRUE(t2.Begin()); + + EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); + t2.Rollback(); + EXPECT_FALSE(t2.is_open()); + + // Nothing should have been committed since it was explicitly rolled back. + EXPECT_EQ(0, CountFoo()); +} + +// Rolling back any part of a transaction should roll back all of them. +TEST_F(SQLTransactionTest, NestedRollback) { + EXPECT_EQ(0, db().transaction_nesting()); + + // Outermost transaction. + { + sql::Transaction outer(&db()); + EXPECT_TRUE(outer.Begin()); + EXPECT_EQ(1, db().transaction_nesting()); + + // The first inner one gets committed. + { + sql::Transaction inner1(&db()); + EXPECT_TRUE(inner1.Begin()); + EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); + EXPECT_EQ(2, db().transaction_nesting()); + + inner1.Commit(); + EXPECT_EQ(1, db().transaction_nesting()); + } + + // One row should have gotten inserted. + EXPECT_EQ(1, CountFoo()); + + // The second inner one gets rolled back. + { + sql::Transaction inner2(&db()); + EXPECT_TRUE(inner2.Begin()); + EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); + EXPECT_EQ(2, db().transaction_nesting()); + + inner2.Rollback(); + EXPECT_EQ(1, db().transaction_nesting()); + } + + // A third inner one will fail in Begin since one has already been rolled + // back. + EXPECT_EQ(1, db().transaction_nesting()); + { + sql::Transaction inner3(&db()); + EXPECT_FALSE(inner3.Begin()); + EXPECT_EQ(1, db().transaction_nesting()); + } + } + EXPECT_EQ(0, db().transaction_nesting()); + EXPECT_EQ(0, CountFoo()); +} |