Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(812)

Side by Side Diff: webkit/dom_storage/dom_storage_database.cc

Issue 15990007: Move dom_storage to new locations. (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: Created 7 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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/bind.h"
8 #include "base/file_util.h"
9 #include "base/logging.h"
10 #include "sql/statement.h"
11 #include "sql/transaction.h"
12 #include "third_party/sqlite/sqlite3.h"
13
14 namespace {
15
16 const base::FilePath::CharType kJournal[] = FILE_PATH_LITERAL("-journal");
17
18 void DatabaseErrorCallback(int error, sql::Statement* stmt) {
19 // Without a callback to ignore errors,
20 // DomStorageDatabaseTest.TestCanOpenFileThatIsNotADatabase fails with:
21 // ERROR:connection.cc(735)] sqlite error 522, errno 0: disk I/O error
22 // FATAL:connection.cc(750)] disk I/O error
23 // <backtrace>
24 // <crash>
25 //
26 // TODO(shess): If/when infrastructure lands which can allow tests
27 // to handle SQLite errors appropriately, remove this.
28 }
29
30 } // anon namespace
31
32 namespace dom_storage {
33
34 // static
35 base::FilePath DomStorageDatabase::GetJournalFilePath(
36 const base::FilePath& database_path) {
37 base::FilePath::StringType journal_file_name =
38 database_path.BaseName().value() + kJournal;
39 return database_path.DirName().Append(journal_file_name);
40 }
41
42 DomStorageDatabase::DomStorageDatabase(const base::FilePath& file_path)
43 : file_path_(file_path) {
44 // Note: in normal use we should never get an empty backing path here.
45 // However, the unit test for this class can contruct an instance
46 // with an empty path.
47 Init();
48 }
49
50 DomStorageDatabase::DomStorageDatabase() {
51 Init();
52 }
53
54 void DomStorageDatabase::Init() {
55 failed_to_open_ = false;
56 tried_to_recreate_ = false;
57 known_to_be_empty_ = false;
58 }
59
60 DomStorageDatabase::~DomStorageDatabase() {
61 if (known_to_be_empty_ && !file_path_.empty()) {
62 // Delete the db and any lingering journal file from disk.
63 Close();
64 file_util::Delete(file_path_, false);
65 file_util::Delete(GetJournalFilePath(file_path_), false);
66 }
67 }
68
69 void DomStorageDatabase::ReadAllValues(ValuesMap* result) {
70 if (!LazyOpen(false))
71 return;
72
73 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE,
74 "SELECT * from ItemTable"));
75 DCHECK(statement.is_valid());
76
77 while (statement.Step()) {
78 base::string16 key = statement.ColumnString16(0);
79 base::string16 value;
80 statement.ColumnBlobAsString16(1, &value);
81 (*result)[key] = NullableString16(value, false);
82 }
83 known_to_be_empty_ = result->empty();
84 }
85
86 bool DomStorageDatabase::CommitChanges(bool clear_all_first,
87 const ValuesMap& changes) {
88 if (!LazyOpen(!changes.empty())) {
89 // If we're being asked to commit changes that will result in an
90 // empty database, we return true if the database file doesn't exist.
91 return clear_all_first && changes.empty() &&
92 !file_util::PathExists(file_path_);
93 }
94
95 bool old_known_to_be_empty = known_to_be_empty_;
96 sql::Transaction transaction(db_.get());
97 if (!transaction.Begin())
98 return false;
99
100 if (clear_all_first) {
101 if (!db_->Execute("DELETE FROM ItemTable"))
102 return false;
103 known_to_be_empty_ = true;
104 }
105
106 bool did_delete = false;
107 bool did_insert = false;
108 ValuesMap::const_iterator it = changes.begin();
109 for(; it != changes.end(); ++it) {
110 sql::Statement statement;
111 base::string16 key = it->first;
112 NullableString16 value = it->second;
113 if (value.is_null()) {
114 statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE,
115 "DELETE FROM ItemTable WHERE key=?"));
116 statement.BindString16(0, key);
117 did_delete = true;
118 } else {
119 statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE,
120 "INSERT INTO ItemTable VALUES (?,?)"));
121 statement.BindString16(0, key);
122 statement.BindBlob(1, value.string().data(),
123 value.string().length() * sizeof(char16));
124 known_to_be_empty_ = false;
125 did_insert = true;
126 }
127 DCHECK(statement.is_valid());
128 statement.Run();
129 }
130
131 if (!known_to_be_empty_ && did_delete && !did_insert) {
132 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE,
133 "SELECT count(key) from ItemTable"));
134 if (statement.Step())
135 known_to_be_empty_ = statement.ColumnInt(0) == 0;
136 }
137
138 bool success = transaction.Commit();
139 if (!success)
140 known_to_be_empty_ = old_known_to_be_empty;
141 return success;
142 }
143
144 bool DomStorageDatabase::LazyOpen(bool create_if_needed) {
145 if (failed_to_open_) {
146 // Don't try to open a database that we know has failed
147 // already.
148 return false;
149 }
150
151 if (IsOpen())
152 return true;
153
154 bool database_exists = file_util::PathExists(file_path_);
155
156 if (!database_exists && !create_if_needed) {
157 // If the file doesn't exist already and we haven't been asked to create
158 // a file on disk, then we don't bother opening the database. This means
159 // we wait until we absolutely need to put something onto disk before we
160 // do so.
161 return false;
162 }
163
164 db_.reset(new sql::Connection());
165 db_->set_histogram_tag("DomStorageDatabase");
166 db_->set_error_callback(base::Bind(&DatabaseErrorCallback));
167
168 if (file_path_.empty()) {
169 // This code path should only be triggered by unit tests.
170 if (!db_->OpenInMemory()) {
171 NOTREACHED() << "Unable to open DOM storage database in memory.";
172 failed_to_open_ = true;
173 return false;
174 }
175 } else {
176 if (!db_->Open(file_path_)) {
177 LOG(ERROR) << "Unable to open DOM storage database at "
178 << file_path_.value()
179 << " error: " << db_->GetErrorMessage();
180 if (database_exists && !tried_to_recreate_)
181 return DeleteFileAndRecreate();
182 failed_to_open_ = true;
183 return false;
184 }
185 }
186
187 // sql::Connection uses UTF-8 encoding, but WebCore style databases use
188 // UTF-16, so ensure we match.
189 ignore_result(db_->Execute("PRAGMA encoding=\"UTF-16\""));
190
191 if (!database_exists) {
192 // This is a new database, create the table and we're done!
193 if (CreateTableV2())
194 return true;
195 } else {
196 // The database exists already - check if we need to upgrade
197 // and whether it's usable (i.e. not corrupted).
198 SchemaVersion current_version = DetectSchemaVersion();
199
200 if (current_version == V2) {
201 return true;
202 } else if (current_version == V1) {
203 if (UpgradeVersion1To2())
204 return true;
205 }
206 }
207
208 // This is the exceptional case - to try and recover we'll attempt
209 // to delete the file and start again.
210 Close();
211 return DeleteFileAndRecreate();
212 }
213
214 DomStorageDatabase::SchemaVersion DomStorageDatabase::DetectSchemaVersion() {
215 DCHECK(IsOpen());
216
217 // Connection::Open() may succeed even if the file we try and open is not a
218 // database, however in the case that the database is corrupted to the point
219 // that SQLite doesn't actually think it's a database,
220 // sql::Connection::GetCachedStatement will DCHECK when we later try and
221 // run statements. So we run a query here that will not DCHECK but fail
222 // on an invalid database to verify that what we've opened is usable.
223 if (db_->ExecuteAndReturnErrorCode("PRAGMA auto_vacuum") != SQLITE_OK)
224 return INVALID;
225
226 // Look at the current schema - if it doesn't look right, assume corrupt.
227 if (!db_->DoesTableExist("ItemTable") ||
228 !db_->DoesColumnExist("ItemTable", "key") ||
229 !db_->DoesColumnExist("ItemTable", "value"))
230 return INVALID;
231
232 // We must use a unique statement here as we aren't going to step it.
233 sql::Statement statement(
234 db_->GetUniqueStatement("SELECT key,value from ItemTable LIMIT 1"));
235 if (statement.DeclaredColumnType(0) != sql::COLUMN_TYPE_TEXT)
236 return INVALID;
237
238 switch (statement.DeclaredColumnType(1)) {
239 case sql::COLUMN_TYPE_BLOB:
240 return V2;
241 case sql::COLUMN_TYPE_TEXT:
242 return V1;
243 default:
244 return INVALID;
245 }
246 NOTREACHED();
247 return INVALID;
248 }
249
250 bool DomStorageDatabase::CreateTableV2() {
251 DCHECK(IsOpen());
252
253 return db_->Execute(
254 "CREATE TABLE ItemTable ("
255 "key TEXT UNIQUE ON CONFLICT REPLACE, "
256 "value BLOB NOT NULL ON CONFLICT FAIL)");
257 }
258
259 bool DomStorageDatabase::DeleteFileAndRecreate() {
260 DCHECK(!IsOpen());
261 DCHECK(file_util::PathExists(file_path_));
262
263 // We should only try and do this once.
264 if (tried_to_recreate_)
265 return false;
266
267 tried_to_recreate_ = true;
268
269 // If it's not a directory and we can delete the file, try and open it again.
270 if (!file_util::DirectoryExists(file_path_) &&
271 file_util::Delete(file_path_, false))
272 return LazyOpen(true);
273
274 failed_to_open_ = true;
275 return false;
276 }
277
278 bool DomStorageDatabase::UpgradeVersion1To2() {
279 DCHECK(IsOpen());
280 DCHECK(DetectSchemaVersion() == V1);
281
282 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE,
283 "SELECT * FROM ItemTable"));
284 DCHECK(statement.is_valid());
285
286 // Need to migrate from TEXT value column to BLOB.
287 // Store the current database content so we can re-insert
288 // the data into the new V2 table.
289 ValuesMap values;
290 while (statement.Step()) {
291 base::string16 key = statement.ColumnString16(0);
292 NullableString16 value(statement.ColumnString16(1), false);
293 values[key] = value;
294 }
295
296 sql::Transaction migration(db_.get());
297 return migration.Begin() &&
298 db_->Execute("DROP TABLE ItemTable") &&
299 CreateTableV2() &&
300 CommitChanges(false, values) &&
301 migration.Commit();
302 }
303
304 void DomStorageDatabase::Close() {
305 db_.reset(NULL);
306 }
307
308 } // namespace dom_storage
OLDNEW
« no previous file with comments | « webkit/dom_storage/dom_storage_database.h ('k') | webkit/dom_storage/dom_storage_database_adapter.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698