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/shortcuts_database.h" 6 7 #include <string> 8 9 #include "base/guid.h" 10 #include "base/logging.h" 11 #include "base/strings/stringprintf.h" 12 #include "base/time/time.h" 13 #include "chrome/common/autocomplete_match_type.h" 14 #include "content/public/common/page_transition_types.h" 15 #include "sql/meta_table.h" 16 #include "sql/statement.h" 17 #include "sql/transaction.h" 18 19 20 // Helpers -------------------------------------------------------------------- 21 22 namespace { 23 24 // Current version number. We write databases at the "current" version number, 25 // but any previous version that can read the "compatible" one can make do with 26 // our database without *too* many bad effects. 27 const int kCurrentVersionNumber = 1; 28 const int kCompatibleVersionNumber = 1; 29 30 void BindShortcutToStatement( 31 const history::ShortcutsDatabase::Shortcut& shortcut, 32 sql::Statement* s) { 33 DCHECK(base::IsValidGUID(shortcut.id)); 34 s->BindString(0, shortcut.id); 35 s->BindString16(1, shortcut.text); 36 s->BindString16(2, shortcut.match_core.fill_into_edit); 37 s->BindString(3, shortcut.match_core.destination_url.spec()); 38 s->BindString16(4, shortcut.match_core.contents); 39 s->BindString(5, shortcut.match_core.contents_class); 40 s->BindString16(6, shortcut.match_core.description); 41 s->BindString(7, shortcut.match_core.description_class); 42 s->BindInt(8, shortcut.match_core.transition); 43 s->BindInt(9, shortcut.match_core.type); 44 s->BindString16(10, shortcut.match_core.keyword); 45 s->BindInt64(11, shortcut.last_access_time.ToInternalValue()); 46 s->BindInt(12, shortcut.number_of_hits); 47 } 48 49 bool DeleteShortcut(const char* field_name, 50 const std::string& id, 51 sql::Connection& db) { 52 sql::Statement s(db.GetUniqueStatement( 53 base::StringPrintf("DELETE FROM omni_box_shortcuts WHERE %s = ?", 54 field_name).c_str())); 55 s.BindString(0, id); 56 return s.Run(); 57 } 58 59 } // namespace 60 61 62 namespace history { 63 64 // ShortcutsDatabase::Shortcut::MatchCore ------------------------------------- 65 66 ShortcutsDatabase::Shortcut::MatchCore::MatchCore( 67 const base::string16& fill_into_edit, 68 const GURL& destination_url, 69 const base::string16& contents, 70 const std::string& contents_class, 71 const base::string16& description, 72 const std::string& description_class, 73 int transition, 74 int type, 75 const base::string16& keyword) 76 : fill_into_edit(fill_into_edit), 77 destination_url(destination_url), 78 contents(contents), 79 contents_class(contents_class), 80 description(description), 81 description_class(description_class), 82 transition(transition), 83 type(type), 84 keyword(keyword) { 85 } 86 87 ShortcutsDatabase::Shortcut::MatchCore::~MatchCore() { 88 } 89 90 // ShortcutsDatabase::Shortcut ------------------------------------------------ 91 92 ShortcutsDatabase::Shortcut::Shortcut( 93 const std::string& id, 94 const base::string16& text, 95 const MatchCore& match_core, 96 const base::Time& last_access_time, 97 int number_of_hits) 98 : id(id), 99 text(text), 100 match_core(match_core), 101 last_access_time(last_access_time), 102 number_of_hits(number_of_hits) { 103 } 104 105 ShortcutsDatabase::Shortcut::Shortcut() 106 : match_core(base::string16(), GURL(), base::string16(), std::string(), 107 base::string16(), std::string(), 0, 0, base::string16()), 108 last_access_time(base::Time::Now()), 109 number_of_hits(0) { 110 } 111 112 ShortcutsDatabase::Shortcut::~Shortcut() { 113 } 114 115 116 // ShortcutsDatabase ---------------------------------------------------------- 117 118 ShortcutsDatabase::ShortcutsDatabase(const base::FilePath& database_path) 119 : database_path_(database_path) { 120 } 121 122 bool ShortcutsDatabase::Init() { 123 db_.set_histogram_tag("Shortcuts"); 124 125 // Set the database page size to something a little larger to give us 126 // better performance (we're typically seek rather than bandwidth limited). 127 // This only has an effect before any tables have been created, otherwise 128 // this is a NOP. Must be a power of 2 and a max of 8192. 129 db_.set_page_size(4096); 130 131 // Run the database in exclusive mode. Nobody else should be accessing the 132 // database while we're running, and this will give somewhat improved perf. 133 db_.set_exclusive_locking(); 134 135 // Attach the database to our index file. 136 return db_.Open(database_path_) && EnsureTable(); 137 } 138 139 bool ShortcutsDatabase::AddShortcut(const Shortcut& shortcut) { 140 sql::Statement s(db_.GetCachedStatement( 141 SQL_FROM_HERE, 142 "INSERT INTO omni_box_shortcuts (id, text, fill_into_edit, url, " 143 "contents, contents_class, description, description_class, " 144 "transition, type, keyword, last_access_time, number_of_hits) " 145 "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)")); 146 BindShortcutToStatement(shortcut, &s); 147 return s.Run(); 148 } 149 150 bool ShortcutsDatabase::UpdateShortcut(const Shortcut& shortcut) { 151 sql::Statement s(db_.GetCachedStatement( 152 SQL_FROM_HERE, 153 "UPDATE omni_box_shortcuts SET id=?, text=?, fill_into_edit=?, url=?, " 154 "contents=?, contents_class=?, description=?, description_class=?, " 155 "transition=?, type=?, keyword=?, last_access_time=?, " 156 "number_of_hits=? WHERE id=?")); 157 BindShortcutToStatement(shortcut, &s); 158 s.BindString(13, shortcut.id); 159 return s.Run(); 160 } 161 162 bool ShortcutsDatabase::DeleteShortcutsWithIDs( 163 const ShortcutIDs& shortcut_ids) { 164 bool success = true; 165 db_.BeginTransaction(); 166 for (ShortcutIDs::const_iterator it(shortcut_ids.begin()); 167 it != shortcut_ids.end(); ++it) { 168 success &= DeleteShortcut("id", *it, db_); 169 } 170 db_.CommitTransaction(); 171 return success; 172 } 173 174 bool ShortcutsDatabase::DeleteShortcutsWithURL( 175 const std::string& shortcut_url_spec) { 176 return DeleteShortcut("url", shortcut_url_spec, db_); 177 } 178 179 bool ShortcutsDatabase::DeleteAllShortcuts() { 180 if (!db_.Execute("DELETE FROM omni_box_shortcuts")) 181 return false; 182 183 ignore_result(db_.Execute("VACUUM")); 184 return true; 185 } 186 187 void ShortcutsDatabase::LoadShortcuts(GuidToShortcutMap* shortcuts) { 188 DCHECK(shortcuts); 189 sql::Statement s(db_.GetCachedStatement( 190 SQL_FROM_HERE, 191 "SELECT id, text, fill_into_edit, url, contents, contents_class, " 192 "description, description_class, transition, type, keyword, " 193 "last_access_time, number_of_hits FROM omni_box_shortcuts")); 194 195 shortcuts->clear(); 196 while (s.Step()) { 197 shortcuts->insert(std::make_pair( 198 s.ColumnString(0), 199 Shortcut( 200 s.ColumnString(0), // id 201 s.ColumnString16(1), // text 202 Shortcut::MatchCore( 203 s.ColumnString16(2), // fill_into_edit 204 GURL(s.ColumnString(3)), // destination_url 205 s.ColumnString16(4), // contents 206 s.ColumnString(5), // contents_class 207 s.ColumnString16(6), // description 208 s.ColumnString(7), // description_class 209 s.ColumnInt(8), // transition 210 s.ColumnInt(9), // type 211 s.ColumnString16(10)), // keyword 212 base::Time::FromInternalValue(s.ColumnInt64(11)), 213 // last_access_time 214 s.ColumnInt(12)))); // number_of_hits 215 } 216 } 217 218 ShortcutsDatabase::~ShortcutsDatabase() { 219 } 220 221 bool ShortcutsDatabase::EnsureTable() { 222 if (!db_.DoesTableExist("omni_box_shortcuts")) { 223 return db_.Execute( 224 "CREATE TABLE omni_box_shortcuts (id VARCHAR PRIMARY KEY, " 225 "text VARCHAR, fill_into_edit VARCHAR, url VARCHAR, " 226 "contents VARCHAR, contents_class VARCHAR, description VARCHAR, " 227 "description_class VARCHAR, transition INTEGER, type INTEGER, " 228 "keyword VARCHAR, last_access_time INTEGER, " 229 "number_of_hits INTEGER)"); 230 } 231 232 // The first version of the shortcuts table lacked the fill_into_edit, 233 // transition, type, and keyword columns. 234 if (!db_.DoesColumnExist("omni_box_shortcuts", "fill_into_edit")) { 235 // Perform the upgrade in a transaction to ensure it doesn't happen 236 // incompletely. 237 sql::Transaction transaction(&db_); 238 if (!(transaction.Begin() && 239 db_.Execute("ALTER TABLE omni_box_shortcuts " 240 "ADD COLUMN fill_into_edit VARCHAR") && 241 db_.Execute("UPDATE omni_box_shortcuts SET fill_into_edit = url") && 242 db_.Execute("ALTER TABLE omni_box_shortcuts " 243 "ADD COLUMN transition INTEGER") && 244 db_.Execute(base::StringPrintf( 245 "UPDATE omni_box_shortcuts SET transition = %d", 246 static_cast<int>(content::PAGE_TRANSITION_TYPED)).c_str()) && 247 db_.Execute("ALTER TABLE omni_box_shortcuts ADD COLUMN type INTEGER") && 248 db_.Execute(base::StringPrintf( 249 "UPDATE omni_box_shortcuts SET type = %d", 250 static_cast<int>(AutocompleteMatchType::HISTORY_TITLE)).c_str()) && 251 db_.Execute("ALTER TABLE omni_box_shortcuts " 252 "ADD COLUMN keyword VARCHAR") && 253 transaction.Commit())) { 254 return false; 255 } 256 } 257 258 if (!sql::MetaTable::DoesTableExist(&db_)) { 259 meta_table_.Init(&db_, kCurrentVersionNumber, kCompatibleVersionNumber); 260 sql::Transaction transaction(&db_); 261 if (!(transaction.Begin() && 262 // Migrate old SEARCH_OTHER_ENGINE values to the new type value. 263 db_.Execute(base::StringPrintf("UPDATE omni_box_shortcuts " 264 "SET type = 13 WHERE type = 9").c_str()) && 265 // Migrate old EXTENSION_APP values to the new type value. 266 db_.Execute(base::StringPrintf("UPDATE omni_box_shortcuts " 267 "SET type = 14 WHERE type = 10").c_str()) && 268 // Migrate old CONTACT values to the new type value. 269 db_.Execute(base::StringPrintf("UPDATE omni_box_shortcuts " 270 "SET type = 15 WHERE type = 11").c_str()) && 271 // Migrate old BOOKMARK_TITLE values to the new type value. 272 db_.Execute(base::StringPrintf("UPDATE omni_box_shortcuts " 273 "SET type = 16 WHERE type = 12").c_str()) && 274 transaction.Commit())) { 275 return false; 276 } 277 } 278 return true; 279 } 280 281 } // namespace history 282