summaryrefslogtreecommitdiff
path: root/sql
diff options
context:
space:
mode:
authorBen Murdoch <benm@google.com>2013-07-17 14:55:54 +0100
committerBen Murdoch <benm@google.com>2013-07-17 14:55:54 +0100
commit7dbb3d5cf0c15f500944d211057644d6a2f37371 (patch)
tree701119ba0596f51b0ab466d6472b0f98211359c5 /sql
parentb2ecf4836a0eb284ddac7746b1f7ba613777a739 (diff)
downloadchromium_org-7dbb3d5cf0c15f500944d211057644d6a2f37371.tar.gz
Merge from Chromium at DEPS revision r212014
This commit was generated by merge_to_master.py. Change-Id: Ie0f261e9682cd8abea1eea1e51beab83d5eea21a
Diffstat (limited to 'sql')
-rw-r--r--sql/connection.cc125
-rw-r--r--sql/connection.h12
-rw-r--r--sql/connection_unittest.cc294
-rw-r--r--sql/sql.gyp2
-rw-r--r--sql/sql.target.darwin-arm.mk6
-rw-r--r--sql/sql.target.darwin-mips.mk4
-rw-r--r--sql/sql.target.darwin-x86.mk6
-rw-r--r--sql/sql.target.linux-arm.mk6
-rw-r--r--sql/sql.target.linux-mips.mk4
-rw-r--r--sql/sql.target.linux-x86.mk6
-rw-r--r--sql/statement_unittest.cc56
-rw-r--r--sql/test/error_callback_support.cc28
-rw-r--r--sql/test/error_callback_support.h36
13 files changed, 492 insertions, 93 deletions
diff --git a/sql/connection.cc b/sql/connection.cc
index a11bc72893..95d09c24ff 100644
--- a/sql/connection.cc
+++ b/sql/connection.cc
@@ -18,6 +18,10 @@
#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
@@ -64,6 +68,32 @@ class ScopedWritableSchema {
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;
+}
+
} // namespace
namespace sql {
@@ -164,15 +194,15 @@ bool Connection::Open(const base::FilePath& path) {
}
#if defined(OS_WIN)
- return OpenInternal(WideToUTF8(path.value()));
+ return OpenInternal(WideToUTF8(path.value()), RETRY_ON_POISON);
#elif defined(OS_POSIX)
- return OpenInternal(path.value());
+ return OpenInternal(path.value(), RETRY_ON_POISON);
#endif
}
bool Connection::OpenInMemory() {
in_memory_ = true;
- return OpenInternal(":memory:");
+ return OpenInternal(":memory:", NO_RETRY);
}
void Connection::CloseInternal(bool forced) {
@@ -208,8 +238,8 @@ void Connection::CloseInternal(bool forced) {
AssertIOAllowed();
// TODO(shess): Histogram for failure.
sqlite3_close(db_);
- db_ = NULL;
}
+ db_ = NULL;
}
void Connection::Close() {
@@ -313,33 +343,54 @@ bool Connection::Raze() {
// page_size" can be used to query such a database.
ScopedWritableSchema writable_schema(db_);
- sqlite3_backup* backup = sqlite3_backup_init(db_, "main",
- null_db.db_, "main");
- if (!backup) {
- DLOG(FATAL) << "Unable to start sqlite3_backup().";
- return false;
- }
-
- // -1 backs up the entire database.
- int rc = sqlite3_backup_step(backup, -1);
- int pages = sqlite3_backup_pagecount(backup);
- sqlite3_backup_finish(backup);
+ 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;
}
- // Exactly one page should have been backed up. If this breaks,
- // check this function to make sure assumptions aren't being broken.
- DCHECK_EQ(pages, 1);
-
return true;
}
@@ -394,13 +445,13 @@ 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::Delete(journal_path, false);
- base::Delete(wal_path, false);
- base::Delete(path, false);
+ base::DeleteFile(journal_path, false);
+ base::DeleteFile(wal_path, false);
+ base::DeleteFile(path, false);
- return !file_util::PathExists(journal_path) &&
- !file_util::PathExists(wal_path) &&
- !file_util::PathExists(path);
+ return !base::PathExists(journal_path) &&
+ !base::PathExists(wal_path) &&
+ !base::PathExists(path);
}
bool Connection::BeginTransaction() {
@@ -648,7 +699,8 @@ const char* Connection::GetErrorMessage() const {
return sqlite3_errmsg(db_);
}
-bool Connection::OpenInternal(const std::string& file_name) {
+bool Connection::OpenInternal(const std::string& file_name,
+ Connection::Retry retry_flag) {
AssertIOAllowed();
if (db_) {
@@ -663,6 +715,7 @@ bool Connection::OpenInternal(const std::string& file_name) {
// 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) {
@@ -671,8 +724,11 @@ bool Connection::OpenInternal(const std::string& file_name) {
UMA_HISTOGRAM_ENUMERATION("Sqlite.OpenFailure", err & 0xff, 50);
OnSqliteError(err, NULL);
+ bool was_poisoned = poisoned_;
Close();
- db_ = NULL;
+
+ if (was_poisoned && retry_flag == RETRY_ON_POISON)
+ return OpenInternal(file_name, NO_RETRY);
return false;
}
@@ -701,6 +757,13 @@ bool Connection::OpenInternal(const std::string& file_name) {
err = sqlite3_extended_result_codes(db_, 1);
DCHECK_EQ(err, SQLITE_OK) << "Could not enable extended result codes";
+#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
@@ -747,7 +810,10 @@ bool Connection::OpenInternal(const std::string& file_name) {
}
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;
}
@@ -801,7 +867,10 @@ int Connection::OnSqliteError(int err, sql::Statement *stmt) {
<< ": " << GetErrorMessage();
if (!error_callback_.is_null()) {
- error_callback_.Run(err, stmt);
+ // 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;
}
diff --git a/sql/connection.h b/sql/connection.h
index a2e8f67f94..1c4d72cfff 100644
--- a/sql/connection.h
+++ b/sql/connection.h
@@ -125,6 +125,9 @@ class SQL_EXPORT Connection {
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();
}
@@ -357,7 +360,14 @@ class SQL_EXPORT Connection {
// 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.
- bool OpenInternal(const std::string& file_name);
+ //
+ // |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.
diff --git a/sql/connection_unittest.cc b/sql/connection_unittest.cc
index 1afd2dded3..8cf32fb3f8 100644
--- a/sql/connection_unittest.cc
+++ b/sql/connection_unittest.cc
@@ -2,16 +2,75 @@
// 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);
+}
+
class SQLConnectionTest : public testing::Test {
public:
SQLConnectionTest() {}
@@ -31,6 +90,12 @@ class SQLConnectionTest : public testing::Test {
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_;
@@ -150,6 +215,61 @@ TEST_F(SQLConnectionTest, ScopedIgnoreError) {
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));
+
+ // Inserting something other than a number into the primary key
+ // should result in the callback seeing SQLITE_MISMATCH.
+ 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 causes
+ // re/set_error_callback() to be called, the storage for those
+ // arguments can be deleted.
+ //
+ // 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) {
@@ -193,10 +313,7 @@ TEST_F(SQLConnectionTest, Raze) {
EXPECT_EQ(1, s.ColumnInt(0));
}
- {
- sql::Statement s(db().GetUniqueStatement("SELECT * FROM sqlite_master"));
- ASSERT_FALSE(s.Step());
- }
+ ASSERT_EQ(0, SqliteMasterCount(&db()));
{
sql::Statement s(db().GetUniqueStatement("PRAGMA auto_vacuum"));
@@ -245,18 +362,12 @@ TEST_F(SQLConnectionTest, RazeMultiple) {
ASSERT_TRUE(other_db.Open(db_path()));
// Check that the second connection sees the table.
- const char *kTablesQuery = "SELECT COUNT(*) FROM sqlite_master";
- sql::Statement s(other_db.GetUniqueStatement(kTablesQuery));
- ASSERT_TRUE(s.Step());
- ASSERT_EQ(1, s.ColumnInt(0));
- ASSERT_FALSE(s.Step()); // Releases the shared lock.
+ ASSERT_EQ(1, SqliteMasterCount(&other_db));
ASSERT_TRUE(db().Raze());
// The second connection sees the updated database.
- s.Reset(true);
- ASSERT_TRUE(s.Step());
- ASSERT_EQ(0, s.ColumnInt(0));
+ ASSERT_EQ(0, SqliteMasterCount(&other_db));
}
TEST_F(SQLConnectionTest, RazeLocked) {
@@ -294,6 +405,145 @@ TEST_F(SQLConnectionTest, RazeLocked) {
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(), "r+"));
+ 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(), "w"));
+ 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(), "r+"));
+ 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(), "r+"));
+ 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)";
@@ -307,10 +557,7 @@ TEST_F(SQLConnectionTest, RazeAndClose) {
ASSERT_FALSE(db().is_open());
db().Close();
ASSERT_TRUE(db().Open(db_path()));
- {
- sql::Statement s(db().GetUniqueStatement("SELECT * FROM sqlite_master"));
- ASSERT_FALSE(s.Step());
- }
+ ASSERT_EQ(0, SqliteMasterCount(&db()));
// Test that RazeAndClose() can break transactions.
ASSERT_TRUE(db().Execute(kCreateSql));
@@ -321,10 +568,7 @@ TEST_F(SQLConnectionTest, RazeAndClose) {
ASSERT_FALSE(db().CommitTransaction());
db().Close();
ASSERT_TRUE(db().Open(db_path()));
- {
- sql::Statement s(db().GetUniqueStatement("SELECT * FROM sqlite_master"));
- ASSERT_FALSE(s.Step());
- }
+ ASSERT_EQ(0, SqliteMasterCount(&db()));
}
// Test that various operations fail without crashing after
@@ -419,10 +663,12 @@ TEST_F(SQLConnectionTest, Delete) {
// 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(file_util::PathExists(db_path()));
- ASSERT_TRUE(file_util::PathExists(journal));
+ ASSERT_TRUE(base::PathExists(db_path()));
+ ASSERT_TRUE(base::PathExists(journal));
sql::Connection::Delete(db_path());
- EXPECT_FALSE(file_util::PathExists(db_path()));
- EXPECT_FALSE(file_util::PathExists(journal));
+ EXPECT_FALSE(base::PathExists(db_path()));
+ EXPECT_FALSE(base::PathExists(journal));
}
+
+} // namespace
diff --git a/sql/sql.gyp b/sql/sql.gyp
index 944defd6bc..2e8fc184db 100644
--- a/sql/sql.gyp
+++ b/sql/sql.gyp
@@ -55,6 +55,8 @@
'../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',
],
diff --git a/sql/sql.target.darwin-arm.mk b/sql/sql.target.darwin-arm.mk
index 0eb909fb4a..214df24e9e 100644
--- a/sql/sql.target.darwin-arm.mk
+++ b/sql/sql.target.darwin-arm.mk
@@ -66,8 +66,9 @@ MY_CFLAGS_Debug := \
MY_DEFS_Debug := \
'-DANGLE_DX11' \
'-D_FILE_OFFSET_BITS=64' \
- '-DUSE_LINUX_BREAKPAD' \
'-DNO_TCMALLOC' \
+ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \
+ '-DSYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE' \
'-DDISABLE_NACL' \
'-DCHROMIUM_BUILD' \
'-DUSE_LIBJPEG_TURBO=1' \
@@ -149,8 +150,9 @@ MY_CFLAGS_Release := \
MY_DEFS_Release := \
'-DANGLE_DX11' \
'-D_FILE_OFFSET_BITS=64' \
- '-DUSE_LINUX_BREAKPAD' \
'-DNO_TCMALLOC' \
+ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \
+ '-DSYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE' \
'-DDISABLE_NACL' \
'-DCHROMIUM_BUILD' \
'-DUSE_LIBJPEG_TURBO=1' \
diff --git a/sql/sql.target.darwin-mips.mk b/sql/sql.target.darwin-mips.mk
index 66b9d20103..1a63837383 100644
--- a/sql/sql.target.darwin-mips.mk
+++ b/sql/sql.target.darwin-mips.mk
@@ -66,6 +66,8 @@ MY_DEFS_Debug := \
'-DANGLE_DX11' \
'-D_FILE_OFFSET_BITS=64' \
'-DNO_TCMALLOC' \
+ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \
+ '-DSYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE' \
'-DDISABLE_NACL' \
'-DCHROMIUM_BUILD' \
'-DUSE_LIBJPEG_TURBO=1' \
@@ -147,6 +149,8 @@ MY_DEFS_Release := \
'-DANGLE_DX11' \
'-D_FILE_OFFSET_BITS=64' \
'-DNO_TCMALLOC' \
+ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \
+ '-DSYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE' \
'-DDISABLE_NACL' \
'-DCHROMIUM_BUILD' \
'-DUSE_LIBJPEG_TURBO=1' \
diff --git a/sql/sql.target.darwin-x86.mk b/sql/sql.target.darwin-x86.mk
index ada2ff0848..5625a1c768 100644
--- a/sql/sql.target.darwin-x86.mk
+++ b/sql/sql.target.darwin-x86.mk
@@ -68,8 +68,9 @@ MY_CFLAGS_Debug := \
MY_DEFS_Debug := \
'-DANGLE_DX11' \
'-D_FILE_OFFSET_BITS=64' \
- '-DUSE_LINUX_BREAKPAD' \
'-DNO_TCMALLOC' \
+ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \
+ '-DSYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE' \
'-DDISABLE_NACL' \
'-DCHROMIUM_BUILD' \
'-DUSE_LIBJPEG_TURBO=1' \
@@ -154,8 +155,9 @@ MY_CFLAGS_Release := \
MY_DEFS_Release := \
'-DANGLE_DX11' \
'-D_FILE_OFFSET_BITS=64' \
- '-DUSE_LINUX_BREAKPAD' \
'-DNO_TCMALLOC' \
+ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \
+ '-DSYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE' \
'-DDISABLE_NACL' \
'-DCHROMIUM_BUILD' \
'-DUSE_LIBJPEG_TURBO=1' \
diff --git a/sql/sql.target.linux-arm.mk b/sql/sql.target.linux-arm.mk
index 0eb909fb4a..214df24e9e 100644
--- a/sql/sql.target.linux-arm.mk
+++ b/sql/sql.target.linux-arm.mk
@@ -66,8 +66,9 @@ MY_CFLAGS_Debug := \
MY_DEFS_Debug := \
'-DANGLE_DX11' \
'-D_FILE_OFFSET_BITS=64' \
- '-DUSE_LINUX_BREAKPAD' \
'-DNO_TCMALLOC' \
+ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \
+ '-DSYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE' \
'-DDISABLE_NACL' \
'-DCHROMIUM_BUILD' \
'-DUSE_LIBJPEG_TURBO=1' \
@@ -149,8 +150,9 @@ MY_CFLAGS_Release := \
MY_DEFS_Release := \
'-DANGLE_DX11' \
'-D_FILE_OFFSET_BITS=64' \
- '-DUSE_LINUX_BREAKPAD' \
'-DNO_TCMALLOC' \
+ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \
+ '-DSYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE' \
'-DDISABLE_NACL' \
'-DCHROMIUM_BUILD' \
'-DUSE_LIBJPEG_TURBO=1' \
diff --git a/sql/sql.target.linux-mips.mk b/sql/sql.target.linux-mips.mk
index 66b9d20103..1a63837383 100644
--- a/sql/sql.target.linux-mips.mk
+++ b/sql/sql.target.linux-mips.mk
@@ -66,6 +66,8 @@ MY_DEFS_Debug := \
'-DANGLE_DX11' \
'-D_FILE_OFFSET_BITS=64' \
'-DNO_TCMALLOC' \
+ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \
+ '-DSYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE' \
'-DDISABLE_NACL' \
'-DCHROMIUM_BUILD' \
'-DUSE_LIBJPEG_TURBO=1' \
@@ -147,6 +149,8 @@ MY_DEFS_Release := \
'-DANGLE_DX11' \
'-D_FILE_OFFSET_BITS=64' \
'-DNO_TCMALLOC' \
+ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \
+ '-DSYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE' \
'-DDISABLE_NACL' \
'-DCHROMIUM_BUILD' \
'-DUSE_LIBJPEG_TURBO=1' \
diff --git a/sql/sql.target.linux-x86.mk b/sql/sql.target.linux-x86.mk
index ada2ff0848..5625a1c768 100644
--- a/sql/sql.target.linux-x86.mk
+++ b/sql/sql.target.linux-x86.mk
@@ -68,8 +68,9 @@ MY_CFLAGS_Debug := \
MY_DEFS_Debug := \
'-DANGLE_DX11' \
'-D_FILE_OFFSET_BITS=64' \
- '-DUSE_LINUX_BREAKPAD' \
'-DNO_TCMALLOC' \
+ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \
+ '-DSYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE' \
'-DDISABLE_NACL' \
'-DCHROMIUM_BUILD' \
'-DUSE_LIBJPEG_TURBO=1' \
@@ -154,8 +155,9 @@ MY_CFLAGS_Release := \
MY_DEFS_Release := \
'-DANGLE_DX11' \
'-D_FILE_OFFSET_BITS=64' \
- '-DUSE_LINUX_BREAKPAD' \
'-DNO_TCMALLOC' \
+ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \
+ '-DSYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE' \
'-DDISABLE_NACL' \
'-DCHROMIUM_BUILD' \
'-DUSE_LIBJPEG_TURBO=1' \
diff --git a/sql/statement_unittest.cc b/sql/statement_unittest.cc
index 3d32862c79..c5217aa6c9 100644
--- a/sql/statement_unittest.cc
+++ b/sql/statement_unittest.cc
@@ -9,55 +9,29 @@
#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 {
-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 SQLStatementTest : public testing::Test {
public:
- SQLStatementTest() : 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_; }
-
- void ResetError() {
- error_ = SQLITE_OK;
- sql_text_.clear();
- }
-
private:
base::ScopedTempDir temp_dir_;
sql::Connection db_;
-
- // The error code of the most recent error.
- int error_;
- // Original statement which caused the error.
- std::string sql_text_;
};
} // namespace
@@ -101,9 +75,14 @@ TEST_F(SQLStatementTest, Run) {
EXPECT_TRUE(s.Succeeded());
}
-TEST_F(SQLStatementTest, BasicErrorCallback) {
+// Error callback called for error running a statement.
+TEST_F(SQLStatementTest, ErrorCallback) {
ASSERT_TRUE(db().Execute("CREATE TABLE foo (a INTEGER PRIMARY KEY, b)"));
- EXPECT_EQ(SQLITE_OK, sqlite_error());
+
+ 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.
@@ -111,8 +90,21 @@ TEST_F(SQLStatementTest, BasicErrorCallback) {
EXPECT_TRUE(s.is_valid());
s.BindCString(0, "bad bad");
EXPECT_FALSE(s.Run());
- EXPECT_EQ(SQLITE_MISMATCH, sqlite_error());
- ResetError();
+ 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) {
diff --git a/sql/test/error_callback_support.cc b/sql/test/error_callback_support.cc
new file mode 100644
index 0000000000..9f0e22f381
--- /dev/null
+++ b/sql/test/error_callback_support.cc
@@ -0,0 +1,28 @@
+// 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/test/error_callback_support.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace sql {
+
+void CaptureErrorCallback(int* error_pointer, int error, sql::Statement* stmt) {
+ *error_pointer = error;
+}
+
+ScopedErrorCallback::ScopedErrorCallback(
+ sql::Connection* db,
+ const sql::Connection::ErrorCallback& cb)
+ : db_(db) {
+ // Make sure someone isn't trying to nest things.
+ EXPECT_FALSE(db_->has_error_callback());
+ db_->set_error_callback(cb);
+}
+
+ScopedErrorCallback::~ScopedErrorCallback() {
+ db_->reset_error_callback();
+}
+
+} // namespace
diff --git a/sql/test/error_callback_support.h b/sql/test/error_callback_support.h
new file mode 100644
index 0000000000..30268570f8
--- /dev/null
+++ b/sql/test/error_callback_support.h
@@ -0,0 +1,36 @@
+// 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_TEST_ERROR_CALLBACK_SUPPORT_H_
+#define SQL_TEST_ERROR_CALLBACK_SUPPORT_H_
+
+#include "sql/connection.h"
+
+namespace sql {
+
+// Helper to capture any errors into a local variable for testing.
+// For instance:
+// int error = SQLITE_OK;
+// ScopedErrorCallback sec(db, base::Bind(&CaptureErrorCallback, &error));
+// // Provoke SQLITE_CONSTRAINT on db.
+// EXPECT_EQ(SQLITE_CONSTRAINT, error);
+void CaptureErrorCallback(int* error_pointer, int error, sql::Statement* stmt);
+
+// Helper to set db's error callback and then reset it when it goes
+// out of scope.
+class ScopedErrorCallback {
+ public:
+ ScopedErrorCallback(sql::Connection* db,
+ const sql::Connection::ErrorCallback& cb);
+ ~ScopedErrorCallback();
+
+ private:
+ sql::Connection* db_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedErrorCallback);
+};
+
+} // namespace sql
+
+#endif // SQL_TEST_ERROR_CALLBACK_SUPPORT_H_