summaryrefslogtreecommitdiff
path: root/sql
diff options
context:
space:
mode:
authorTorne (Richard Coles) <torne@google.com>2013-03-28 15:31:22 +0000
committerTorne (Richard Coles) <torne@google.com>2013-03-28 15:31:22 +0000
commit2a99a7e74a7f215066514fe81d2bfa6639d9eddd (patch)
tree7c2d04841fcd599fd83b0f0bb1100e1c89a35bae /sql
parent61c449bbbb53310a8c041d8cefdd6b01a126cc7e (diff)
downloadchromium_org-2a99a7e74a7f215066514fe81d2bfa6639d9eddd.tar.gz
Merge from Chromium at DEPS revision r190564
This commit was generated by merge_to_master.py. Change-Id: Icadecbce29854b8fa25fd335b2c1949b5ca5d170
Diffstat (limited to 'sql')
-rw-r--r--sql/connection.cc207
-rw-r--r--sql/connection.h71
-rw-r--r--sql/connection_unittest.cc122
-rw-r--r--sql/run_all_unittests.cc2
-rw-r--r--sql/sql.gyp4
-rw-r--r--sql/sql.target.linux-arm.mk (renamed from sql/sql.target.mk)32
-rw-r--r--sql/sql.target.linux-x86.mk152
-rw-r--r--sql/sqlite_features_unittest.cc8
-rw-r--r--sql/statement.cc12
-rw-r--r--sql/statement_unittest.cc8
-rw-r--r--sql/transaction_unittest.cc8
11 files changed, 513 insertions, 113 deletions
diff --git a/sql/connection.cc b/sql/connection.cc
index 1de9fc5cfa..5f6b5906e0 100644
--- a/sql/connection.cc
+++ b/sql/connection.cc
@@ -6,8 +6,9 @@
#include <string.h>
-#include "base/file_path.h"
+#include "base/files/file_path.h"
#include "base/logging.h"
+#include "base/metrics/histogram.h"
#include "base/string_util.h"
#include "base/stringprintf.h"
#include "base/utf_string_conversions.h"
@@ -73,30 +74,23 @@ bool StatementID::operator<(const StatementID& other) const {
ErrorDelegate::~ErrorDelegate() {
}
-Connection::StatementRef::StatementRef()
- : connection_(NULL),
- stmt_(NULL) {
-}
-
-Connection::StatementRef::StatementRef(sqlite3_stmt* stmt)
- : connection_(NULL),
- stmt_(stmt) {
-}
-
Connection::StatementRef::StatementRef(Connection* connection,
- sqlite3_stmt* stmt)
+ sqlite3_stmt* stmt,
+ bool was_valid)
: connection_(connection),
- stmt_(stmt) {
- connection_->StatementRefCreated(this);
+ stmt_(stmt),
+ was_valid_(was_valid) {
+ if (connection)
+ connection_->StatementRefCreated(this);
}
Connection::StatementRef::~StatementRef() {
if (connection_)
connection_->StatementRefDeleted(this);
- Close();
+ Close(false);
}
-void Connection::StatementRef::Close() {
+void Connection::StatementRef::Close(bool forced) {
if (stmt_) {
// Call to AssertIOAllowed() cannot go at the beginning of the function
// because Close() is called unconditionally from destructor to clean
@@ -110,6 +104,11 @@ void Connection::StatementRef::Close() {
stmt_ = NULL;
}
connection_ = NULL; // The connection may be getting deleted.
+
+ // Forced close is expected to happen from a statement error
+ // handler. In that case maintain the sense of |was_valid_| which
+ // previously held for this ref.
+ was_valid_ = was_valid_ && forced;
}
Connection::Connection()
@@ -120,6 +119,7 @@ Connection::Connection()
transaction_nesting_(0),
needs_rollback_(false),
in_memory_(false),
+ poisoned_(false),
error_delegate_(NULL) {
}
@@ -127,7 +127,7 @@ Connection::~Connection() {
Close();
}
-bool Connection::Open(const FilePath& path) {
+bool Connection::Open(const base::FilePath& path) {
#if defined(OS_WIN)
return OpenInternal(WideToUTF8(path.value()));
#elif defined(OS_POSIX)
@@ -140,22 +140,28 @@ bool Connection::OpenInMemory() {
return OpenInternal(":memory:");
}
-void Connection::Close() {
+void Connection::CloseInternal(bool forced) {
// TODO(shess): Calling "PRAGMA journal_mode = DELETE" at this point
// will delete the -journal file. For ChromiumOS or other more
// embedded systems, this is probably not appropriate, whereas on
// desktop it might make some sense.
// sqlite3_close() needs all prepared statements to be finalized.
- // Release all cached statements, then assert that the client has
- // released all statements.
+
+ // Release cached statements.
statement_cache_.clear();
- DCHECK(open_statements_.empty());
- // Additionally clear the prepared statements, because they contain
- // weak references to this connection. This case has come up when
- // error-handling code is hit in production.
- ClearCache();
+ // With cached statements released, in-use statements will remain.
+ // Closing the database while statements are in use is an API
+ // violation, except for forced close (which happens from within a
+ // statement's error handler).
+ DCHECK(forced || open_statements_.empty());
+
+ // Deactivate any outstanding statements so sqlite3_close() works.
+ for (StatementRefSet::iterator i = open_statements_.begin();
+ i != open_statements_.end(); ++i)
+ (*i)->Close(forced);
+ open_statements_.clear();
if (db_) {
// Call to AssertIOAllowed() cannot go at the beginning of the function
@@ -171,11 +177,23 @@ void Connection::Close() {
}
}
+void Connection::Close() {
+ // If the database was already closed by RazeAndClose(), then no
+ // need to close again. Clear the |poisoned_| bit so that incorrect
+ // API calls are caught.
+ if (poisoned_) {
+ poisoned_ = false;
+ return;
+ }
+
+ CloseInternal(false);
+}
+
void Connection::Preload() {
AssertIOAllowed();
if (!db_) {
- DLOG(FATAL) << "Cannot preload null db";
+ DLOG_IF(FATAL, !poisoned_) << "Cannot preload null db";
return;
}
@@ -201,7 +219,7 @@ bool Connection::Raze() {
AssertIOAllowed();
if (!db_) {
- DLOG(FATAL) << "Cannot raze null db";
+ DLOG_IF(FATAL, !poisoned_) << "Cannot raze null db";
return false;
}
@@ -222,7 +240,8 @@ bool Connection::Raze() {
<< " page_size_ " << page_size_ << " is not a power of two.";
const int kSqliteMaxPageSize = 32768; // from sqliteLimit.h
DCHECK_LE(page_size_, kSqliteMaxPageSize);
- const std::string sql = StringPrintf("PRAGMA page_size=%d", page_size_);
+ const std::string sql =
+ base::StringPrintf("PRAGMA page_size=%d", page_size_);
if (!null_db.Execute(sql.c_str()))
return false;
}
@@ -291,7 +310,7 @@ bool Connection::Raze() {
bool Connection::RazeWithTimout(base::TimeDelta timeout) {
if (!db_) {
- DLOG(FATAL) << "Cannot raze null db";
+ DLOG_IF(FATAL, !poisoned_) << "Cannot raze null db";
return false;
}
@@ -300,6 +319,29 @@ bool Connection::RazeWithTimout(base::TimeDelta timeout) {
return Raze();
}
+bool Connection::RazeAndClose() {
+ if (!db_) {
+ DLOG_IF(FATAL, !poisoned_) << "Cannot raze null db";
+ return false;
+ }
+
+ // Raze() cannot run in a transaction.
+ while (transaction_nesting_) {
+ RollbackTransaction();
+ }
+
+ bool result = Raze();
+
+ CloseInternal(true);
+
+ // Mark the database so that future API calls fail appropriately,
+ // but don't DCHECK (because after calling this function they are
+ // expected to fail).
+ poisoned_ = true;
+
+ return result;
+}
+
bool Connection::BeginTransaction() {
if (needs_rollback_) {
DCHECK_GT(transaction_nesting_, 0);
@@ -323,7 +365,7 @@ bool Connection::BeginTransaction() {
void Connection::RollbackTransaction() {
if (!transaction_nesting_) {
- DLOG(FATAL) << "Rolling back a nonexistent transaction";
+ DLOG_IF(FATAL, !poisoned_) << "Rolling back a nonexistent transaction";
return;
}
@@ -340,7 +382,7 @@ void Connection::RollbackTransaction() {
bool Connection::CommitTransaction() {
if (!transaction_nesting_) {
- DLOG(FATAL) << "Rolling back a nonexistent transaction";
+ DLOG_IF(FATAL, !poisoned_) << "Rolling back a nonexistent transaction";
return false;
}
transaction_nesting_--;
@@ -361,12 +403,19 @@ bool Connection::CommitTransaction() {
int Connection::ExecuteAndReturnErrorCode(const char* sql) {
AssertIOAllowed();
- if (!db_)
- return false;
+ if (!db_) {
+ DLOG_IF(FATAL, !poisoned_) << "Illegal use of connection without a db";
+ return SQLITE_ERROR;
+ }
return sqlite3_exec(db_, sql, NULL, NULL, NULL);
}
bool Connection::Execute(const char* sql) {
+ if (!db_) {
+ DLOG_IF(FATAL, !poisoned_) << "Illegal use of connection without a db";
+ return false;
+ }
+
int error = ExecuteAndReturnErrorCode(sql);
if (error != SQLITE_OK)
error = OnSqliteError(error, NULL);
@@ -380,8 +429,10 @@ bool Connection::Execute(const char* sql) {
}
bool Connection::ExecuteWithTimeout(const char* sql, base::TimeDelta timeout) {
- if (!db_)
+ if (!db_) {
+ DLOG_IF(FATAL, !poisoned_) << "Illegal use of connection without a db";
return false;
+ }
ScopedBusyTimeout busy_timeout(db_);
busy_timeout.SetTimeout(timeout);
@@ -416,8 +467,9 @@ scoped_refptr<Connection::StatementRef> Connection::GetUniqueStatement(
const char* sql) {
AssertIOAllowed();
+ // Return inactive statement.
if (!db_)
- return new StatementRef(); // Return inactive statement.
+ return new StatementRef(NULL, NULL, poisoned_);
sqlite3_stmt* stmt = NULL;
int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, NULL);
@@ -427,28 +479,34 @@ scoped_refptr<Connection::StatementRef> Connection::GetUniqueStatement(
// It could also be database corruption.
OnSqliteError(rc, NULL);
- return new StatementRef();
+ return new StatementRef(NULL, NULL, false);
}
- return new StatementRef(this, stmt);
+ return new StatementRef(this, stmt, true);
}
scoped_refptr<Connection::StatementRef> Connection::GetUntrackedStatement(
const char* sql) const {
+ // Return inactive statement.
if (!db_)
- return new StatementRef(); // Return inactive statement.
+ return new StatementRef(NULL, NULL, poisoned_);
sqlite3_stmt* stmt = NULL;
int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
// This is evidence of a syntax error in the incoming SQL.
DLOG(FATAL) << "SQL compile error " << GetErrorMessage();
- return new StatementRef();
+ return new StatementRef(NULL, NULL, false);
}
- return new StatementRef(stmt);
+ return new StatementRef(NULL, stmt, true);
}
bool Connection::IsSQLValid(const char* sql) {
AssertIOAllowed();
+ if (!db_) {
+ DLOG_IF(FATAL, !poisoned_) << "Illegal use of connection without a db";
+ return false;
+ }
+
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db_, sql, -1, &stmt, NULL) != SQLITE_OK)
return false;
@@ -491,7 +549,7 @@ bool Connection::DoesColumnExist(const char* table_name,
int64 Connection::GetLastInsertRowId() const {
if (!db_) {
- DLOG(FATAL) << "Illegal use of connection without a db";
+ DLOG_IF(FATAL, !poisoned_) << "Illegal use of connection without a db";
return 0;
}
return sqlite3_last_insert_rowid(db_);
@@ -499,7 +557,7 @@ int64 Connection::GetLastInsertRowId() const {
int Connection::GetLastChangeCount() const {
if (!db_) {
- DLOG(FATAL) << "Illegal use of connection without a db";
+ DLOG_IF(FATAL, !poisoned_) << "Illegal use of connection without a db";
return 0;
}
return sqlite3_changes(db_);
@@ -536,14 +594,36 @@ bool Connection::OpenInternal(const std::string& file_name) {
return false;
}
+ // If |poisoned_| is set, it means an error handler called
+ // RazeAndClose(). Until regular Close() is called, the caller
+ // should be treating the database as open, but is_open() currently
+ // only considers the sqlite3 handle's state.
+ // TODO(shess): Revise is_open() to consider poisoned_, and review
+ // to see if any non-testing code even depends on it.
+ DLOG_IF(FATAL, poisoned_) << "sql::Connection is already open.";
+
int err = sqlite3_open(file_name.c_str(), &db_);
if (err != SQLITE_OK) {
+ // Histogram failures specific to initial open for debugging
+ // purposes.
+ UMA_HISTOGRAM_ENUMERATION("Sqlite.OpenFailure", err & 0xff, 50);
+
OnSqliteError(err, NULL);
Close();
db_ = NULL;
return false;
}
+ // sqlite3_open() does not actually read the database file (unless a
+ // hot journal is found). Successfully executing this pragma on an
+ // existing database requires a valid header on page 1.
+ // TODO(shess): For now, just probing to see what the lay of the
+ // land is. If it's mostly SQLITE_NOTADB, then the database should
+ // be razed.
+ err = ExecuteAndReturnErrorCode("PRAGMA auto_vacuum");
+ if (err != SQLITE_OK)
+ UMA_HISTOGRAM_ENUMERATION("Sqlite.OpenProbeFailure", err & 0xff, 50);
+
// Enable extended result codes to provide more color on I/O errors.
// Not having extended result codes is not a fatal problem, as
// Chromium code does not attempt to handle I/O errors anyhow. The
@@ -587,13 +667,15 @@ bool Connection::OpenInternal(const std::string& file_name) {
<< " page_size_ " << page_size_ << " is not a power of two.";
const int kSqliteMaxPageSize = 32768; // from sqliteLimit.h
DCHECK_LE(page_size_, kSqliteMaxPageSize);
- const std::string sql = StringPrintf("PRAGMA page_size=%d", page_size_);
+ const std::string sql =
+ base::StringPrintf("PRAGMA page_size=%d", page_size_);
if (!ExecuteWithTimeout(sql.c_str(), kBusyTimeout))
DLOG(FATAL) << "Could not set page size: " << GetErrorMessage();
}
if (cache_size_ != 0) {
- const std::string sql = StringPrintf("PRAGMA cache_size=%d", cache_size_);
+ const std::string sql =
+ base::StringPrintf("PRAGMA cache_size=%d", cache_size_);
if (!ExecuteWithTimeout(sql.c_str(), kBusyTimeout))
DLOG(FATAL) << "Could not set cache size: " << GetErrorMessage();
}
@@ -626,20 +708,35 @@ void Connection::StatementRefDeleted(StatementRef* ref) {
open_statements_.erase(i);
}
-void Connection::ClearCache() {
- statement_cache_.clear();
-
- // The cache clear will get most statements. There may be still be references
- // to some statements that are held by others (including one-shot statements).
- // This will deactivate them so they can't be used again.
- for (StatementRefSet::iterator i = open_statements_.begin();
- i != open_statements_.end(); ++i)
- (*i)->Close();
-}
-
int Connection::OnSqliteError(int err, sql::Statement *stmt) {
+ // Strip extended error codes.
+ int base_err = err&0xff;
+
+ static size_t kSqliteErrorMax = 50;
+ UMA_HISTOGRAM_ENUMERATION("Sqlite.Error", base_err, kSqliteErrorMax);
+ if (!error_histogram_name_.empty()) {
+ // TODO(shess): The histogram macros create a bit of static
+ // storage for caching the histogram object. Since SQLite is
+ // being used for I/O, generally without error, this code
+ // shouldn't execute often enough for such caching to be crucial.
+ // If it becomes an issue, the object could be cached alongside
+ // error_histogram_name_.
+ base::HistogramBase* histogram =
+ base::LinearHistogram::FactoryGet(
+ error_histogram_name_, 1, kSqliteErrorMax, kSqliteErrorMax + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag);
+ if (histogram)
+ histogram->Add(base_err);
+ }
+
+ // Always log the error.
+ LOG(ERROR) << "sqlite error " << err
+ << ", errno " << GetLastErrno()
+ << ": " << GetErrorMessage();
+
if (error_delegate_.get())
return error_delegate_->OnError(err, this, stmt);
+
// The default handling is to assert on debug and to ignore on release.
DLOG(FATAL) << GetErrorMessage();
return err;
diff --git a/sql/connection.h b/sql/connection.h
index 8cf4d715f5..cebc774fa5 100644
--- a/sql/connection.h
+++ b/sql/connection.h
@@ -17,10 +17,13 @@
#include "base/time.h"
#include "sql/sql_export.h"
-class FilePath;
struct sqlite3;
struct sqlite3_stmt;
+namespace base {
+class FilePath;
+}
+
namespace sql {
class Statement;
@@ -143,18 +146,25 @@ class SQL_EXPORT Connection {
error_delegate_.reset(delegate);
}
+ // SQLite error codes for errors on all connections are logged to
+ // enum histogram "Sqlite.Error". Setting this additionally logs
+ // errors to the histogram |name|.
+ void set_error_histogram_name(const std::string& name) {
+ error_histogram_name_ = name;
+ }
+
// Initialization ------------------------------------------------------------
// Initializes the SQL connection for the given file, returning true if the
// file could be opened. You can call this or OpenInMemory.
- bool Open(const FilePath& path) WARN_UNUSED_RESULT;
+ bool Open(const base::FilePath& path) WARN_UNUSED_RESULT;
// Initializes the SQL connection for a temporary in-memory database. There
// will be no associated file on disk, and the initial database will be
// empty. You can call this or Open.
bool OpenInMemory() WARN_UNUSED_RESULT;
- // Returns trie if the database has been successfully opened.
+ // Returns true if the database has been successfully opened.
bool is_open() const { return !!db_; }
// Closes the database. This is automatically performed on destruction for
@@ -211,6 +221,16 @@ class SQL_EXPORT Connection {
bool Raze();
bool RazeWithTimout(base::TimeDelta timeout);
+ // Breaks all outstanding transactions (as initiated by
+ // BeginTransaction()), calls Raze() to destroy the database, then
+ // closes the database. After this is called, any operations
+ // against the connections (or statements prepared by the
+ // connection) should fail safely.
+ //
+ // The value from Raze() is returned, with Close() called in all
+ // cases.
+ bool RazeAndClose();
+
// Transactions --------------------------------------------------------------
// Transaction management. We maintain a virtual transaction stack to emulate
@@ -331,6 +351,10 @@ class SQL_EXPORT Connection {
// sqlite3_open. The string can also be sqlite's special ":memory:" string.
bool OpenInternal(const std::string& file_name);
+ // Internal close function used by Close() and RazeAndClose().
+ // |forced| indicates that orderly-shutdown checks should not apply.
+ void CloseInternal(bool forced);
+
// Check whether the current thread is allowed to make IO calls, but only
// if database wasn't open in memory. Function is inlined to be a no-op in
// official build.
@@ -355,16 +379,27 @@ class SQL_EXPORT Connection {
// should always check validity before using.
class SQL_EXPORT StatementRef : public base::RefCounted<StatementRef> {
public:
- // Default constructor initializes to an invalid statement.
- StatementRef();
- explicit StatementRef(sqlite3_stmt* stmt);
- StatementRef(Connection* connection, sqlite3_stmt* stmt);
+ // |connection| is the sql::Connection instance associated with
+ // the statement, and is used for tracking outstanding statements
+ // and for error handling. Set to NULL for invalid or untracked
+ // refs. |stmt| is the actual statement, and should only be NULL
+ // to create an invalid ref. |was_valid| indicates whether the
+ // statement should be considered valid for diagnistic purposes.
+ // |was_valid| can be true for NULL |stmt| if the connection has
+ // been forcibly closed by an error handler.
+ StatementRef(Connection* connection, sqlite3_stmt* stmt, bool was_valid);
// When true, the statement can be used.
bool is_valid() const { return !!stmt_; }
- // If we've not been linked to a connection, this will be NULL. Guaranteed
- // non-NULL when is_valid().
+ // When true, the statement is either currently valid, or was
+ // previously valid but the connection was forcibly closed. Used
+ // for diagnostic checks.
+ bool was_valid() const { return was_valid_; }
+
+ // If we've not been linked to a connection, this will be NULL.
+ // TODO(shess): connection_ can be NULL in case of GetUntrackedStatement(),
+ // which prevents Statement::OnError() from forwarding errors.
Connection* connection() const { return connection_; }
// Returns the sqlite statement if any. If the statement is not active,
@@ -372,8 +407,9 @@ class SQL_EXPORT Connection {
sqlite3_stmt* stmt() const { return stmt_; }
// Destroys the compiled statement and marks it NULL. The statement will
- // no longer be active.
- void Close();
+ // no longer be active. |forced| is used to indicate if orderly-shutdown
+ // checks should apply (see Connection::RazeAndClose()).
+ void Close(bool forced);
// Check whether the current thread is allowed to make IO calls, but only
// if database wasn't open in memory.
@@ -386,6 +422,7 @@ class SQL_EXPORT Connection {
Connection* connection_;
sqlite3_stmt* stmt_;
+ bool was_valid_;
DISALLOW_COPY_AND_ASSIGN(StatementRef);
};
@@ -400,9 +437,6 @@ class SQL_EXPORT Connection {
void StatementRefCreated(StatementRef* ref);
void StatementRefDeleted(StatementRef* ref);
- // Frees all cached statements from statement_cache_.
- void ClearCache();
-
// Called by Statement objects when an sqlite function returns an error.
// The return value is the error code reflected back to client code.
int OnSqliteError(int err, Statement* stmt);
@@ -453,10 +487,19 @@ class SQL_EXPORT Connection {
// with Open().
bool in_memory_;
+ // |true| if the connection was closed using RazeAndClose(). Used
+ // to enable diagnostics to distinguish calls to never-opened
+ // databases (incorrect use of the API) from calls to once-valid
+ // databases.
+ bool poisoned_;
+
// This object handles errors resulting from all forms of executing sqlite
// commands or statements. It can be null which means default handling.
scoped_ptr<ErrorDelegate> error_delegate_;
+ // Auxiliary error-code histogram.
+ std::string error_histogram_name_;
+
DISALLOW_COPY_AND_ASSIGN(Connection);
};
diff --git a/sql/connection_unittest.cc b/sql/connection_unittest.cc
index 8036409613..2aaeb27b33 100644
--- a/sql/connection_unittest.cc
+++ b/sql/connection_unittest.cc
@@ -3,10 +3,11 @@
// found in the LICENSE file.
#include "base/file_util.h"
-#include "base/scoped_temp_dir.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
#include "sql/connection.h"
-#include "sql/statement.h"
#include "sql/meta_table.h"
+#include "sql/statement.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/sqlite/sqlite3.h"
@@ -14,23 +15,23 @@ class SQLConnectionTest : public testing::Test {
public:
SQLConnectionTest() {}
- void SetUp() {
+ virtual void SetUp() {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
ASSERT_TRUE(db_.Open(db_path()));
}
- void TearDown() {
+ virtual void TearDown() {
db_.Close();
}
sql::Connection& db() { return db_; }
- FilePath db_path() {
+ base::FilePath db_path() {
return temp_dir_.path().AppendASCII("SQLConnectionTest.db");
}
private:
- ScopedTempDir temp_dir_;
+ base::ScopedTempDir temp_dir_;
sql::Connection db_;
};
@@ -279,6 +280,111 @@ TEST_F(SQLConnectionTest, RazeLocked) {
ASSERT_TRUE(db().Raze());
}
+// Basic test of RazeAndClose() operation.
+TEST_F(SQLConnectionTest, RazeAndClose) {
+ const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
+ const char* kPopulateSql = "INSERT INTO foo (value) VALUES (12)";
+
+ // Test that RazeAndClose() closes the database, and that the
+ // database is empty when re-opened.
+ ASSERT_TRUE(db().Execute(kCreateSql));
+ ASSERT_TRUE(db().Execute(kPopulateSql));
+ ASSERT_TRUE(db().RazeAndClose());
+ ASSERT_FALSE(db().is_open());
+ db().Close();
+ ASSERT_TRUE(db().Open(db_path()));
+ {
+ sql::Statement s(db().GetUniqueStatement("SELECT * FROM sqlite_master"));
+ ASSERT_FALSE(s.Step());
+ }
+
+ // Test that RazeAndClose() can break transactions.
+ ASSERT_TRUE(db().Execute(kCreateSql));
+ ASSERT_TRUE(db().Execute(kPopulateSql));
+ ASSERT_TRUE(db().BeginTransaction());
+ ASSERT_TRUE(db().RazeAndClose());
+ ASSERT_FALSE(db().is_open());
+ ASSERT_FALSE(db().CommitTransaction());
+ db().Close();
+ ASSERT_TRUE(db().Open(db_path()));
+ {
+ sql::Statement s(db().GetUniqueStatement("SELECT * FROM sqlite_master"));
+ ASSERT_FALSE(s.Step());
+ }
+}
+
+// Test that various operations fail without crashing after
+// RazeAndClose().
+TEST_F(SQLConnectionTest, RazeAndCloseDiagnostics) {
+ const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
+ const char* kPopulateSql = "INSERT INTO foo (value) VALUES (12)";
+ const char* kSimpleSql = "SELECT 1";
+
+ ASSERT_TRUE(db().Execute(kCreateSql));
+ ASSERT_TRUE(db().Execute(kPopulateSql));
+
+ // Test baseline expectations.
+ db().Preload();
+ ASSERT_TRUE(db().DoesTableExist("foo"));
+ ASSERT_TRUE(db().IsSQLValid(kSimpleSql));
+ ASSERT_EQ(SQLITE_OK, db().ExecuteAndReturnErrorCode(kSimpleSql));
+ ASSERT_TRUE(db().Execute(kSimpleSql));
+ ASSERT_TRUE(db().is_open());
+ {
+ sql::Statement s(db().GetUniqueStatement(kSimpleSql));
+ ASSERT_TRUE(s.Step());
+ }
+ {
+ sql::Statement s(db().GetCachedStatement(SQL_FROM_HERE, kSimpleSql));
+ ASSERT_TRUE(s.Step());
+ }
+ ASSERT_TRUE(db().BeginTransaction());
+ ASSERT_TRUE(db().CommitTransaction());
+ ASSERT_TRUE(db().BeginTransaction());
+ db().RollbackTransaction();
+
+ ASSERT_TRUE(db().RazeAndClose());
+
+ // At this point, they should all fail, but not crash.
+ db().Preload();
+ ASSERT_FALSE(db().DoesTableExist("foo"));
+ ASSERT_FALSE(db().IsSQLValid(kSimpleSql));
+ ASSERT_EQ(SQLITE_ERROR, db().ExecuteAndReturnErrorCode(kSimpleSql));
+ ASSERT_FALSE(db().Execute(kSimpleSql));
+ ASSERT_FALSE(db().is_open());
+ {
+ sql::Statement s(db().GetUniqueStatement(kSimpleSql));
+ ASSERT_FALSE(s.Step());
+ }
+ {
+ sql::Statement s(db().GetCachedStatement(SQL_FROM_HERE, kSimpleSql));
+ ASSERT_FALSE(s.Step());
+ }
+ ASSERT_FALSE(db().BeginTransaction());
+ ASSERT_FALSE(db().CommitTransaction());
+ ASSERT_FALSE(db().BeginTransaction());
+ db().RollbackTransaction();
+
+ // Close normally to reset the poisoned flag.
+ db().Close();
+
+ // DEATH tests not supported on Android or iOS.
+#if !defined(OS_ANDROID) && !defined(OS_IOS)
+ // Once the real Close() has been called, various calls enforce API
+ // usage by becoming fatal in debug mode. Since DEATH tests are
+ // expensive, just test one of them.
+ if (DLOG_IS_ON(FATAL)) {
+ ASSERT_DEATH({
+ db().IsSQLValid(kSimpleSql);
+ }, "Illegal use of connection without a db");
+ }
+#endif
+}
+
+// TODO(shess): Spin up a background thread to hold other_db, to more
+// closely match real life. That would also allow testing
+// RazeWithTimeout().
+
#if defined(OS_ANDROID)
TEST_F(SQLConnectionTest, SetTempDirForSQL) {
@@ -291,7 +397,3 @@ TEST_F(SQLConnectionTest, SetTempDirForSQL) {
ASSERT_TRUE(meta_table.Init(&db(), 4, 4));
}
#endif
-
-// TODO(shess): Spin up a background thread to hold other_db, to more
-// closely match real life. That would also allow testing
-// RazeWithTimeout().
diff --git a/sql/run_all_unittests.cc b/sql/run_all_unittests.cc
index 969b09164a..2c8d29c6d0 100644
--- a/sql/run_all_unittests.cc
+++ b/sql/run_all_unittests.cc
@@ -2,10 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "base/test/main_hook.h"
#include "base/test/test_suite.h"
int main(int argc, char** argv) {
- MainHook hook(main, argc, argv);
return base::TestSuite(argc, argv).Run();
}
diff --git a/sql/sql.gyp b/sql/sql.gyp
index 86962b3d02..0d8f8be967 100644
--- a/sql/sql.gyp
+++ b/sql/sql.gyp
@@ -29,6 +29,8 @@
'transaction.cc',
'transaction.h',
],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [4267, ],
},
{
'target_name': 'sql_unittests',
@@ -64,6 +66,8 @@
],
}],
],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [4267, ],
},
],
'conditions': [
diff --git a/sql/sql.target.mk b/sql/sql.target.linux-arm.mk
index 029beef85b..046707eff9 100644
--- a/sql/sql.target.mk
+++ b/sql/sql.target.linux-arm.mk
@@ -32,6 +32,8 @@ LOCAL_SRC_FILES := \
# Flags passed to both C and C++ files.
MY_CFLAGS := \
+ -fstack-protector \
+ --param=ssp-buffer-size=4 \
-Werror \
-fno-exceptions \
-fno-strict-aliasing \
@@ -41,15 +43,9 @@ MY_CFLAGS := \
-fvisibility=hidden \
-pipe \
-fPIC \
- -mthumb \
- -march=armv7-a \
- -mtune=cortex-a8 \
- -mfloat-abi=softfp \
- -mfpu=vfpv3-d16 \
-fno-tree-sra \
-fuse-ld=gold \
-Wno-psabi \
- -mthumb-interwork \
-ffunction-sections \
-funwind-tables \
-g \
@@ -57,11 +53,10 @@ MY_CFLAGS := \
-fno-short-enums \
-finline-limit=64 \
-Wa,--noexecstack \
- -Wno-error=extra \
- -Wno-error=ignored-qualifiers \
- -Wno-error=type-limits \
- -Wno-error=non-virtual-dtor \
- -Wno-error=sign-promo \
+ -U_FORTIFY_SOURCE \
+ -Wno-extra \
+ -Wno-ignored-qualifiers \
+ -Wno-type-limits \
-Os \
-g \
-fomit-frame-pointer \
@@ -71,7 +66,9 @@ MY_CFLAGS := \
MY_CFLAGS_C :=
MY_DEFS := \
+ '-DUSE_SKIA' \
'-D_FILE_OFFSET_BITS=64' \
+ '-DUSE_LINUX_BREAKPAD' \
'-DNO_TCMALLOC' \
'-DDISABLE_NACL' \
'-DCHROMIUM_BUILD' \
@@ -81,7 +78,7 @@ MY_DEFS := \
'-DENABLE_GPU=1' \
'-DUSE_OPENSSL=1' \
'-DENABLE_EGLIMAGE=1' \
- '-DUSE_SKIA=1' \
+ '-DENABLE_LANGUAGE_DETECTION=1' \
'-DSQL_IMPLEMENTATION' \
'-D__STDC_CONSTANT_MACROS' \
'-D__STDC_FORMAT_MACROS' \
@@ -89,8 +86,7 @@ MY_DEFS := \
'-D__GNU_SOURCE=1' \
'-DUSE_STLPORT=1' \
'-D_STLP_USE_PTR_SPECIALIZATIONS=1' \
- '-DCHROME_SYMBOLS_ID=""' \
- '-DANDROID_UPSTREAM_BRINGUP=1' \
+ '-DCHROME_BUILD_ID=""' \
'-DDYNAMIC_ANNOTATIONS_ENABLED=1' \
'-DWTF_USE_DYNAMIC_ANNOTATIONS=1' \
'-D_DEBUG'
@@ -99,6 +95,8 @@ LOCAL_CFLAGS := $(MY_CFLAGS_C) $(MY_CFLAGS) $(MY_DEFS)
# Include paths placed before CFLAGS/CPPFLAGS
LOCAL_C_INCLUDES := \
+ $(gyp_shared_intermediate_dir)/shim_headers/icui18n/target \
+ $(gyp_shared_intermediate_dir)/shim_headers/icuuc/target \
$(LOCAL_PATH) \
$(LOCAL_PATH)/third_party/sqlite \
$(GYP_ABS_ANDROID_TOP_DIR)/frameworks/wilhelm/include \
@@ -114,11 +112,15 @@ LOCAL_CPPFLAGS := \
-fvisibility-inlines-hidden \
-Wsign-compare \
-Wno-abi \
- -Wno-error=c++0x-compat
+ -Wno-error=c++0x-compat \
+ -Wno-non-virtual-dtor \
+ -Wno-sign-promo
### Rules for final target.
LOCAL_LDFLAGS := \
+ -Wl,-z,now \
+ -Wl,-z,relro \
-Wl,-z,noexecstack \
-fPIC \
-Wl,-z,relro \
diff --git a/sql/sql.target.linux-x86.mk b/sql/sql.target.linux-x86.mk
new file mode 100644
index 0000000000..1b5acd2dd0
--- /dev/null
+++ b/sql/sql.target.linux-x86.mk
@@ -0,0 +1,152 @@
+# This file is generated by gyp; do not edit.
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_MODULE := sql_sql_gyp
+LOCAL_MODULE_SUFFIX := .a
+LOCAL_MODULE_TAGS := optional
+gyp_intermediate_dir := $(call local-intermediates-dir)
+gyp_shared_intermediate_dir := $(call intermediates-dir-for,GYP,shared)
+
+# Make sure our deps are built first.
+GYP_TARGET_DEPENDENCIES :=
+
+GYP_GENERATED_OUTPUTS :=
+
+# Make sure our deps and generated files are built first.
+LOCAL_ADDITIONAL_DEPENDENCIES := $(GYP_TARGET_DEPENDENCIES) $(GYP_GENERATED_OUTPUTS)
+
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_GENERATED_SOURCES :=
+
+GYP_COPIED_SOURCE_ORIGIN_DIRS :=
+
+LOCAL_SRC_FILES := \
+ sql/connection.cc \
+ sql/error_delegate_util.cc \
+ sql/meta_table.cc \
+ sql/statement.cc \
+ sql/transaction.cc
+
+
+# Flags passed to both C and C++ files.
+MY_CFLAGS := \
+ --param=ssp-buffer-size=4 \
+ -Werror \
+ -fno-exceptions \
+ -fno-strict-aliasing \
+ -Wall \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -fvisibility=hidden \
+ -pipe \
+ -fPIC \
+ -m32 \
+ -mmmx \
+ -march=pentium4 \
+ -msse2 \
+ -mfpmath=sse \
+ -ffunction-sections \
+ -funwind-tables \
+ -g \
+ -fno-short-enums \
+ -finline-limit=64 \
+ -Wa,--noexecstack \
+ -U_FORTIFY_SOURCE \
+ -Wno-extra \
+ -Wno-ignored-qualifiers \
+ -Wno-type-limits \
+ -fno-stack-protector \
+ -Os \
+ -g \
+ -fomit-frame-pointer \
+ -fdata-sections \
+ -ffunction-sections
+
+MY_CFLAGS_C :=
+
+MY_DEFS := \
+ '-DUSE_SKIA' \
+ '-D_FILE_OFFSET_BITS=64' \
+ '-DUSE_LINUX_BREAKPAD' \
+ '-DNO_TCMALLOC' \
+ '-DDISABLE_NACL' \
+ '-DCHROMIUM_BUILD' \
+ '-DUSE_LIBJPEG_TURBO=1' \
+ '-DUSE_PROPRIETARY_CODECS' \
+ '-DENABLE_PEPPER_THREADING' \
+ '-DENABLE_GPU=1' \
+ '-DUSE_OPENSSL=1' \
+ '-DENABLE_EGLIMAGE=1' \
+ '-DENABLE_LANGUAGE_DETECTION=1' \
+ '-DSQL_IMPLEMENTATION' \
+ '-D__STDC_CONSTANT_MACROS' \
+ '-D__STDC_FORMAT_MACROS' \
+ '-DANDROID' \
+ '-D__GNU_SOURCE=1' \
+ '-DUSE_STLPORT=1' \
+ '-D_STLP_USE_PTR_SPECIALIZATIONS=1' \
+ '-DCHROME_BUILD_ID=""' \
+ '-DDYNAMIC_ANNOTATIONS_ENABLED=1' \
+ '-DWTF_USE_DYNAMIC_ANNOTATIONS=1' \
+ '-D_DEBUG'
+
+LOCAL_CFLAGS := $(MY_CFLAGS_C) $(MY_CFLAGS) $(MY_DEFS)
+
+# Include paths placed before CFLAGS/CPPFLAGS
+LOCAL_C_INCLUDES := \
+ $(gyp_shared_intermediate_dir)/shim_headers/icui18n/target \
+ $(gyp_shared_intermediate_dir)/shim_headers/icuuc/target \
+ $(LOCAL_PATH) \
+ $(LOCAL_PATH)/third_party/sqlite \
+ $(GYP_ABS_ANDROID_TOP_DIR)/frameworks/wilhelm/include \
+ $(GYP_ABS_ANDROID_TOP_DIR)/bionic \
+ $(GYP_ABS_ANDROID_TOP_DIR)/external/stlport/stlport
+
+LOCAL_C_INCLUDES := $(GYP_COPIED_SOURCE_ORIGIN_DIRS) $(LOCAL_C_INCLUDES)
+
+# Flags passed to only C++ (and not C) files.
+LOCAL_CPPFLAGS := \
+ -fno-rtti \
+ -fno-threadsafe-statics \
+ -fvisibility-inlines-hidden \
+ -Wsign-compare \
+ -Wno-error=c++0x-compat \
+ -Wno-non-virtual-dtor \
+ -Wno-sign-promo
+
+### Rules for final target.
+
+LOCAL_LDFLAGS := \
+ -Wl,-z,now \
+ -Wl,-z,relro \
+ -Wl,-z,noexecstack \
+ -fPIC \
+ -m32 \
+ -nostdlib \
+ -Wl,--no-undefined \
+ -Wl,--exclude-libs=ALL \
+ -Wl,-O1 \
+ -Wl,--as-needed \
+ -Wl,--gc-sections
+
+
+LOCAL_STATIC_LIBRARIES :=
+
+# Enable grouping to fix circular references
+LOCAL_GROUP_STATIC_LIBRARIES := true
+
+LOCAL_SHARED_LIBRARIES := \
+ libstlport \
+ libdl
+
+# Add target alias to "gyp_all_modules" target.
+.PHONY: gyp_all_modules
+gyp_all_modules: sql_sql_gyp
+
+# Alias gyp target name.
+.PHONY: sql
+sql: sql_sql_gyp
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/sql/sqlite_features_unittest.cc b/sql/sqlite_features_unittest.cc
index 7e750e97c6..2f6a49eb48 100644
--- a/sql/sqlite_features_unittest.cc
+++ b/sql/sqlite_features_unittest.cc
@@ -5,7 +5,7 @@
#include <string>
#include "base/file_util.h"
-#include "base/scoped_temp_dir.h"
+#include "base/files/scoped_temp_dir.h"
#include "sql/connection.h"
#include "sql/statement.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -42,7 +42,7 @@ class SQLiteFeaturesTest : public testing::Test {
public:
SQLiteFeaturesTest() : error_(SQLITE_OK) {}
- void SetUp() {
+ virtual void SetUp() {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
ASSERT_TRUE(db_.Open(temp_dir_.path().AppendASCII("SQLStatementTest.db")));
@@ -51,7 +51,7 @@ class SQLiteFeaturesTest : public testing::Test {
db_.set_error_delegate(new StatementErrorHandler(&error_, &sql_text_));
}
- void TearDown() {
+ virtual void TearDown() {
// If any error happened the original sql statement can be found in
// |sql_text_|.
EXPECT_EQ(SQLITE_OK, error_);
@@ -65,7 +65,7 @@ class SQLiteFeaturesTest : public testing::Test {
}
private:
- ScopedTempDir temp_dir_;
+ base::ScopedTempDir temp_dir_;
sql::Connection db_;
// The error code of the most recent error.
diff --git a/sql/statement.cc b/sql/statement.cc
index 84dfd2eb9a..d92be010fd 100644
--- a/sql/statement.cc
+++ b/sql/statement.cc
@@ -15,7 +15,7 @@ namespace sql {
// we don't have to NULL-check the ref_ to see if the statement is valid: we
// only have to check the ref's validity bit.
Statement::Statement()
- : ref_(new Connection::StatementRef),
+ : ref_(new Connection::StatementRef(NULL, NULL, false)),
succeeded_(false) {
}
@@ -37,13 +37,15 @@ void Statement::Assign(scoped_refptr<Connection::StatementRef> ref) {
}
void Statement::Clear() {
- Assign(new Connection::StatementRef);
+ Assign(new Connection::StatementRef(NULL, NULL, false));
succeeded_ = false;
}
bool Statement::CheckValid() const {
- if (!is_valid())
- DLOG(FATAL) << "Cannot call mutating statements on an invalid statement.";
+ // Allow operations to fail silently if a statement was invalidated
+ // because the database was closed by an error handler.
+ DLOG_IF(FATAL, !ref_->was_valid())
+ << "Cannot call mutating statements on an invalid statement.";
return is_valid();
}
@@ -306,7 +308,7 @@ bool Statement::CheckOk(int err) const {
int Statement::CheckError(int err) {
// Please don't add DCHECKs here, OnSqliteError() already has them.
succeeded_ = (err == SQLITE_OK || err == SQLITE_ROW || err == SQLITE_DONE);
- if (!succeeded_ && is_valid())
+ if (!succeeded_ && ref_ && ref_->connection())
return ref_->connection()->OnSqliteError(err, this);
return err;
}
diff --git a/sql/statement_unittest.cc b/sql/statement_unittest.cc
index a7a23d8294..ffa8aef1f8 100644
--- a/sql/statement_unittest.cc
+++ b/sql/statement_unittest.cc
@@ -5,7 +5,7 @@
#include <string>
#include "base/file_util.h"
-#include "base/scoped_temp_dir.h"
+#include "base/files/scoped_temp_dir.h"
#include "sql/connection.h"
#include "sql/statement.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -40,7 +40,7 @@ class SQLStatementTest : public testing::Test {
public:
SQLStatementTest() : error_(SQLITE_OK) {}
- void SetUp() {
+ 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
@@ -48,7 +48,7 @@ class SQLStatementTest : public testing::Test {
db_.set_error_delegate(new StatementErrorHandler(&error_, &sql_text_));
}
- void TearDown() {
+ virtual void TearDown() {
// If any error happened the original sql statement can be found in
// |sql_text_|.
EXPECT_EQ(SQLITE_OK, error_);
@@ -65,7 +65,7 @@ class SQLStatementTest : public testing::Test {
}
private:
- ScopedTempDir temp_dir_;
+ base::ScopedTempDir temp_dir_;
sql::Connection db_;
// The error code of the most recent error.
diff --git a/sql/transaction_unittest.cc b/sql/transaction_unittest.cc
index f306a5cb6e..ceaa4dbd7d 100644
--- a/sql/transaction_unittest.cc
+++ b/sql/transaction_unittest.cc
@@ -3,7 +3,7 @@
// found in the LICENSE file.
#include "base/file_util.h"
-#include "base/scoped_temp_dir.h"
+#include "base/files/scoped_temp_dir.h"
#include "sql/connection.h"
#include "sql/statement.h"
#include "sql/transaction.h"
@@ -14,7 +14,7 @@ class SQLTransactionTest : public testing::Test {
public:
SQLTransactionTest() {}
- void SetUp() {
+ virtual void SetUp() {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
ASSERT_TRUE(db_.Open(
temp_dir_.path().AppendASCII("SQLTransactionTest.db")));
@@ -22,7 +22,7 @@ class SQLTransactionTest : public testing::Test {
ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
}
- void TearDown() {
+ virtual void TearDown() {
db_.Close();
}
@@ -36,7 +36,7 @@ class SQLTransactionTest : public testing::Test {
}
private:
- ScopedTempDir temp_dir_;
+ base::ScopedTempDir temp_dir_;
sql::Connection db_;
};