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 "components/history/core/browser/android/android_cache_database.h"
6
7 #include "base/files/file_util.h"
8 #include "base/logging.h"
9 #include "components/history/core/browser/android/android_time.h"
10 #include "sql/database.h"
11 #include "sql/statement.h"
12
13 using base::Time;
14 using base::TimeDelta;
15
16 namespace history {
17
AndroidCacheDatabase()18 AndroidCacheDatabase::AndroidCacheDatabase() {
19 }
20
~AndroidCacheDatabase()21 AndroidCacheDatabase::~AndroidCacheDatabase() {
22 }
23
InitAndroidCacheDatabase(const base::FilePath & db_name)24 sql::InitStatus AndroidCacheDatabase::InitAndroidCacheDatabase(
25 const base::FilePath& db_name) {
26 if (!CreateDatabase(db_name))
27 return sql::INIT_FAILURE;
28
29 if (!Attach())
30 return sql::INIT_FAILURE;
31
32 if (!CreateBookmarkCacheTable())
33 return sql::INIT_FAILURE;
34
35 if (!CreateSearchTermsTable())
36 return sql::INIT_FAILURE;
37
38 return sql::INIT_OK;
39 }
40
AddBookmarkCacheRow(const Time & created_time,const Time & last_visit_time,URLID url_id)41 bool AndroidCacheDatabase::AddBookmarkCacheRow(const Time& created_time,
42 const Time& last_visit_time,
43 URLID url_id) {
44 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
45 "INSERT INTO android_cache_db.bookmark_cache (created_time, "
46 "last_visit_time, url_id) VALUES (?, ?, ?)"));
47
48 statement.BindInt64(0, ToDatabaseTime(created_time));
49 statement.BindInt64(1, ToDatabaseTime(last_visit_time));
50 statement.BindInt64(2, url_id);
51
52 if (!statement.Run()) {
53 LOG(ERROR) << GetDB().GetErrorMessage();
54 return false;
55 }
56
57 return true;
58 }
59
ClearAllBookmarkCache()60 bool AndroidCacheDatabase::ClearAllBookmarkCache() {
61 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
62 "DELETE FROM android_cache_db.bookmark_cache"));
63 if (!statement.Run()) {
64 LOG(ERROR) << GetDB().GetErrorMessage();
65 return false;
66 }
67 return true;
68 }
69
MarkURLsAsBookmarked(const std::vector<URLID> & url_ids)70 bool AndroidCacheDatabase::MarkURLsAsBookmarked(
71 const std::vector<URLID>& url_ids) {
72 bool has_id = false;
73 std::ostringstream oss;
74 for (const auto& url_id : url_ids) {
75 if (has_id)
76 oss << ", ";
77 else
78 has_id = true;
79 oss << url_id;
80 }
81
82 if (!has_id)
83 return true;
84
85 std::string sql("UPDATE android_cache_db.bookmark_cache "
86 "SET bookmark = 1 WHERE url_id in (");
87 sql.append(oss.str());
88 sql.append(")");
89 if (!GetDB().Execute(sql.c_str())) {
90 LOG(ERROR) << GetDB().GetErrorMessage();
91 return false;
92 }
93 return true;
94 }
95
SetFaviconID(URLID url_id,favicon_base::FaviconID favicon_id)96 bool AndroidCacheDatabase::SetFaviconID(URLID url_id,
97 favicon_base::FaviconID favicon_id) {
98 sql::Statement update_statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
99 "UPDATE android_cache_db.bookmark_cache "
100 "SET favicon_id = ? WHERE url_id = ? "));
101
102 update_statement.BindInt64(0, favicon_id);
103 update_statement.BindInt64(1, url_id);
104 if (!update_statement.Run()) {
105 LOG(ERROR) << GetDB().GetErrorMessage();
106 return false;
107 }
108 return true;
109 }
110
AddSearchTerm(const base::string16 & term,const base::Time & last_visit_time)111 SearchTermID AndroidCacheDatabase::AddSearchTerm(
112 const base::string16& term,
113 const base::Time& last_visit_time) {
114 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
115 "INSERT INTO android_cache_db.search_terms (search, "
116 "date) VALUES (?, ?)"));
117
118 statement.BindString16(0, term);
119 statement.BindInt64(1, ToDatabaseTime(last_visit_time));
120
121 if (!statement.Run()) {
122 LOG(ERROR) << GetDB().GetErrorMessage();
123 return 0;
124 }
125
126 return GetDB().GetLastInsertRowId();
127 }
128
UpdateSearchTerm(SearchTermID id,const SearchTermRow & row)129 bool AndroidCacheDatabase::UpdateSearchTerm(SearchTermID id,
130 const SearchTermRow& row) {
131 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
132 "UPDATE android_cache_db.search_terms "
133 "SET search = ?, date = ? "
134 "WHERE _id = ?"
135 ));
136 statement.BindString16(0, row.term);
137 statement.BindInt64(1, ToDatabaseTime(row.last_visit_time));
138 statement.BindInt64(2, id);
139
140 return statement.Run();
141 }
142
GetSearchTerm(const base::string16 & term,SearchTermRow * row)143 SearchTermID AndroidCacheDatabase::GetSearchTerm(const base::string16& term,
144 SearchTermRow* row) {
145 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
146 "SELECT _id, search, date "
147 "FROM android_cache_db.search_terms "
148 "WHERE search = ?"
149 ));
150 if (!statement.is_valid()) {
151 LOG(ERROR) << GetDB().GetErrorMessage();
152 return 0;
153 }
154 statement.BindString16(0, term);
155 if (!statement.Step())
156 return 0;
157
158 if (row) {
159 row->id = statement.ColumnInt64(0);
160 row->term = statement.ColumnString16(1);
161 row->last_visit_time = FromDatabaseTime(statement.ColumnInt64(2));
162 }
163 return statement.ColumnInt64(0);
164 }
165
DeleteUnusedSearchTerms()166 bool AndroidCacheDatabase::DeleteUnusedSearchTerms() {
167 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
168 "DELETE FROM android_cache_db.search_terms "
169 "WHERE search NOT IN (SELECT DISTINCT term FROM keyword_search_terms)"
170 ));
171 if (!statement.is_valid())
172 return false;
173 return statement.Run();
174 }
175
CreateDatabase(const base::FilePath & db_name)176 bool AndroidCacheDatabase::CreateDatabase(const base::FilePath& db_name) {
177 db_name_ = db_name;
178 sql::Database::Delete(db_name_);
179
180 // Using a new connection, otherwise we can not create the database.
181 //
182 // The db doesn't store too much data, so we don't need that big a page
183 // size or cache.
184 //
185 // The database is open in exclusive mode. Nobody else should be accessing the
186 // database while we're running, and this will give somewhat improved perf.
187 sql::Database connection(
188 {.exclusive_locking = true, .page_size = 2048, .cache_size = 32});
189
190 if (!connection.Open(db_name_)) {
191 LOG(ERROR) << connection.GetErrorMessage();
192 return false;
193 }
194 connection.Close();
195 return true;
196 }
197
CreateBookmarkCacheTable()198 bool AndroidCacheDatabase::CreateBookmarkCacheTable() {
199 const char* name = "android_cache_db.bookmark_cache";
200 DCHECK(!GetDB().DoesTableExist(name));
201
202 std::string sql;
203 sql.append("CREATE TABLE ");
204 sql.append(name);
205 sql.append("("
206 "id INTEGER PRIMARY KEY,"
207 "created_time INTEGER NOT NULL," // Time in millisecond.
208 "last_visit_time INTEGER NOT NULL," // Time in millisecond.
209 "url_id INTEGER NOT NULL," // url id in urls table.
210 "favicon_id INTEGER DEFAULT NULL," // favicon id.
211 "bookmark INTEGER DEFAULT 0" // whether is bookmark.
212 ")");
213 if (!GetDB().Execute(sql.c_str())) {
214 LOG(ERROR) << GetDB().GetErrorMessage();
215 return false;
216 }
217
218 sql.assign("CREATE INDEX ");
219 sql.append("android_cache_db.bookmark_cache_url_id_idx ON "
220 "bookmark_cache(url_id)");
221 if (!GetDB().Execute(sql.c_str())) {
222 LOG(ERROR) << GetDB().GetErrorMessage();
223 return false;
224 }
225 return true;
226 }
227
CreateSearchTermsTable()228 bool AndroidCacheDatabase::CreateSearchTermsTable() {
229 const char* name = "android_cache_db.search_terms";
230
231 // The table's column name matchs Android's definition.
232 std::string sql;
233 sql.append("CREATE TABLE ");
234 sql.append(name);
235 sql.append("("
236 "_id INTEGER PRIMARY KEY,"
237 "date INTEGER NOT NULL," // last visit time in millisecond.
238 "search LONGVARCHAR NOT NULL)"); // The actual search term.
239
240 if (!GetDB().Execute(sql.c_str())) {
241 LOG(ERROR) << GetDB().GetErrorMessage();
242 return false;
243 }
244
245 sql.assign("CREATE INDEX "
246 "android_cache_db.search_terms_term_idx ON "
247 "search_terms(search)");
248 if (!GetDB().Execute(sql.c_str())) {
249 LOG(ERROR) << GetDB().GetErrorMessage();
250 return false;
251 }
252 return true;
253 }
254
Attach()255 bool AndroidCacheDatabase::Attach() {
256 // Commit all open transactions to make attach succeed.
257 int transaction_nesting = GetDB().transaction_nesting();
258 int count = transaction_nesting;
259 while (count--)
260 GetDB().CommitTransaction();
261
262 bool result = DoAttach();
263
264 // No matter whether the attach succeeded or not, we need to create the
265 // transaction stack again.
266 count = transaction_nesting;
267 while (count--)
268 GetDB().BeginTransaction();
269 return result;
270 }
271
DoAttach()272 bool AndroidCacheDatabase::DoAttach() {
273 std::string sql("ATTACH ? AS android_cache_db");
274 sql::Statement attach(GetDB().GetUniqueStatement(sql.c_str()));
275 if (!attach.is_valid())
276 // Keep the transaction open, even though we failed.
277 return false;
278
279 attach.BindString(0, db_name_.value());
280 if (!attach.Run()) {
281 LOG(ERROR) << GetDB().GetErrorMessage();
282 return false;
283 }
284
285 return true;
286 }
287
288 } // namespace history
289