Chromium Code Reviews| Index: sql/connection.cc |
| diff --git a/sql/connection.cc b/sql/connection.cc |
| index aefac60fac7e3fa37b75611eebf3dba457e48204..1a795b20525fd86c74ea6f9b383176bfc4f93936 100644 |
| --- a/sql/connection.cc |
| +++ b/sql/connection.cc |
| @@ -76,28 +76,31 @@ ErrorDelegate::~ErrorDelegate() { |
| Connection::StatementRef::StatementRef() |
| : connection_(NULL), |
| - stmt_(NULL) { |
| + stmt_(NULL), |
| + was_valid_(false) { |
| } |
| Connection::StatementRef::StatementRef(sqlite3_stmt* stmt) |
| : connection_(NULL), |
| - stmt_(stmt) { |
| + stmt_(stmt), |
| + was_valid_(stmt != NULL) { |
| } |
| Connection::StatementRef::StatementRef(Connection* connection, |
| sqlite3_stmt* stmt) |
| : connection_(connection), |
| - stmt_(stmt) { |
| + stmt_(stmt), |
| + was_valid_(stmt != NULL) { |
| 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 |
| @@ -111,6 +114,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 |
|
pkotwicz
2013/01/31 19:22:00
Nit: Use a ',' instead of a '.'
Scott Hess - ex-Googler
2013/02/05 01:20:25
I'll assume you meant it the other way around, or
|
| + // previously held for this ref. |
| + was_valid_ = was_valid_ && forced; |
| } |
| Connection::Connection() |
| @@ -141,22 +149,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 |
| @@ -172,11 +186,19 @@ void Connection::Close() { |
| } |
| } |
| +void Connection::Close() { |
| + // Database was already closed via RazeAndClose(), ignore. |
| + if (poisoned_) |
| + return; |
| + |
| + CloseInternal(false); |
| +} |
| + |
| void Connection::Preload() { |
| AssertIOAllowed(); |
| if (!db_) { |
| - DLOG(FATAL) << "Cannot preload null db"; |
| + DLOG_IF(FATAL, !poisoned_) << "Cannot preload null db"; |
| return; |
| } |
| @@ -202,7 +224,7 @@ bool Connection::Raze() { |
| AssertIOAllowed(); |
| if (!db_) { |
| - DLOG(FATAL) << "Cannot raze null db"; |
| + DLOG_IF(FATAL, !poisoned_) << "Cannot raze null db"; |
| return false; |
| } |
| @@ -292,7 +314,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; |
| } |
| @@ -301,6 +323,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_) { |
|
pkotwicz
2013/01/31 19:22:00
Nit: The braces are not necessary
Scott Hess - ex-Googler
2013/02/05 01:20:25
I'll plead the personal-preference exemption in th
|
| + 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); |
| @@ -324,7 +369,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; |
| } |
| @@ -341,7 +386,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_--; |
| @@ -450,6 +495,11 @@ scoped_refptr<Connection::StatementRef> Connection::GetUntrackedStatement( |
| 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; |
| @@ -492,7 +542,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_); |
| @@ -500,7 +550,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_); |
| @@ -532,6 +582,10 @@ const char* Connection::GetErrorMessage() const { |
| bool Connection::OpenInternal(const std::string& file_name) { |
| AssertIOAllowed(); |
| + // Reset, in case connection is re-used. This usage may not be an |
| + // intentional API contract, but tests do it. |
| + poisoned_ = false; |
| + |
| if (db_) { |
| DLOG(FATAL) << "sql::Connection is already open."; |
| return false; |
| @@ -641,17 +695,6 @@ 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; |