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/browser/dom_storage/dom_storage_database.h" | 5 #include "content/browser/dom_storage/dom_storage_database.h" |
6 | 6 |
7 #include "base/bind.h" | 7 #include "base/bind.h" |
8 #include "base/file_util.h" | 8 #include "base/file_util.h" |
9 #include "base/logging.h" | 9 #include "base/logging.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 base::FilePath::CharType kJournal[] = FILE_PATH_LITERAL("-journal"); | 16 const base::FilePath::CharType kJournal[] = FILE_PATH_LITERAL("-journal"); |
17 | 17 |
18 } // anon namespace | 18 } // anon namespace |
19 | 19 |
20 namespace dom_storage { | 20 namespace content { |
21 | 21 |
22 // static | 22 // static |
23 base::FilePath DomStorageDatabase::GetJournalFilePath( | 23 base::FilePath DOMStorageDatabase::GetJournalFilePath( |
24 const base::FilePath& database_path) { | 24 const base::FilePath& database_path) { |
25 base::FilePath::StringType journal_file_name = | 25 base::FilePath::StringType journal_file_name = |
26 database_path.BaseName().value() + kJournal; | 26 database_path.BaseName().value() + kJournal; |
27 return database_path.DirName().Append(journal_file_name); | 27 return database_path.DirName().Append(journal_file_name); |
28 } | 28 } |
29 | 29 |
30 DomStorageDatabase::DomStorageDatabase(const base::FilePath& file_path) | 30 DOMStorageDatabase::DOMStorageDatabase(const base::FilePath& file_path) |
31 : file_path_(file_path) { | 31 : file_path_(file_path) { |
32 // Note: in normal use we should never get an empty backing path here. | 32 // Note: in normal use we should never get an empty backing path here. |
33 // However, the unit test for this class can contruct an instance | 33 // However, the unit test for this class can contruct an instance |
34 // with an empty path. | 34 // with an empty path. |
35 Init(); | 35 Init(); |
36 } | 36 } |
37 | 37 |
38 DomStorageDatabase::DomStorageDatabase() { | 38 DOMStorageDatabase::DOMStorageDatabase() { |
39 Init(); | 39 Init(); |
40 } | 40 } |
41 | 41 |
42 void DomStorageDatabase::Init() { | 42 void DOMStorageDatabase::Init() { |
43 failed_to_open_ = false; | 43 failed_to_open_ = false; |
44 tried_to_recreate_ = false; | 44 tried_to_recreate_ = false; |
45 known_to_be_empty_ = false; | 45 known_to_be_empty_ = false; |
46 } | 46 } |
47 | 47 |
48 DomStorageDatabase::~DomStorageDatabase() { | 48 DOMStorageDatabase::~DOMStorageDatabase() { |
49 if (known_to_be_empty_ && !file_path_.empty()) { | 49 if (known_to_be_empty_ && !file_path_.empty()) { |
50 // Delete the db and any lingering journal file from disk. | 50 // Delete the db and any lingering journal file from disk. |
51 Close(); | 51 Close(); |
52 sql::Connection::Delete(file_path_); | 52 sql::Connection::Delete(file_path_); |
53 } | 53 } |
54 } | 54 } |
55 | 55 |
56 void DomStorageDatabase::ReadAllValues(ValuesMap* result) { | 56 void DOMStorageDatabase::ReadAllValues(DOMStorageValuesMap* result) { |
57 if (!LazyOpen(false)) | 57 if (!LazyOpen(false)) |
58 return; | 58 return; |
59 | 59 |
60 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, | 60 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, |
61 "SELECT * from ItemTable")); | 61 "SELECT * from ItemTable")); |
62 DCHECK(statement.is_valid()); | 62 DCHECK(statement.is_valid()); |
63 | 63 |
64 while (statement.Step()) { | 64 while (statement.Step()) { |
65 base::string16 key = statement.ColumnString16(0); | 65 base::string16 key = statement.ColumnString16(0); |
66 base::string16 value; | 66 base::string16 value; |
67 statement.ColumnBlobAsString16(1, &value); | 67 statement.ColumnBlobAsString16(1, &value); |
68 (*result)[key] = base::NullableString16(value, false); | 68 (*result)[key] = base::NullableString16(value, false); |
69 } | 69 } |
70 known_to_be_empty_ = result->empty(); | 70 known_to_be_empty_ = result->empty(); |
71 } | 71 } |
72 | 72 |
73 bool DomStorageDatabase::CommitChanges(bool clear_all_first, | 73 bool DOMStorageDatabase::CommitChanges(bool clear_all_first, |
74 const ValuesMap& changes) { | 74 const DOMStorageValuesMap& changes) { |
75 if (!LazyOpen(!changes.empty())) { | 75 if (!LazyOpen(!changes.empty())) { |
76 // If we're being asked to commit changes that will result in an | 76 // If we're being asked to commit changes that will result in an |
77 // empty database, we return true if the database file doesn't exist. | 77 // empty database, we return true if the database file doesn't exist. |
78 return clear_all_first && changes.empty() && | 78 return clear_all_first && changes.empty() && |
79 !base::PathExists(file_path_); | 79 !base::PathExists(file_path_); |
80 } | 80 } |
81 | 81 |
82 bool old_known_to_be_empty = known_to_be_empty_; | 82 bool old_known_to_be_empty = known_to_be_empty_; |
83 sql::Transaction transaction(db_.get()); | 83 sql::Transaction transaction(db_.get()); |
84 if (!transaction.Begin()) | 84 if (!transaction.Begin()) |
85 return false; | 85 return false; |
86 | 86 |
87 if (clear_all_first) { | 87 if (clear_all_first) { |
88 if (!db_->Execute("DELETE FROM ItemTable")) | 88 if (!db_->Execute("DELETE FROM ItemTable")) |
89 return false; | 89 return false; |
90 known_to_be_empty_ = true; | 90 known_to_be_empty_ = true; |
91 } | 91 } |
92 | 92 |
93 bool did_delete = false; | 93 bool did_delete = false; |
94 bool did_insert = false; | 94 bool did_insert = false; |
95 ValuesMap::const_iterator it = changes.begin(); | 95 DOMStorageValuesMap::const_iterator it = changes.begin(); |
96 for(; it != changes.end(); ++it) { | 96 for(; it != changes.end(); ++it) { |
97 sql::Statement statement; | 97 sql::Statement statement; |
98 base::string16 key = it->first; | 98 base::string16 key = it->first; |
99 base::NullableString16 value = it->second; | 99 base::NullableString16 value = it->second; |
100 if (value.is_null()) { | 100 if (value.is_null()) { |
101 statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, | 101 statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, |
102 "DELETE FROM ItemTable WHERE key=?")); | 102 "DELETE FROM ItemTable WHERE key=?")); |
103 statement.BindString16(0, key); | 103 statement.BindString16(0, key); |
104 did_delete = true; | 104 did_delete = true; |
105 } else { | 105 } else { |
(...skipping 15 matching lines...) Expand all Loading... |
121 if (statement.Step()) | 121 if (statement.Step()) |
122 known_to_be_empty_ = statement.ColumnInt(0) == 0; | 122 known_to_be_empty_ = statement.ColumnInt(0) == 0; |
123 } | 123 } |
124 | 124 |
125 bool success = transaction.Commit(); | 125 bool success = transaction.Commit(); |
126 if (!success) | 126 if (!success) |
127 known_to_be_empty_ = old_known_to_be_empty; | 127 known_to_be_empty_ = old_known_to_be_empty; |
128 return success; | 128 return success; |
129 } | 129 } |
130 | 130 |
131 bool DomStorageDatabase::LazyOpen(bool create_if_needed) { | 131 bool DOMStorageDatabase::LazyOpen(bool create_if_needed) { |
132 if (failed_to_open_) { | 132 if (failed_to_open_) { |
133 // Don't try to open a database that we know has failed | 133 // Don't try to open a database that we know has failed |
134 // already. | 134 // already. |
135 return false; | 135 return false; |
136 } | 136 } |
137 | 137 |
138 if (IsOpen()) | 138 if (IsOpen()) |
139 return true; | 139 return true; |
140 | 140 |
141 bool database_exists = base::PathExists(file_path_); | 141 bool database_exists = base::PathExists(file_path_); |
142 | 142 |
143 if (!database_exists && !create_if_needed) { | 143 if (!database_exists && !create_if_needed) { |
144 // If the file doesn't exist already and we haven't been asked to create | 144 // If the file doesn't exist already and we haven't been asked to create |
145 // a file on disk, then we don't bother opening the database. This means | 145 // a file on disk, then we don't bother opening the database. This means |
146 // we wait until we absolutely need to put something onto disk before we | 146 // we wait until we absolutely need to put something onto disk before we |
147 // do so. | 147 // do so. |
148 return false; | 148 return false; |
149 } | 149 } |
150 | 150 |
151 db_.reset(new sql::Connection()); | 151 db_.reset(new sql::Connection()); |
152 db_->set_histogram_tag("DomStorageDatabase"); | 152 db_->set_histogram_tag("DOMStorageDatabase"); |
153 | 153 |
154 if (file_path_.empty()) { | 154 if (file_path_.empty()) { |
155 // This code path should only be triggered by unit tests. | 155 // This code path should only be triggered by unit tests. |
156 if (!db_->OpenInMemory()) { | 156 if (!db_->OpenInMemory()) { |
157 NOTREACHED() << "Unable to open DOM storage database in memory."; | 157 NOTREACHED() << "Unable to open DOM storage database in memory."; |
158 failed_to_open_ = true; | 158 failed_to_open_ = true; |
159 return false; | 159 return false; |
160 } | 160 } |
161 } else { | 161 } else { |
162 if (!db_->Open(file_path_)) { | 162 if (!db_->Open(file_path_)) { |
(...skipping 27 matching lines...) Expand all Loading... |
190 return true; | 190 return true; |
191 } | 191 } |
192 } | 192 } |
193 | 193 |
194 // This is the exceptional case - to try and recover we'll attempt | 194 // This is the exceptional case - to try and recover we'll attempt |
195 // to delete the file and start again. | 195 // to delete the file and start again. |
196 Close(); | 196 Close(); |
197 return DeleteFileAndRecreate(); | 197 return DeleteFileAndRecreate(); |
198 } | 198 } |
199 | 199 |
200 DomStorageDatabase::SchemaVersion DomStorageDatabase::DetectSchemaVersion() { | 200 DOMStorageDatabase::SchemaVersion DOMStorageDatabase::DetectSchemaVersion() { |
201 DCHECK(IsOpen()); | 201 DCHECK(IsOpen()); |
202 | 202 |
203 // Connection::Open() may succeed even if the file we try and open is not a | 203 // Connection::Open() may succeed even if the file we try and open is not a |
204 // database, however in the case that the database is corrupted to the point | 204 // database, however in the case that the database is corrupted to the point |
205 // that SQLite doesn't actually think it's a database, | 205 // that SQLite doesn't actually think it's a database, |
206 // sql::Connection::GetCachedStatement will DCHECK when we later try and | 206 // sql::Connection::GetCachedStatement will DCHECK when we later try and |
207 // run statements. So we run a query here that will not DCHECK but fail | 207 // run statements. So we run a query here that will not DCHECK but fail |
208 // on an invalid database to verify that what we've opened is usable. | 208 // on an invalid database to verify that what we've opened is usable. |
209 if (db_->ExecuteAndReturnErrorCode("PRAGMA auto_vacuum") != SQLITE_OK) | 209 if (db_->ExecuteAndReturnErrorCode("PRAGMA auto_vacuum") != SQLITE_OK) |
210 return INVALID; | 210 return INVALID; |
(...skipping 15 matching lines...) Expand all Loading... |
226 return V2; | 226 return V2; |
227 case sql::COLUMN_TYPE_TEXT: | 227 case sql::COLUMN_TYPE_TEXT: |
228 return V1; | 228 return V1; |
229 default: | 229 default: |
230 return INVALID; | 230 return INVALID; |
231 } | 231 } |
232 NOTREACHED(); | 232 NOTREACHED(); |
233 return INVALID; | 233 return INVALID; |
234 } | 234 } |
235 | 235 |
236 bool DomStorageDatabase::CreateTableV2() { | 236 bool DOMStorageDatabase::CreateTableV2() { |
237 DCHECK(IsOpen()); | 237 DCHECK(IsOpen()); |
238 | 238 |
239 return db_->Execute( | 239 return db_->Execute( |
240 "CREATE TABLE ItemTable (" | 240 "CREATE TABLE ItemTable (" |
241 "key TEXT UNIQUE ON CONFLICT REPLACE, " | 241 "key TEXT UNIQUE ON CONFLICT REPLACE, " |
242 "value BLOB NOT NULL ON CONFLICT FAIL)"); | 242 "value BLOB NOT NULL ON CONFLICT FAIL)"); |
243 } | 243 } |
244 | 244 |
245 bool DomStorageDatabase::DeleteFileAndRecreate() { | 245 bool DOMStorageDatabase::DeleteFileAndRecreate() { |
246 DCHECK(!IsOpen()); | 246 DCHECK(!IsOpen()); |
247 DCHECK(base::PathExists(file_path_)); | 247 DCHECK(base::PathExists(file_path_)); |
248 | 248 |
249 // We should only try and do this once. | 249 // We should only try and do this once. |
250 if (tried_to_recreate_) | 250 if (tried_to_recreate_) |
251 return false; | 251 return false; |
252 | 252 |
253 tried_to_recreate_ = true; | 253 tried_to_recreate_ = true; |
254 | 254 |
255 // If it's not a directory and we can delete the file, try and open it again. | 255 // If it's not a directory and we can delete the file, try and open it again. |
256 if (!base::DirectoryExists(file_path_) && | 256 if (!base::DirectoryExists(file_path_) && |
257 sql::Connection::Delete(file_path_)) { | 257 sql::Connection::Delete(file_path_)) { |
258 return LazyOpen(true); | 258 return LazyOpen(true); |
259 } | 259 } |
260 | 260 |
261 failed_to_open_ = true; | 261 failed_to_open_ = true; |
262 return false; | 262 return false; |
263 } | 263 } |
264 | 264 |
265 bool DomStorageDatabase::UpgradeVersion1To2() { | 265 bool DOMStorageDatabase::UpgradeVersion1To2() { |
266 DCHECK(IsOpen()); | 266 DCHECK(IsOpen()); |
267 DCHECK(DetectSchemaVersion() == V1); | 267 DCHECK(DetectSchemaVersion() == V1); |
268 | 268 |
269 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, | 269 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, |
270 "SELECT * FROM ItemTable")); | 270 "SELECT * FROM ItemTable")); |
271 DCHECK(statement.is_valid()); | 271 DCHECK(statement.is_valid()); |
272 | 272 |
273 // Need to migrate from TEXT value column to BLOB. | 273 // Need to migrate from TEXT value column to BLOB. |
274 // Store the current database content so we can re-insert | 274 // Store the current database content so we can re-insert |
275 // the data into the new V2 table. | 275 // the data into the new V2 table. |
276 ValuesMap values; | 276 DOMStorageValuesMap values; |
277 while (statement.Step()) { | 277 while (statement.Step()) { |
278 base::string16 key = statement.ColumnString16(0); | 278 base::string16 key = statement.ColumnString16(0); |
279 base::NullableString16 value(statement.ColumnString16(1), false); | 279 base::NullableString16 value(statement.ColumnString16(1), false); |
280 values[key] = value; | 280 values[key] = value; |
281 } | 281 } |
282 | 282 |
283 sql::Transaction migration(db_.get()); | 283 sql::Transaction migration(db_.get()); |
284 return migration.Begin() && | 284 return migration.Begin() && |
285 db_->Execute("DROP TABLE ItemTable") && | 285 db_->Execute("DROP TABLE ItemTable") && |
286 CreateTableV2() && | 286 CreateTableV2() && |
287 CommitChanges(false, values) && | 287 CommitChanges(false, values) && |
288 migration.Commit(); | 288 migration.Commit(); |
289 } | 289 } |
290 | 290 |
291 void DomStorageDatabase::Close() { | 291 void DOMStorageDatabase::Close() { |
292 db_.reset(NULL); | 292 db_.reset(NULL); |
293 } | 293 } |
294 | 294 |
295 } // namespace dom_storage | 295 } // namespace content |
OLD | NEW |