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

Side by Side Diff: components/offline_pages/background/request_queue_store_sql.cc

Issue 2053163002: [Offline pages] Adding persistent request queue based on SQLite (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebasing Created 4 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
OLDNEW
(Empty)
1 // Copyright 2016 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 "components/offline_pages/background/request_queue_store_sql.h"
6
7 #include "base/bind.h"
8 #include "base/files/file_path.h"
9 #include "base/files/file_util.h"
10 #include "base/location.h"
11 #include "base/logging.h"
12 #include "base/sequenced_task_runner.h"
13 #include "base/threading/thread_task_runner_handle.h"
14 #include "components/offline_pages/background/save_page_request.h"
15 #include "sql/connection.h"
16 #include "sql/statement.h"
17 #include "sql/transaction.h"
18
19 namespace offline_pages {
20
21 namespace {
22
23 // This is a macro instead of a const so that
24 // it can be used inline in other SQL statements below.
25 #define REQUEST_QUEUE_TABLE_NAME "request_queue_v1"
26
27 // New columns should be added at the end of the list in order to avoid
28 // complicated table upgrade.
29 const char kOfflinePagesColumns[] =
30 "(request_id INTEGER PRIMARY KEY NOT NULL,"
31 " creation_time INTEGER NOT NULL,"
32 " activation_time INTEGER NOT NULL DEFAULT 0,"
33 " last_attempt_time INTEGER NOT NULL DEFAULT 0,"
34 " attempt_count INTEGER NOT NULL,"
35 " url VARCHAR NOT NULL,"
36 " client_namespace VARCHAR NOT NULL,"
37 " client_id VARCHAR NOT NULL"
38 ")";
39
40 // Defines indices of the columns in a SELECT * FROM query. Should be kept in
41 // sync with above.
42 enum : int {
43 RQ_REQUEST_ID,
44 RQ_CREATION_TIME,
45 RQ_ACTIVATION_TIME,
46 RQ_LAST_ATTMEPT_TIME,
47 RQ_ATTEMPT_COUNT,
48 RQ_URL,
49 RQ_CLIENT_NAMESPACE,
50 RQ_CLIENT_ID,
51 };
52
53 enum class RequestExistsResult {
54 SQL_FAILED,
55 REQUEST_EXISTS,
56 REQUEST_DOES_NOT_EXIST,
57 };
58
59 // This is cloned from //content/browser/appcache/appcache_database.cc
60 struct TableInfo {
61 const char* table_name;
62 const char* columns;
63 };
64
65 const TableInfo kRequestQueueTable{REQUEST_QUEUE_TABLE_NAME,
66 kOfflinePagesColumns};
67
68 bool CreateTable(sql::Connection* db, const TableInfo& table_info) {
69 std::string sql("CREATE TABLE ");
70 sql += table_info.table_name;
71 sql += table_info.columns;
72 return db->Execute(sql.c_str());
73 }
74
75 bool CreateSchema(sql::Connection* db) {
76 // If you create a transaction but don't Commit() it is automatically
77 // rolled back by its destructor when it falls out of scope.
78 sql::Transaction transaction(db);
79 if (!transaction.Begin())
80 return false;
81
82 if (!db->DoesTableExist(kRequestQueueTable.table_name)) {
83 if (!CreateTable(db, kRequestQueueTable))
84 return false;
85 }
86
87 // TODO(fgorski): Add indices here.
88 return transaction.Commit();
89 }
90
91 bool DeleteByRequestId(sql::Connection* db, int64_t request_id) {
92 static const char kSql[] =
93 "DELETE FROM " REQUEST_QUEUE_TABLE_NAME " WHERE request_id=?";
94 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
95 statement.BindInt64(0, request_id);
96 LOG(ERROR) << "In DeleteRequestById: "
97 << request_id; // statement.GetSQLStatement();
98 return statement.Run();
99 }
100
101 // Create a save page request from a SQL result. Expects complete rows with
102 // all columns present.
103 SavePageRequest MakeSavePageRequest(sql::Statement* statement) {
Pete Williamson 2016/06/14 21:54:45 Note to self: What is this statement object? It i
Pete Williamson 2016/06/14 21:54:47 Should this return a pointer so we don't keep copy
fgorski 2016/06/14 22:38:05 It defines a statement before execution and acts a
fgorski 2016/06/14 22:38:05 The object is not that big. It is ok to copy for n
104 int64_t id = statement->ColumnInt64(RQ_REQUEST_ID);
105 GURL url(statement->ColumnString(RQ_URL));
106 ClientId client_id(statement->ColumnString(RQ_CLIENT_NAMESPACE),
107 statement->ColumnString(RQ_CLIENT_ID));
108 int64_t attempt_count = statement->ColumnInt64(RQ_ATTEMPT_COUNT);
109 base::Time creation_time =
110 base::Time::FromInternalValue(statement->ColumnInt64(RQ_CREATION_TIME));
111 base::Time activation_time =
112 base::Time::FromInternalValue(statement->ColumnInt64(RQ_ACTIVATION_TIME));
113
114 SavePageRequest request(id, url, client_id, creation_time, activation_time);
115 base::Time last_attempt_time = base::Time::FromInternalValue(
116 statement->ColumnInt64(RQ_LAST_ATTMEPT_TIME));
117 request.MarkAttemptCompleted(last_attempt_time);
118 request.set_attempt_count(attempt_count);
119
120 return request;
121 }
122
123 RequestExistsResult RequestExists(sql::Connection* db, int64_t request_id) {
124 const char kSql[] =
125 "SELECT COUNT(*) FROM " REQUEST_QUEUE_TABLE_NAME " WHERE request_id = ?";
126 sql::Statement statement(db->GetUniqueStatement(kSql));
127 statement.BindInt64(0, request_id);
128 if (!statement.Step()) {
129 LOG(ERROR) << "Failed to check if request exists: " << request_id
130 << ", SQL statement: " << statement.GetSQLStatement();
131 return RequestExistsResult::SQL_FAILED;
132 }
133 return statement.ColumnInt64(0) ? RequestExistsResult::REQUEST_EXISTS
134 : RequestExistsResult::REQUEST_DOES_NOT_EXIST;
135 }
136
137 RequestQueueStore::UpdateStatus InsertOrReplace(
138 sql::Connection* db,
139 const SavePageRequest& request) {
140 // In order to use the enums in the Bind* methods, keep the order of fields
141 // the same as in the definition/select query.
142 const char kInsertSql[] =
143 "INSERT OR REPLACE INTO " REQUEST_QUEUE_TABLE_NAME
144 " (request_id, creation_time, activation_time, last_attempt_time, "
145 " attempt_count, url, client_namespace, client_id) "
146 " VALUES "
147 " (?, ?, ?, ?, ?, ?, ?, ?)";
148
149 // Count and add/update as part of a single transaction.
Pete Williamson 2016/06/14 21:54:47 Grammar nit: Did you mean "Count an" instead of "
fgorski 2016/06/14 22:38:05 I meant it how it was, but rephrased so that it is
150 sql::Transaction transaction(db);
151 if (!transaction.Begin()) {
152 LOG(ERROR) << "Failed to start transaction on InsertOrReplace.";
153 return RequestQueueStore::UpdateStatus::FAILED;
154 }
155
156 RequestExistsResult exists = RequestExists(db, request.request_id());
157 if (exists == RequestExistsResult::SQL_FAILED) {
158 LOG(ERROR) << "Failed to check if request exists: " << request.request_id();
159 return RequestQueueStore::UpdateStatus::FAILED;
160 }
161
162 RequestQueueStore::UpdateStatus status =
163 exists == RequestExistsResult::REQUEST_EXISTS
164 ? RequestQueueStore::UpdateStatus::UPDATED
165 : RequestQueueStore::UpdateStatus::ADDED;
166
167 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kInsertSql));
168 statement.BindInt64(RQ_REQUEST_ID, request.request_id());
169 statement.BindString(RQ_URL, request.url().spec());
170 statement.BindString(RQ_CLIENT_NAMESPACE, request.client_id().name_space);
171 statement.BindString(RQ_CLIENT_ID, request.client_id().id);
172 statement.BindInt64(RQ_CREATION_TIME,
173 request.creation_time().ToInternalValue());
174 statement.BindInt64(RQ_ACTIVATION_TIME,
175 request.activation_time().ToInternalValue());
176 statement.BindInt64(RQ_LAST_ATTMEPT_TIME,
177 request.last_attempt_time().ToInternalValue());
178 statement.BindInt64(RQ_ATTEMPT_COUNT, request.attempt_count());
179
180 if (!statement.Run() || !transaction.Commit()) {
181 LOG(ERROR) << "Failed to add/update a request: " << request.request_id();
182 return RequestQueueStore::UpdateStatus::FAILED;
183 }
184
185 return status;
186 }
187
188 bool InitDatabase(sql::Connection* db, const base::FilePath& path) {
189 db->set_page_size(4096);
Pete Williamson 2016/06/14 21:54:45 Maybe these numbers (and string) should be constan
fgorski 2016/06/14 22:38:05 I looked through the source code. With only a few
190 db->set_cache_size(500);
191 db->set_histogram_tag("BackgroundRequestQueue");
192 db->set_exclusive_locking();
193
194 base::File::Error err;
195 if (!base::CreateDirectoryAndGetError(path.DirName(), &err)) {
196 LOG(ERROR) << "Failed to create background request queue db directory: "
197 << base::File::ErrorToString(err);
198 return false;
199 }
200 if (!db->Open(path)) {
201 LOG(ERROR) << "Failed to open database";
202 return false;
203 }
204 db->Preload();
205
206 return CreateSchema(db);
207 }
208
209 } // anonymous namespace
210
211 RequestQueueStoreSQL::RequestQueueStoreSQL(
212 scoped_refptr<base::SequencedTaskRunner> background_task_runner,
213 const base::FilePath& path)
214 : background_task_runner_(std::move(background_task_runner)),
215 db_file_path_(path.AppendASCII("RequestQueue.db")),
216 weak_ptr_factory_(this) {
217 OpenConnection();
218 }
219
220 RequestQueueStoreSQL::~RequestQueueStoreSQL() {
221 if (db_.get() &&
222 !background_task_runner_->DeleteSoon(FROM_HERE, db_.release())) {
223 DLOG(WARNING) << "SQL database will not be deleted.";
224 }
225 }
226
227 // static
228 void RequestQueueStoreSQL::OpenConnectionSync(
229 sql::Connection* db,
230 scoped_refptr<base::SingleThreadTaskRunner> runner,
231 const base::FilePath& path,
232 const base::Callback<void(bool)>& callback) {
233 bool success = InitDatabase(db, path);
234 runner->PostTask(FROM_HERE, base::Bind(callback, success));
235 }
236
237 // static
238 void RequestQueueStoreSQL::GetRequestsSync(
239 sql::Connection* db,
240 scoped_refptr<base::SingleThreadTaskRunner> runner,
241 const GetRequestsCallback& callback) {
242 const char kSql[] = "SELECT * FROM " REQUEST_QUEUE_TABLE_NAME;
243
244 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
245
246 std::vector<SavePageRequest> result;
247 while (statement.Step()) {
248 result.push_back(MakeSavePageRequest(&statement));
249 }
250
251 runner->PostTask(FROM_HERE,
252 base::Bind(callback, statement.Succeeded(), result));
253 }
254
255 // static
256 void RequestQueueStoreSQL::AddOrUpdateRequestSync(
257 sql::Connection* db,
258 scoped_refptr<base::SingleThreadTaskRunner> runner,
259 const SavePageRequest& request,
260 const UpdateCallback& callback) {
261 // TODO(fgorski): add UMA metrics here.
262 RequestQueueStore::UpdateStatus status = InsertOrReplace(db, request);
263 runner->PostTask(FROM_HERE, base::Bind(callback, status));
264 }
265
266 // static
267 void RequestQueueStoreSQL::RemoveRequestsSync(
268 sql::Connection* db,
269 scoped_refptr<base::SingleThreadTaskRunner> runner,
270 const std::vector<int64_t>& request_ids,
271 const RemoveCallback& callback) {
272 // TODO(fgorski): add UMA metrics here.
273
274 // If you create a transaction but don't Commit() it is automatically
275 // rolled back by its destructor when it falls out of scope.
276 sql::Transaction transaction(db);
277 if (!transaction.Begin()) {
278 runner->PostTask(FROM_HERE, base::Bind(callback, false, 0));
279 return;
280 }
281 int count = 0;
282 for (auto request_id : request_ids) {
283 RequestExistsResult exists = RequestExists(db, request_id);
284 if (exists == RequestExistsResult::SQL_FAILED) {
285 runner->PostTask(FROM_HERE, base::Bind(callback, false, 0));
286 return;
287 }
288 if (exists == RequestExistsResult::REQUEST_DOES_NOT_EXIST)
289 continue;
290
291 ++count;
292 if (!DeleteByRequestId(db, request_id)) {
293 runner->PostTask(FROM_HERE, base::Bind(callback, false, 0));
294 return;
295 }
296 }
297
298 if (!transaction.Commit()) {
299 runner->PostTask(FROM_HERE, base::Bind(callback, false, 0));
300 return;
301 }
302
303 runner->PostTask(FROM_HERE, base::Bind(callback, true, count));
304 }
305
306 // static
307 void RequestQueueStoreSQL::ResetSync(
Pete Williamson 2016/06/14 21:54:46 What does this delete, everything? A comment woul
fgorski 2016/06/14 22:38:05 Done.
308 std::unique_ptr<sql::Connection> db,
309 scoped_refptr<base::SingleThreadTaskRunner> runner,
310 const ResetCallback& callback) {
311 const char kSql[] = "DELETE FROM " REQUEST_QUEUE_TABLE_NAME;
312 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
313 runner->PostTask(FROM_HERE, base::Bind(callback, statement.Run()));
314 }
315
316 void RequestQueueStoreSQL::GetRequests(const GetRequestsCallback& callback) {
Pete Williamson 2016/06/14 21:54:45 The next three methods seem awfully similar - is t
fgorski 2016/06/14 22:38:05 They follow the same structure: check for db issue
317 DCHECK(db_.get());
318 if (!db_.get()) {
319 // Nothing to do, but post a callback instead of calling directly
320 // to preserve the async style behavior to prevent bugs.
321 base::ThreadTaskRunnerHandle::Get()->PostTask(
322 FROM_HERE, base::Bind(callback, false, std::vector<SavePageRequest>()));
323 return;
324 }
325
326 background_task_runner_->PostTask(
327 FROM_HERE, base::Bind(&RequestQueueStoreSQL::GetRequestsSync, db_.get(),
328 base::ThreadTaskRunnerHandle::Get(), callback));
329 }
330
331 void RequestQueueStoreSQL::AddOrUpdateRequest(const SavePageRequest& request,
332 const UpdateCallback& callback) {
333 DCHECK(db_.get());
334 if (!db_.get()) {
335 // Nothing to do, but post a callback instead of calling directly
336 // to preserve the async style behavior to prevent bugs.
337 base::ThreadTaskRunnerHandle::Get()->PostTask(
338 FROM_HERE, base::Bind(callback, UpdateStatus::FAILED));
339 return;
340 }
341
342 background_task_runner_->PostTask(
343 FROM_HERE,
344 base::Bind(&RequestQueueStoreSQL::AddOrUpdateRequestSync, db_.get(),
345 base::ThreadTaskRunnerHandle::Get(), request, callback));
346 }
347
348 void RequestQueueStoreSQL::RemoveRequests(
349 const std::vector<int64_t>& request_ids,
350 const RemoveCallback& callback) {
351 DCHECK(db_.get());
352 if (!db_.get()) {
353 // Nothing to do, but post a callback instead of calling directly
354 // to preserve the async style behavior to prevent bugs.
355 base::ThreadTaskRunnerHandle::Get()->PostTask(
356 FROM_HERE, base::Bind(callback, false, 0));
357 return;
358 }
359
360 if (request_ids.empty()) {
361 // Nothing to do, but post a callback instead of calling directly
362 // to preserve the async style behavior to prevent bugs.
363 base::ThreadTaskRunnerHandle::Get()->PostTask(
364 FROM_HERE, base::Bind(callback, true, 0));
365 return;
366 }
367
368 background_task_runner_->PostTask(
369 FROM_HERE,
370 base::Bind(&RequestQueueStoreSQL::RemoveRequestsSync, db_.get(),
371 base::ThreadTaskRunnerHandle::Get(), request_ids, callback));
372 }
373
374 void RequestQueueStoreSQL::Reset(const ResetCallback& callback) {
375 DCHECK(db_.get());
376 if (!db_.get()) {
377 // Nothing to do, but post a callback instead of calling directly
378 // to preserve the async style behavior to prevent bugs.
379 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
380 base::Bind(callback, false));
381 return;
382 }
383
384 background_task_runner_->PostTask(
385 FROM_HERE,
386 base::Bind(&RequestQueueStoreSQL::ResetSync, base::Passed(&db_),
387 base::ThreadTaskRunnerHandle::Get(), callback));
388 }
389
390 void RequestQueueStoreSQL::OpenConnection() {
391 DCHECK(!db_);
392 db_.reset(new sql::Connection());
393 background_task_runner_->PostTask(
394 FROM_HERE,
395 base::Bind(&RequestQueueStoreSQL::OpenConnectionSync, db_.get(),
396 base::ThreadTaskRunnerHandle::Get(), db_file_path_,
397 base::Bind(&RequestQueueStoreSQL::OnOpenConnectionDone,
398 weak_ptr_factory_.GetWeakPtr())));
399 }
400
401 void RequestQueueStoreSQL::OnOpenConnectionDone(bool success) {
402 DCHECK(db_.get());
403
404 // Unfortunately we were not able to open DB connection.
405 if (!success) {
406 LOG(ERROR) << "Database creation fialed.";
407 db_.reset();
408 }
409 }
410
411 } // namespace offline_pages
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698