OLD | NEW |
(Empty) | |
| 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 |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "webkit/dom_storage/dom_storage_database.h" |
| 6 |
| 7 #include "base/file_util.h" |
| 8 #include "base/logging.h" |
| 9 #include "sql/diagnostic_error_delegate.h" |
| 10 #include "sql/statement.h" |
| 11 #include "sql/transaction.h" |
| 12 #include "third_party/sqlite/sqlite3.h" |
| 13 |
| 14 namespace { |
| 15 |
| 16 class HistogramUniquifier { |
| 17 public: |
| 18 static const char* name() { return "Sqlite.DomStorageDatabase.Error"; } |
| 19 }; |
| 20 |
| 21 sql::ErrorDelegate* GetErrorHandlerForDomStorageDatabase() { |
| 22 return new sql::DiagnosticErrorDelegate<HistogramUniquifier>(); |
| 23 } |
| 24 |
| 25 } // anon namespace |
| 26 |
| 27 namespace dom_storage { |
| 28 |
| 29 DomStorageDatabase::DomStorageDatabase(const FilePath& file_path) |
| 30 : file_path_(file_path), |
| 31 db_(NULL), |
| 32 failed_to_open_(false), |
| 33 tried_to_recreate_(false) { |
| 34 // Note: in normal use we should never get an empty backing path here. |
| 35 // However, the unit test for this class defines another constructor |
| 36 // that will bypass this check to allow an empty path that signifies |
| 37 // we should operate on an in-memory database for performance/reliability |
| 38 // reasons. |
| 39 DCHECK(!file_path_.empty()); |
| 40 } |
| 41 |
| 42 DomStorageDatabase::~DomStorageDatabase() { |
| 43 } |
| 44 |
| 45 void DomStorageDatabase::ReadAllValues( |
| 46 DomStorageDatabase::ValuesMap* result) { |
| 47 if (!LazyOpen(false)) |
| 48 return; |
| 49 |
| 50 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, |
| 51 "SELECT * from ItemTable")); |
| 52 DCHECK(statement.is_valid()); |
| 53 |
| 54 while (statement.Step()) { |
| 55 string16 key = statement.ColumnString16(0); |
| 56 string16 value; |
| 57 statement.ColumnBlobAsString16(1, &value); |
| 58 (*result)[key] = NullableString16(value, false); |
| 59 } |
| 60 } |
| 61 |
| 62 bool DomStorageDatabase::CommitChanges(bool clear_all_first, |
| 63 const ValuesMap& changes) { |
| 64 if (!LazyOpen(!changes.empty())) |
| 65 return false; |
| 66 |
| 67 sql::Transaction transaction(db_.get()); |
| 68 if (!transaction.Begin()) |
| 69 return false; |
| 70 |
| 71 if (clear_all_first) { |
| 72 if (!db_->Execute("DELETE FROM ItemTable")) |
| 73 return false; |
| 74 } |
| 75 |
| 76 ValuesMap::const_iterator it = changes.begin(); |
| 77 for(; it != changes.end(); ++it) { |
| 78 sql::Statement statement; |
| 79 string16 key = it->first; |
| 80 NullableString16 value = it->second; |
| 81 if (value.is_null()) { |
| 82 statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, |
| 83 "DELETE FROM ItemTable WHERE key=?")); |
| 84 statement.BindString16(0, key); |
| 85 } else { |
| 86 statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, |
| 87 "INSERT INTO ItemTable VALUES (?,?)")); |
| 88 statement.BindString16(0, key); |
| 89 statement.BindBlob(1, value.string().data(), |
| 90 value.string().length() * sizeof(char16)); |
| 91 } |
| 92 DCHECK(statement.is_valid()); |
| 93 statement.Run(); |
| 94 } |
| 95 return transaction.Commit(); |
| 96 } |
| 97 |
| 98 bool DomStorageDatabase::LazyOpen(bool create_if_needed) { |
| 99 if (failed_to_open_) { |
| 100 // Don't try to open a database that we know has failed |
| 101 // already. |
| 102 return false; |
| 103 } |
| 104 |
| 105 if (IsOpen()) |
| 106 return true; |
| 107 |
| 108 bool database_exists = file_util::PathExists(file_path_); |
| 109 |
| 110 if (!database_exists && !create_if_needed) { |
| 111 // If the file doesn't exist already and we haven't been asked to create |
| 112 // a file on disk, then we don't bother opening the database. This means |
| 113 // we wait until we absolutely need to put something onto disk before we |
| 114 // do so. |
| 115 return false; |
| 116 } |
| 117 |
| 118 db_.reset(new sql::Connection()); |
| 119 db_->set_error_delegate(GetErrorHandlerForDomStorageDatabase()); |
| 120 |
| 121 if (file_path_.empty()) { |
| 122 // This code path should only be triggered by unit tests. |
| 123 if (!db_->OpenInMemory()) { |
| 124 LOG(ERROR) << "Unable to open DOM storage database in memory."; |
| 125 failed_to_open_ = true; |
| 126 return false; |
| 127 } |
| 128 } else { |
| 129 if (!db_->Open(file_path_)) { |
| 130 LOG(ERROR) << "Unable to open DOM storage database at " |
| 131 << file_path_.value() |
| 132 << " error: " << db_->GetErrorMessage(); |
| 133 if (database_exists && !tried_to_recreate_) |
| 134 return DeleteFileAndRecreate(); |
| 135 failed_to_open_ = true; |
| 136 return false; |
| 137 } |
| 138 } |
| 139 |
| 140 // Open() may succeed even if the file we try and open is not a database so |
| 141 // we check here that what we've got is something sensible. |
| 142 // TODO(benm): It might be useful to actually verify the output of the |
| 143 // quick_check too and try to recover in the case errors are detected. |
| 144 // However, in the case that the database is corrupted to the point that |
| 145 // SQLite doesn't actually think it's a database, |
| 146 // sql::Connection::GetCachedStatement will DCHECK. |
| 147 if (db_->ExecuteAndReturnErrorCode("PRAGMA quick_check(1)") != SQLITE_OK) { |
| 148 Close(); |
| 149 return DeleteFileAndRecreate(); |
| 150 } |
| 151 |
| 152 // sql::Connection uses UTF-8 encoding, but WebCore style databases use |
| 153 // UTF-16, so ensure we match. |
| 154 ignore_result(db_->Execute("PRAGMA encoding=\"UTF-16\"")); |
| 155 |
| 156 // If the table doesn't exist, try to create it at the current version. |
| 157 if (!db_->DoesTableExist("ItemTable")) { |
| 158 if (CreateTable()) |
| 159 return true; |
| 160 // Couldn't create the table. |
| 161 Close(); |
| 162 return DeleteFileAndRecreate(); |
| 163 } |
| 164 |
| 165 // Table exists, so ensure we're at the right version, upgrading if |
| 166 // necessary. |
| 167 if (UpgradeVersion1To2IfNeeded()) |
| 168 return true; |
| 169 |
| 170 // We were not able to verify that the database is at the correct version |
| 171 // so close the connection and attempt to recreate the database from |
| 172 // scratch. |
| 173 Close(); |
| 174 return DeleteFileAndRecreate(); |
| 175 } |
| 176 |
| 177 bool DomStorageDatabase::CreateTable() { |
| 178 DCHECK(IsOpen()); |
| 179 // Current version is 2. |
| 180 return CreateTableV2(); |
| 181 } |
| 182 |
| 183 bool DomStorageDatabase::CreateTableV2() { |
| 184 DCHECK(IsOpen()); |
| 185 |
| 186 return db_->Execute( |
| 187 "CREATE TABLE IF NOT EXISTS ItemTable (" |
| 188 "key TEXT UNIQUE ON CONFLICT REPLACE, " |
| 189 "value BLOB NOT NULL ON CONFLICT FAIL)"); |
| 190 } |
| 191 |
| 192 bool DomStorageDatabase::DeleteFileAndRecreate() { |
| 193 DCHECK(!IsOpen()); |
| 194 DCHECK(file_util::PathExists(file_path_)); |
| 195 |
| 196 // We should only try and do this once. |
| 197 if (tried_to_recreate_) |
| 198 return false; |
| 199 |
| 200 tried_to_recreate_ = true; |
| 201 |
| 202 // If it's not a directory and we can delete the file, try and open it again. |
| 203 if (!file_util::DirectoryExists(file_path_) && |
| 204 file_util::Delete(file_path_, false)) |
| 205 return LazyOpen(true); |
| 206 |
| 207 failed_to_open_ = true; |
| 208 return false; |
| 209 } |
| 210 |
| 211 bool DomStorageDatabase::UpgradeVersion1To2IfNeeded() { |
| 212 DCHECK(IsOpen()); |
| 213 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, |
| 214 "SELECT * FROM ItemTable")); |
| 215 DCHECK(statement.is_valid()); |
| 216 |
| 217 // Quick check to see if we need to upgrade or not. The single |
| 218 // effect of V1 -> V2 is to change the value column from type |
| 219 // TEXT to type BLOB. |
| 220 sql::ColType value_column_type = statement.DeclaredColumnType(1); |
| 221 if (value_column_type == sql::COLUMN_TYPE_BLOB) |
| 222 return true; |
| 223 |
| 224 if (value_column_type != sql::COLUMN_TYPE_TEXT) { |
| 225 // Something is messed up. This is not a V1 database. |
| 226 return false; |
| 227 } |
| 228 |
| 229 // Need to migrate from TEXT value column to BLOB. |
| 230 // Store the current database content so we can re-insert |
| 231 // the data into the new V2 table. |
| 232 ValuesMap values; |
| 233 while (statement.Step()) { |
| 234 string16 key = statement.ColumnString16(0); |
| 235 NullableString16 value(statement.ColumnString16(1), false); |
| 236 values[key] = value; |
| 237 } |
| 238 |
| 239 sql::Transaction migration(db_.get()); |
| 240 if (!migration.Begin()) |
| 241 return false; |
| 242 |
| 243 if (db_->Execute("DROP TABLE ItemTable")) { |
| 244 CreateTableV2(); |
| 245 if (CommitChanges(false, values)) |
| 246 return migration.Commit(); |
| 247 } |
| 248 return false; |
| 249 } |
| 250 |
| 251 void DomStorageDatabase::Close() { |
| 252 db_.reset(NULL); |
| 253 } |
| 254 |
| 255 } // namespace dom_storage |
OLD | NEW |