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