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/file_util.h" 8 #include "chrome/browser/history/android/android_time.h" 9 #include "sql/statement.h" 10 11 using base::Time; 12 using base::TimeDelta; 13 14 namespace history { 15 16 AndroidCacheDatabase::AndroidCacheDatabase() { 17 } 18 19 AndroidCacheDatabase::~AndroidCacheDatabase() { 20 } 21 22 sql::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 39 bool 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 58 bool 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 68 bool 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 95 bool AndroidCacheDatabase::SetFaviconID(URLID url_id, 96 chrome::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 110 SearchTermID 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 128 bool 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 142 SearchTermID 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 165 bool 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 175 bool 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 199 bool 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 229 bool 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 256 bool 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 273 bool 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