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

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

Issue 9159020: Create a class to represent a DOM Storage Database. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Disable the WebCore DB test to keep try bots happy while the binary test data is landed Created 8 years, 10 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
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/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(ValuesMap* result) {
46 if (!LazyOpen(false))
47 return;
48
49 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE,
50 "SELECT * from ItemTable"));
51 DCHECK(statement.is_valid());
52
53 while (statement.Step()) {
54 string16 key = statement.ColumnString16(0);
55 string16 value;
56 statement.ColumnBlobAsString16(1, &value);
57 (*result)[key] = NullableString16(value, false);
58 }
59 }
60
61 bool DomStorageDatabase::CommitChanges(bool clear_all_first,
62 const ValuesMap& changes) {
63 if (!LazyOpen(!changes.empty()))
64 return false;
65
66 sql::Transaction transaction(db_.get());
67 if (!transaction.Begin())
68 return false;
69
70 if (clear_all_first) {
71 if (!db_->Execute("DELETE FROM ItemTable"))
72 return false;
73 }
74
75 ValuesMap::const_iterator it = changes.begin();
76 for(; it != changes.end(); ++it) {
77 sql::Statement statement;
78 string16 key = it->first;
79 NullableString16 value = it->second;
80 if (value.is_null()) {
81 statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE,
82 "DELETE FROM ItemTable WHERE key=?"));
83 statement.BindString16(0, key);
84 } else {
85 statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE,
86 "INSERT INTO ItemTable VALUES (?,?)"));
87 statement.BindString16(0, key);
88 statement.BindBlob(1, value.string().data(),
89 value.string().length() * sizeof(char16));
90 }
91 DCHECK(statement.is_valid());
92 statement.Run();
93 }
94 return transaction.Commit();
95 }
96
97 bool DomStorageDatabase::LazyOpen(bool create_if_needed) {
98 if (failed_to_open_) {
99 // Don't try to open a database that we know has failed
100 // already.
101 return false;
102 }
103
104 if (IsOpen())
105 return true;
106
107 bool database_exists = file_util::PathExists(file_path_);
108
109 if (!database_exists && !create_if_needed) {
110 // If the file doesn't exist already and we haven't been asked to create
111 // a file on disk, then we don't bother opening the database. This means
112 // we wait until we absolutely need to put something onto disk before we
113 // do so.
114 return false;
115 }
116
117 db_.reset(new sql::Connection());
118 db_->set_error_delegate(GetErrorHandlerForDomStorageDatabase());
119
120 if (file_path_.empty()) {
121 // This code path should only be triggered by unit tests.
122 if (!db_->OpenInMemory()) {
123 NOTREACHED() << "Unable to open DOM storage database in memory.";
124 failed_to_open_ = true;
125 return false;
126 }
127 } else {
128 if (!db_->Open(file_path_)) {
129 LOG(ERROR) << "Unable to open DOM storage database at "
130 << file_path_.value()
131 << " error: " << db_->GetErrorMessage();
132 if (database_exists && !tried_to_recreate_)
133 return DeleteFileAndRecreate();
134 failed_to_open_ = true;
135 return false;
136 }
137 }
138
139 // sql::Connection uses UTF-8 encoding, but WebCore style databases use
140 // UTF-16, so ensure we match.
141 ignore_result(db_->Execute("PRAGMA encoding=\"UTF-16\""));
142
143 if (!database_exists) {
144 // This is a new database, create the table and we're done!
145 if (CreateTableV2())
michaeln 2012/02/08 22:12:08 Do we want to DeleteAndRecreate() in this case of
benm (inactive) 2012/02/09 11:59:57 My thinking was that it doesn't hurt to have anoth
146 return true;
147 } else {
148 // The database exists already - check if we need to upgrade
149 // and whether it's usable (i.e. not corrupted).
150 SchemaVersion current_version = DetectSchemaVersion();
151
152 if (current_version == V2) {
153 return true;
154 } else if (current_version == V1) {
155 if (UpgradeVersion1To2())
156 return true;
157 }
158 }
159
160 // This is the exceptional case - to try and recover we'll attempt
161 // to delete the file and start again.
162 Close();
163 return DeleteFileAndRecreate();
164 }
165
166 DomStorageDatabase::SchemaVersion DomStorageDatabase::DetectSchemaVersion() {
167 DCHECK(IsOpen());
168
169 // Connection::Open() may succeed even if the file we try and open is not a
170 // database, however in the case that the database is corrupted to the point
171 // that SQLite doesn't actually think it's a database,
172 // sql::Connection::GetCachedStatement will DCHECK when we later try and
173 // run statements. So we run a query here that will not DCHECK but fail
174 // on an invalid database to verify that what we've opened is usable.
175 // TODO(benm): It might be useful to actually verify the output of the
176 // quick_check too and try to recover in the case errors are detected.
177 if (db_->ExecuteAndReturnErrorCode("PRAGMA quick_check(1)") != SQLITE_OK)
michaeln 2012/02/08 22:12:08 Thnx for the explanation about statements yacking
benm (inactive) 2012/02/09 11:59:57 I just tried VACUUM and it seems to work. But I'm
178 return INVALID;
179
180 // Look at the current schema - if it doesn't look right, assume corrupt.
181 if (!db_->DoesTableExist("ItemTable") ||
182 !db_->DoesColumnExist("ItemTable", "key") ||
183 !db_->DoesColumnExist("ItemTable", "value"))
184 return INVALID;
185
186 // We must use a unique statement here as we aren't going to step it.
187 sql::Statement statement(
188 db_->GetUniqueStatement("SELECT key,value from ItemTable LIMIT 1"));
189 if (statement.DeclaredColumnType(0) != sql::COLUMN_TYPE_TEXT)
190 return INVALID;
191
192 switch (statement.DeclaredColumnType(1)) {
193 case sql::COLUMN_TYPE_BLOB:
194 return V2;
195 case sql::COLUMN_TYPE_TEXT:
196 return V1;
197 default:
198 return INVALID;
199 }
200 NOTREACHED();
201 return INVALID;
202 }
203
204 bool DomStorageDatabase::CreateTableV2() {
205 DCHECK(IsOpen());
206
207 return db_->Execute(
208 "CREATE TABLE ItemTable ("
209 "key TEXT UNIQUE ON CONFLICT REPLACE, "
210 "value BLOB NOT NULL ON CONFLICT FAIL)");
211 }
212
213 bool DomStorageDatabase::DeleteFileAndRecreate() {
214 DCHECK(!IsOpen());
215 DCHECK(file_util::PathExists(file_path_));
216
217 // We should only try and do this once.
218 if (tried_to_recreate_)
219 return false;
220
221 tried_to_recreate_ = true;
222
223 // If it's not a directory and we can delete the file, try and open it again.
michaeln 2012/02/08 22:12:08 why are we careful not to avoid deleting if the in
benm (inactive) 2012/02/09 11:59:57 I was a bit scared about accidentally deleting ent
224 if (!file_util::DirectoryExists(file_path_) &&
225 file_util::Delete(file_path_, false))
226 return LazyOpen(true);
227
228 failed_to_open_ = true;
229 return false;
230 }
231
232 bool DomStorageDatabase::UpgradeVersion1To2() {
233 DCHECK(IsOpen());
234 DCHECK(DetectSchemaVersion() == V1);
235
236 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE,
237 "SELECT * FROM ItemTable"));
238 DCHECK(statement.is_valid());
239
240 // Need to migrate from TEXT value column to BLOB.
241 // Store the current database content so we can re-insert
242 // the data into the new V2 table.
243 ValuesMap values;
244 while (statement.Step()) {
245 string16 key = statement.ColumnString16(0);
246 NullableString16 value(statement.ColumnString16(1), false);
247 values[key] = value;
248 }
249
250 sql::Transaction migration(db_.get());
251 if (!migration.Begin())
252 return false;
253
254 if (db_->Execute("DROP TABLE ItemTable")) {
255 if (CreateTableV2()) {
256 if (CommitChanges(false, values))
257 return migration.Commit();
michaeln 2012/02/08 22:12:08 style: not sure, would this be more readable? Tra
benm (inactive) 2012/02/09 11:59:57 I like it.
258 }
259 }
260 return false;
261 }
262
263 void DomStorageDatabase::Close() {
264 db_.reset(NULL);
265 }
266
267 } // namespace dom_storage
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698