summaryrefslogtreecommitdiff
path: root/chromium/sql
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@theqtcompany.com>2015-08-14 11:38:45 +0200
committerAllan Sandfeld Jensen <allan.jensen@theqtcompany.com>2015-08-14 17:16:47 +0000
commit3a97ca8dd9b96b599ae2d33e40df0dd2f7ea5859 (patch)
tree43cc572ba067417c7341db81f71ae7cc6e0fcc3e /chromium/sql
parentf61ab1ac7f855cd281809255c0aedbb1895e1823 (diff)
downloadqtwebengine-chromium-3a97ca8dd9b96b599ae2d33e40df0dd2f7ea5859.tar.gz
BASELINE: Update chromium to 45.0.2454.40
Change-Id: Id2121d9f11a8fc633677236c65a3e41feef589e4 Reviewed-by: Andras Becsi <andras.becsi@theqtcompany.com>
Diffstat (limited to 'chromium/sql')
-rw-r--r--chromium/sql/BUILD.gn20
-rw-r--r--chromium/sql/connection.cc247
-rw-r--r--chromium/sql/connection.h120
-rw-r--r--chromium/sql/connection_unittest.cc506
-rw-r--r--chromium/sql/correct_sql_test_base.h27
-rw-r--r--chromium/sql/meta_table_unittest.cc17
-rw-r--r--chromium/sql/mojo/BUILD.gn62
-rw-r--r--chromium/sql/mojo/DEPS6
-rw-r--r--chromium/sql/mojo/mojo_vfs.cc413
-rw-r--r--chromium/sql/mojo/mojo_vfs.h45
-rw-r--r--chromium/sql/mojo/sql_test_base.cc156
-rw-r--r--chromium/sql/mojo/sql_test_base.h85
-rw-r--r--chromium/sql/mojo/vfs_unittest.cc317
-rw-r--r--chromium/sql/proxy.cc28
-rw-r--r--chromium/sql/proxy.h39
-rw-r--r--chromium/sql/recovery_unittest.cc28
-rw-r--r--chromium/sql/sql.gyp5
-rw-r--r--chromium/sql/sql_unittests.isolate19
-rw-r--r--chromium/sql/sqlite_features_unittest.cc17
-rw-r--r--chromium/sql/statement.cc57
-rw-r--r--chromium/sql/statement.h12
-rw-r--r--chromium/sql/statement_unittest.cc17
-rw-r--r--chromium/sql/transaction_unittest.cc15
23 files changed, 2083 insertions, 175 deletions
diff --git a/chromium/sql/BUILD.gn b/chromium/sql/BUILD.gn
index 8b75d800936..fdf8c7be96e 100644
--- a/chromium/sql/BUILD.gn
+++ b/chromium/sql/BUILD.gn
@@ -13,6 +13,8 @@ component("sql") {
"init_status.h",
"meta_table.cc",
"meta_table.h",
+ "proxy.cc",
+ "proxy.h",
"recovery.cc",
"recovery.h",
"statement.cc",
@@ -52,6 +54,17 @@ source_set("test_support") {
]
}
+source_set("redirection_header") {
+ # This target exists because we need a way to switch between
+ # "test/sql_test_base.h" and "mojo/sql_test_base.h" at compile time, to allow
+ # us to switch out the gtest vs mojo:apptest frameworks.
+ check_includes = false
+
+ sources = [
+ "correct_sql_test_base.h",
+ ]
+}
+
test("sql_unittests") {
sources = [
"connection_unittest.cc",
@@ -62,6 +75,8 @@ test("sql_unittests") {
"test/paths.cc",
"test/paths.h",
"test/run_all_unittests.cc",
+ "test/sql_test_base.cc",
+ "test/sql_test_base.h",
"test/sql_test_suite.cc",
"test/sql_test_suite.h",
"transaction_unittest.cc",
@@ -72,6 +87,7 @@ test("sql_unittests") {
deps = [
":sql",
+ ":redirection_header",
":test_support",
"//base/allocator",
"//base/test:test_support",
@@ -79,6 +95,10 @@ test("sql_unittests") {
"//third_party/sqlite",
]
+ if (is_android) {
+ isolate_file = "sql_unittests.isolate"
+ }
+
# TODO(GYP)
#['OS == "android"', {
# 'dependencies': [
diff --git a/chromium/sql/connection.cc b/chromium/sql/connection.cc
index b91234bac3d..d1b104908c9 100644
--- a/chromium/sql/connection.cc
+++ b/chromium/sql/connection.cc
@@ -141,6 +141,24 @@ int GetSqlite3File(sqlite3* db, sqlite3_file** file) {
return rc;
}
+// This should match UMA_HISTOGRAM_MEDIUM_TIMES().
+base::HistogramBase* GetMediumTimeHistogram(const std::string& name) {
+ return base::Histogram::FactoryTimeGet(
+ name,
+ base::TimeDelta::FromMilliseconds(10),
+ base::TimeDelta::FromMinutes(3),
+ 50,
+ base::HistogramBase::kUmaTargetedHistogramFlag);
+}
+
+std::string AsUTF8ForSQL(const base::FilePath& path) {
+#if defined(OS_WIN)
+ return base::WideToUTF8(path.value());
+#elif defined(OS_POSIX)
+ return path.value();
+#endif
+}
+
} // namespace
namespace sql {
@@ -219,13 +237,74 @@ Connection::Connection()
transaction_nesting_(0),
needs_rollback_(false),
in_memory_(false),
- poisoned_(false) {
+ poisoned_(false),
+ stats_histogram_(NULL),
+ commit_time_histogram_(NULL),
+ autocommit_time_histogram_(NULL),
+ update_time_histogram_(NULL),
+ query_time_histogram_(NULL),
+ clock_(new TimeSource()) {
}
Connection::~Connection() {
Close();
}
+void Connection::RecordEvent(Events event, size_t count) {
+ for (size_t i = 0; i < count; ++i) {
+ UMA_HISTOGRAM_ENUMERATION("Sqlite.Stats", event, EVENT_MAX_VALUE);
+ }
+
+ if (stats_histogram_) {
+ for (size_t i = 0; i < count; ++i) {
+ stats_histogram_->Add(event);
+ }
+ }
+}
+
+void Connection::RecordCommitTime(const base::TimeDelta& delta) {
+ RecordUpdateTime(delta);
+ UMA_HISTOGRAM_MEDIUM_TIMES("Sqlite.CommitTime", delta);
+ if (commit_time_histogram_)
+ commit_time_histogram_->AddTime(delta);
+}
+
+void Connection::RecordAutoCommitTime(const base::TimeDelta& delta) {
+ RecordUpdateTime(delta);
+ UMA_HISTOGRAM_MEDIUM_TIMES("Sqlite.AutoCommitTime", delta);
+ if (autocommit_time_histogram_)
+ autocommit_time_histogram_->AddTime(delta);
+}
+
+void Connection::RecordUpdateTime(const base::TimeDelta& delta) {
+ RecordQueryTime(delta);
+ UMA_HISTOGRAM_MEDIUM_TIMES("Sqlite.UpdateTime", delta);
+ if (update_time_histogram_)
+ update_time_histogram_->AddTime(delta);
+}
+
+void Connection::RecordQueryTime(const base::TimeDelta& delta) {
+ UMA_HISTOGRAM_MEDIUM_TIMES("Sqlite.QueryTime", delta);
+ if (query_time_histogram_)
+ query_time_histogram_->AddTime(delta);
+}
+
+void Connection::RecordTimeAndChanges(
+ const base::TimeDelta& delta, bool read_only) {
+ if (read_only) {
+ RecordQueryTime(delta);
+ } else {
+ const int changes = sqlite3_changes(db_);
+ if (sqlite3_get_autocommit(db_)) {
+ RecordAutoCommitTime(delta);
+ RecordEvent(EVENT_CHANGES_AUTOCOMMIT, changes);
+ } else {
+ RecordUpdateTime(delta);
+ RecordEvent(EVENT_CHANGES, changes);
+ }
+ }
+}
+
bool Connection::Open(const base::FilePath& path) {
if (!histogram_tag_.empty()) {
int64_t size_64 = 0;
@@ -241,11 +320,7 @@ bool Connection::Open(const base::FilePath& path) {
}
}
-#if defined(OS_WIN)
- return OpenInternal(base::WideToUTF8(path.value()), RETRY_ON_POISON);
-#elif defined(OS_POSIX)
- return OpenInternal(path.value(), RETRY_ON_POISON);
-#endif
+ return OpenInternal(AsUTF8ForSQL(path), RETRY_ON_POISON);
}
bool Connection::OpenInMemory() {
@@ -553,13 +628,38 @@ bool Connection::Delete(const base::FilePath& path) {
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);
+ std::string journal_str = AsUTF8ForSQL(journal_path);
+ std::string wal_str = AsUTF8ForSQL(wal_path);
+ std::string path_str = AsUTF8ForSQL(path);
+
+ sqlite3_vfs* vfs = sqlite3_vfs_find(NULL);
+ CHECK(vfs);
+ CHECK(vfs->xDelete);
+ CHECK(vfs->xAccess);
- return !base::PathExists(journal_path) &&
- !base::PathExists(wal_path) &&
- !base::PathExists(path);
+ // We only work with unix, win32 and mojo filesystems. If you're trying to
+ // use this code with any other VFS, you're not in a good place.
+ CHECK(strncmp(vfs->zName, "unix", 4) == 0 ||
+ strncmp(vfs->zName, "win32", 5) == 0 ||
+ strcmp(vfs->zName, "mojo") == 0);
+
+ vfs->xDelete(vfs, journal_str.c_str(), 0);
+ vfs->xDelete(vfs, wal_str.c_str(), 0);
+ vfs->xDelete(vfs, path_str.c_str(), 0);
+
+ int journal_exists = 0;
+ vfs->xAccess(vfs, journal_str.c_str(), SQLITE_ACCESS_EXISTS,
+ &journal_exists);
+
+ int wal_exists = 0;
+ vfs->xAccess(vfs, wal_str.c_str(), SQLITE_ACCESS_EXISTS,
+ &wal_exists);
+
+ int path_exists = 0;
+ vfs->xAccess(vfs, path_str.c_str(), SQLITE_ACCESS_EXISTS,
+ &path_exists);
+
+ return !journal_exists && !wal_exists && !path_exists;
}
bool Connection::BeginTransaction() {
@@ -576,6 +676,7 @@ bool Connection::BeginTransaction() {
needs_rollback_ = false;
Statement begin(GetCachedStatement(SQL_FROM_HERE, "BEGIN TRANSACTION"));
+ RecordOneEvent(EVENT_BEGIN);
if (!begin.Run())
return false;
}
@@ -618,7 +719,17 @@ bool Connection::CommitTransaction() {
}
Statement commit(GetCachedStatement(SQL_FROM_HERE, "COMMIT"));
- return commit.Run();
+
+ // Collect the commit time manually, sql::Statement would register it as query
+ // time only.
+ const base::TimeTicks before = Now();
+ bool ret = commit.RunWithoutTimers();
+ const base::TimeDelta delta = Now() - before;
+
+ RecordCommitTime(delta);
+ RecordOneEvent(EVENT_COMMIT);
+
+ return ret;
}
void Connection::RollbackAllTransactions() {
@@ -650,13 +761,65 @@ bool Connection::DetachDatabase(const char* attachment_point) {
return s.Run();
}
+// TODO(shess): Consider changing this to execute exactly one statement. If a
+// caller wishes to execute multiple statements, that should be explicit, and
+// perhaps tucked into an explicit transaction with rollback in case of error.
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);
+ DCHECK(sql);
+
+ RecordOneEvent(EVENT_EXECUTE);
+ int rc = SQLITE_OK;
+ while ((rc == SQLITE_OK) && *sql) {
+ sqlite3_stmt *stmt = NULL;
+ const char *leftover_sql;
+
+ const base::TimeTicks before = Now();
+ rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, &leftover_sql);
+ sql = leftover_sql;
+
+ // Stop if an error is encountered.
+ if (rc != SQLITE_OK)
+ break;
+
+ // This happens if |sql| originally only contained comments or whitespace.
+ // TODO(shess): Audit to see if this can become a DCHECK(). Having
+ // extraneous comments and whitespace in the SQL statements increases
+ // runtime cost and can easily be shifted out to the C++ layer.
+ if (!stmt)
+ continue;
+
+ // Save for use after statement is finalized.
+ const bool read_only = !!sqlite3_stmt_readonly(stmt);
+
+ RecordOneEvent(Connection::EVENT_STATEMENT_RUN);
+ while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
+ // TODO(shess): Audit to see if this can become a DCHECK. I think PRAGMA
+ // is the only legitimate case for this.
+ RecordOneEvent(Connection::EVENT_STATEMENT_ROWS);
+ }
+
+ // sqlite3_finalize() returns SQLITE_OK if the most recent sqlite3_step()
+ // returned SQLITE_DONE or SQLITE_ROW, otherwise the error code.
+ rc = sqlite3_finalize(stmt);
+ if (rc == SQLITE_OK)
+ RecordOneEvent(Connection::EVENT_STATEMENT_SUCCESS);
+
+ // sqlite3_exec() does this, presumably to avoid spinning the parser for
+ // trailing whitespace.
+ // TODO(shess): Audit to see if this can become a DCHECK.
+ while (base::IsAsciiWhitespace(*sql)) {
+ sql++;
+ }
+
+ const base::TimeDelta delta = Now() - before;
+ RecordTimeAndChanges(delta, read_only);
+ }
+ return rc;
}
bool Connection::Execute(const char* sql) {
@@ -886,6 +1049,32 @@ bool Connection::OpenInternal(const std::string& file_name,
// Make sure sqlite3_initialize() is called before anything else.
InitializeSqlite();
+ // Setup the stats histograms immediately rather than allocating lazily.
+ // Connections which won't exercise all of these probably shouldn't exist.
+ if (!histogram_tag_.empty()) {
+ stats_histogram_ =
+ base::LinearHistogram::FactoryGet(
+ "Sqlite.Stats." + histogram_tag_,
+ 1, EVENT_MAX_VALUE, EVENT_MAX_VALUE + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag);
+
+ // The timer setup matches UMA_HISTOGRAM_MEDIUM_TIMES(). 3 minutes is an
+ // unreasonable time for any single operation, so there is not much value to
+ // knowing if it was 3 minutes or 5 minutes. In reality at that point
+ // things are entirely busted.
+ commit_time_histogram_ =
+ GetMediumTimeHistogram("Sqlite.CommitTime." + histogram_tag_);
+
+ autocommit_time_histogram_ =
+ GetMediumTimeHistogram("Sqlite.AutoCommitTime." + histogram_tag_);
+
+ update_time_histogram_ =
+ GetMediumTimeHistogram("Sqlite.UpdateTime." + histogram_tag_);
+
+ query_time_histogram_ =
+ GetMediumTimeHistogram("Sqlite.QueryTime." + histogram_tag_);
+ }
+
// 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
@@ -988,12 +1177,10 @@ bool Connection::OpenInternal(const std::string& file_name,
// 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"));
+ // TRUNCATE should be faster than DELETE because it won't need directory
+ // changes for each transaction. PERSIST may break the spirit of using
+ // secure_delete.
+ ignore_result(Execute("PRAGMA journal_mode = TRUNCATE"));
const base::TimeDelta kBusyTimeout =
base::TimeDelta::FromSeconds(kBusyTimeoutSeconds);
@@ -1028,7 +1215,16 @@ bool Connection::OpenInternal(const std::string& file_name,
void Connection::DoRollback() {
Statement rollback(GetCachedStatement(SQL_FROM_HERE, "ROLLBACK"));
- rollback.Run();
+
+ // Collect the rollback time manually, sql::Statement would register it as
+ // query time only.
+ const base::TimeTicks before = Now();
+ rollback.RunWithoutTimers();
+ const base::TimeDelta delta = Now() - before;
+
+ RecordUpdateTime(delta);
+ RecordOneEvent(EVENT_ROLLBACK);
+
needs_rollback_ = false;
}
@@ -1045,6 +1241,11 @@ void Connection::StatementRefDeleted(StatementRef* ref) {
open_statements_.erase(i);
}
+void Connection::set_histogram_tag(const std::string& tag) {
+ DCHECK(!is_open());
+ histogram_tag_ = tag;
+}
+
void Connection::AddTaggedHistogram(const std::string& name,
size_t sample) const {
if (histogram_tag_.empty())
@@ -1137,4 +1338,8 @@ bool Connection::IntegrityCheckHelper(
return ret;
}
+base::TimeTicks TimeSource::Now() {
+ return base::TimeTicks::Now();
+}
+
} // namespace sql
diff --git a/chromium/sql/connection.h b/chromium/sql/connection.h
index 17d11914ae0..19592d9f0e4 100644
--- a/chromium/sql/connection.h
+++ b/chromium/sql/connection.h
@@ -25,6 +25,7 @@ struct sqlite3_stmt;
namespace base {
class FilePath;
+class HistogramBase;
}
namespace sql {
@@ -32,6 +33,13 @@ namespace sql {
class Recovery;
class Statement;
+// To allow some test classes to be friended.
+namespace test {
+class ScopedCommitHook;
+class ScopedScalarFunction;
+class ScopedMockTimeSource;
+}
+
// 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
@@ -80,6 +88,20 @@ class StatementID {
class Connection;
+// Abstract the source of timing information for metrics (RecordCommitTime, etc)
+// to allow testing control.
+class SQL_EXPORT TimeSource {
+ public:
+ TimeSource() {}
+ virtual ~TimeSource() {}
+
+ // Return the current time (by default base::TimeTicks::Now()).
+ virtual base::TimeTicks Now();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TimeSource);
+};
+
class SQL_EXPORT Connection {
private:
class StatementRef; // Forward declaration, see real one below.
@@ -140,17 +162,52 @@ class SQL_EXPORT Connection {
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;
- }
+ // Set this to enable additional per-connection histogramming. Must be called
+ // before Open().
+ void set_histogram_tag(const std::string& 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;
+ // Track various API calls and results. Values corrospond to UMA
+ // histograms, do not modify, or add or delete other than directly
+ // before EVENT_MAX_VALUE.
+ enum Events {
+ // Number of statements run, either with sql::Statement or Execute*().
+ EVENT_STATEMENT_RUN = 0,
+
+ // Number of rows returned by statements run.
+ EVENT_STATEMENT_ROWS,
+
+ // Number of statements successfully run (all steps returned SQLITE_DONE or
+ // SQLITE_ROW).
+ EVENT_STATEMENT_SUCCESS,
+
+ // Number of statements run by Execute() or ExecuteAndReturnErrorCode().
+ EVENT_EXECUTE,
+
+ // Number of rows changed by autocommit statements.
+ EVENT_CHANGES_AUTOCOMMIT,
+
+ // Number of rows changed by statements in transactions.
+ EVENT_CHANGES,
+
+ // Count actual SQLite transaction statements (not including nesting).
+ EVENT_BEGIN,
+ EVENT_COMMIT,
+ EVENT_ROLLBACK,
+
+ // Leave this at the end.
+ // TODO(shess): |EVENT_MAX| causes compile fail on Windows.
+ EVENT_MAX_VALUE
+ };
+ void RecordEvent(Events event, size_t count);
+ void RecordOneEvent(Events event) {
+ RecordEvent(event, 1);
+ }
+
// 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
@@ -415,6 +472,10 @@ class SQL_EXPORT Connection {
// (they should go through Statement).
friend class Statement;
+ friend class test::ScopedCommitHook;
+ friend class test::ScopedScalarFunction;
+ friend class test::ScopedMockTimeSource;
+
// 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.
@@ -548,6 +609,35 @@ class SQL_EXPORT Connection {
const char* pragma_sql,
std::vector<std::string>* messages) WARN_UNUSED_RESULT;
+ // Record time spent executing explicit COMMIT statements.
+ void RecordCommitTime(const base::TimeDelta& delta);
+
+ // Record time in DML (Data Manipulation Language) statements such as INSERT
+ // or UPDATE outside of an explicit transaction. Due to implementation
+ // limitations time spent on DDL (Data Definition Language) statements such as
+ // ALTER and CREATE is not included.
+ void RecordAutoCommitTime(const base::TimeDelta& delta);
+
+ // Record all time spent on updating the database. This includes CommitTime()
+ // and AutoCommitTime(), plus any time spent spilling to the journal if
+ // transactions do not fit in cache.
+ void RecordUpdateTime(const base::TimeDelta& delta);
+
+ // Record all time spent running statements, including time spent doing
+ // updates and time spent on read-only queries.
+ void RecordQueryTime(const base::TimeDelta& delta);
+
+ // Record |delta| as query time if |read_only| (from sqlite3_stmt_readonly) is
+ // true, autocommit time if the database is not in a transaction, or update
+ // time if the database is in a transaction. Also records change count to
+ // EVENT_CHANGES_AUTOCOMMIT or EVENT_CHANGES_COMMIT.
+ void RecordTimeAndChanges(const base::TimeDelta& delta, bool read_only);
+
+ // Helper to return the current time from the time source.
+ base::TimeTicks Now() {
+ return clock_->Now();
+ }
+
// The actual sqlite database. Will be NULL before Init has been called or if
// Init resulted in an error.
sqlite3* db_;
@@ -594,6 +684,26 @@ class SQL_EXPORT Connection {
// Tag for auxiliary histograms.
std::string histogram_tag_;
+ // Linear histogram for RecordEvent().
+ base::HistogramBase* stats_histogram_;
+
+ // Histogram for tracking time taken in commit.
+ base::HistogramBase* commit_time_histogram_;
+
+ // Histogram for tracking time taken in autocommit updates.
+ base::HistogramBase* autocommit_time_histogram_;
+
+ // Histogram for tracking time taken in updates (including commit and
+ // autocommit).
+ base::HistogramBase* update_time_histogram_;
+
+ // Histogram for tracking time taken in all queries.
+ base::HistogramBase* query_time_histogram_;
+
+ // Source for timing information, provided to allow tests to inject time
+ // changes.
+ scoped_ptr<TimeSource> clock_;
+
DISALLOW_COPY_AND_ASSIGN(Connection);
};
diff --git a/chromium/sql/connection_unittest.cc b/chromium/sql/connection_unittest.cc
index e3b97739ed1..c7d9080c8f9 100644
--- a/chromium/sql/connection_unittest.cc
+++ b/chromium/sql/connection_unittest.cc
@@ -7,8 +7,12 @@
#include "base/files/scoped_file.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/test/histogram_tester.h"
#include "sql/connection.h"
+#include "sql/correct_sql_test_base.h"
#include "sql/meta_table.h"
+#include "sql/proxy.h"
#include "sql/statement.h"
#include "sql/test/error_callback_support.h"
#include "sql/test/scoped_error_ignorer.h"
@@ -16,6 +20,122 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/sqlite/sqlite3.h"
+namespace sql {
+namespace test {
+
+// Replaces the database time source with an object that steps forward 1ms on
+// each check, and which can be jumped forward an arbitrary amount of time
+// programmatically.
+class ScopedMockTimeSource {
+ public:
+ ScopedMockTimeSource(Connection& db)
+ : db_(db),
+ delta_(base::TimeDelta::FromMilliseconds(1)) {
+ // Save the current source and replace it.
+ save_.swap(db_.clock_);
+ db_.clock_.reset(new MockTimeSource(*this));
+ }
+ ~ScopedMockTimeSource() {
+ // Put original source back.
+ db_.clock_.swap(save_);
+ }
+
+ void adjust(const base::TimeDelta& delta) {
+ current_time_ += delta;
+ }
+
+ private:
+ class MockTimeSource : public TimeSource {
+ public:
+ MockTimeSource(ScopedMockTimeSource& owner)
+ : owner_(owner) {
+ }
+ ~MockTimeSource() override {}
+
+ base::TimeTicks Now() override {
+ base::TimeTicks ret(owner_.current_time_);
+ owner_.current_time_ += owner_.delta_;
+ return ret;
+ }
+
+ private:
+ ScopedMockTimeSource& owner_;
+ DISALLOW_COPY_AND_ASSIGN(MockTimeSource);
+ };
+
+ Connection& db_;
+
+ // Saves original source from |db_|.
+ scoped_ptr<TimeSource> save_;
+
+ // Current time returned by mock.
+ base::TimeTicks current_time_;
+
+ // How far to jump on each Now() call.
+ base::TimeDelta delta_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedMockTimeSource);
+};
+
+// Allow a test to add a SQLite function in a scoped context.
+class ScopedScalarFunction {
+ public:
+ ScopedScalarFunction(
+ sql::Connection& db,
+ const char* function_name,
+ int args,
+ base::Callback<void(sqlite3_context*,int,sqlite3_value**)> cb)
+ : db_(db.db_), function_name_(function_name), cb_(cb) {
+ sql::sqlite3_create_function_v2(db_, function_name, args, SQLITE_UTF8,
+ this, &Run, NULL, NULL, NULL);
+ }
+ ~ScopedScalarFunction() {
+ sql::sqlite3_create_function_v2(db_, function_name_, 0, SQLITE_UTF8,
+ NULL, NULL, NULL, NULL, NULL);
+ }
+
+ private:
+ static void Run(sqlite3_context* context, int argc, sqlite3_value** argv) {
+ ScopedScalarFunction* t = static_cast<ScopedScalarFunction*>(
+ sqlite3_user_data(context));
+ t->cb_.Run(context, argc, argv);
+ }
+
+ sqlite3* db_;
+ const char* function_name_;
+ base::Callback<void(sqlite3_context*,int,sqlite3_value**)> cb_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedScalarFunction);
+};
+
+// Allow a test to add a SQLite commit hook in a scoped context.
+class ScopedCommitHook {
+ public:
+ ScopedCommitHook(sql::Connection& db,
+ base::Callback<int(void)> cb)
+ : db_(db.db_),
+ cb_(cb) {
+ sql::sqlite3_commit_hook(db_, &Run, this);
+ }
+ ~ScopedCommitHook() {
+ sql::sqlite3_commit_hook(db_, NULL, NULL);
+ }
+
+ private:
+ static int Run(void* p) {
+ ScopedCommitHook* t = static_cast<ScopedCommitHook*>(p);
+ return t->cb_.Run();
+ }
+
+ sqlite3* db_;
+ base::Callback<int(void)> cb_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedCommitHook);
+};
+
+} // namespace test
+} // namespace sql
+
namespace {
// Helper to return the count of items in sqlite_master. Return -1 in
@@ -88,29 +208,21 @@ class ScopedUmaskSetter {
};
#endif
-class SQLConnectionTest : public testing::Test {
+class SQLConnectionTest : public sql::SQLTestBase {
public:
void SetUp() override {
- ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
- db_path_ = temp_dir_.path().AppendASCII("SQLConnectionTest.db");
- ASSERT_TRUE(db_.Open(db_path_));
- }
+ // Any macro histograms which fire before the recorder is initialized cannot
+ // be tested. So this needs to be ahead of Open().
+ base::StatisticsRecorder::Initialize();
- void TearDown() override { db_.Close(); }
-
- sql::Connection& db() { return db_; }
- const base::FilePath& db_path() { return db_path_; }
+ SQLTestBase::SetUp();
+ }
// Handle errors by blowing away the database.
void RazeErrorCallback(int expected_error, int error, sql::Statement* stmt) {
EXPECT_EQ(expected_error, error);
- db_.RazeAndClose();
+ db().RazeAndClose();
}
-
- private:
- sql::Connection db_;
- base::FilePath db_path_;
- base::ScopedTempDir temp_dir_;
};
TEST_F(SQLConnectionTest, Execute) {
@@ -243,7 +355,7 @@ TEST_F(SQLConnectionTest, ScopedIgnoreUntracked) {
db().Close();
// Corrupt the database so that nothing works, including PRAGMAs.
- ASSERT_TRUE(sql::test::CorruptSizeInHeader(db_path()));
+ ASSERT_TRUE(CorruptSizeInHeaderOfDB());
{
sql::ScopedErrorIgnorer ignore_errors;
@@ -411,6 +523,8 @@ TEST_F(SQLConnectionTest, RazeMultiple) {
ASSERT_EQ(0, SqliteMasterCount(&other_db));
}
+// TODO(erg): Enable this in the next patch once I add locking.
+#if !defined(MOJO_APPTEST_IMPL)
TEST_F(SQLConnectionTest, RazeLocked) {
const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
ASSERT_TRUE(db().Execute(kCreateSql));
@@ -445,6 +559,7 @@ TEST_F(SQLConnectionTest, RazeLocked) {
ASSERT_FALSE(s.Step());
ASSERT_TRUE(db().Raze());
}
+#endif
// Verify that Raze() can handle an empty file. SQLite should treat
// this as an empty database.
@@ -453,12 +568,7 @@ TEST_F(SQLConnectionTest, RazeEmptyDB) {
ASSERT_TRUE(db().Execute(kCreateSql));
db().Close();
- {
- base::ScopedFILE file(base::OpenFile(db_path(), "rb+"));
- ASSERT_TRUE(file.get() != NULL);
- ASSERT_EQ(0, fseek(file.get(), 0, SEEK_SET));
- ASSERT_TRUE(base::TruncateFile(file.get()));
- }
+ TruncateDatabase();
ASSERT_TRUE(db().Open(db_path()));
ASSERT_TRUE(db().Raze());
@@ -469,16 +579,10 @@ TEST_F(SQLConnectionTest, RazeEmptyDB) {
TEST_F(SQLConnectionTest, RazeNOTADB) {
db().Close();
sql::Connection::Delete(db_path());
- ASSERT_FALSE(base::PathExists(db_path()));
-
- {
- base::ScopedFILE file(base::OpenFile(db_path(), "wb"));
- ASSERT_TRUE(file.get() != NULL);
+ ASSERT_FALSE(GetPathExists(db_path()));
- const char* kJunk = "This is the hour of our discontent.";
- fputs(kJunk, file.get());
- }
- ASSERT_TRUE(base::PathExists(db_path()));
+ WriteJunkToDatabase(SQLTestBase::TYPE_OVERWRITE_AND_TRUNCATE);
+ ASSERT_TRUE(GetPathExists(db_path()));
// SQLite will successfully open the handle, but fail when running PRAGMA
// statements that access the database.
@@ -512,14 +616,7 @@ TEST_F(SQLConnectionTest, RazeNOTADB2) {
ASSERT_EQ(1, SqliteMasterCount(&db()));
db().Close();
- {
- base::ScopedFILE file(base::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());
- }
+ WriteJunkToDatabase(SQLTestBase::TYPE_OVERWRITE);
// SQLite will successfully open the handle, but will fail with
// SQLITE_NOTADB on pragma statemenets which attempt to read the
@@ -549,7 +646,7 @@ TEST_F(SQLConnectionTest, RazeCallbackReopen) {
db().Close();
// Corrupt the database so that nothing works, including PRAGMAs.
- ASSERT_TRUE(sql::test::CorruptSizeInHeader(db_path()));
+ ASSERT_TRUE(CorruptSizeInHeaderOfDB());
// Open() will succeed, even though the PRAGMA calls within will
// fail with SQLITE_CORRUPT, as will this PRAGMA.
@@ -692,17 +789,19 @@ TEST_F(SQLConnectionTest, Delete) {
db().Close();
// Should have both a main database file and a journal file because
- // of journal_mode PERSIST.
+ // of journal_mode TRUNCATE.
base::FilePath journal(db_path().value() + FILE_PATH_LITERAL("-journal"));
- ASSERT_TRUE(base::PathExists(db_path()));
- ASSERT_TRUE(base::PathExists(journal));
+ ASSERT_TRUE(GetPathExists(db_path()));
+ ASSERT_TRUE(GetPathExists(journal));
sql::Connection::Delete(db_path());
- EXPECT_FALSE(base::PathExists(db_path()));
- EXPECT_FALSE(base::PathExists(journal));
+ EXPECT_FALSE(GetPathExists(db_path()));
+ EXPECT_FALSE(GetPathExists(journal));
}
-#if defined(OS_POSIX)
+// This test manually sets on disk permissions; this doesn't apply to the mojo
+// fork.
+#if defined(OS_POSIX) && !defined(MOJO_APPTEST_IMPL)
// Test that set_restrict_to_user() trims database permissions so that
// only the owner (and root) can read.
TEST_F(SQLConnectionTest, UserPermission) {
@@ -712,7 +811,7 @@ TEST_F(SQLConnectionTest, UserPermission) {
// Temporarily provide a more permissive umask.
db().Close();
sql::Connection::Delete(db_path());
- ASSERT_FALSE(base::PathExists(db_path()));
+ ASSERT_FALSE(GetPathExists(db_path()));
ScopedUmaskSetter permissive_umask(S_IWGRP | S_IWOTH);
ASSERT_TRUE(db().Open(db_path()));
@@ -726,8 +825,8 @@ TEST_F(SQLConnectionTest, UserPermission) {
// 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));
+ ASSERT_TRUE(GetPathExists(db_path()));
+ ASSERT_TRUE(GetPathExists(journal));
mode = base::FILE_PERMISSION_MASK;
EXPECT_TRUE(base::GetPosixFilePermissions(db_path(), &mode));
ASSERT_NE((mode & base::FILE_PERMISSION_USER_MASK), mode);
@@ -740,8 +839,8 @@ TEST_F(SQLConnectionTest, UserPermission) {
db().Close();
db().set_restrict_to_user();
ASSERT_TRUE(db().Open(db_path()));
- ASSERT_TRUE(base::PathExists(db_path()));
- ASSERT_TRUE(base::PathExists(journal));
+ ASSERT_TRUE(GetPathExists(db_path()));
+ ASSERT_TRUE(GetPathExists(journal));
mode = base::FILE_PERMISSION_MASK;
EXPECT_TRUE(base::GetPosixFilePermissions(db_path(), &mode));
ASSERT_EQ((mode & base::FILE_PERMISSION_USER_MASK), mode);
@@ -753,15 +852,15 @@ TEST_F(SQLConnectionTest, UserPermission) {
db().Close();
sql::Connection::Delete(db_path());
ASSERT_TRUE(db().Open(db_path()));
- ASSERT_TRUE(base::PathExists(db_path()));
- ASSERT_FALSE(base::PathExists(journal));
+ ASSERT_TRUE(GetPathExists(db_path()));
+ ASSERT_FALSE(GetPathExists(journal));
mode = base::FILE_PERMISSION_MASK;
EXPECT_TRUE(base::GetPosixFilePermissions(db_path(), &mode));
ASSERT_EQ((mode & base::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));
+ ASSERT_TRUE(GetPathExists(journal));
mode = base::FILE_PERMISSION_MASK;
EXPECT_TRUE(base::GetPosixFilePermissions(journal, &mode));
ASSERT_EQ((mode & base::FILE_PERMISSION_USER_MASK), mode);
@@ -867,7 +966,7 @@ TEST_F(SQLConnectionTest, Basic_QuickIntegrityCheck) {
EXPECT_TRUE(db().QuickIntegrityCheck());
db().Close();
- ASSERT_TRUE(sql::test::CorruptSizeInHeader(db_path()));
+ ASSERT_TRUE(CorruptSizeInHeaderOfDB());
{
sql::ScopedErrorIgnorer ignore_errors;
@@ -889,7 +988,7 @@ TEST_F(SQLConnectionTest, Basic_FullIntegrityCheck) {
EXPECT_EQ(kOk, messages[0]);
db().Close();
- ASSERT_TRUE(sql::test::CorruptSizeInHeader(db_path()));
+ ASSERT_TRUE(CorruptSizeInHeaderOfDB());
{
sql::ScopedErrorIgnorer ignore_errors;
@@ -905,4 +1004,299 @@ TEST_F(SQLConnectionTest, Basic_FullIntegrityCheck) {
// file that would pass the quick check and fail the full check.
}
+// Test Sqlite.Stats histogram for execute-oriented calls.
+TEST_F(SQLConnectionTest, EventsExecute) {
+ // Re-open with histogram tag.
+ db().Close();
+ db().set_histogram_tag("Test");
+ ASSERT_TRUE(db().Open(db_path()));
+
+ // Open() uses Execute() extensively, don't track those calls.
+ base::HistogramTester tester;
+
+ const char kHistogramName[] = "Sqlite.Stats.Test";
+ const char kGlobalHistogramName[] = "Sqlite.Stats";
+
+ ASSERT_TRUE(db().BeginTransaction());
+ const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
+ EXPECT_TRUE(db().Execute(kCreateSql));
+ EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (10, 'text')"));
+ EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (11, 'text')"));
+ EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (12, 'text')"));
+ EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (13, 'text')"));
+ EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (14, 'text')"));
+ EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (15, 'text');"
+ "INSERT INTO foo VALUES (16, 'text');"
+ "INSERT INTO foo VALUES (17, 'text');"
+ "INSERT INTO foo VALUES (18, 'text');"
+ "INSERT INTO foo VALUES (19, 'text')"));
+ ASSERT_TRUE(db().CommitTransaction());
+ ASSERT_TRUE(db().BeginTransaction());
+ EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (20, 'text')"));
+ db().RollbackTransaction();
+ EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (20, 'text')"));
+ EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (21, 'text')"));
+
+ // The create, 5 inserts, multi-statement insert, rolled-back insert, 2
+ // inserts outside transaction.
+ tester.ExpectBucketCount(kHistogramName, sql::Connection::EVENT_EXECUTE, 10);
+ tester.ExpectBucketCount(kGlobalHistogramName,
+ sql::Connection::EVENT_EXECUTE, 10);
+
+ // All of the executes, with the multi-statement inserts broken out, plus one
+ // for each begin, commit, and rollback.
+ tester.ExpectBucketCount(kHistogramName,
+ sql::Connection::EVENT_STATEMENT_RUN, 18);
+ tester.ExpectBucketCount(kGlobalHistogramName,
+ sql::Connection::EVENT_STATEMENT_RUN, 18);
+
+ tester.ExpectBucketCount(kHistogramName,
+ sql::Connection::EVENT_STATEMENT_ROWS, 0);
+ tester.ExpectBucketCount(kGlobalHistogramName,
+ sql::Connection::EVENT_STATEMENT_ROWS, 0);
+ tester.ExpectBucketCount(kHistogramName,
+ sql::Connection::EVENT_STATEMENT_SUCCESS, 18);
+ tester.ExpectBucketCount(kGlobalHistogramName,
+ sql::Connection::EVENT_STATEMENT_SUCCESS, 18);
+
+ // The 2 inserts outside the transaction.
+ tester.ExpectBucketCount(kHistogramName,
+ sql::Connection::EVENT_CHANGES_AUTOCOMMIT, 2);
+ tester.ExpectBucketCount(kGlobalHistogramName,
+ sql::Connection::EVENT_CHANGES_AUTOCOMMIT, 2);
+
+ // 11 inserts inside transactions.
+ tester.ExpectBucketCount(kHistogramName, sql::Connection::EVENT_CHANGES, 11);
+ tester.ExpectBucketCount(kGlobalHistogramName,
+ sql::Connection::EVENT_CHANGES, 11);
+
+ tester.ExpectBucketCount(kHistogramName, sql::Connection::EVENT_BEGIN, 2);
+ tester.ExpectBucketCount(kGlobalHistogramName,
+ sql::Connection::EVENT_BEGIN, 2);
+ tester.ExpectBucketCount(kHistogramName, sql::Connection::EVENT_COMMIT, 1);
+ tester.ExpectBucketCount(kGlobalHistogramName,
+ sql::Connection::EVENT_COMMIT, 1);
+ tester.ExpectBucketCount(kHistogramName, sql::Connection::EVENT_ROLLBACK, 1);
+ tester.ExpectBucketCount(kGlobalHistogramName,
+ sql::Connection::EVENT_ROLLBACK, 1);
+}
+
+// Test Sqlite.Stats histogram for prepared statements.
+TEST_F(SQLConnectionTest, EventsStatement) {
+ // Re-open with histogram tag.
+ db().Close();
+ db().set_histogram_tag("Test");
+ ASSERT_TRUE(db().Open(db_path()));
+
+ const char kHistogramName[] = "Sqlite.Stats.Test";
+ const char kGlobalHistogramName[] = "Sqlite.Stats";
+
+ const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
+ EXPECT_TRUE(db().Execute(kCreateSql));
+ EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (10, 'text')"));
+ EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (11, 'text')"));
+ EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (12, 'text')"));
+
+ {
+ base::HistogramTester tester;
+
+ {
+ sql::Statement s(db().GetUniqueStatement("SELECT value FROM foo"));
+ while (s.Step()) {
+ }
+ }
+
+ tester.ExpectBucketCount(kHistogramName,
+ sql::Connection::EVENT_STATEMENT_RUN, 1);
+ tester.ExpectBucketCount(kGlobalHistogramName,
+ sql::Connection::EVENT_STATEMENT_RUN, 1);
+ tester.ExpectBucketCount(kHistogramName,
+ sql::Connection::EVENT_STATEMENT_ROWS, 3);
+ tester.ExpectBucketCount(kGlobalHistogramName,
+ sql::Connection::EVENT_STATEMENT_ROWS, 3);
+ tester.ExpectBucketCount(kHistogramName,
+ sql::Connection::EVENT_STATEMENT_SUCCESS, 1);
+ tester.ExpectBucketCount(kGlobalHistogramName,
+ sql::Connection::EVENT_STATEMENT_SUCCESS, 1);
+ }
+
+ {
+ base::HistogramTester tester;
+
+ {
+ sql::Statement s(db().GetUniqueStatement(
+ "SELECT value FROM foo WHERE id > 10"));
+ while (s.Step()) {
+ }
+ }
+
+ tester.ExpectBucketCount(kHistogramName,
+ sql::Connection::EVENT_STATEMENT_RUN, 1);
+ tester.ExpectBucketCount(kGlobalHistogramName,
+ sql::Connection::EVENT_STATEMENT_RUN, 1);
+ tester.ExpectBucketCount(kHistogramName,
+ sql::Connection::EVENT_STATEMENT_ROWS, 2);
+ tester.ExpectBucketCount(kGlobalHistogramName,
+ sql::Connection::EVENT_STATEMENT_ROWS, 2);
+ tester.ExpectBucketCount(kHistogramName,
+ sql::Connection::EVENT_STATEMENT_SUCCESS, 1);
+ tester.ExpectBucketCount(kGlobalHistogramName,
+ sql::Connection::EVENT_STATEMENT_SUCCESS, 1);
+ }
+}
+
+// SQLite function to adjust mock time by |argv[0]| milliseconds.
+void sqlite_adjust_millis(sql::test::ScopedMockTimeSource* time_mock,
+ sqlite3_context* context,
+ int argc, sqlite3_value** argv) {
+ int64 milliseconds = argc > 0 ? sqlite3_value_int64(argv[0]) : 1000;
+ time_mock->adjust(base::TimeDelta::FromMilliseconds(milliseconds));
+ sqlite3_result_int64(context, milliseconds);
+}
+
+// Adjust mock time by |milliseconds| on commit.
+int adjust_commit_hook(sql::test::ScopedMockTimeSource* time_mock,
+ int64 milliseconds) {
+ time_mock->adjust(base::TimeDelta::FromMilliseconds(milliseconds));
+ return SQLITE_OK;
+}
+
+const char kCommitTime[] = "Sqlite.CommitTime.Test";
+const char kAutoCommitTime[] = "Sqlite.AutoCommitTime.Test";
+const char kUpdateTime[] = "Sqlite.UpdateTime.Test";
+const char kQueryTime[] = "Sqlite.QueryTime.Test";
+
+// Read-only query allocates time to QueryTime, but not others.
+TEST_F(SQLConnectionTest, TimeQuery) {
+ // Re-open with histogram tag. Use an in-memory database to minimize variance
+ // due to filesystem.
+ db().Close();
+ db().set_histogram_tag("Test");
+ ASSERT_TRUE(db().OpenInMemory());
+
+ sql::test::ScopedMockTimeSource time_mock(db());
+
+ const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
+ EXPECT_TRUE(db().Execute(kCreateSql));
+
+ // Function to inject pauses into statements.
+ sql::test::ScopedScalarFunction scoper(
+ db(), "milliadjust", 1, base::Bind(&sqlite_adjust_millis, &time_mock));
+
+ base::HistogramTester tester;
+
+ EXPECT_TRUE(db().Execute("SELECT milliadjust(10)"));
+
+ scoped_ptr<base::HistogramSamples> samples(
+ tester.GetHistogramSamplesSinceCreation(kQueryTime));
+ ASSERT_TRUE(samples);
+ // 10 for the adjust, 1 for the measurement.
+ EXPECT_EQ(11, samples->sum());
+
+ samples = tester.GetHistogramSamplesSinceCreation(kUpdateTime);
+ EXPECT_TRUE(!samples || samples->sum() == 0);
+
+ samples = tester.GetHistogramSamplesSinceCreation(kCommitTime);
+ EXPECT_TRUE(!samples || samples->sum() == 0);
+
+ samples = tester.GetHistogramSamplesSinceCreation(kAutoCommitTime);
+ EXPECT_TRUE(!samples || samples->sum() == 0);
+}
+
+// Autocommit update allocates time to QueryTime, UpdateTime, and
+// AutoCommitTime.
+TEST_F(SQLConnectionTest, TimeUpdateAutocommit) {
+ // Re-open with histogram tag. Use an in-memory database to minimize variance
+ // due to filesystem.
+ db().Close();
+ db().set_histogram_tag("Test");
+ ASSERT_TRUE(db().OpenInMemory());
+
+ sql::test::ScopedMockTimeSource time_mock(db());
+
+ const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
+ EXPECT_TRUE(db().Execute(kCreateSql));
+
+ // Function to inject pauses into statements.
+ sql::test::ScopedScalarFunction scoper(
+ db(), "milliadjust", 1, base::Bind(&sqlite_adjust_millis, &time_mock));
+
+ base::HistogramTester tester;
+
+ EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (10, milliadjust(10))"));
+
+ scoped_ptr<base::HistogramSamples> samples(
+ tester.GetHistogramSamplesSinceCreation(kQueryTime));
+ ASSERT_TRUE(samples);
+ // 10 for the adjust, 1 for the measurement.
+ EXPECT_EQ(11, samples->sum());
+
+ samples = tester.GetHistogramSamplesSinceCreation(kUpdateTime);
+ ASSERT_TRUE(samples);
+ // 10 for the adjust, 1 for the measurement.
+ EXPECT_EQ(11, samples->sum());
+
+ samples = tester.GetHistogramSamplesSinceCreation(kCommitTime);
+ EXPECT_TRUE(!samples || samples->sum() == 0);
+
+ samples = tester.GetHistogramSamplesSinceCreation(kAutoCommitTime);
+ ASSERT_TRUE(samples);
+ // 10 for the adjust, 1 for the measurement.
+ EXPECT_EQ(11, samples->sum());
+}
+
+// Update with explicit transaction allocates time to QueryTime, UpdateTime, and
+// CommitTime.
+TEST_F(SQLConnectionTest, TimeUpdateTransaction) {
+ // Re-open with histogram tag. Use an in-memory database to minimize variance
+ // due to filesystem.
+ db().Close();
+ db().set_histogram_tag("Test");
+ ASSERT_TRUE(db().OpenInMemory());
+
+ sql::test::ScopedMockTimeSource time_mock(db());
+
+ const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
+ EXPECT_TRUE(db().Execute(kCreateSql));
+
+ // Function to inject pauses into statements.
+ sql::test::ScopedScalarFunction scoper(
+ db(), "milliadjust", 1, base::Bind(&sqlite_adjust_millis, &time_mock));
+
+ base::HistogramTester tester;
+
+ {
+ // Make the commit slow.
+ sql::test::ScopedCommitHook scoped_hook(
+ db(), base::Bind(adjust_commit_hook, &time_mock, 100));
+ ASSERT_TRUE(db().BeginTransaction());
+ EXPECT_TRUE(db().Execute(
+ "INSERT INTO foo VALUES (11, milliadjust(10))"));
+ EXPECT_TRUE(db().Execute(
+ "UPDATE foo SET value = milliadjust(10) WHERE id = 11"));
+ EXPECT_TRUE(db().CommitTransaction());
+ }
+
+ scoped_ptr<base::HistogramSamples> samples(
+ tester.GetHistogramSamplesSinceCreation(kQueryTime));
+ ASSERT_TRUE(samples);
+ // 10 for insert adjust, 10 for update adjust, 100 for commit adjust, 1 for
+ // measuring each of BEGIN, INSERT, UPDATE, and COMMIT.
+ EXPECT_EQ(124, samples->sum());
+
+ samples = tester.GetHistogramSamplesSinceCreation(kUpdateTime);
+ ASSERT_TRUE(samples);
+ // 10 for insert adjust, 10 for update adjust, 100 for commit adjust, 1 for
+ // measuring each of INSERT, UPDATE, and COMMIT.
+ EXPECT_EQ(123, samples->sum());
+
+ samples = tester.GetHistogramSamplesSinceCreation(kCommitTime);
+ ASSERT_TRUE(samples);
+ // 100 for commit adjust, 1 for measuring COMMIT.
+ EXPECT_EQ(101, samples->sum());
+
+ samples = tester.GetHistogramSamplesSinceCreation(kAutoCommitTime);
+ EXPECT_TRUE(!samples || samples->sum() == 0);
+}
+
} // namespace
diff --git a/chromium/sql/correct_sql_test_base.h b/chromium/sql/correct_sql_test_base.h
new file mode 100644
index 00000000000..7056dea19ab
--- /dev/null
+++ b/chromium/sql/correct_sql_test_base.h
@@ -0,0 +1,27 @@
+// Copyright 2015 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_CORRECT_SQL_TEST_BASE_H_
+#define SQL_CORRECT_SQL_TEST_BASE_H_
+
+// This header exists to get around gn check. We want to use the same testing
+// code in both the sql_unittests target (which uses gtest and targets the
+// filesystem directly) and sql_apptests.mojo (which uses mojo:apptest and
+// proxies the additional filesystem access to mojo:filesystem). Both of these
+// files define a class named sql::SQLTestBase and have the same interface.
+//
+// Unfortunately, gn check does not understand preprocessor directives. If it
+// did, the following code would be gn check clean, but since it isn't, we
+// stuff this redirection header in its own file, give it its own source_set
+// target, and then set check_includes to false.
+//
+// This work around was suggested by brettw@.
+#if defined(MOJO_APPTEST_IMPL)
+#include "sql/mojo/sql_test_base.h"
+#else
+#include "sql/test/sql_test_base.h"
+#endif
+
+#endif // SQL_CORRECT_SQL_TEST_BASE_H_
+
diff --git a/chromium/sql/meta_table_unittest.cc b/chromium/sql/meta_table_unittest.cc
index 14123921793..13d0b5d2a1d 100644
--- a/chromium/sql/meta_table_unittest.cc
+++ b/chromium/sql/meta_table_unittest.cc
@@ -8,25 +8,12 @@
#include "base/files/scoped_temp_dir.h"
#include "sql/connection.h"
#include "sql/statement.h"
+#include "sql/test/sql_test_base.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
-class SQLMetaTableTest : public testing::Test {
- public:
- void SetUp() override {
- ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
- ASSERT_TRUE(db_.Open(temp_dir_.path().AppendASCII("SQLMetaTableTest.db")));
- }
-
- void TearDown() override { db_.Close(); }
-
- sql::Connection& db() { return db_; }
-
- private:
- base::ScopedTempDir temp_dir_;
- sql::Connection db_;
-};
+using SQLMetaTableTest = sql::SQLTestBase;
TEST_F(SQLMetaTableTest, DoesTableExist) {
EXPECT_FALSE(sql::MetaTable::DoesTableExist(&db()));
diff --git a/chromium/sql/mojo/BUILD.gn b/chromium/sql/mojo/BUILD.gn
new file mode 100644
index 00000000000..18feaec1094
--- /dev/null
+++ b/chromium/sql/mojo/BUILD.gn
@@ -0,0 +1,62 @@
+# Copyright 2015 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.
+
+import("//mojo/public/mojo_application.gni")
+
+source_set("mojo") {
+ sources = [
+ "mojo_vfs.cc",
+ "mojo_vfs.h",
+ ]
+
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ configs += [ "//build/config/compiler:no_size_t_to_int_warning" ]
+
+ defines = [ "SQL_IMPLEMENTATION" ]
+
+ deps = [
+ "//base",
+ "//base/third_party/dynamic_annotations",
+ "//components/filesystem/public/interfaces",
+ "//mojo/application/public/cpp",
+ "//mojo/common",
+ "//mojo/platform_handle",
+ "//third_party/sqlite",
+ ]
+}
+
+mojo_native_application("apptests") {
+ output_name = "sql_apptests"
+
+ testonly = true
+
+ # Instead of using the code in //sql/test/sql_test_base.h, we should use the
+ # mojo test base class.
+ defines = [ "MOJO_APPTEST_IMPL" ]
+
+ sources = [
+ "../connection_unittest.cc",
+ "../statement_unittest.cc",
+ "../test/paths.cc",
+ "../test/paths.h",
+ "../transaction_unittest.cc",
+ "sql_test_base.cc",
+ "sql_test_base.h",
+ "vfs_unittest.cc",
+ ]
+
+ deps = [
+ ":mojo",
+ "//base",
+ "//base/test:test_support",
+ "//components/filesystem/public/interfaces",
+ "//mojo/application/public/cpp:sources",
+ "//mojo/application/public/cpp:test_support",
+ "//sql",
+ "//sql:redirection_header",
+ "//sql:test_support",
+ "//testing/gtest:gtest",
+ "//third_party/mojo/src/mojo/public/cpp/bindings",
+ ]
+}
diff --git a/chromium/sql/mojo/DEPS b/chromium/sql/mojo/DEPS
new file mode 100644
index 00000000000..be63411efe9
--- /dev/null
+++ b/chromium/sql/mojo/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ "+components/filesystem",
+ "+mojo/application",
+ "+mojo/public",
+ "+mojo/util",
+]
diff --git a/chromium/sql/mojo/mojo_vfs.cc b/chromium/sql/mojo/mojo_vfs.cc
new file mode 100644
index 00000000000..6e38af97040
--- /dev/null
+++ b/chromium/sql/mojo/mojo_vfs.cc
@@ -0,0 +1,413 @@
+// Copyright 2015 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/mojo/mojo_vfs.h"
+
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "components/filesystem/public/interfaces/file.mojom.h"
+#include "components/filesystem/public/interfaces/file_system.mojom.h"
+#include "components/filesystem/public/interfaces/types.mojom.h"
+#include "mojo/public/cpp/bindings/lib/template_util.h"
+#include "mojo/util/capture_util.h"
+#include "third_party/sqlite/sqlite3.h"
+
+using mojo::Capture;
+
+namespace sql {
+
+sqlite3_vfs* GetParentVFS(sqlite3_vfs* mojo_vfs) {
+ return static_cast<ScopedMojoFilesystemVFS*>(mojo_vfs->pAppData)->parent_;
+}
+
+filesystem::DirectoryPtr& GetRootDirectory(sqlite3_vfs* mojo_vfs) {
+ return static_cast<ScopedMojoFilesystemVFS*>(mojo_vfs->pAppData)->
+ root_directory_;
+}
+
+namespace {
+
+// Implementation of the sqlite3 Mojo proxying vfs.
+//
+// This is a bunch of C callback objects which transparently proxy sqlite3's
+// filesystem reads/writes over the mojo:filesystem service. The main
+// entrypoint is sqlite3_mojovfs(), which proxies all the file open/delete/etc
+// operations. mojo:filesystem has support for passing a raw file descriptor
+// over the IPC barrier, and most of the implementation of sqlite3_io_methods
+// is derived from the default sqlite3 unix VFS and operates on the raw file
+// descriptors.
+
+const int kMaxPathName = 512;
+
+// A struct which extends the base sqlite3_file to also hold on to a file
+// pipe. We reinterpret_cast our sqlite3_file structs to this struct
+// instead. This is "safe" because this struct is really just a slab of
+// malloced memory, of which we tell sqlite how large we want with szOsFile.
+struct MojoVFSFile {
+ // The "vtable" of our sqlite3_file "subclass".
+ sqlite3_file base;
+
+ // We keep an open pipe to the File object to keep it from cleaning itself
+ // up.
+ filesystem::FilePtr file_ptr;
+};
+
+filesystem::FilePtr& GetFSFile(sqlite3_file* vfs_file) {
+ return reinterpret_cast<MojoVFSFile*>(vfs_file)->file_ptr;
+}
+
+int MojoVFSClose(sqlite3_file* file) {
+ DVLOG(1) << "MojoVFSClose(*)";
+ using filesystem::FilePtr;
+ GetFSFile(file).~FilePtr();
+ return SQLITE_OK;
+}
+
+int MojoVFSRead(sqlite3_file* sql_file,
+ void* buffer,
+ int size,
+ sqlite3_int64 offset) {
+ DVLOG(1) << "MojoVFSRead (" << size << " @ " << offset << ")";
+ filesystem::FileError error = filesystem::FILE_ERROR_FAILED;
+ mojo::Array<uint8_t> mojo_data;
+ GetFSFile(sql_file)->Read(size, offset, filesystem::WHENCE_FROM_BEGIN,
+ Capture(&error, &mojo_data));
+ GetFSFile(sql_file).WaitForIncomingResponse();
+
+ if (error != filesystem::FILE_ERROR_OK) {
+ // TODO(erg): Better implementation here.
+ NOTIMPLEMENTED();
+ return SQLITE_IOERR_READ;
+ }
+
+ if (mojo_data.size())
+ memcpy(buffer, &mojo_data.front(), mojo_data.size());
+ if (mojo_data.size() == static_cast<size_t>(size))
+ return SQLITE_OK;
+
+ // We didn't read the entire buffer. Fill the rest of the buffer with zeros.
+ memset(reinterpret_cast<char*>(buffer) + mojo_data.size(), 0,
+ size - mojo_data.size());
+
+ return SQLITE_IOERR_SHORT_READ;
+}
+
+int MojoVFSWrite(sqlite3_file* sql_file,
+ const void* buffer,
+ int size,
+ sqlite_int64 offset) {
+ DVLOG(1) << "MojoVFSWrite(*, " << size << ", " << offset << ")";
+ mojo::Array<uint8_t> mojo_data(size);
+ memcpy(&mojo_data.front(), buffer, size);
+
+ filesystem::FileError error = filesystem::FILE_ERROR_FAILED;
+ uint32_t num_bytes_written = 0;
+ GetFSFile(sql_file)->Write(mojo_data.Pass(), offset,
+ filesystem::WHENCE_FROM_BEGIN,
+ Capture(&error, &num_bytes_written));
+ GetFSFile(sql_file).WaitForIncomingResponse();
+ if (error != filesystem::FILE_ERROR_OK) {
+ // TODO(erg): Better implementation here.
+ NOTIMPLEMENTED();
+ return SQLITE_IOERR_WRITE;
+ }
+ if (num_bytes_written != static_cast<uint32_t>(size)) {
+ NOTIMPLEMENTED();
+ return SQLITE_IOERR_WRITE;
+ }
+
+ return SQLITE_OK;
+}
+
+int MojoVFSTruncate(sqlite3_file* sql_file, sqlite_int64 size) {
+ DVLOG(1) << "MojoVFSTruncate(*, " << size << ")";
+ filesystem::FileError error = filesystem::FILE_ERROR_FAILED;
+ GetFSFile(sql_file)->Truncate(size, Capture(&error));
+ GetFSFile(sql_file).WaitForIncomingResponse();
+ if (error != filesystem::FILE_ERROR_OK) {
+ // TODO(erg): Better implementation here.
+ NOTIMPLEMENTED();
+ return SQLITE_IOERR_TRUNCATE;
+ }
+
+ return SQLITE_OK;
+}
+
+int MojoVFSSync(sqlite3_file* sql_file, int flags) {
+ DVLOG(1) << "MojoVFSSync(*, " << flags << ")";
+ filesystem::FileError error = filesystem::FILE_ERROR_FAILED;
+ GetFSFile(sql_file)->Flush(Capture(&error));
+ GetFSFile(sql_file).WaitForIncomingResponse();
+ if (error != filesystem::FILE_ERROR_OK) {
+ // TODO(erg): Better implementation here.
+ NOTIMPLEMENTED();
+ return SQLITE_IOERR_FSYNC;
+ }
+
+ return SQLITE_OK;
+}
+
+int MojoVFSFileSize(sqlite3_file* sql_file, sqlite_int64* size) {
+ DVLOG(1) << "MojoVFSFileSize(*)";
+
+ filesystem::FileError err = filesystem::FILE_ERROR_FAILED;
+ filesystem::FileInformationPtr file_info;
+ GetFSFile(sql_file)->Stat(Capture(&err, &file_info));
+ GetFSFile(sql_file).WaitForIncomingResponse();
+
+ if (err != filesystem::FILE_ERROR_OK) {
+ // TODO(erg): Better implementation here.
+ NOTIMPLEMENTED();
+ return SQLITE_IOERR_FSTAT;
+ }
+
+ *size = file_info->size;
+ return SQLITE_OK;
+}
+
+// TODO(erg): The current base::File interface isn't sufficient to handle
+// sqlite's locking primitives, which are done on byte ranges in the file. (See
+// "File Locking Notes" in sqlite3.c.)
+int MojoVFSLock(sqlite3_file* pFile, int eLock) {
+ DVLOG(1) << "MojoVFSLock(*, " << eLock << ")";
+ return SQLITE_OK;
+}
+int MojoVFSUnlock(sqlite3_file* pFile, int eLock) {
+ DVLOG(1) << "MojoVFSUnlock(*, " << eLock << ")";
+ return SQLITE_OK;
+}
+int MojoVFSCheckReservedLock(sqlite3_file* pFile, int* pResOut) {
+ DVLOG(1) << "MojoVFSCheckReservedLock(*)";
+ *pResOut = 0;
+ return SQLITE_OK;
+}
+
+// TODO(erg): This is the minimal implementation to get a few tests passing;
+// lots more needs to be done here.
+int MojoVFSFileControl(sqlite3_file* pFile, int op, void* pArg) {
+ DVLOG(1) << "MojoVFSFileControl(*, " << op << ", *)";
+ if (op == SQLITE_FCNTL_PRAGMA) {
+ // Returning NOTFOUND tells sqlite that we aren't doing any processing.
+ return SQLITE_NOTFOUND;
+ }
+
+ return SQLITE_OK;
+}
+
+int MojoVFSSectorSize(sqlite3_file* pFile) {
+ DVLOG(1) << "MojoVFSSectorSize(*)";
+ // Use the default sector size.
+ return 0;
+}
+
+int MojoVFSDeviceCharacteristics(sqlite3_file* pFile) {
+ DVLOG(1) << "MojoVFSDeviceCharacteristics(*)";
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+static sqlite3_io_methods mojo_vfs_io_methods = {
+ 1, /* iVersion */
+ MojoVFSClose, /* xClose */
+ MojoVFSRead, /* xRead */
+ MojoVFSWrite, /* xWrite */
+ MojoVFSTruncate, /* xTruncate */
+ MojoVFSSync, /* xSync */
+ MojoVFSFileSize, /* xFileSize */
+ MojoVFSLock, /* xLock */
+ MojoVFSUnlock, /* xUnlock */
+ MojoVFSCheckReservedLock, /* xCheckReservedLock */
+ MojoVFSFileControl, /* xFileControl */
+ MojoVFSSectorSize, /* xSectorSize */
+ MojoVFSDeviceCharacteristics, /* xDeviceCharacteristics */
+};
+
+int MojoVFSOpen(sqlite3_vfs* mojo_vfs,
+ const char* name,
+ sqlite3_file* file,
+ int flags,
+ int* pOutFlags) {
+ DVLOG(1) << "MojoVFSOpen(*, " << name << ", *, " << flags << ")";
+ int open_flags = 0;
+ if (flags & SQLITE_OPEN_EXCLUSIVE) {
+ DCHECK(flags & SQLITE_OPEN_CREATE);
+ open_flags = filesystem::kFlagCreate;
+ } else if (flags & SQLITE_OPEN_CREATE) {
+ DCHECK(flags & SQLITE_OPEN_READWRITE);
+ open_flags = filesystem::kFlagOpenAlways;
+ } else {
+ open_flags = filesystem::kFlagOpen;
+ }
+ open_flags |= filesystem::kFlagRead;
+ if (flags & SQLITE_OPEN_READWRITE)
+ open_flags |= filesystem::kFlagWrite;
+ if (flags & SQLITE_OPEN_DELETEONCLOSE)
+ open_flags |= filesystem::kDeleteOnClose;
+
+ // Grab the incoming file
+ filesystem::FilePtr file_ptr;
+ filesystem::FileError error = filesystem::FILE_ERROR_FAILED;
+ GetRootDirectory(mojo_vfs)->OpenFile(mojo::String(name), GetProxy(&file_ptr),
+ open_flags, Capture(&error));
+ GetRootDirectory(mojo_vfs).WaitForIncomingResponse();
+ if (error != filesystem::FILE_ERROR_OK) {
+ // TODO(erg): Translate more of the mojo error codes into sqlite error
+ // codes.
+ return SQLITE_CANTOPEN;
+ }
+
+ // Set the method table so we can be closed (and run the manual dtor call to
+ // match the following placement news).
+ file->pMethods = &mojo_vfs_io_methods;
+
+ // |file| is actually a malloced buffer of size szOsFile. This means that we
+ // need to manually use placement new to construct the C++ object which owns
+ // the pipe to our file.
+ new (&GetFSFile(file)) filesystem::FilePtr(file_ptr.Pass());
+
+ return SQLITE_OK;
+}
+
+int MojoVFSDelete(sqlite3_vfs* mojo_vfs, const char* filename, int sync_dir) {
+ DVLOG(1) << "MojoVFSDelete(*, " << filename << ", " << sync_dir << ")";
+ // TODO(erg): The default windows sqlite VFS has retry code to work around
+ // antivirus software keeping files open. We'll probably have to do something
+ // like that in the far future if we ever support Windows.
+ filesystem::FileError error = filesystem::FILE_ERROR_FAILED;
+ GetRootDirectory(mojo_vfs)->Delete(filename, 0, Capture(&error));
+ GetRootDirectory(mojo_vfs).WaitForIncomingResponse();
+
+ if (error == filesystem::FILE_ERROR_OK && sync_dir) {
+ GetRootDirectory(mojo_vfs)->Flush(Capture(&error));
+ GetRootDirectory(mojo_vfs).WaitForIncomingResponse();
+ }
+
+ return error == filesystem::FILE_ERROR_OK ? SQLITE_OK : SQLITE_IOERR_DELETE;
+}
+
+int MojoVFSAccess(sqlite3_vfs* mojo_vfs,
+ const char* filename,
+ int flags,
+ int* result) {
+ DVLOG(1) << "MojoVFSAccess(*, " << filename << ", " << flags << ", *)";
+ filesystem::FileError error = filesystem::FILE_ERROR_FAILED;
+
+ if (flags == SQLITE_ACCESS_READWRITE || flags == SQLITE_ACCESS_READ) {
+ bool is_writable = false;
+ GetRootDirectory(mojo_vfs)
+ ->IsWritable(filename, Capture(&error, &is_writable));
+ GetRootDirectory(mojo_vfs).WaitForIncomingResponse();
+ *result = is_writable;
+ return SQLITE_OK;
+ }
+
+ if (flags == SQLITE_ACCESS_EXISTS) {
+ bool exists = false;
+ GetRootDirectory(mojo_vfs)->Exists(filename, Capture(&error, &exists));
+ GetRootDirectory(mojo_vfs).WaitForIncomingResponse();
+ *result = exists;
+ return SQLITE_OK;
+ }
+
+ return SQLITE_IOERR;
+}
+
+int MojoVFSFullPathname(sqlite3_vfs* mojo_vfs,
+ const char* relative_path,
+ int absolute_path_size,
+ char* absolute_path) {
+ // The sandboxed process doesn't need to know the absolute path of the file.
+ sqlite3_snprintf(absolute_path_size, absolute_path, "%s", relative_path);
+ return SQLITE_OK;
+}
+
+// Don't let SQLite dynamically load things. (If we are using the
+// mojo:filesystem proxying VFS, then it's highly likely that we are sandboxed
+// and that any attempt to dlopen() a shared object is folly.)
+void* MojoVFSDlOpen(sqlite3_vfs*, const char*) {
+ return 0;
+}
+
+void MojoVFSDlError(sqlite3_vfs*, int buf_size, char* error_msg) {
+ sqlite3_snprintf(buf_size, error_msg, "Dynamic loading not supported");
+}
+
+void (*MojoVFSDlSym(sqlite3_vfs*, void*, const char*))(void) {
+ return 0;
+}
+
+void MojoVFSDlClose(sqlite3_vfs*, void*) {
+ return;
+}
+
+int MojoVFSRandomness(sqlite3_vfs* mojo_vfs, int size, char* out) {
+ base::RandBytes(out, size);
+ return size;
+}
+
+// Proxy the rest of the calls down to the OS specific handler.
+int MojoVFSSleep(sqlite3_vfs* mojo_vfs, int micro) {
+ return GetParentVFS(mojo_vfs)->xSleep(GetParentVFS(mojo_vfs), micro);
+}
+
+int MojoVFSCurrentTime(sqlite3_vfs* mojo_vfs, double* time) {
+ return GetParentVFS(mojo_vfs)->xCurrentTime(GetParentVFS(mojo_vfs), time);
+}
+
+int MojoVFSGetLastError(sqlite3_vfs* mojo_vfs, int a, char* b) {
+ return 0;
+}
+
+int MojoVFSCurrentTimeInt64(sqlite3_vfs* mojo_vfs, sqlite3_int64* out) {
+ return GetParentVFS(mojo_vfs)->xCurrentTimeInt64(GetParentVFS(mojo_vfs), out);
+}
+
+static sqlite3_vfs mojo_vfs = {
+ 1, /* iVersion */
+ sizeof(MojoVFSFile), /* szOsFile */
+ kMaxPathName, /* mxPathname */
+ 0, /* pNext */
+ "mojo", /* zName */
+ 0, /* pAppData */
+ MojoVFSOpen, /* xOpen */
+ MojoVFSDelete, /* xDelete */
+ MojoVFSAccess, /* xAccess */
+ MojoVFSFullPathname, /* xFullPathname */
+ MojoVFSDlOpen, /* xDlOpen */
+ MojoVFSDlError, /* xDlError */
+ MojoVFSDlSym, /* xDlSym */
+ MojoVFSDlClose, /* xDlClose */
+ MojoVFSRandomness, /* xRandomness */
+ MojoVFSSleep, /* xSleep */
+ MojoVFSCurrentTime, /* xCurrentTime */
+ MojoVFSGetLastError, /* xGetLastError */
+ MojoVFSCurrentTimeInt64 /* xCurrentTimeInt64 */
+};
+
+} // namespace
+
+ScopedMojoFilesystemVFS::ScopedMojoFilesystemVFS(
+ filesystem::DirectoryPtr root_directory)
+ : parent_(sqlite3_vfs_find(NULL)),
+ root_directory_(root_directory.Pass()) {
+ CHECK(!mojo_vfs.pAppData);
+ mojo_vfs.pAppData = this;
+ mojo_vfs.mxPathname = parent_->mxPathname;
+
+ CHECK(sqlite3_vfs_register(&mojo_vfs, 1) == SQLITE_OK);
+}
+
+ScopedMojoFilesystemVFS::~ScopedMojoFilesystemVFS() {
+ CHECK(mojo_vfs.pAppData);
+ mojo_vfs.pAppData = nullptr;
+
+ CHECK(sqlite3_vfs_register(parent_, 1) == SQLITE_OK);
+ CHECK(sqlite3_vfs_unregister(&mojo_vfs) == SQLITE_OK);
+}
+
+filesystem::DirectoryPtr& ScopedMojoFilesystemVFS::GetDirectory() {
+ return root_directory_;
+}
+
+} // namespace sql
diff --git a/chromium/sql/mojo/mojo_vfs.h b/chromium/sql/mojo/mojo_vfs.h
new file mode 100644
index 00000000000..dc835938eff
--- /dev/null
+++ b/chromium/sql/mojo/mojo_vfs.h
@@ -0,0 +1,45 @@
+// Copyright 2015 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_MOJO_MOJO_VFS_H_
+#define SQL_MOJO_MOJO_VFS_H_
+
+#include "base/macros.h"
+#include "components/filesystem/public/interfaces/directory.mojom.h"
+
+typedef struct sqlite3_vfs sqlite3_vfs;
+
+namespace sql {
+
+// Changes the default sqlite3 vfs to a vfs that uses proxies calls to the
+// mojo:filesystem service. Instantiating this object transparently changes how
+// the entire //sql/ subsystem works in the process of the caller; all paths
+// are treated as relative to |directory|.
+class ScopedMojoFilesystemVFS {
+ public:
+ explicit ScopedMojoFilesystemVFS(filesystem::DirectoryPtr directory);
+ ~ScopedMojoFilesystemVFS();
+
+ // Returns the directory of the current VFS.
+ filesystem::DirectoryPtr& GetDirectory();
+
+ private:
+ friend sqlite3_vfs* GetParentVFS(sqlite3_vfs* mojo_vfs);
+ friend filesystem::DirectoryPtr& GetRootDirectory(sqlite3_vfs* mojo_vfs);
+
+ // The default vfs at the time MojoVFS was installed. We use the to pass
+ // through things like randomness requests and per-platform sleep calls.
+ sqlite3_vfs* parent_;
+
+ // When we initialize the subsystem, we are given a filesystem::Directory
+ // object, which is the root directory of a mojo:filesystem. All access to
+ // various files are specified from this root directory.
+ filesystem::DirectoryPtr root_directory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedMojoFilesystemVFS);
+};
+
+} // namespace sql
+
+#endif // SQL_MOJO_MOJO_VFS_H_
diff --git a/chromium/sql/mojo/sql_test_base.cc b/chromium/sql/mojo/sql_test_base.cc
new file mode 100644
index 00000000000..57645dd55d1
--- /dev/null
+++ b/chromium/sql/mojo/sql_test_base.cc
@@ -0,0 +1,156 @@
+// Copyright 2015 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/mojo/sql_test_base.h"
+
+#include "mojo/application/public/cpp/application_impl.h"
+#include "mojo/util/capture_util.h"
+#include "sql/mojo/mojo_vfs.h"
+#include "sql/test/test_helpers.h"
+
+using mojo::Capture;
+
+namespace sql {
+
+SQLTestBase::SQLTestBase() {
+}
+
+SQLTestBase::~SQLTestBase() {
+}
+
+base::FilePath SQLTestBase::db_path() {
+ return base::FilePath(FILE_PATH_LITERAL("SQLTest.db"));
+}
+
+sql::Connection& SQLTestBase::db() {
+ return db_;
+}
+
+bool SQLTestBase::Reopen() {
+ db_.Close();
+ return db_.Open(db_path());
+}
+
+bool SQLTestBase::GetPathExists(const base::FilePath& path) {
+ filesystem::FileError error = filesystem::FILE_ERROR_FAILED;
+ bool exists = false;
+ vfs_->GetDirectory()->Exists(path.AsUTF8Unsafe(), Capture(&error, &exists));
+ vfs_->GetDirectory().WaitForIncomingResponse();
+ if (error != filesystem::FILE_ERROR_OK)
+ return false;
+ return exists;
+}
+
+bool SQLTestBase::CorruptSizeInHeaderOfDB() {
+ // See http://www.sqlite.org/fileformat.html#database_header
+ const size_t kHeaderSize = 100;
+
+ mojo::Array<uint8_t> header;
+
+ filesystem::FileError error = filesystem::FILE_ERROR_FAILED;
+ filesystem::FilePtr file_ptr;
+ vfs_->GetDirectory()->OpenFile(
+ mojo::String(db_path().AsUTF8Unsafe()), GetProxy(&file_ptr),
+ filesystem::kFlagRead | filesystem::kFlagWrite |
+ filesystem::kFlagOpenAlways,
+ Capture(&error));
+ vfs_->GetDirectory().WaitForIncomingResponse();
+ if (error != filesystem::FILE_ERROR_OK)
+ return false;
+
+ file_ptr->Read(kHeaderSize, 0, filesystem::WHENCE_FROM_BEGIN,
+ Capture(&error, &header));
+ file_ptr.WaitForIncomingResponse();
+ if (error != filesystem::FILE_ERROR_OK)
+ return false;
+
+ filesystem::FileInformationPtr info;
+ file_ptr->Stat(Capture(&error, &info));
+ file_ptr.WaitForIncomingResponse();
+ if (error != filesystem::FILE_ERROR_OK)
+ return false;
+ int64_t db_size = info->size;
+
+ test::CorruptSizeInHeaderMemory(&header.front(), db_size);
+
+ uint32_t num_bytes_written = 0;
+ file_ptr->Write(header.Pass(), 0, filesystem::WHENCE_FROM_BEGIN,
+ Capture(&error, &num_bytes_written));
+ file_ptr.WaitForIncomingResponse();
+ if (error != filesystem::FILE_ERROR_OK)
+ return false;
+ if (num_bytes_written != kHeaderSize)
+ return false;
+
+ return true;
+}
+
+void SQLTestBase::WriteJunkToDatabase(WriteJunkType type) {
+ uint32_t flags = 0;
+ if (type == TYPE_OVERWRITE_AND_TRUNCATE)
+ flags = filesystem::kFlagWrite | filesystem::kFlagCreate;
+ else
+ flags = filesystem::kFlagWrite | filesystem::kFlagOpen;
+
+ filesystem::FileError error = filesystem::FILE_ERROR_FAILED;
+ filesystem::FilePtr file_ptr;
+ vfs_->GetDirectory()->OpenFile(
+ mojo::String(db_path().AsUTF8Unsafe()), GetProxy(&file_ptr),
+ flags,
+ Capture(&error));
+ vfs_->GetDirectory().WaitForIncomingResponse();
+ if (error != filesystem::FILE_ERROR_OK)
+ return;
+
+ const char* kJunk = "Now is the winter of our discontent.";
+ mojo::Array<uint8_t> data(strlen(kJunk));
+ memcpy(&data.front(), kJunk, strlen(kJunk));
+
+ uint32_t num_bytes_written = 0;
+ file_ptr->Write(data.Pass(), 0, filesystem::WHENCE_FROM_BEGIN,
+ Capture(&error, &num_bytes_written));
+ file_ptr.WaitForIncomingResponse();
+}
+
+void SQLTestBase::TruncateDatabase() {
+ filesystem::FileError error = filesystem::FILE_ERROR_FAILED;
+ filesystem::FilePtr file_ptr;
+ vfs_->GetDirectory()->OpenFile(
+ mojo::String(db_path().AsUTF8Unsafe()), GetProxy(&file_ptr),
+ filesystem::kFlagWrite | filesystem::kFlagOpen,
+ Capture(&error));
+ vfs_->GetDirectory().WaitForIncomingResponse();
+ if (error != filesystem::FILE_ERROR_OK)
+ return;
+
+ file_ptr->Truncate(0, Capture(&error));
+ file_ptr.WaitForIncomingResponse();
+ ASSERT_EQ(filesystem::FILE_ERROR_OK, error);
+}
+
+void SQLTestBase::SetUp() {
+ ApplicationTestBase::SetUp();
+
+ mojo::URLRequestPtr request(mojo::URLRequest::New());
+ request->url = mojo::String::From("mojo:filesystem");
+ application_impl()->ConnectToService(request.Pass(), &files_);
+
+ filesystem::FileError error = filesystem::FILE_ERROR_FAILED;
+ filesystem::DirectoryPtr directory;
+ files()->OpenFileSystem("temp", GetProxy(&directory), Capture(&error));
+ ASSERT_TRUE(files().WaitForIncomingResponse());
+ ASSERT_EQ(filesystem::FILE_ERROR_OK, error);
+
+ vfs_.reset(new ScopedMojoFilesystemVFS(directory.Pass()));
+ ASSERT_TRUE(db_.Open(db_path()));
+}
+
+void SQLTestBase::TearDown() {
+ db_.Close();
+ vfs_.reset();
+
+ ApplicationTestBase::TearDown();
+}
+
+} // namespace sql
diff --git a/chromium/sql/mojo/sql_test_base.h b/chromium/sql/mojo/sql_test_base.h
new file mode 100644
index 00000000000..f2bfb5d4c48
--- /dev/null
+++ b/chromium/sql/mojo/sql_test_base.h
@@ -0,0 +1,85 @@
+// Copyright 2015 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_MOJO_SQL_TEST_BASE_H_
+#define SQL_MOJO_SQL_TEST_BASE_H_
+
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/filesystem/public/interfaces/file_system.mojom.h"
+#include "mojo/application/public/cpp/application_test_base.h"
+#include "sql/connection.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace sql {
+
+class Connection;
+class ScopedMojoFilesystemVFS;
+
+// Base class for SQL tests.
+//
+// WARNING: We want to run the same gtest based unit test code both against
+// chromium (which uses this implementation here), and the mojo code (which
+// uses a different class named SQLTestBase). These two classes need to have
+// the same interface because we compile time switch them based on a
+// #define. We need to have two different implementations because the mojo
+// version derives from mojo::test::ApplicationTestBase instead of
+// testing::Test.
+class SQLTestBase : public mojo::test::ApplicationTestBase {
+ public:
+ SQLTestBase();
+ ~SQLTestBase() override;
+
+ enum WriteJunkType {
+ TYPE_OVERWRITE_AND_TRUNCATE,
+ TYPE_OVERWRITE
+ };
+
+ // Returns the path to the database.
+ base::FilePath db_path();
+
+ // Returns a connection to the database at db_path().
+ sql::Connection& db();
+
+ // Closes the current connection to the database and reopens it.
+ bool Reopen();
+
+ // Proxying method around base::PathExists.
+ bool GetPathExists(const base::FilePath& path);
+
+ // SQLite stores the database size in the header, and if the actual
+ // OS-derived size is smaller, the database is considered corrupt.
+ // [This case is actually a common form of corruption in the wild.]
+ // This helper sets the in-header size to one page larger than the
+ // actual file size. The resulting file will return SQLITE_CORRUPT
+ // for most operations unless PRAGMA writable_schema is turned ON.
+ //
+ // Returns false if any error occurs accessing the file.
+ bool CorruptSizeInHeaderOfDB();
+
+ // Writes junk to the start of the file.
+ void WriteJunkToDatabase(WriteJunkType type);
+
+ // Sets the database file size to 0.
+ void TruncateDatabase();
+
+ // Overridden from testing::Test:
+ void SetUp() override;
+ void TearDown() override;
+
+ protected:
+ filesystem::FileSystemPtr& files() { return files_; }
+
+ private:
+ filesystem::FileSystemPtr files_;
+
+ scoped_ptr<ScopedMojoFilesystemVFS> vfs_;
+ sql::Connection db_;
+
+ DISALLOW_COPY_AND_ASSIGN(SQLTestBase);
+};
+
+} // namespace sql
+
+#endif // SQL_MOJO_SQL_TEST_BASE_H_
diff --git a/chromium/sql/mojo/vfs_unittest.cc b/chromium/sql/mojo/vfs_unittest.cc
new file mode 100644
index 00000000000..8ca7c5c21a7
--- /dev/null
+++ b/chromium/sql/mojo/vfs_unittest.cc
@@ -0,0 +1,317 @@
+// Copyright 2015 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 "components/filesystem/public/interfaces/file_system.mojom.h"
+#include "mojo/application/public/cpp/application_impl.h"
+#include "mojo/application/public/cpp/application_test_base.h"
+#include "mojo/util/capture_util.h"
+#include "sql/mojo/mojo_vfs.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/sqlite/sqlite3.h"
+
+namespace base {
+
+// This deleter lets us be safe with sqlite3 objects, which aren't really the
+// structs, but slabs of new uint8_t[size].
+template <>
+struct DefaultDeleter<sqlite3_file> {
+ inline void operator()(sqlite3_file* ptr) const {
+ // Why don't we call file->pMethods->xClose() here? Because it's not
+ // guaranteed to be valid. sqlite3_file "objects" can be in partially
+ // initialized states.
+ delete [] reinterpret_cast<uint8_t*>(ptr);
+ }
+};
+
+} // namespace base
+
+namespace sql {
+
+const char kFileName[] = "TestingDatabase.db";
+
+class VFSTest : public mojo::test::ApplicationTestBase {
+ public:
+ VFSTest() {}
+ ~VFSTest() override {}
+
+ sqlite3_vfs* vfs() {
+ return sqlite3_vfs_find(NULL);
+ }
+
+ scoped_ptr<sqlite3_file> MakeFile() {
+ return scoped_ptr<sqlite3_file>(reinterpret_cast<sqlite3_file*>(
+ new uint8_t[vfs()->szOsFile]));
+ }
+
+ void SetUp() override {
+ mojo::test::ApplicationTestBase::SetUp();
+
+ mojo::URLRequestPtr request(mojo::URLRequest::New());
+ request->url = mojo::String::From("mojo:filesystem");
+ application_impl()->ConnectToService(request.Pass(), &files_);
+
+ filesystem::FileError error = filesystem::FILE_ERROR_FAILED;
+ filesystem::DirectoryPtr directory;
+ files_->OpenFileSystem("temp", GetProxy(&directory), mojo::Capture(&error));
+ ASSERT_TRUE(files_.WaitForIncomingResponse());
+ ASSERT_EQ(filesystem::FILE_ERROR_OK, error);
+
+ vfs_.reset(new ScopedMojoFilesystemVFS(directory.Pass()));
+ }
+
+ void TearDown() override {
+ vfs_.reset();
+ mojo::test::ApplicationTestBase::TearDown();
+ }
+
+ private:
+ filesystem::FileSystemPtr files_;
+ scoped_ptr<ScopedMojoFilesystemVFS> vfs_;
+
+ DISALLOW_COPY_AND_ASSIGN(VFSTest);
+};
+
+TEST_F(VFSTest, TestInstalled) {
+ EXPECT_EQ("mojo", vfs()->zName);
+}
+
+TEST_F(VFSTest, ExclusiveOpen) {
+ // Opening a non-existent file exclusively should work.
+ scoped_ptr<sqlite3_file> file(MakeFile());
+ int out_flags;
+ int rc = vfs()->xOpen(vfs(), kFileName, file.get(),
+ SQLITE_OPEN_EXCLUSIVE | SQLITE_OPEN_CREATE,
+ &out_flags);
+ EXPECT_EQ(SQLITE_OK, rc);
+
+ // Opening it a second time exclusively shouldn't.
+ scoped_ptr<sqlite3_file> file2(MakeFile());
+ rc = vfs()->xOpen(vfs(), kFileName, file2.get(),
+ SQLITE_OPEN_EXCLUSIVE | SQLITE_OPEN_CREATE,
+ &out_flags);
+ EXPECT_NE(SQLITE_OK, rc);
+
+ file->pMethods->xClose(file.get());
+}
+
+TEST_F(VFSTest, NonexclusiveOpen) {
+ // Opening a non-existent file should work.
+ scoped_ptr<sqlite3_file> file(MakeFile());
+ int out_flags;
+ int rc = vfs()->xOpen(vfs(), kFileName, file.get(),
+ SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE,
+ &out_flags);
+ EXPECT_EQ(SQLITE_OK, rc);
+
+ // Opening it a second time should work.
+ scoped_ptr<sqlite3_file> file2(MakeFile());
+ rc = vfs()->xOpen(vfs(), kFileName, file2.get(),
+ SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE,
+ &out_flags);
+ EXPECT_EQ(SQLITE_OK, rc);
+
+ file->pMethods->xClose(file.get());
+ file->pMethods->xClose(file2.get());
+}
+
+TEST_F(VFSTest, DeleteOnClose) {
+ {
+ scoped_ptr<sqlite3_file> file(MakeFile());
+ int out_flags;
+ int rc = vfs()->xOpen(
+ vfs(), kFileName, file.get(),
+ SQLITE_OPEN_DELETEONCLOSE | SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE,
+ &out_flags);
+ EXPECT_EQ(SQLITE_OK, rc);
+ file->pMethods->xClose(file.get());
+ }
+
+ // The file shouldn't exist now.
+ int result = 0;
+ vfs()->xAccess(vfs(), kFileName, SQLITE_ACCESS_EXISTS, &result);
+ EXPECT_FALSE(result);
+}
+
+TEST_F(VFSTest, TestNonExistence) {
+ // We shouldn't have a file exist yet in a fresh directory.
+ int result = 0;
+ vfs()->xAccess(vfs(), kFileName, SQLITE_ACCESS_EXISTS, &result);
+ EXPECT_FALSE(result);
+}
+
+TEST_F(VFSTest, TestExistence) {
+ {
+ scoped_ptr<sqlite3_file> file(MakeFile());
+ int out_flags;
+ int rc = vfs()->xOpen(vfs(), kFileName, file.get(),
+ SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE,
+ &out_flags);
+ EXPECT_EQ(SQLITE_OK, rc);
+
+ file->pMethods->xClose(file.get());
+ }
+
+ int result = 0;
+ vfs()->xAccess(vfs(), kFileName, SQLITE_ACCESS_EXISTS, &result);
+ EXPECT_TRUE(result);
+}
+
+TEST_F(VFSTest, TestDelete) {
+ {
+ scoped_ptr<sqlite3_file> file(MakeFile());
+ int out_flags;
+ int rc = vfs()->xOpen(vfs(), kFileName, file.get(),
+ SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE,
+ &out_flags);
+ EXPECT_EQ(SQLITE_OK, rc);
+
+ file->pMethods->xClose(file.get());
+ }
+
+ int result = 0;
+ vfs()->xAccess(vfs(), kFileName, SQLITE_ACCESS_EXISTS, &result);
+ EXPECT_TRUE(result);
+
+ vfs()->xDelete(vfs(), kFileName, 0);
+
+ vfs()->xAccess(vfs(), kFileName, SQLITE_ACCESS_EXISTS, &result);
+ EXPECT_FALSE(result);
+}
+
+TEST_F(VFSTest, TestWriteAndRead) {
+ const char kBuffer[] = "One Two Three Four Five Six Seven";
+ const int kBufferSize = arraysize(kBuffer);
+
+ {
+ scoped_ptr<sqlite3_file> file(MakeFile());
+ int out_flags;
+ int rc = vfs()->xOpen(vfs(), kFileName, file.get(),
+ SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE,
+ &out_flags);
+ EXPECT_EQ(SQLITE_OK, rc);
+
+ for (int i = 0; i < 10; ++i) {
+ rc = file->pMethods->xWrite(file.get(), kBuffer, kBufferSize,
+ i * kBufferSize);
+ EXPECT_EQ(SQLITE_OK, rc);
+ }
+
+ file->pMethods->xClose(file.get());
+ }
+
+ // Expect that the size of the file is 10 * arraysize(kBuffer);
+ {
+ scoped_ptr<sqlite3_file> file(MakeFile());
+ int out_flags;
+ int rc = vfs()->xOpen(vfs(), kFileName, file.get(),
+ SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE,
+ &out_flags);
+ EXPECT_EQ(SQLITE_OK, rc);
+
+ sqlite_int64 size;
+ rc = file->pMethods->xFileSize(file.get(), &size);
+ EXPECT_EQ(SQLITE_OK, rc);
+ EXPECT_EQ(10 * kBufferSize, size);
+
+ file->pMethods->xClose(file.get());
+ }
+
+ // We should be able to read things back.
+ {
+ scoped_ptr<sqlite3_file> file(MakeFile());
+ int out_flags;
+ int rc = vfs()->xOpen(vfs(), kFileName, file.get(),
+ SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE,
+ &out_flags);
+ EXPECT_EQ(SQLITE_OK, rc);
+
+ char data_buffer[kBufferSize];
+ memset(data_buffer, '8', kBufferSize);
+ for (int i = 0; i < 10; ++i) {
+ rc = file->pMethods->xRead(file.get(), data_buffer, kBufferSize,
+ i * kBufferSize);
+ EXPECT_EQ(SQLITE_OK, rc);
+ EXPECT_TRUE(strncmp(kBuffer, &data_buffer[0], kBufferSize) == 0);
+ }
+
+ file->pMethods->xClose(file.get());
+ }
+}
+
+TEST_F(VFSTest, PartialRead) {
+ const char kBuffer[] = "One Two Three Four Five Six Seven";
+ const int kBufferSize = arraysize(kBuffer);
+
+ // Write the data once.
+ {
+ scoped_ptr<sqlite3_file> file(MakeFile());
+ int out_flags;
+ int rc = vfs()->xOpen(vfs(), kFileName, file.get(),
+ SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE,
+ &out_flags);
+ EXPECT_EQ(SQLITE_OK, rc);
+
+ rc = file->pMethods->xWrite(file.get(), kBuffer, kBufferSize, 0);
+ EXPECT_EQ(SQLITE_OK, rc);
+
+ file->pMethods->xClose(file.get());
+ }
+
+ // Now attempt to read kBufferSize + 5 from a file sized to kBufferSize.
+ {
+ scoped_ptr<sqlite3_file> file(MakeFile());
+ int out_flags;
+ int rc = vfs()->xOpen(vfs(), kFileName, file.get(),
+ SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE,
+ &out_flags);
+ EXPECT_EQ(SQLITE_OK, rc);
+
+ const char kBufferWithFiveNulls[] =
+ "One Two Three Four Five Six Seven\0\0\0\0\0";
+ const int kBufferWithFiveNullsSize = arraysize(kBufferWithFiveNulls);
+
+ char data_buffer[kBufferWithFiveNullsSize];
+ memset(data_buffer, '8', kBufferWithFiveNullsSize);
+ rc = file->pMethods->xRead(file.get(), data_buffer,
+ kBufferWithFiveNullsSize, 0);
+ EXPECT_EQ(SQLITE_IOERR_SHORT_READ, rc);
+
+ EXPECT_TRUE(strncmp(kBufferWithFiveNulls, &data_buffer[0],
+ kBufferWithFiveNullsSize) == 0);
+
+ file->pMethods->xClose(file.get());
+ }
+}
+
+TEST_F(VFSTest, Truncate) {
+ const char kBuffer[] = "One Two Three Four Five Six Seven";
+ const int kBufferSize = arraysize(kBuffer);
+ const int kCharsToThree = 13;
+
+ scoped_ptr<sqlite3_file> file(MakeFile());
+ int out_flags;
+ int rc = vfs()->xOpen(vfs(), kFileName, file.get(),
+ SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE,
+ &out_flags);
+ EXPECT_EQ(SQLITE_OK, rc);
+
+ rc = file->pMethods->xWrite(file.get(), kBuffer, kBufferSize, 0);
+ EXPECT_EQ(SQLITE_OK, rc);
+
+ sqlite_int64 size;
+ rc = file->pMethods->xFileSize(file.get(), &size);
+ EXPECT_EQ(SQLITE_OK, rc);
+ EXPECT_EQ(kBufferSize, size);
+
+ rc = file->pMethods->xTruncate(file.get(), kCharsToThree);
+ EXPECT_EQ(SQLITE_OK, rc);
+
+ rc = file->pMethods->xFileSize(file.get(), &size);
+ EXPECT_EQ(SQLITE_OK, rc);
+ EXPECT_EQ(kCharsToThree, size);
+
+ file->pMethods->xClose(file.get());
+}
+
+} // namespace sql
diff --git a/chromium/sql/proxy.cc b/chromium/sql/proxy.cc
new file mode 100644
index 00000000000..51048122c3e
--- /dev/null
+++ b/chromium/sql/proxy.cc
@@ -0,0 +1,28 @@
+// Copyright 2015 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/proxy.h"
+
+namespace sql {
+
+int sqlite3_create_function_v2(
+ sqlite3 *db,
+ const char *zFunctionName,
+ int nArg,
+ int eTextRep,
+ void *pApp,
+ void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
+ void (*xStep)(sqlite3_context*,int,sqlite3_value**),
+ void (*xFinal)(sqlite3_context*),
+ void (*xDestroy)(void*)) {
+ return ::sqlite3_create_function_v2(
+ db, zFunctionName, nArg, eTextRep, pApp,
+ xFunc, xStep, xFinal, xDestroy);
+}
+
+void *sqlite3_commit_hook(sqlite3* db, int(*func)(void*), void* arg) {
+ return ::sqlite3_commit_hook(db, func, arg);
+}
+
+} // namespace sql
diff --git a/chromium/sql/proxy.h b/chromium/sql/proxy.h
new file mode 100644
index 00000000000..7a2863bfa78
--- /dev/null
+++ b/chromium/sql/proxy.h
@@ -0,0 +1,39 @@
+// Copyright 2015 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_PROXY_H_
+#define SQL_PROXY_H_
+
+#include "sql/sql_export.h"
+#include "third_party/sqlite/sqlite3.h"
+
+// TODO(shess): third_party/sqlite does not track component build correctly, so
+// each shared library gets a private copy of everything, so sqlite3_* calls
+// outside of the main sql/ component don't work right. Hack around this by
+// adding pass-through functions while I land a separate fix for the component
+// issue.
+
+// This is only required for tests - if these abilities are desired for
+// production code, they should probably do obvious things like live in
+// sql::Connection and use C++ wrappers.
+
+// http://crbug.com/489444
+
+namespace sql {
+
+SQL_EXPORT int sqlite3_create_function_v2(
+ sqlite3 *db,
+ const char *zFunctionName,
+ int nArg,
+ int eTextRep,
+ void *pApp,
+ void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
+ void (*xStep)(sqlite3_context*,int,sqlite3_value**),
+ void (*xFinal)(sqlite3_context*),
+ void (*xDestroy)(void*));
+SQL_EXPORT void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*);
+
+} // namespace sql
+
+#endif // SQL_PROXY_H_
diff --git a/chromium/sql/recovery_unittest.cc b/chromium/sql/recovery_unittest.cc
index 78a147815f9..1f930cbad34 100644
--- a/chromium/sql/recovery_unittest.cc
+++ b/chromium/sql/recovery_unittest.cc
@@ -16,6 +16,7 @@
#include "sql/statement.h"
#include "sql/test/paths.h"
#include "sql/test/scoped_error_ignorer.h"
+#include "sql/test/sql_test_base.h"
#include "sql/test/test_helpers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/sqlite/sqlite3.h"
@@ -61,32 +62,7 @@ std::string GetSchema(sql::Connection* db) {
return ExecuteWithResults(db, kSql, "|", "\n");
}
-class SQLRecoveryTest : public testing::Test {
- public:
- SQLRecoveryTest() {}
-
- void SetUp() override {
- ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
- ASSERT_TRUE(db_.Open(db_path()));
- }
-
- void TearDown() override { 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_;
-};
+using SQLRecoveryTest = sql::SQLTestBase;
TEST_F(SQLRecoveryTest, RecoverBasic) {
const char kCreateSql[] = "CREATE TABLE x (t TEXT)";
diff --git a/chromium/sql/sql.gyp b/chromium/sql/sql.gyp
index 825d3d07bab..d983a458089 100644
--- a/chromium/sql/sql.gyp
+++ b/chromium/sql/sql.gyp
@@ -27,6 +27,8 @@
'init_status.h',
'meta_table.cc',
'meta_table.h',
+ 'proxy.cc',
+ 'proxy.h',
'recovery.cc',
'recovery.h',
'statement.cc',
@@ -94,6 +96,8 @@
'test/paths.cc',
'test/paths.h',
'test/run_all_unittests.cc',
+ 'test/sql_test_base.cc',
+ 'test/sql_test_base.h',
'test/sql_test_suite.cc',
'test/sql_test_suite.h',
'transaction_unittest.cc',
@@ -132,6 +136,7 @@
],
'variables': {
'test_suite_name': 'sql_unittests',
+ 'isolate_file': 'sql_unittests.isolate',
},
'includes': [ '../build/apk_test.gypi' ],
},
diff --git a/chromium/sql/sql_unittests.isolate b/chromium/sql/sql_unittests.isolate
index f422ae6eab6..44246d7da3f 100644
--- a/chromium/sql/sql_unittests.isolate
+++ b/chromium/sql/sql_unittests.isolate
@@ -1,4 +1,4 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2015 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.
{
@@ -10,6 +10,23 @@
],
},
}],
+ ['OS=="linux" or OS=="mac" or OS=="win"', {
+ 'variables': {
+ 'command': [
+ '../testing/test_env.py',
+ '<(PRODUCT_DIR)/sql_unittests<(EXECUTABLE_SUFFIX)',
+ '--brave-new-test-launcher',
+ '--test-launcher-bot-mode',
+ '--asan=<(asan)',
+ '--msan=<(msan)',
+ '--tsan=<(tsan)',
+ ],
+ 'files': [
+ '../testing/test_env.py',
+ '<(PRODUCT_DIR)/sql_unittests<(EXECUTABLE_SUFFIX)',
+ ],
+ },
+ }],
],
'includes': [
'../base/base.isolate',
diff --git a/chromium/sql/sqlite_features_unittest.cc b/chromium/sql/sqlite_features_unittest.cc
index 2b95bb8d3e7..20e002d3916 100644
--- a/chromium/sql/sqlite_features_unittest.cc
+++ b/chromium/sql/sqlite_features_unittest.cc
@@ -9,6 +9,7 @@
#include "base/files/scoped_temp_dir.h"
#include "sql/connection.h"
#include "sql/statement.h"
+#include "sql/test/sql_test_base.h"
#include "sql/test/test_helpers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/sqlite/sqlite3.h"
@@ -24,34 +25,30 @@ void CaptureErrorCallback(int* error_pointer, std::string* sql_text,
*sql_text = text ? text : "no statement available";
}
-class SQLiteFeaturesTest : public testing::Test {
+class SQLiteFeaturesTest : public sql::SQLTestBase {
public:
SQLiteFeaturesTest() : error_(SQLITE_OK) {}
void SetUp() override {
- ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
- ASSERT_TRUE(db_.Open(temp_dir_.path().AppendASCII("SQLStatementTest.db")));
+ SQLTestBase::SetUp();
// 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_));
+ db().set_error_callback(
+ base::Bind(&CaptureErrorCallback, &error_, &sql_text_));
}
void TearDown() override {
// If any error happened the original sql statement can be found in
// |sql_text_|.
EXPECT_EQ(SQLITE_OK, error_) << sql_text_;
- db_.Close();
+
+ SQLTestBase::TearDown();
}
- sql::Connection& db() { return db_; }
int error() { 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.
diff --git a/chromium/sql/statement.cc b/chromium/sql/statement.cc
index 42b4598c51d..038ebde0885 100644
--- a/chromium/sql/statement.cc
+++ b/chromium/sql/statement.cc
@@ -51,34 +51,63 @@ bool Statement::CheckValid() const {
return is_valid();
}
-bool Statement::Run() {
- DCHECK(!stepped_);
+int Statement::StepInternal(bool timer_flag) {
ref_->AssertIOAllowed();
if (!CheckValid())
- return false;
+ return SQLITE_ERROR;
+ const bool was_stepped = stepped_;
stepped_ = true;
- return CheckError(sqlite3_step(ref_->stmt())) == SQLITE_DONE;
+ int ret = SQLITE_ERROR;
+ if (!ref_->connection()) {
+ ret = sqlite3_step(ref_->stmt());
+ } else {
+ if (!timer_flag) {
+ ret = sqlite3_step(ref_->stmt());
+ } else {
+ const base::TimeTicks before = ref_->connection()->Now();
+ ret = sqlite3_step(ref_->stmt());
+ const base::TimeTicks after = ref_->connection()->Now();
+ const bool read_only = !!sqlite3_stmt_readonly(ref_->stmt());
+ ref_->connection()->RecordTimeAndChanges(after - before, read_only);
+ }
+
+ if (!was_stepped)
+ ref_->connection()->RecordOneEvent(Connection::EVENT_STATEMENT_RUN);
+
+ if (ret == SQLITE_ROW)
+ ref_->connection()->RecordOneEvent(Connection::EVENT_STATEMENT_ROWS);
+ }
+ return CheckError(ret);
}
-bool Statement::Step() {
- ref_->AssertIOAllowed();
- if (!CheckValid())
- return false;
+bool Statement::Run() {
+ DCHECK(!stepped_);
+ return StepInternal(true) == SQLITE_DONE;
+}
- stepped_ = true;
- return CheckError(sqlite3_step(ref_->stmt())) == SQLITE_ROW;
+bool Statement::RunWithoutTimers() {
+ DCHECK(!stepped_);
+ return StepInternal(false) == SQLITE_DONE;
+}
+
+bool Statement::Step() {
+ return StepInternal(true) == 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());
+
+ // StepInternal() cannot track success because statements may be reset
+ // before reaching SQLITE_DONE. Don't call CheckError() because
+ // sqlite3_reset() returns the last step error, which StepInternal() already
+ // checked.
+ const int rc =sqlite3_reset(ref_->stmt());
+ if (rc == SQLITE_OK && ref_->connection())
+ ref_->connection()->RecordOneEvent(Connection::EVENT_STATEMENT_SUCCESS);
}
succeeded_ = false;
diff --git a/chromium/sql/statement.h b/chromium/sql/statement.h
index b411e809b6c..83a5a2dfa83 100644
--- a/chromium/sql/statement.h
+++ b/chromium/sql/statement.h
@@ -150,6 +150,8 @@ class SQL_EXPORT Statement {
const char* GetSQLStatement();
private:
+ friend class Connection;
+
// 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
@@ -174,6 +176,16 @@ class SQL_EXPORT Statement {
// ensuring that contracts are honored in error edge cases.
bool CheckValid() const;
+ // Helper for Run() and Step(), calls sqlite3_step() and then generates
+ // sql::Connection histograms based on the results. Timing and change count
+ // are only recorded if |timer_flag| is true. The checked value from
+ // sqlite3_step() is returned.
+ int StepInternal(bool timer_flag);
+
+ // sql::Connection uses cached statments for transactions, but tracks their
+ // runtime independently.
+ bool RunWithoutTimers();
+
// 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.
diff --git a/chromium/sql/statement_unittest.cc b/chromium/sql/statement_unittest.cc
index 38f1778d736..1565b3e48a9 100644
--- a/chromium/sql/statement_unittest.cc
+++ b/chromium/sql/statement_unittest.cc
@@ -8,6 +8,7 @@
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "sql/connection.h"
+#include "sql/correct_sql_test_base.h"
#include "sql/statement.h"
#include "sql/test/error_callback_support.h"
#include "sql/test/scoped_error_ignorer.h"
@@ -16,21 +17,7 @@
namespace {
-class SQLStatementTest : public testing::Test {
- public:
- void SetUp() override {
- ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
- ASSERT_TRUE(db_.Open(temp_dir_.path().AppendASCII("SQLStatementTest.db")));
- }
-
- void TearDown() override { db_.Close(); }
-
- sql::Connection& db() { return db_; }
-
- private:
- base::ScopedTempDir temp_dir_;
- sql::Connection db_;
-};
+using SQLStatementTest = sql::SQLTestBase;
} // namespace
diff --git a/chromium/sql/transaction_unittest.cc b/chromium/sql/transaction_unittest.cc
index 83d41259b24..179adcf3e55 100644
--- a/chromium/sql/transaction_unittest.cc
+++ b/chromium/sql/transaction_unittest.cc
@@ -5,35 +5,26 @@
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "sql/connection.h"
+#include "sql/correct_sql_test_base.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 {
+class SQLTransactionTest : public sql::SQLTestBase {
public:
void SetUp() override {
- ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
- ASSERT_TRUE(db_.Open(
- temp_dir_.path().AppendASCII("SQLTransactionTest.db")));
+ SQLTestBase::SetUp();
ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
}
- void TearDown() override { 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) {