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/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