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