| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "webkit/dom_storage/dom_storage_database.h" | 5 #include "webkit/dom_storage/dom_storage_database.h" |
| 6 | 6 |
| 7 #include "base/file_util.h" | 7 #include "base/file_util.h" |
| 8 #include "base/logging.h" | 8 #include "base/logging.h" |
| 9 #include "sql/diagnostic_error_delegate.h" | 9 #include "sql/diagnostic_error_delegate.h" |
| 10 #include "sql/statement.h" | 10 #include "sql/statement.h" |
| 11 #include "sql/transaction.h" | 11 #include "sql/transaction.h" |
| 12 #include "third_party/sqlite/sqlite3.h" | 12 #include "third_party/sqlite/sqlite3.h" |
| 13 | 13 |
| 14 namespace { | 14 namespace { |
| 15 | 15 |
| 16 const FilePath::CharType kJournal[] = FILE_PATH_LITERAL("-journal"); | 16 const FilePath::CharType kJournal[] = FILE_PATH_LITERAL("-journal"); |
| 17 | 17 |
| 18 class HistogramUniquifier { | 18 class HistogramUniquifier { |
| 19 public: | 19 public: |
| 20 static const char* name() { return "Sqlite.DomStorageDatabase.Error"; } | 20 static const char* name() { return "Sqlite.DomStorageDatabase.Error"; } |
| 21 }; | 21 }; |
| 22 | 22 |
| 23 sql::ErrorDelegate* GetErrorHandlerForDomStorageDatabase() { | 23 sql::ErrorDelegate* GetErrorHandlerForLocalStorageDatabase() { |
| 24 return new sql::DiagnosticErrorDelegate<HistogramUniquifier>(); | 24 return new sql::DiagnosticErrorDelegate<HistogramUniquifier>(); |
| 25 } | 25 } |
| 26 | 26 |
| 27 } // anon namespace | 27 } // anon namespace |
| 28 | 28 |
| 29 namespace dom_storage { | 29 namespace dom_storage { |
| 30 | 30 |
| 31 // static | 31 // static |
| 32 FilePath DomStorageDatabase::GetJournalFilePath( | 32 FilePath LocalStorageDatabase::GetJournalFilePath( |
| 33 const FilePath& database_path) { | 33 const FilePath& database_path) { |
| 34 FilePath::StringType journal_file_name = | 34 FilePath::StringType journal_file_name = |
| 35 database_path.BaseName().value() + kJournal; | 35 database_path.BaseName().value() + kJournal; |
| 36 return database_path.DirName().Append(journal_file_name); | 36 return database_path.DirName().Append(journal_file_name); |
| 37 } | 37 } |
| 38 | 38 |
| 39 DomStorageDatabase::DomStorageDatabase(const FilePath& file_path) | 39 DomStorageDatabase::DomStorageDatabase(const FilePath& file_path) |
| 40 : file_path_(file_path) { | 40 : file_path_(file_path), |
| 41 failed_to_open_(false) { } |
| 42 |
| 43 DomStorageDatabase::~DomStorageDatabase() { } |
| 44 |
| 45 LocalStorageDatabase::LocalStorageDatabase(const FilePath& file_path) |
| 46 : DomStorageDatabase(file_path) { |
| 41 // Note: in normal use we should never get an empty backing path here. | 47 // Note: in normal use we should never get an empty backing path here. |
| 42 // However, the unit test for this class can contruct an instance | 48 // However, the unit test for this class can contruct an instance |
| 43 // with an empty path. | 49 // with an empty path. |
| 44 Init(); | 50 Init(); |
| 45 } | 51 } |
| 46 | 52 |
| 47 DomStorageDatabase::DomStorageDatabase() { | 53 LocalStorageDatabase::LocalStorageDatabase() |
| 54 : DomStorageDatabase(FilePath(FILE_PATH_LITERAL(""))) { |
| 48 Init(); | 55 Init(); |
| 49 } | 56 } |
| 50 | 57 |
| 51 void DomStorageDatabase::Init() { | 58 void LocalStorageDatabase::Init() { |
| 52 failed_to_open_ = false; | 59 failed_to_open_ = false; |
| 53 tried_to_recreate_ = false; | 60 tried_to_recreate_ = false; |
| 54 known_to_be_empty_ = false; | 61 known_to_be_empty_ = false; |
| 55 } | 62 } |
| 56 | 63 |
| 57 DomStorageDatabase::~DomStorageDatabase() { | 64 LocalStorageDatabase::~LocalStorageDatabase() { |
| 58 if (known_to_be_empty_ && !file_path_.empty()) { | 65 if (known_to_be_empty_ && !file_path_.empty()) { |
| 59 // Delete the db and any lingering journal file from disk. | 66 // Delete the db and any lingering journal file from disk. |
| 60 Close(); | 67 Close(); |
| 61 file_util::Delete(file_path_, false); | 68 file_util::Delete(file_path_, false); |
| 62 file_util::Delete(GetJournalFilePath(file_path_), false); | 69 file_util::Delete(GetJournalFilePath(file_path_), false); |
| 63 } | 70 } |
| 64 } | 71 } |
| 65 | 72 |
| 66 void DomStorageDatabase::ReadAllValues(ValuesMap* result) { | 73 void LocalStorageDatabase::ReadAllValues( |
| 74 int64 namespace_id, const GURL& origin, ValuesMap* result) { |
| 67 if (!LazyOpen(false)) | 75 if (!LazyOpen(false)) |
| 68 return; | 76 return; |
| 69 | 77 |
| 70 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, | 78 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, |
| 71 "SELECT * from ItemTable")); | 79 "SELECT * from ItemTable")); |
| 72 DCHECK(statement.is_valid()); | 80 DCHECK(statement.is_valid()); |
| 73 | 81 |
| 74 while (statement.Step()) { | 82 while (statement.Step()) { |
| 75 string16 key = statement.ColumnString16(0); | 83 string16 key = statement.ColumnString16(0); |
| 76 string16 value; | 84 string16 value; |
| 77 statement.ColumnBlobAsString16(1, &value); | 85 statement.ColumnBlobAsString16(1, &value); |
| 78 (*result)[key] = NullableString16(value, false); | 86 (*result)[key] = NullableString16(value, false); |
| 79 } | 87 } |
| 80 known_to_be_empty_ = result->empty(); | 88 known_to_be_empty_ = result->empty(); |
| 81 } | 89 } |
| 82 | 90 |
| 83 bool DomStorageDatabase::CommitChanges(bool clear_all_first, | 91 bool LocalStorageDatabase::CommitChanges( |
| 84 const ValuesMap& changes) { | 92 int64 namespace_id, const GURL& origin, bool clear_all_first, |
| 93 const ValuesMap& changes) { |
| 85 if (!LazyOpen(!changes.empty())) { | 94 if (!LazyOpen(!changes.empty())) { |
| 86 // If we're being asked to commit changes that will result in an | 95 // If we're being asked to commit changes that will result in an |
| 87 // empty database, we return true if the database file doesn't exist. | 96 // empty database, we return true if the database file doesn't exist. |
| 88 return clear_all_first && changes.empty() && | 97 return clear_all_first && changes.empty() && |
| 89 !file_util::PathExists(file_path_); | 98 !file_util::PathExists(file_path_); |
| 90 } | 99 } |
| 91 | 100 |
| 92 bool old_known_to_be_empty = known_to_be_empty_; | 101 bool old_known_to_be_empty = known_to_be_empty_; |
| 93 sql::Transaction transaction(db_.get()); | 102 sql::Transaction transaction(db_.get()); |
| 94 if (!transaction.Begin()) | 103 if (!transaction.Begin()) |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 131 if (statement.Step()) | 140 if (statement.Step()) |
| 132 known_to_be_empty_ = statement.ColumnInt(0) == 0; | 141 known_to_be_empty_ = statement.ColumnInt(0) == 0; |
| 133 } | 142 } |
| 134 | 143 |
| 135 bool success = transaction.Commit(); | 144 bool success = transaction.Commit(); |
| 136 if (!success) | 145 if (!success) |
| 137 known_to_be_empty_ = old_known_to_be_empty; | 146 known_to_be_empty_ = old_known_to_be_empty; |
| 138 return success; | 147 return success; |
| 139 } | 148 } |
| 140 | 149 |
| 141 bool DomStorageDatabase::LazyOpen(bool create_if_needed) { | 150 bool LocalStorageDatabase::LazyOpen(bool create_if_needed) { |
| 142 if (failed_to_open_) { | 151 if (failed_to_open_) { |
| 143 // Don't try to open a database that we know has failed | 152 // Don't try to open a database that we know has failed |
| 144 // already. | 153 // already. |
| 145 return false; | 154 return false; |
| 146 } | 155 } |
| 147 | 156 |
| 148 if (IsOpen()) | 157 if (IsOpen()) |
| 149 return true; | 158 return true; |
| 150 | 159 |
| 151 bool database_exists = file_util::PathExists(file_path_); | 160 bool database_exists = file_util::PathExists(file_path_); |
| 152 | 161 |
| 153 if (!database_exists && !create_if_needed) { | 162 if (!database_exists && !create_if_needed) { |
| 154 // If the file doesn't exist already and we haven't been asked to create | 163 // If the file doesn't exist already and we haven't been asked to create |
| 155 // a file on disk, then we don't bother opening the database. This means | 164 // a file on disk, then we don't bother opening the database. This means |
| 156 // we wait until we absolutely need to put something onto disk before we | 165 // we wait until we absolutely need to put something onto disk before we |
| 157 // do so. | 166 // do so. |
| 158 return false; | 167 return false; |
| 159 } | 168 } |
| 160 | 169 |
| 161 db_.reset(new sql::Connection()); | 170 db_.reset(new sql::Connection()); |
| 162 db_->set_error_delegate(GetErrorHandlerForDomStorageDatabase()); | 171 db_->set_error_delegate(GetErrorHandlerForLocalStorageDatabase()); |
| 163 | 172 |
| 164 if (file_path_.empty()) { | 173 if (file_path_.empty()) { |
| 165 // This code path should only be triggered by unit tests. | 174 // This code path should only be triggered by unit tests. |
| 166 if (!db_->OpenInMemory()) { | 175 if (!db_->OpenInMemory()) { |
| 167 NOTREACHED() << "Unable to open DOM storage database in memory."; | 176 NOTREACHED() << "Unable to open DOM storage database in memory."; |
| 168 failed_to_open_ = true; | 177 failed_to_open_ = true; |
| 169 return false; | 178 return false; |
| 170 } | 179 } |
| 171 } else { | 180 } else { |
| 172 if (!db_->Open(file_path_)) { | 181 if (!db_->Open(file_path_)) { |
| (...skipping 27 matching lines...) Expand all Loading... |
| 200 return true; | 209 return true; |
| 201 } | 210 } |
| 202 } | 211 } |
| 203 | 212 |
| 204 // This is the exceptional case - to try and recover we'll attempt | 213 // This is the exceptional case - to try and recover we'll attempt |
| 205 // to delete the file and start again. | 214 // to delete the file and start again. |
| 206 Close(); | 215 Close(); |
| 207 return DeleteFileAndRecreate(); | 216 return DeleteFileAndRecreate(); |
| 208 } | 217 } |
| 209 | 218 |
| 210 DomStorageDatabase::SchemaVersion DomStorageDatabase::DetectSchemaVersion() { | 219 LocalStorageDatabase::SchemaVersion |
| 220 LocalStorageDatabase::DetectSchemaVersion() { |
| 211 DCHECK(IsOpen()); | 221 DCHECK(IsOpen()); |
| 212 | 222 |
| 213 // Connection::Open() may succeed even if the file we try and open is not a | 223 // Connection::Open() may succeed even if the file we try and open is not a |
| 214 // database, however in the case that the database is corrupted to the point | 224 // database, however in the case that the database is corrupted to the point |
| 215 // that SQLite doesn't actually think it's a database, | 225 // that SQLite doesn't actually think it's a database, |
| 216 // sql::Connection::GetCachedStatement will DCHECK when we later try and | 226 // sql::Connection::GetCachedStatement will DCHECK when we later try and |
| 217 // run statements. So we run a query here that will not DCHECK but fail | 227 // run statements. So we run a query here that will not DCHECK but fail |
| 218 // on an invalid database to verify that what we've opened is usable. | 228 // on an invalid database to verify that what we've opened is usable. |
| 219 if (db_->ExecuteAndReturnErrorCode("PRAGMA auto_vacuum") != SQLITE_OK) | 229 if (db_->ExecuteAndReturnErrorCode("PRAGMA auto_vacuum") != SQLITE_OK) |
| 220 return INVALID; | 230 return INVALID; |
| (...skipping 15 matching lines...) Expand all Loading... |
| 236 return V2; | 246 return V2; |
| 237 case sql::COLUMN_TYPE_TEXT: | 247 case sql::COLUMN_TYPE_TEXT: |
| 238 return V1; | 248 return V1; |
| 239 default: | 249 default: |
| 240 return INVALID; | 250 return INVALID; |
| 241 } | 251 } |
| 242 NOTREACHED(); | 252 NOTREACHED(); |
| 243 return INVALID; | 253 return INVALID; |
| 244 } | 254 } |
| 245 | 255 |
| 246 bool DomStorageDatabase::CreateTableV2() { | 256 bool LocalStorageDatabase::CreateTableV2() { |
| 247 DCHECK(IsOpen()); | 257 DCHECK(IsOpen()); |
| 248 | 258 |
| 249 return db_->Execute( | 259 return db_->Execute( |
| 250 "CREATE TABLE ItemTable (" | 260 "CREATE TABLE ItemTable (" |
| 251 "key TEXT UNIQUE ON CONFLICT REPLACE, " | 261 "key TEXT UNIQUE ON CONFLICT REPLACE, " |
| 252 "value BLOB NOT NULL ON CONFLICT FAIL)"); | 262 "value BLOB NOT NULL ON CONFLICT FAIL)"); |
| 253 } | 263 } |
| 254 | 264 |
| 255 bool DomStorageDatabase::DeleteFileAndRecreate() { | 265 bool LocalStorageDatabase::DeleteFileAndRecreate() { |
| 256 DCHECK(!IsOpen()); | 266 DCHECK(!IsOpen()); |
| 257 DCHECK(file_util::PathExists(file_path_)); | 267 DCHECK(file_util::PathExists(file_path_)); |
| 258 | 268 |
| 259 // We should only try and do this once. | 269 // We should only try and do this once. |
| 260 if (tried_to_recreate_) | 270 if (tried_to_recreate_) |
| 261 return false; | 271 return false; |
| 262 | 272 |
| 263 tried_to_recreate_ = true; | 273 tried_to_recreate_ = true; |
| 264 | 274 |
| 265 // If it's not a directory and we can delete the file, try and open it again. | 275 // If it's not a directory and we can delete the file, try and open it again. |
| 266 if (!file_util::DirectoryExists(file_path_) && | 276 if (!file_util::DirectoryExists(file_path_) && |
| 267 file_util::Delete(file_path_, false)) | 277 file_util::Delete(file_path_, false)) |
| 268 return LazyOpen(true); | 278 return LazyOpen(true); |
| 269 | 279 |
| 270 failed_to_open_ = true; | 280 failed_to_open_ = true; |
| 271 return false; | 281 return false; |
| 272 } | 282 } |
| 273 | 283 |
| 274 bool DomStorageDatabase::UpgradeVersion1To2() { | 284 bool LocalStorageDatabase::UpgradeVersion1To2() { |
| 275 DCHECK(IsOpen()); | 285 DCHECK(IsOpen()); |
| 276 DCHECK(DetectSchemaVersion() == V1); | 286 DCHECK(DetectSchemaVersion() == V1); |
| 277 | 287 |
| 278 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, | 288 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, |
| 279 "SELECT * FROM ItemTable")); | 289 "SELECT * FROM ItemTable")); |
| 280 DCHECK(statement.is_valid()); | 290 DCHECK(statement.is_valid()); |
| 281 | 291 |
| 282 // Need to migrate from TEXT value column to BLOB. | 292 // Need to migrate from TEXT value column to BLOB. |
| 283 // Store the current database content so we can re-insert | 293 // Store the current database content so we can re-insert |
| 284 // the data into the new V2 table. | 294 // the data into the new V2 table. |
| 285 ValuesMap values; | 295 ValuesMap values; |
| 286 while (statement.Step()) { | 296 while (statement.Step()) { |
| 287 string16 key = statement.ColumnString16(0); | 297 string16 key = statement.ColumnString16(0); |
| 288 NullableString16 value(statement.ColumnString16(1), false); | 298 NullableString16 value(statement.ColumnString16(1), false); |
| 289 values[key] = value; | 299 values[key] = value; |
| 290 } | 300 } |
| 291 | 301 |
| 292 sql::Transaction migration(db_.get()); | 302 sql::Transaction migration(db_.get()); |
| 293 return migration.Begin() && | 303 return migration.Begin() && |
| 294 db_->Execute("DROP TABLE ItemTable") && | 304 db_->Execute("DROP TABLE ItemTable") && |
| 295 CreateTableV2() && | 305 CreateTableV2() && |
| 296 CommitChanges(false, values) && | 306 CommitChanges(0, GURL(""), false, values) && |
| 297 migration.Commit(); | 307 migration.Commit(); |
| 298 } | 308 } |
| 299 | 309 |
| 300 void DomStorageDatabase::Close() { | 310 bool LocalStorageDatabase::IsOpen() const { |
| 311 return db_.get() ? db_->is_open() : false; |
| 312 } |
| 313 |
| 314 void LocalStorageDatabase::Close() { |
| 301 db_.reset(NULL); | 315 db_.reset(NULL); |
| 302 } | 316 } |
| 303 | 317 |
| 304 } // namespace dom_storage | 318 } // namespace dom_storage |
| OLD | NEW |