summaryrefslogtreecommitdiff
path: root/chromium/sql
diff options
context:
space:
mode:
authorZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
committerZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
commit679147eead574d186ebf3069647b4c23e8ccace6 (patch)
treefc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/sql
downloadqtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz
Initial import.
Diffstat (limited to 'chromium/sql')
-rw-r--r--chromium/sql/DEPS3
-rw-r--r--chromium/sql/OWNERS3
-rw-r--r--chromium/sql/connection.cc1060
-rw-r--r--chromium/sql/connection.h576
-rw-r--r--chromium/sql/connection_unittest.cc845
-rw-r--r--chromium/sql/error_delegate_util.cc80
-rw-r--r--chromium/sql/error_delegate_util.h18
-rw-r--r--chromium/sql/init_status.h23
-rw-r--r--chromium/sql/meta_table.cc155
-rw-r--r--chromium/sql/meta_table.h87
-rw-r--r--chromium/sql/recovery.cc207
-rw-r--r--chromium/sql/recovery.h106
-rw-r--r--chromium/sql/recovery_unittest.cc425
-rw-r--r--chromium/sql/run_all_unittests.cc9
-rw-r--r--chromium/sql/sql.gyp136
-rw-r--r--chromium/sql/sql_export.h29
-rw-r--r--chromium/sql/sqlite_features_unittest.cc86
-rw-r--r--chromium/sql/statement.cc316
-rw-r--r--chromium/sql/statement.h189
-rw-r--r--chromium/sql/statement_unittest.cc130
-rw-r--r--chromium/sql/transaction.cc51
-rw-r--r--chromium/sql/transaction.h60
-rw-r--r--chromium/sql/transaction_unittest.cc133
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());
+}