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