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