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/webdata/keyword_table.h" 6 7 #include <set> 8 9 #include "base/json/json_reader.h" 10 #include "base/json/json_writer.h" 11 #include "base/logging.h" 12 #include "base/memory/scoped_ptr.h" 13 #include "base/strings/string_number_conversions.h" 14 #include "base/strings/string_split.h" 15 #include "base/strings/string_util.h" 16 #include "base/strings/utf_string_conversions.h" 17 #include "base/values.h" 18 #include "chrome/browser/history/history_database.h" 19 #include "chrome/browser/search_engines/search_terms_data.h" 20 #include "chrome/browser/search_engines/template_url.h" 21 #include "chrome/browser/search_engines/template_url_service.h" 22 #include "components/webdata/common/web_database.h" 23 #include "sql/statement.h" 24 #include "sql/transaction.h" 25 #include "url/gurl.h" 26 27 using base::Time; 28 29 // static 30 const char KeywordTable::kDefaultSearchProviderKey[] = 31 "Default Search Provider ID"; 32 33 namespace { 34 35 // Keys used in the meta table. 36 const char kBuiltinKeywordVersion[] = "Builtin Keyword Version"; 37 38 const std::string ColumnsForVersion(int version, bool concatenated) { 39 std::vector<std::string> columns; 40 41 columns.push_back("id"); 42 columns.push_back("short_name"); 43 columns.push_back("keyword"); 44 columns.push_back("favicon_url"); 45 columns.push_back("url"); 46 columns.push_back("safe_for_autoreplace"); 47 columns.push_back("originating_url"); 48 columns.push_back("date_created"); 49 columns.push_back("usage_count"); 50 columns.push_back("input_encodings"); 51 columns.push_back("show_in_default_list"); 52 columns.push_back("suggest_url"); 53 columns.push_back("prepopulate_id"); 54 if (version <= 44) { 55 // Columns removed after version 44. 56 columns.push_back("autogenerate_keyword"); 57 columns.push_back("logo_id"); 58 } 59 columns.push_back("created_by_policy"); 60 columns.push_back("instant_url"); 61 columns.push_back("last_modified"); 62 columns.push_back("sync_guid"); 63 if (version >= 47) { 64 // Column added in version 47. 65 columns.push_back("alternate_urls"); 66 } 67 if (version >= 49) { 68 // Column added in version 49. 69 columns.push_back("search_terms_replacement_key"); 70 } 71 if (version >= 52) { 72 // Column added in version 52. 73 columns.push_back("image_url"); 74 columns.push_back("search_url_post_params"); 75 columns.push_back("suggest_url_post_params"); 76 columns.push_back("instant_url_post_params"); 77 columns.push_back("image_url_post_params"); 78 } 79 80 return JoinString(columns, std::string(concatenated ? " || " : ", ")); 81 } 82 83 84 // Inserts the data from |data| into |s|. |s| is assumed to have slots for all 85 // the columns in the keyword table. |id_column| is the slot number to bind 86 // |data|'s |id| to; |starting_column| is the slot number of the first of a 87 // contiguous set of slots to bind all the other fields to. 88 void BindURLToStatement(const TemplateURLData& data, 89 sql::Statement* s, 90 int id_column, 91 int starting_column) { 92 // Serialize |alternate_urls| to JSON. 93 // TODO(beaudoin): Check what it would take to use a new table to store 94 // alternate_urls while keeping backups and table signature in a good state. 95 // See: crbug.com/153520 96 ListValue alternate_urls_value; 97 for (size_t i = 0; i < data.alternate_urls.size(); ++i) 98 alternate_urls_value.AppendString(data.alternate_urls[i]); 99 std::string alternate_urls; 100 base::JSONWriter::Write(&alternate_urls_value, &alternate_urls); 101 102 s->BindInt64(id_column, data.id); 103 s->BindString16(starting_column, data.short_name); 104 s->BindString16(starting_column + 1, data.keyword()); 105 s->BindString(starting_column + 2, data.favicon_url.is_valid() ? 106 history::HistoryDatabase::GURLToDatabaseURL(data.favicon_url) : 107 std::string()); 108 s->BindString(starting_column + 3, data.url()); 109 s->BindBool(starting_column + 4, data.safe_for_autoreplace); 110 s->BindString(starting_column + 5, data.originating_url.is_valid() ? 111 history::HistoryDatabase::GURLToDatabaseURL(data.originating_url) : 112 std::string()); 113 s->BindInt64(starting_column + 6, data.date_created.ToTimeT()); 114 s->BindInt(starting_column + 7, data.usage_count); 115 s->BindString(starting_column + 8, JoinString(data.input_encodings, ';')); 116 s->BindBool(starting_column + 9, data.show_in_default_list); 117 s->BindString(starting_column + 10, data.suggestions_url); 118 s->BindInt(starting_column + 11, data.prepopulate_id); 119 s->BindBool(starting_column + 12, data.created_by_policy); 120 s->BindString(starting_column + 13, data.instant_url); 121 s->BindInt64(starting_column + 14, data.last_modified.ToTimeT()); 122 s->BindString(starting_column + 15, data.sync_guid); 123 s->BindString(starting_column + 16, alternate_urls); 124 s->BindString(starting_column + 17, data.search_terms_replacement_key); 125 s->BindString(starting_column + 18, data.image_url); 126 s->BindString(starting_column + 19, data.search_url_post_params); 127 s->BindString(starting_column + 20, data.suggestions_url_post_params); 128 s->BindString(starting_column + 21, data.instant_url_post_params); 129 s->BindString(starting_column + 22, data.image_url_post_params); 130 } 131 132 WebDatabaseTable::TypeKey GetKey() { 133 // We just need a unique constant. Use the address of a static that 134 // COMDAT folding won't touch in an optimizing linker. 135 static int table_key = 0; 136 return reinterpret_cast<void*>(&table_key); 137 } 138 139 } // namespace 140 141 KeywordTable::KeywordTable() { 142 } 143 144 KeywordTable::~KeywordTable() {} 145 146 KeywordTable* KeywordTable::FromWebDatabase(WebDatabase* db) { 147 return static_cast<KeywordTable*>(db->GetTable(GetKey())); 148 } 149 150 WebDatabaseTable::TypeKey KeywordTable::GetTypeKey() const { 151 return GetKey(); 152 } 153 154 bool KeywordTable::Init(sql::Connection* db, sql::MetaTable* meta_table) { 155 WebDatabaseTable::Init(db, meta_table); 156 return db_->DoesTableExist("keywords") || 157 db_->Execute("CREATE TABLE keywords (" 158 "id INTEGER PRIMARY KEY," 159 "short_name VARCHAR NOT NULL," 160 "keyword VARCHAR NOT NULL," 161 "favicon_url VARCHAR NOT NULL," 162 "url VARCHAR NOT NULL," 163 "safe_for_autoreplace INTEGER," 164 "originating_url VARCHAR," 165 "date_created INTEGER DEFAULT 0," 166 "usage_count INTEGER DEFAULT 0," 167 "input_encodings VARCHAR," 168 "show_in_default_list INTEGER," 169 "suggest_url VARCHAR," 170 "prepopulate_id INTEGER DEFAULT 0," 171 "created_by_policy INTEGER DEFAULT 0," 172 "instant_url VARCHAR," 173 "last_modified INTEGER DEFAULT 0," 174 "sync_guid VARCHAR," 175 "alternate_urls VARCHAR," 176 "search_terms_replacement_key VARCHAR," 177 "image_url VARCHAR," 178 "search_url_post_params VARCHAR," 179 "suggest_url_post_params VARCHAR," 180 "instant_url_post_params VARCHAR," 181 "image_url_post_params VARCHAR)"); 182 } 183 184 bool KeywordTable::IsSyncable() { 185 return true; 186 } 187 188 bool KeywordTable::MigrateToVersion(int version, 189 bool* update_compatible_version) { 190 // Migrate if necessary. 191 switch (version) { 192 case 21: 193 *update_compatible_version = true; 194 return MigrateToVersion21AutoGenerateKeywordColumn(); 195 case 25: 196 *update_compatible_version = true; 197 return MigrateToVersion25AddLogoIDColumn(); 198 case 26: 199 *update_compatible_version = true; 200 return MigrateToVersion26AddCreatedByPolicyColumn(); 201 case 28: 202 *update_compatible_version = true; 203 return MigrateToVersion28SupportsInstantColumn(); 204 case 29: 205 *update_compatible_version = true; 206 return MigrateToVersion29InstantURLToSupportsInstant(); 207 case 38: 208 *update_compatible_version = true; 209 return MigrateToVersion38AddLastModifiedColumn(); 210 case 39: 211 *update_compatible_version = true; 212 return MigrateToVersion39AddSyncGUIDColumn(); 213 case 44: 214 *update_compatible_version = true; 215 return MigrateToVersion44AddDefaultSearchProviderBackup(); 216 case 45: 217 *update_compatible_version = true; 218 return MigrateToVersion45RemoveLogoIDAndAutogenerateColumns(); 219 case 47: 220 *update_compatible_version = true; 221 return MigrateToVersion47AddAlternateURLsColumn(); 222 case 48: 223 *update_compatible_version = true; 224 return MigrateToVersion48RemoveKeywordsBackup(); 225 case 49: 226 *update_compatible_version = true; 227 return MigrateToVersion49AddSearchTermsReplacementKeyColumn(); 228 case 52: 229 *update_compatible_version = true; 230 return MigrateToVersion52AddImageSearchAndPOSTSupport(); 231 } 232 233 return true; 234 } 235 236 bool KeywordTable::AddKeyword(const TemplateURLData& data) { 237 DCHECK(data.id); 238 std::string query("INSERT INTO keywords (" + GetKeywordColumns() + ") " 239 "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); 240 sql::Statement s(db_->GetUniqueStatement(query.c_str())); 241 BindURLToStatement(data, &s, 0, 1); 242 243 return s.Run(); 244 } 245 246 bool KeywordTable::RemoveKeyword(TemplateURLID id) { 247 DCHECK(id); 248 sql::Statement s( 249 db_->GetUniqueStatement("DELETE FROM keywords WHERE id = ?")); 250 s.BindInt64(0, id); 251 252 return s.Run(); 253 } 254 255 bool KeywordTable::GetKeywords(Keywords* keywords) { 256 std::string query("SELECT " + GetKeywordColumns() + 257 " FROM keywords ORDER BY id ASC"); 258 sql::Statement s(db_->GetUniqueStatement(query.c_str())); 259 260 std::set<TemplateURLID> bad_entries; 261 while (s.Step()) { 262 keywords->push_back(TemplateURLData()); 263 if (!GetKeywordDataFromStatement(s, &keywords->back())) { 264 bad_entries.insert(s.ColumnInt64(0)); 265 keywords->pop_back(); 266 } 267 } 268 bool succeeded = s.Succeeded(); 269 for (std::set<TemplateURLID>::const_iterator i(bad_entries.begin()); 270 i != bad_entries.end(); ++i) 271 succeeded &= RemoveKeyword(*i); 272 return succeeded; 273 } 274 275 bool KeywordTable::UpdateKeyword(const TemplateURLData& data) { 276 DCHECK(data.id); 277 sql::Statement s(db_->GetUniqueStatement("UPDATE keywords SET short_name=?, " 278 "keyword=?, favicon_url=?, url=?, safe_for_autoreplace=?, " 279 "originating_url=?, date_created=?, usage_count=?, input_encodings=?, " 280 "show_in_default_list=?, suggest_url=?, prepopulate_id=?, " 281 "created_by_policy=?, instant_url=?, last_modified=?, sync_guid=?, " 282 "alternate_urls=?, search_terms_replacement_key=?, image_url=?," 283 "search_url_post_params=?, suggest_url_post_params=?, " 284 "instant_url_post_params=?, image_url_post_params=? WHERE id=?")); 285 BindURLToStatement(data, &s, 23, 0); // "23" binds id() as the last item. 286 287 return s.Run(); 288 } 289 290 bool KeywordTable::SetDefaultSearchProviderID(int64 id) { 291 return meta_table_->SetValue(kDefaultSearchProviderKey, id); 292 } 293 294 int64 KeywordTable::GetDefaultSearchProviderID() { 295 int64 value = kInvalidTemplateURLID; 296 meta_table_->GetValue(kDefaultSearchProviderKey, &value); 297 return value; 298 } 299 300 bool KeywordTable::SetBuiltinKeywordVersion(int version) { 301 return meta_table_->SetValue(kBuiltinKeywordVersion, version); 302 } 303 304 int KeywordTable::GetBuiltinKeywordVersion() { 305 int version = 0; 306 return meta_table_->GetValue(kBuiltinKeywordVersion, &version) ? version : 0; 307 } 308 309 // static 310 std::string KeywordTable::GetKeywordColumns() { 311 return ColumnsForVersion(WebDatabase::kCurrentVersionNumber, false); 312 } 313 314 bool KeywordTable::MigrateToVersion21AutoGenerateKeywordColumn() { 315 return db_->Execute("ALTER TABLE keywords ADD COLUMN autogenerate_keyword " 316 "INTEGER DEFAULT 0"); 317 } 318 319 bool KeywordTable::MigrateToVersion25AddLogoIDColumn() { 320 return db_->Execute( 321 "ALTER TABLE keywords ADD COLUMN logo_id INTEGER DEFAULT 0"); 322 } 323 324 bool KeywordTable::MigrateToVersion26AddCreatedByPolicyColumn() { 325 return db_->Execute("ALTER TABLE keywords ADD COLUMN created_by_policy " 326 "INTEGER DEFAULT 0"); 327 } 328 329 bool KeywordTable::MigrateToVersion28SupportsInstantColumn() { 330 return db_->Execute("ALTER TABLE keywords ADD COLUMN supports_instant " 331 "INTEGER DEFAULT 0"); 332 } 333 334 bool KeywordTable::MigrateToVersion29InstantURLToSupportsInstant() { 335 sql::Transaction transaction(db_); 336 return transaction.Begin() && 337 db_->Execute("ALTER TABLE keywords ADD COLUMN instant_url VARCHAR") && 338 db_->Execute("CREATE TABLE keywords_temp (" 339 "id INTEGER PRIMARY KEY," 340 "short_name VARCHAR NOT NULL," 341 "keyword VARCHAR NOT NULL," 342 "favicon_url VARCHAR NOT NULL," 343 "url VARCHAR NOT NULL," 344 "safe_for_autoreplace INTEGER," 345 "originating_url VARCHAR," 346 "date_created INTEGER DEFAULT 0," 347 "usage_count INTEGER DEFAULT 0," 348 "input_encodings VARCHAR," 349 "show_in_default_list INTEGER," 350 "suggest_url VARCHAR," 351 "prepopulate_id INTEGER DEFAULT 0," 352 "autogenerate_keyword INTEGER DEFAULT 0," 353 "logo_id INTEGER DEFAULT 0," 354 "created_by_policy INTEGER DEFAULT 0," 355 "instant_url VARCHAR)") && 356 db_->Execute("INSERT INTO keywords_temp SELECT id, short_name, keyword, " 357 "favicon_url, url, safe_for_autoreplace, originating_url, " 358 "date_created, usage_count, input_encodings, " 359 "show_in_default_list, suggest_url, prepopulate_id, " 360 "autogenerate_keyword, logo_id, created_by_policy, " 361 "instant_url FROM keywords") && 362 db_->Execute("DROP TABLE keywords") && 363 db_->Execute("ALTER TABLE keywords_temp RENAME TO keywords") && 364 transaction.Commit(); 365 } 366 367 bool KeywordTable::MigrateToVersion38AddLastModifiedColumn() { 368 return db_->Execute( 369 "ALTER TABLE keywords ADD COLUMN last_modified INTEGER DEFAULT 0"); 370 } 371 372 bool KeywordTable::MigrateToVersion39AddSyncGUIDColumn() { 373 return db_->Execute("ALTER TABLE keywords ADD COLUMN sync_guid VARCHAR"); 374 } 375 376 bool KeywordTable::MigrateToVersion44AddDefaultSearchProviderBackup() { 377 sql::Transaction transaction(db_); 378 if (!transaction.Begin()) 379 return false; 380 381 int64 default_search_id = GetDefaultSearchProviderID(); 382 if (!meta_table_->SetValue("Default Search Provider ID Backup", 383 default_search_id)) 384 return false; 385 386 // Backup of all keywords. 387 if (db_->DoesTableExist("keywords_backup") && 388 !db_->Execute("DROP TABLE keywords_backup")) 389 return false; 390 391 std::string query("CREATE TABLE keywords_backup AS SELECT " + 392 ColumnsForVersion(44, false) + " FROM keywords ORDER BY id ASC"); 393 if (!db_->Execute(query.c_str())) 394 return false; 395 396 return transaction.Commit(); 397 } 398 399 bool KeywordTable::MigrateToVersion45RemoveLogoIDAndAutogenerateColumns() { 400 sql::Transaction transaction(db_); 401 if (!transaction.Begin()) 402 return false; 403 404 // The version 43 migration should have been written to do this, but since it 405 // wasn't, we'll do it now. Unfortunately a previous change deleted this for 406 // some users, so we can't be sure this will succeed (so don't bail on error). 407 meta_table_->DeleteKey("Default Search Provider Backup"); 408 409 if (!MigrateKeywordsTableForVersion45("keywords")) 410 return false; 411 412 // Migrate the keywords backup table as well. 413 if (!MigrateKeywordsTableForVersion45("keywords_backup") || 414 !meta_table_->SetValue("Default Search Provider ID Backup Signature", 415 std::string())) 416 return false; 417 418 return transaction.Commit(); 419 } 420 421 bool KeywordTable::MigrateToVersion47AddAlternateURLsColumn() { 422 sql::Transaction transaction(db_); 423 424 // Fill the |alternate_urls| column with empty strings, otherwise it breaks 425 // code relying on GetTableContents that concatenates the strings from all 426 // the columns. 427 if (!transaction.Begin() || 428 !db_->Execute("ALTER TABLE keywords ADD COLUMN " 429 "alternate_urls VARCHAR DEFAULT ''")) 430 return false; 431 432 // Migrate the keywords backup table as well. 433 if (!db_->Execute("ALTER TABLE keywords_backup ADD COLUMN " 434 "alternate_urls VARCHAR DEFAULT ''") || 435 !meta_table_->SetValue("Default Search Provider ID Backup Signature", 436 std::string())) 437 return false; 438 439 return transaction.Commit(); 440 } 441 442 bool KeywordTable::MigrateToVersion48RemoveKeywordsBackup() { 443 sql::Transaction transaction(db_); 444 if (!transaction.Begin()) 445 return false; 446 447 if (!meta_table_->DeleteKey("Default Search Provider ID Backup") || 448 !meta_table_->DeleteKey("Default Search Provider ID Backup Signature")) 449 return false; 450 451 if (!db_->Execute("DROP TABLE keywords_backup")) 452 return false; 453 454 return transaction.Commit(); 455 } 456 457 bool KeywordTable::MigrateToVersion49AddSearchTermsReplacementKeyColumn() { 458 sql::Transaction transaction(db_); 459 460 // Fill the |search_terms_replacement_key| column with empty strings; 461 // See comments in MigrateToVersion47AddAlternateURLsColumn(). 462 if (!transaction.Begin() || 463 !db_->Execute("ALTER TABLE keywords ADD COLUMN " 464 "search_terms_replacement_key VARCHAR DEFAULT ''")) 465 return false; 466 467 return transaction.Commit(); 468 } 469 470 bool KeywordTable::MigrateToVersion52AddImageSearchAndPOSTSupport() { 471 sql::Transaction transaction(db_); 472 473 // Fill the |image_url| and other four post params columns with empty strings; 474 return transaction.Begin() && 475 db_->Execute("ALTER TABLE keywords ADD COLUMN image_url " 476 "VARCHAR DEFAULT ''") && 477 db_->Execute("ALTER TABLE keywords ADD COLUMN search_url_post_params " 478 "VARCHAR DEFAULT ''") && 479 db_->Execute("ALTER TABLE keywords ADD COLUMN suggest_url_post_params " 480 "VARCHAR DEFAULT ''") && 481 db_->Execute("ALTER TABLE keywords ADD COLUMN instant_url_post_params " 482 "VARCHAR DEFAULT ''") && 483 db_->Execute("ALTER TABLE keywords ADD COLUMN image_url_post_params " 484 "VARCHAR DEFAULT ''") && 485 transaction.Commit(); 486 } 487 488 // static 489 bool KeywordTable::GetKeywordDataFromStatement(const sql::Statement& s, 490 TemplateURLData* data) { 491 DCHECK(data); 492 493 data->short_name = s.ColumnString16(1); 494 data->SetKeyword(s.ColumnString16(2)); 495 // Due to past bugs, we might have persisted entries with empty URLs. Avoid 496 // reading these out. (GetKeywords() will delete these entries on return.) 497 // NOTE: This code should only be needed as long as we might be reading such 498 // potentially-old data and can be removed afterward. 499 if (s.ColumnString(4).empty()) 500 return false; 501 data->SetURL(s.ColumnString(4)); 502 data->suggestions_url = s.ColumnString(11); 503 data->instant_url = s.ColumnString(14); 504 data->image_url = s.ColumnString(19); 505 data->search_url_post_params = s.ColumnString(20); 506 data->suggestions_url_post_params = s.ColumnString(21); 507 data->instant_url_post_params = s.ColumnString(22); 508 data->image_url_post_params = s.ColumnString(23); 509 data->favicon_url = GURL(s.ColumnString(3)); 510 data->originating_url = GURL(s.ColumnString(6)); 511 data->show_in_default_list = s.ColumnBool(10); 512 data->safe_for_autoreplace = s.ColumnBool(5); 513 base::SplitString(s.ColumnString(9), ';', &data->input_encodings); 514 data->id = s.ColumnInt64(0); 515 data->date_created = Time::FromTimeT(s.ColumnInt64(7)); 516 data->last_modified = Time::FromTimeT(s.ColumnInt64(15)); 517 data->created_by_policy = s.ColumnBool(13); 518 data->usage_count = s.ColumnInt(8); 519 data->prepopulate_id = s.ColumnInt(12); 520 data->sync_guid = s.ColumnString(16); 521 522 data->alternate_urls.clear(); 523 base::JSONReader json_reader; 524 scoped_ptr<Value> value(json_reader.ReadToValue(s.ColumnString(17))); 525 ListValue* alternate_urls_value; 526 if (value.get() && value->GetAsList(&alternate_urls_value)) { 527 std::string alternate_url; 528 for (size_t i = 0; i < alternate_urls_value->GetSize(); ++i) { 529 if (alternate_urls_value->GetString(i, &alternate_url)) 530 data->alternate_urls.push_back(alternate_url); 531 } 532 } 533 534 data->search_terms_replacement_key = s.ColumnString(18); 535 536 return true; 537 } 538 539 bool KeywordTable::GetTableContents(const char* table_name, 540 int table_version, 541 std::string* contents) { 542 DCHECK(contents); 543 544 if (!db_->DoesTableExist(table_name)) 545 return false; 546 547 contents->clear(); 548 std::string query("SELECT " + ColumnsForVersion(table_version, true) + 549 " FROM " + std::string(table_name) + " ORDER BY id ASC"); 550 sql::Statement s((table_version == WebDatabase::kCurrentVersionNumber) ? 551 db_->GetCachedStatement(sql::StatementID(table_name), query.c_str()) : 552 db_->GetUniqueStatement(query.c_str())); 553 while (s.Step()) 554 *contents += s.ColumnString(0); 555 return s.Succeeded(); 556 } 557 558 bool KeywordTable::GetKeywordAsString(TemplateURLID id, 559 const std::string& table_name, 560 std::string* result) { 561 std::string query("SELECT " + 562 ColumnsForVersion(WebDatabase::kCurrentVersionNumber, true) + 563 " FROM " + table_name + " WHERE id=?"); 564 sql::Statement s(db_->GetUniqueStatement(query.c_str())); 565 s.BindInt64(0, id); 566 567 if (!s.Step()) { 568 LOG_IF(WARNING, s.Succeeded()) << "No keyword with id: " << id 569 << ", ignoring."; 570 return true; 571 } 572 573 if (!s.Succeeded()) 574 return false; 575 576 *result = s.ColumnString(0); 577 return true; 578 } 579 580 bool KeywordTable::MigrateKeywordsTableForVersion45(const std::string& name) { 581 // Create a new table without the columns we're dropping. 582 if (!db_->Execute("CREATE TABLE keywords_temp (" 583 "id INTEGER PRIMARY KEY," 584 "short_name VARCHAR NOT NULL," 585 "keyword VARCHAR NOT NULL," 586 "favicon_url VARCHAR NOT NULL," 587 "url VARCHAR NOT NULL," 588 "safe_for_autoreplace INTEGER," 589 "originating_url VARCHAR," 590 "date_created INTEGER DEFAULT 0," 591 "usage_count INTEGER DEFAULT 0," 592 "input_encodings VARCHAR," 593 "show_in_default_list INTEGER," 594 "suggest_url VARCHAR," 595 "prepopulate_id INTEGER DEFAULT 0," 596 "created_by_policy INTEGER DEFAULT 0," 597 "instant_url VARCHAR," 598 "last_modified INTEGER DEFAULT 0," 599 "sync_guid VARCHAR)")) 600 return false; 601 std::string sql("INSERT INTO keywords_temp SELECT " + 602 ColumnsForVersion(46, false) + " FROM " + name); 603 if (!db_->Execute(sql.c_str())) 604 return false; 605 606 // NOTE: The ORDER BY here ensures that the uniquing process for keywords will 607 // happen identically on both the normal and backup tables. 608 sql = "SELECT id, keyword, url, autogenerate_keyword FROM " + name + 609 " ORDER BY id ASC"; 610 sql::Statement s(db_->GetUniqueStatement(sql.c_str())); 611 string16 placeholder_keyword(ASCIIToUTF16("dummy")); 612 std::set<string16> keywords; 613 while (s.Step()) { 614 string16 keyword(s.ColumnString16(1)); 615 bool generate_keyword = keyword.empty() || s.ColumnBool(3); 616 if (generate_keyword) 617 keyword = placeholder_keyword; 618 TemplateURLData data; 619 data.SetKeyword(keyword); 620 data.SetURL(s.ColumnString(2)); 621 TemplateURL turl(NULL, data); 622 // Don't persist extension keywords to disk. These will get added to the 623 // TemplateURLService as the extensions are loaded. 624 bool delete_entry = turl.IsExtensionKeyword(); 625 if (!delete_entry && generate_keyword) { 626 // Explicitly generate keywords for all rows with the autogenerate bit set 627 // or where the keyword is empty. 628 SearchTermsData terms_data; 629 GURL url(TemplateURLService::GenerateSearchURLUsingTermsData(&turl, 630 terms_data)); 631 if (!url.is_valid()) { 632 delete_entry = true; 633 } else { 634 // Ensure autogenerated keywords are unique. 635 keyword = TemplateURLService::GenerateKeyword(url); 636 while (keywords.count(keyword)) 637 keyword.append(ASCIIToUTF16("_")); 638 sql::Statement u(db_->GetUniqueStatement( 639 "UPDATE keywords_temp SET keyword=? WHERE id=?")); 640 u.BindString16(0, keyword); 641 u.BindInt64(1, s.ColumnInt64(0)); 642 if (!u.Run()) 643 return false; 644 } 645 } 646 if (delete_entry) { 647 sql::Statement u(db_->GetUniqueStatement( 648 "DELETE FROM keywords_temp WHERE id=?")); 649 u.BindInt64(0, s.ColumnInt64(0)); 650 if (!u.Run()) 651 return false; 652 } else { 653 keywords.insert(keyword); 654 } 655 } 656 657 // Replace the old table with the new one. 658 sql = "DROP TABLE " + name; 659 if (!db_->Execute(sql.c_str())) 660 return false; 661 sql = "ALTER TABLE keywords_temp RENAME TO " + name; 662 return db_->Execute(sql.c_str()); 663 } 664