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 DCHECK(!file_path_.empty()); | |
33 } | |
34 | |
35 DomStorageDatabase::~DomStorageDatabase() { | |
36 } | |
37 | |
38 void DomStorageDatabase::ReadAllValues( | |
39 DomStorageDatabase::ValuesMap* result) { | |
40 if (!LazyOpen(false)) | |
41 return; | |
42 | |
43 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, | |
44 "SELECT * from ItemTable")); | |
45 DCHECK(statement.is_valid()); | |
46 | |
47 while (statement.Step()) { | |
48 string16 key = statement.ColumnString16(0); | |
49 string16 value; | |
50 statement.ColumnBlobAsString16(1, &value); | |
51 (*result)[key] = NullableString16(value, false); | |
52 } | |
53 } | |
54 | |
55 bool DomStorageDatabase::CommitChanges(bool clear_all_first, | |
56 const ValuesMap& changes) { | |
57 if (!LazyOpen(!changes.empty())) | |
58 return false; | |
59 | |
60 sql::Transaction transaction(db_.get()); | |
61 if (!transaction.Begin()) | |
62 return false; | |
63 | |
64 if (clear_all_first) { | |
65 if (!db_->Execute("DELETE FROM ItemTable")) | |
66 return false; | |
67 } | |
68 | |
69 ValuesMap::const_iterator it = changes.begin(); | |
70 for(; it != changes.end(); ++it) { | |
71 sql::Statement statement; | |
72 string16 key = it->first; | |
73 NullableString16 value = it->second; | |
74 if (value.is_null()) { | |
75 statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, | |
76 "DELETE FROM ItemTable WHERE key=?")); | |
77 statement.BindString16(0, key); | |
78 } else { | |
79 statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, | |
80 "INSERT INTO ItemTable VALUES (?,?)")); | |
81 statement.BindString16(0, key); | |
82 statement.BindBlob(1, value.string().data(), | |
83 value.string().length() * sizeof(char16)); | |
84 } | |
85 DCHECK(statement.is_valid()); | |
86 statement.Run(); | |
87 } | |
88 return transaction.Commit(); | |
89 } | |
90 | |
91 bool DomStorageDatabase::LazyOpen(bool create_if_needed) { | |
92 if (IsOpen()) | |
93 return true; | |
94 | |
95 bool database_exists = file_util::PathExists(file_path_); | |
96 | |
97 if (!database_exists && !create_if_needed) { | |
98 // If the file doesn't exist already and we haven't been asked to create | |
99 // a file on disk, then we don't bother opening the database. This means | |
100 // we wait until we absolutely need to put something onto disk before we | |
101 // do so. | |
102 return false; | |
103 } | |
104 | |
105 db_.reset(new sql::Connection()); | |
106 db_->set_error_delegate(GetErrorHandlerForDomStorageDatabase()); | |
107 if (!db_->Open(file_path_)) { | |
108 // TODO(benm): We should handle the error condition better. At the very | |
109 // least we should set a flag so that we don't try to repeatedly open | |
110 // the database each time LazyOpen is called. We may want to try and | |
111 // delete the file and start from scratch. | |
112 LOG(ERROR) << "Unable to open DOM storage database at " | |
113 << file_path_.value() | |
114 << " error: " << db_->GetErrorMessage(); | |
115 return false; | |
116 } | |
117 | |
118 // Open() may succeed even if the file we try and open is not a database so | |
119 // we check here that what we've got is something sensible. | |
120 // TODO(benm): It might be useful to actually verify the output of the | |
121 // quick_check too and try to recover in the case errors are detected. | |
122 // However, in the case that the database is corrupted to the point that | |
123 // SQLite doesn't actually think it's a database, | |
124 // sql::Connection::GetCachedStatement will DCHECK. | |
125 if (db_->ExecuteAndReturnErrorCode("PRAGMA quick_check(1)") != SQLITE_OK) { | |
michaeln
2012/02/04 00:21:34
oh... this is probably a good idea, but i wonder h
benm (inactive)
2012/02/06 14:02:36
As it stands at the moment, we're executing the qu
| |
126 // TODO(benm): What to do in this case? Delete the file and try again? | |
127 Close(); | |
128 return false; | |
129 } | |
130 | |
131 // sql::Connection uses UTF-8 encoding, but WebCore style databases use | |
132 // UTF-16, so ensure we match. | |
133 ignore_result(db_->Execute("PRAGMA encoding=\"UTF-16\"")); | |
michaeln
2012/02/04 00:21:34
does this pragma need to be applied prior to the p
benm (inactive)
2012/02/06 14:02:36
Mm, good question. The SQLite docs aren't very cle
| |
134 | |
135 // If the table doesn't exist, try to create it at the current version. | |
136 if (!db_->DoesTableExist("ItemTable")) { | |
137 if (CreateTable()) | |
138 return true; | |
139 // Couldn't create the table. | |
140 // TODO(benm): Handle this better. Delete the file on disk and try again? | |
141 Close(); | |
142 return false; | |
143 } | |
144 | |
145 // Table exists, so ensure we're at the right version, upgrading if | |
146 // necessary. | |
147 if (UpgradeVersion1To2IfNeeded()) | |
148 return true; | |
149 | |
150 // Upgrade failed, drop table and start fresh. | |
151 sql::Transaction recreation(db_.get()); | |
152 if (recreation.Begin()) { | |
153 if (db_->Execute("DROP TABLE ItemTable")) { | |
154 if (CreateTable()) { | |
155 if (recreation.Commit()) | |
156 return true; | |
157 } | |
158 } | |
159 } | |
160 | |
161 // We were not able to verify that the database is at the correct version | |
162 // so close the connection. | |
163 // TODO(benm): Handle this better. Delete the file on disk and try again? | |
164 Close(); | |
165 return false; | |
166 } | |
167 | |
168 bool DomStorageDatabase::CreateTable() { | |
169 DCHECK(IsOpen()); | |
170 // Current version is 2. | |
171 return CreateTableV2(); | |
172 } | |
173 | |
174 bool DomStorageDatabase::CreateTableV2() { | |
175 DCHECK(IsOpen()); | |
176 | |
177 return db_->Execute( | |
178 "CREATE TABLE IF NOT EXISTS ItemTable (" | |
179 "key TEXT UNIQUE ON CONFLICT REPLACE, " | |
180 "value BLOB NOT NULL ON CONFLICT FAIL)"); | |
181 } | |
182 | |
183 bool DomStorageDatabase::UpgradeVersion1To2IfNeeded() { | |
184 DCHECK(IsOpen()); | |
185 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, | |
186 "SELECT * FROM ItemTable")); | |
187 DCHECK(statement.is_valid()); | |
188 | |
189 // Quick check to see if we need to upgrade or not. The single | |
190 // effect of V1 -> V2 is to change the value column from type | |
191 // TEXT to type BLOB. | |
192 sql::ColType value_column_type = statement.DeclaredColumnType(1); | |
193 if (value_column_type == sql::COLUMN_TYPE_BLOB) | |
194 return true; | |
195 | |
196 if (value_column_type != sql::COLUMN_TYPE_TEXT) { | |
197 // Something is messed up. This is not a V1 database. | |
198 return false; | |
199 } | |
200 | |
201 // Need to migrate from TEXT value column to BLOB. | |
202 // Store the current database content so we can re-insert | |
203 // the data into the new V2 table. | |
204 ValuesMap values; | |
205 while (statement.Step()) { | |
206 string16 key = statement.ColumnString16(0); | |
207 NullableString16 value(statement.ColumnString16(1), false); | |
208 values[key] = value; | |
209 } | |
210 | |
211 sql::Transaction migration(db_.get()); | |
212 if (!migration.Begin()) | |
213 return false; | |
214 | |
215 if (db_->Execute("DROP TABLE ItemTable")) { | |
216 CreateTableV2(); | |
217 if (CommitChanges(false, values)) | |
218 return migration.Commit(); | |
219 } | |
220 return false; | |
221 } | |
222 | |
223 void DomStorageDatabase::Close() { | |
224 db_.reset(NULL); | |
225 } | |
226 | |
227 } // namespace dom_storage | |
OLD | NEW |