OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "chrome/browser/history/url_database.h" | |
6 | |
7 #include <algorithm> | |
8 #include <limits> | |
9 #include <string> | |
10 #include <vector> | |
11 | |
12 #include "base/i18n/case_conversion.h" | |
13 #include "base/strings/utf_string_conversions.h" | |
14 #include "chrome/common/url_constants.h" | |
15 #include "net/base/net_util.h" | |
16 #include "sql/statement.h" | |
17 #include "ui/base/l10n/l10n_util.h" | |
18 #include "url/gurl.h" | |
19 | |
20 namespace history { | |
21 | |
22 const char URLDatabase::kURLRowFields[] = HISTORY_URL_ROW_FIELDS; | |
23 const int URLDatabase::kNumURLRowFields = 9; | |
24 | |
25 URLDatabase::URLEnumeratorBase::URLEnumeratorBase() | |
26 : initialized_(false) { | |
27 } | |
28 | |
29 URLDatabase::URLEnumeratorBase::~URLEnumeratorBase() { | |
30 } | |
31 | |
32 URLDatabase::URLEnumerator::URLEnumerator() { | |
33 } | |
34 | |
35 bool URLDatabase::URLEnumerator::GetNextURL(URLRow* r) { | |
36 if (statement_.Step()) { | |
37 FillURLRow(statement_, r); | |
38 return true; | |
39 } | |
40 return false; | |
41 } | |
42 | |
43 URLDatabase::URLDatabase() | |
44 : has_keyword_search_terms_(false) { | |
45 } | |
46 | |
47 URLDatabase::~URLDatabase() { | |
48 } | |
49 | |
50 // static | |
51 std::string URLDatabase::GURLToDatabaseURL(const GURL& gurl) { | |
52 // TODO(brettw): do something fancy here with encoding, etc. | |
53 | |
54 // Strip username and password from URL before sending to DB. | |
55 GURL::Replacements replacements; | |
56 replacements.ClearUsername(); | |
57 replacements.ClearPassword(); | |
58 | |
59 return (gurl.ReplaceComponents(replacements)).spec(); | |
60 } | |
61 | |
62 // Convenience to fill a history::URLRow. Must be in sync with the fields in | |
63 // kURLRowFields. | |
64 void URLDatabase::FillURLRow(sql::Statement& s, history::URLRow* i) { | |
65 DCHECK(i); | |
66 i->id_ = s.ColumnInt64(0); | |
67 i->url_ = GURL(s.ColumnString(1)); | |
68 i->title_ = s.ColumnString16(2); | |
69 i->visit_count_ = s.ColumnInt(3); | |
70 i->typed_count_ = s.ColumnInt(4); | |
71 i->last_visit_ = base::Time::FromInternalValue(s.ColumnInt64(5)); | |
72 i->hidden_ = s.ColumnInt(6) != 0; | |
73 } | |
74 | |
75 bool URLDatabase::GetURLRow(URLID url_id, URLRow* info) { | |
76 // TODO(brettw) We need check for empty URLs to handle the case where | |
77 // there are old URLs in the database that are empty that got in before | |
78 // we added any checks. We should eventually be able to remove it | |
79 // when all inputs are using GURL (which prohibit empty input). | |
80 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, | |
81 "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls WHERE id=?")); | |
82 statement.BindInt64(0, url_id); | |
83 | |
84 if (statement.Step()) { | |
85 FillURLRow(statement, info); | |
86 return true; | |
87 } | |
88 return false; | |
89 } | |
90 | |
91 bool URLDatabase::GetAllTypedUrls(URLRows* urls) { | |
92 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, | |
93 "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls WHERE typed_count > 0")); | |
94 | |
95 while (statement.Step()) { | |
96 URLRow info; | |
97 FillURLRow(statement, &info); | |
98 urls->push_back(info); | |
99 } | |
100 return true; | |
101 } | |
102 | |
103 URLID URLDatabase::GetRowForURL(const GURL& url, history::URLRow* info) { | |
104 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, | |
105 "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls WHERE url=?")); | |
106 std::string url_string = GURLToDatabaseURL(url); | |
107 statement.BindString(0, url_string); | |
108 | |
109 if (!statement.Step()) | |
110 return 0; // no data | |
111 | |
112 if (info) | |
113 FillURLRow(statement, info); | |
114 return statement.ColumnInt64(0); | |
115 } | |
116 | |
117 bool URLDatabase::UpdateURLRow(URLID url_id, | |
118 const history::URLRow& info) { | |
119 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, | |
120 "UPDATE urls SET title=?,visit_count=?,typed_count=?,last_visit_time=?," | |
121 "hidden=?" | |
122 "WHERE id=?")); | |
123 statement.BindString16(0, info.title()); | |
124 statement.BindInt(1, info.visit_count()); | |
125 statement.BindInt(2, info.typed_count()); | |
126 statement.BindInt64(3, info.last_visit().ToInternalValue()); | |
127 statement.BindInt(4, info.hidden() ? 1 : 0); | |
128 statement.BindInt64(5, url_id); | |
129 | |
130 return statement.Run() && GetDB().GetLastChangeCount() > 0; | |
131 } | |
132 | |
133 URLID URLDatabase::AddURLInternal(const history::URLRow& info, | |
134 bool is_temporary) { | |
135 // This function is used to insert into two different tables, so we have to | |
136 // do some shuffling. Unfortinately, we can't use the macro | |
137 // HISTORY_URL_ROW_FIELDS because that specifies the table name which is | |
138 // invalid in the insert syntax. | |
139 #define ADDURL_COMMON_SUFFIX \ | |
140 " (url, title, visit_count, typed_count, "\ | |
141 "last_visit_time, hidden) "\ | |
142 "VALUES (?,?,?,?,?,?)" | |
143 const char* statement_name; | |
144 const char* statement_sql; | |
145 if (is_temporary) { | |
146 statement_name = "AddURLTemporary"; | |
147 statement_sql = "INSERT INTO temp_urls" ADDURL_COMMON_SUFFIX; | |
148 } else { | |
149 statement_name = "AddURL"; | |
150 statement_sql = "INSERT INTO urls" ADDURL_COMMON_SUFFIX; | |
151 } | |
152 #undef ADDURL_COMMON_SUFFIX | |
153 | |
154 sql::Statement statement(GetDB().GetCachedStatement( | |
155 sql::StatementID(statement_name), statement_sql)); | |
156 statement.BindString(0, GURLToDatabaseURL(info.url())); | |
157 statement.BindString16(1, info.title()); | |
158 statement.BindInt(2, info.visit_count()); | |
159 statement.BindInt(3, info.typed_count()); | |
160 statement.BindInt64(4, info.last_visit().ToInternalValue()); | |
161 statement.BindInt(5, info.hidden() ? 1 : 0); | |
162 | |
163 if (!statement.Run()) { | |
164 VLOG(0) << "Failed to add url " << info.url().possibly_invalid_spec() | |
165 << " to table history.urls."; | |
166 return 0; | |
167 } | |
168 return GetDB().GetLastInsertRowId(); | |
169 } | |
170 | |
171 bool URLDatabase::InsertOrUpdateURLRowByID(const history::URLRow& info) { | |
172 // SQLite does not support INSERT OR UPDATE, however, it does have INSERT OR | |
173 // REPLACE, which is feasible to use, because of the following. | |
174 // * Before INSERTing, REPLACE will delete all pre-existing rows that cause | |
175 // constraint violations. Here, we only have a PRIMARY KEY constraint, so | |
176 // the only row that might get deleted is an old one with the same ID. | |
177 // * Another difference between the two flavors is that the latter actually | |
178 // deletes the old row, and thus the old values are lost in columns which | |
179 // are not explicitly assigned new values. This is not an issue, however, | |
180 // as we assign values to all columns. | |
181 // * When rows are deleted due to constraint violations, the delete triggers | |
182 // may not be invoked. As of now, we do not have any delete triggers. | |
183 // For more details, see: http://www.sqlite.org/lang_conflict.html. | |
184 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, | |
185 "INSERT OR REPLACE INTO urls " | |
186 "(id, url, title, visit_count, typed_count, last_visit_time, hidden) " | |
187 "VALUES (?, ?, ?, ?, ?, ?, ?)")); | |
188 | |
189 statement.BindInt64(0, info.id()); | |
190 statement.BindString(1, GURLToDatabaseURL(info.url())); | |
191 statement.BindString16(2, info.title()); | |
192 statement.BindInt(3, info.visit_count()); | |
193 statement.BindInt(4, info.typed_count()); | |
194 statement.BindInt64(5, info.last_visit().ToInternalValue()); | |
195 statement.BindInt(6, info.hidden() ? 1 : 0); | |
196 | |
197 return statement.Run(); | |
198 } | |
199 | |
200 bool URLDatabase::DeleteURLRow(URLID id) { | |
201 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, | |
202 "DELETE FROM urls WHERE id = ?")); | |
203 statement.BindInt64(0, id); | |
204 | |
205 if (!statement.Run()) | |
206 return false; | |
207 | |
208 // And delete any keyword visits. | |
209 return !has_keyword_search_terms_ || DeleteKeywordSearchTermForURL(id); | |
210 } | |
211 | |
212 bool URLDatabase::CreateTemporaryURLTable() { | |
213 return CreateURLTable(true); | |
214 } | |
215 | |
216 bool URLDatabase::CommitTemporaryURLTable() { | |
217 // See the comments in the header file as well as | |
218 // HistoryBackend::DeleteAllHistory() for more information on how this works | |
219 // and why it does what it does. | |
220 | |
221 // Swap the url table out and replace it with the temporary one. | |
222 if (!GetDB().Execute("DROP TABLE urls")) { | |
223 NOTREACHED() << GetDB().GetErrorMessage(); | |
224 return false; | |
225 } | |
226 if (!GetDB().Execute("ALTER TABLE temp_urls RENAME TO urls")) { | |
227 NOTREACHED() << GetDB().GetErrorMessage(); | |
228 return false; | |
229 } | |
230 | |
231 // Re-create the index over the now permanent URLs table -- this was not there | |
232 // for the temporary table. | |
233 CreateMainURLIndex(); | |
234 | |
235 return true; | |
236 } | |
237 | |
238 bool URLDatabase::InitURLEnumeratorForEverything(URLEnumerator* enumerator) { | |
239 DCHECK(!enumerator->initialized_); | |
240 std::string sql("SELECT "); | |
241 sql.append(kURLRowFields); | |
242 sql.append(" FROM urls"); | |
243 enumerator->statement_.Assign(GetDB().GetUniqueStatement(sql.c_str())); | |
244 enumerator->initialized_ = enumerator->statement_.is_valid(); | |
245 return enumerator->statement_.is_valid(); | |
246 } | |
247 | |
248 bool URLDatabase::InitURLEnumeratorForSignificant(URLEnumerator* enumerator) { | |
249 DCHECK(!enumerator->initialized_); | |
250 std::string sql("SELECT "); | |
251 sql.append(kURLRowFields); | |
252 sql.append(" FROM urls WHERE last_visit_time >= ? OR visit_count >= ? OR " | |
253 "typed_count >= ?"); | |
254 enumerator->statement_.Assign(GetDB().GetUniqueStatement(sql.c_str())); | |
255 enumerator->statement_.BindInt64( | |
256 0, AutocompleteAgeThreshold().ToInternalValue()); | |
257 enumerator->statement_.BindInt(1, kLowQualityMatchVisitLimit); | |
258 enumerator->statement_.BindInt(2, kLowQualityMatchTypedLimit); | |
259 enumerator->initialized_ = enumerator->statement_.is_valid(); | |
260 return enumerator->statement_.is_valid(); | |
261 } | |
262 | |
263 bool URLDatabase::AutocompleteForPrefix(const std::string& prefix, | |
264 size_t max_results, | |
265 bool typed_only, | |
266 URLRows* results) { | |
267 // NOTE: this query originally sorted by starred as the second parameter. But | |
268 // as bookmarks is no longer part of the db we no longer include the order | |
269 // by clause. | |
270 results->clear(); | |
271 const char* sql; | |
272 int line; | |
273 if (typed_only) { | |
274 sql = "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls " | |
275 "WHERE url >= ? AND url < ? AND hidden = 0 AND typed_count > 0 " | |
276 "ORDER BY typed_count DESC, visit_count DESC, last_visit_time DESC " | |
277 "LIMIT ?"; | |
278 line = __LINE__; | |
279 } else { | |
280 sql = "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls " | |
281 "WHERE url >= ? AND url < ? AND hidden = 0 " | |
282 "ORDER BY typed_count DESC, visit_count DESC, last_visit_time DESC " | |
283 "LIMIT ?"; | |
284 line = __LINE__; | |
285 } | |
286 sql::Statement statement( | |
287 GetDB().GetCachedStatement(sql::StatementID(__FILE__, line), sql)); | |
288 | |
289 // We will find all strings between "prefix" and this string, which is prefix | |
290 // followed by the maximum character size. Use 8-bit strings for everything | |
291 // so we can be sure sqlite is comparing everything in 8-bit mode. Otherwise, | |
292 // it will have to convert strings either to UTF-8 or UTF-16 for comparison. | |
293 std::string end_query(prefix); | |
294 end_query.push_back(std::numeric_limits<unsigned char>::max()); | |
295 | |
296 statement.BindString(0, prefix); | |
297 statement.BindString(1, end_query); | |
298 statement.BindInt(2, static_cast<int>(max_results)); | |
299 | |
300 while (statement.Step()) { | |
301 history::URLRow info; | |
302 FillURLRow(statement, &info); | |
303 if (info.url().is_valid()) | |
304 results->push_back(info); | |
305 } | |
306 return !results->empty(); | |
307 } | |
308 | |
309 bool URLDatabase::IsTypedHost(const std::string& host) { | |
310 const char* schemes[] = { | |
311 url::kHttpScheme, | |
312 url::kHttpsScheme, | |
313 url::kFtpScheme | |
314 }; | |
315 URLRows dummy; | |
316 for (size_t i = 0; i < arraysize(schemes); ++i) { | |
317 std::string scheme_and_host(schemes[i]); | |
318 scheme_and_host += url::kStandardSchemeSeparator + host; | |
319 if (AutocompleteForPrefix(scheme_and_host + '/', 1, true, &dummy) || | |
320 AutocompleteForPrefix(scheme_and_host + ':', 1, true, &dummy)) | |
321 return true; | |
322 } | |
323 return false; | |
324 } | |
325 | |
326 bool URLDatabase::FindShortestURLFromBase(const std::string& base, | |
327 const std::string& url, | |
328 int min_visits, | |
329 int min_typed, | |
330 bool allow_base, | |
331 history::URLRow* info) { | |
332 // Select URLs that start with |base| and are prefixes of |url|. All parts | |
333 // of this query except the substr() call can be done using the index. We | |
334 // could do this query with a couple of LIKE or GLOB statements as well, but | |
335 // those wouldn't use the index, and would run into problems with "wildcard" | |
336 // characters that appear in URLs (% for LIKE, or *, ? for GLOB). | |
337 std::string sql("SELECT "); | |
338 sql.append(kURLRowFields); | |
339 sql.append(" FROM urls WHERE url "); | |
340 sql.append(allow_base ? ">=" : ">"); | |
341 sql.append(" ? AND url < :end AND url = substr(:end, 1, length(url)) " | |
342 "AND hidden = 0 AND visit_count >= ? AND typed_count >= ? " | |
343 "ORDER BY url LIMIT 1"); | |
344 sql::Statement statement(GetDB().GetUniqueStatement(sql.c_str())); | |
345 statement.BindString(0, base); | |
346 statement.BindString(1, url); // :end | |
347 statement.BindInt(2, min_visits); | |
348 statement.BindInt(3, min_typed); | |
349 | |
350 if (!statement.Step()) | |
351 return false; | |
352 | |
353 DCHECK(info); | |
354 FillURLRow(statement, info); | |
355 return true; | |
356 } | |
357 | |
358 bool URLDatabase::GetTextMatches(const base::string16& query, | |
359 URLRows* results) { | |
360 ScopedVector<query_parser::QueryNode> query_nodes; | |
361 query_parser_.ParseQueryNodes(query, &query_nodes.get()); | |
362 | |
363 results->clear(); | |
364 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, | |
365 "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls WHERE hidden = 0")); | |
366 | |
367 while (statement.Step()) { | |
368 query_parser::QueryWordVector query_words; | |
369 base::string16 url = base::i18n::ToLower(statement.ColumnString16(1)); | |
370 query_parser_.ExtractQueryWords(url, &query_words); | |
371 GURL gurl(url); | |
372 if (gurl.is_valid()) { | |
373 // Decode punycode to match IDN. | |
374 // |query_words| won't be shown to user - therefore we can use empty | |
375 // |languages| to reduce dependency (no need to call PrefService). | |
376 base::string16 ascii = base::ASCIIToUTF16(gurl.host()); | |
377 base::string16 utf = net::IDNToUnicode(gurl.host(), std::string()); | |
378 if (ascii != utf) | |
379 query_parser_.ExtractQueryWords(utf, &query_words); | |
380 } | |
381 base::string16 title = base::i18n::ToLower(statement.ColumnString16(2)); | |
382 query_parser_.ExtractQueryWords(title, &query_words); | |
383 | |
384 if (query_parser_.DoesQueryMatch(query_words, query_nodes.get())) { | |
385 history::URLResult info; | |
386 FillURLRow(statement, &info); | |
387 if (info.url().is_valid()) | |
388 results->push_back(info); | |
389 } | |
390 } | |
391 return !results->empty(); | |
392 } | |
393 | |
394 bool URLDatabase::InitKeywordSearchTermsTable() { | |
395 has_keyword_search_terms_ = true; | |
396 if (!GetDB().DoesTableExist("keyword_search_terms")) { | |
397 if (!GetDB().Execute("CREATE TABLE keyword_search_terms (" | |
398 "keyword_id INTEGER NOT NULL," // ID of the TemplateURL. | |
399 "url_id INTEGER NOT NULL," // ID of the url. | |
400 "lower_term LONGVARCHAR NOT NULL," // The search term, in lower case. | |
401 "term LONGVARCHAR NOT NULL)")) // The actual search term. | |
402 return false; | |
403 } | |
404 return true; | |
405 } | |
406 | |
407 bool URLDatabase::CreateKeywordSearchTermsIndices() { | |
408 // For searching. | |
409 if (!GetDB().Execute( | |
410 "CREATE INDEX IF NOT EXISTS keyword_search_terms_index1 ON " | |
411 "keyword_search_terms (keyword_id, lower_term)")) { | |
412 return false; | |
413 } | |
414 | |
415 // For deletion. | |
416 if (!GetDB().Execute( | |
417 "CREATE INDEX IF NOT EXISTS keyword_search_terms_index2 ON " | |
418 "keyword_search_terms (url_id)")) { | |
419 return false; | |
420 } | |
421 | |
422 // For query or deletion by term. | |
423 if (!GetDB().Execute( | |
424 "CREATE INDEX IF NOT EXISTS keyword_search_terms_index3 ON " | |
425 "keyword_search_terms (term)")) { | |
426 return false; | |
427 } | |
428 return true; | |
429 } | |
430 | |
431 bool URLDatabase::DropKeywordSearchTermsTable() { | |
432 // This will implicitly delete the indices over the table. | |
433 return GetDB().Execute("DROP TABLE keyword_search_terms"); | |
434 } | |
435 | |
436 bool URLDatabase::SetKeywordSearchTermsForURL(URLID url_id, | |
437 KeywordID keyword_id, | |
438 const base::string16& term) { | |
439 DCHECK(url_id && keyword_id && !term.empty()); | |
440 | |
441 sql::Statement exist_statement(GetDB().GetCachedStatement(SQL_FROM_HERE, | |
442 "SELECT term FROM keyword_search_terms " | |
443 "WHERE keyword_id = ? AND url_id = ?")); | |
444 exist_statement.BindInt64(0, keyword_id); | |
445 exist_statement.BindInt64(1, url_id); | |
446 | |
447 if (exist_statement.Step()) | |
448 return true; // Term already exists, no need to add it. | |
449 | |
450 if (!exist_statement.Succeeded()) | |
451 return false; | |
452 | |
453 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, | |
454 "INSERT INTO keyword_search_terms (keyword_id, url_id, lower_term, term) " | |
455 "VALUES (?,?,?,?)")); | |
456 statement.BindInt64(0, keyword_id); | |
457 statement.BindInt64(1, url_id); | |
458 statement.BindString16(2, base::i18n::ToLower(term)); | |
459 statement.BindString16(3, term); | |
460 return statement.Run(); | |
461 } | |
462 | |
463 bool URLDatabase::GetKeywordSearchTermRow(URLID url_id, | |
464 KeywordSearchTermRow* row) { | |
465 DCHECK(url_id); | |
466 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, | |
467 "SELECT keyword_id, term FROM keyword_search_terms WHERE url_id=?")); | |
468 statement.BindInt64(0, url_id); | |
469 | |
470 if (!statement.Step()) | |
471 return false; | |
472 | |
473 if (row) { | |
474 row->url_id = url_id; | |
475 row->keyword_id = statement.ColumnInt64(0); | |
476 row->term = statement.ColumnString16(1); | |
477 } | |
478 return true; | |
479 } | |
480 | |
481 bool URLDatabase::GetKeywordSearchTermRows( | |
482 const base::string16& term, | |
483 std::vector<KeywordSearchTermRow>* rows) { | |
484 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, | |
485 "SELECT keyword_id, url_id FROM keyword_search_terms WHERE term=?")); | |
486 statement.BindString16(0, term); | |
487 | |
488 if (!statement.is_valid()) | |
489 return false; | |
490 | |
491 while (statement.Step()) { | |
492 KeywordSearchTermRow row; | |
493 row.url_id = statement.ColumnInt64(1); | |
494 row.keyword_id = statement.ColumnInt64(0); | |
495 row.term = term; | |
496 rows->push_back(row); | |
497 } | |
498 return true; | |
499 } | |
500 | |
501 void URLDatabase::DeleteAllSearchTermsForKeyword( | |
502 KeywordID keyword_id) { | |
503 DCHECK(keyword_id); | |
504 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, | |
505 "DELETE FROM keyword_search_terms WHERE keyword_id=?")); | |
506 statement.BindInt64(0, keyword_id); | |
507 | |
508 statement.Run(); | |
509 } | |
510 | |
511 void URLDatabase::GetMostRecentKeywordSearchTerms( | |
512 KeywordID keyword_id, | |
513 const base::string16& prefix, | |
514 int max_count, | |
515 std::vector<KeywordSearchTermVisit>* matches) { | |
516 // NOTE: the keyword_id can be zero if on first run the user does a query | |
517 // before the TemplateURLService has finished loading. As the chances of this | |
518 // occurring are small, we ignore it. | |
519 if (!keyword_id) | |
520 return; | |
521 | |
522 DCHECK(!prefix.empty()); | |
523 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, | |
524 "SELECT DISTINCT kv.term, u.visit_count, u.last_visit_time " | |
525 "FROM keyword_search_terms kv " | |
526 "JOIN urls u ON kv.url_id = u.id " | |
527 "WHERE kv.keyword_id = ? AND kv.lower_term >= ? AND kv.lower_term < ? " | |
528 "ORDER BY u.last_visit_time DESC LIMIT ?")); | |
529 | |
530 // NOTE: Keep this ToLower() call in sync with search_provider.cc. | |
531 base::string16 lower_prefix = base::i18n::ToLower(prefix); | |
532 // This magic gives us a prefix search. | |
533 base::string16 next_prefix = lower_prefix; | |
534 next_prefix[next_prefix.size() - 1] = | |
535 next_prefix[next_prefix.size() - 1] + 1; | |
536 statement.BindInt64(0, keyword_id); | |
537 statement.BindString16(1, lower_prefix); | |
538 statement.BindString16(2, next_prefix); | |
539 statement.BindInt(3, max_count); | |
540 | |
541 KeywordSearchTermVisit visit; | |
542 while (statement.Step()) { | |
543 visit.term = statement.ColumnString16(0); | |
544 visit.visits = statement.ColumnInt(1); | |
545 visit.time = base::Time::FromInternalValue(statement.ColumnInt64(2)); | |
546 matches->push_back(visit); | |
547 } | |
548 } | |
549 | |
550 bool URLDatabase::DeleteKeywordSearchTerm(const base::string16& term) { | |
551 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, | |
552 "DELETE FROM keyword_search_terms WHERE term=?")); | |
553 statement.BindString16(0, term); | |
554 | |
555 return statement.Run(); | |
556 } | |
557 | |
558 bool URLDatabase::DeleteKeywordSearchTermForURL(URLID url_id) { | |
559 sql::Statement statement(GetDB().GetCachedStatement( | |
560 SQL_FROM_HERE, "DELETE FROM keyword_search_terms WHERE url_id=?")); | |
561 statement.BindInt64(0, url_id); | |
562 return statement.Run(); | |
563 } | |
564 | |
565 bool URLDatabase::DropStarredIDFromURLs() { | |
566 if (!GetDB().DoesColumnExist("urls", "starred_id")) | |
567 return true; // urls is already updated, no need to continue. | |
568 | |
569 // Create a temporary table to contain the new URLs table. | |
570 if (!CreateTemporaryURLTable()) { | |
571 NOTREACHED(); | |
572 return false; | |
573 } | |
574 | |
575 // Copy the contents. | |
576 if (!GetDB().Execute( | |
577 "INSERT INTO temp_urls (id, url, title, visit_count, typed_count, " | |
578 "last_visit_time, hidden, favicon_id) " | |
579 "SELECT id, url, title, visit_count, typed_count, last_visit_time, " | |
580 "hidden, favicon_id FROM urls")) { | |
581 NOTREACHED() << GetDB().GetErrorMessage(); | |
582 return false; | |
583 } | |
584 | |
585 // Rename/commit the tmp table. | |
586 CommitTemporaryURLTable(); | |
587 | |
588 return true; | |
589 } | |
590 | |
591 bool URLDatabase::CreateURLTable(bool is_temporary) { | |
592 const char* name = is_temporary ? "temp_urls" : "urls"; | |
593 if (GetDB().DoesTableExist(name)) | |
594 return true; | |
595 | |
596 // Note: revise implementation for InsertOrUpdateURLRowByID() if you add any | |
597 // new constraints to the schema. | |
598 std::string sql; | |
599 sql.append("CREATE TABLE "); | |
600 sql.append(name); | |
601 sql.append("(" | |
602 "id INTEGER PRIMARY KEY," | |
603 "url LONGVARCHAR," | |
604 "title LONGVARCHAR," | |
605 "visit_count INTEGER DEFAULT 0 NOT NULL," | |
606 "typed_count INTEGER DEFAULT 0 NOT NULL," | |
607 "last_visit_time INTEGER NOT NULL," | |
608 "hidden INTEGER DEFAULT 0 NOT NULL," | |
609 "favicon_id INTEGER DEFAULT 0 NOT NULL)"); // favicon_id is not used now. | |
610 | |
611 return GetDB().Execute(sql.c_str()); | |
612 } | |
613 | |
614 bool URLDatabase::CreateMainURLIndex() { | |
615 return GetDB().Execute( | |
616 "CREATE INDEX IF NOT EXISTS urls_url_index ON urls (url)"); | |
617 } | |
618 | |
619 } // namespace history | |
OLD | NEW |