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

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: Addressed a couple of things I noticed :) 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(
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698