diff options
author | michaelbai@chromium.org <michaelbai@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-31 04:23:22 +0000 |
---|---|---|
committer | michaelbai@chromium.org <michaelbai@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-31 04:23:22 +0000 |
commit | f0b437f60f00985fca65e5095be3eed1402489f8 (patch) | |
tree | 5c09e900a6b4dbf3fa9ab4d0978efb36fe0360fc /chrome | |
parent | bd844b34a70fd98844081ed3d9c99b680556384b (diff) | |
download | chromium_src-f0b437f60f00985fca65e5095be3eed1402489f8.zip chromium_src-f0b437f60f00985fca65e5095be3eed1402489f8.tar.gz chromium_src-f0b437f60f00985fca65e5095be3eed1402489f8.tar.bz2 |
Implement the Android API for search terms.
- Added the keyword_cache table in AndroidCacheDatabase.
- Added the methods for getting and deleting the terms by term in URLDatabase.
- Added the index of term for keyword_search_term table.
BUG=
TEST=
Review URL: http://codereview.chromium.org/9887004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@130041 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/browser/history/android/android_cache_database.cc | 95 | ||||
-rw-r--r-- | chrome/browser/history/android/android_cache_database.h | 48 | ||||
-rw-r--r-- | chrome/browser/history/android/android_cache_database_unittest.cc | 82 | ||||
-rw-r--r-- | chrome/browser/history/android/android_history_types.cc | 15 | ||||
-rw-r--r-- | chrome/browser/history/android/android_history_types.h | 30 | ||||
-rw-r--r-- | chrome/browser/history/android/android_provider_backend.cc | 284 | ||||
-rw-r--r-- | chrome/browser/history/android/android_provider_backend.h | 60 | ||||
-rw-r--r-- | chrome/browser/history/android/android_provider_backend_unittest.cc | 361 | ||||
-rw-r--r-- | chrome/browser/history/url_database.cc | 34 | ||||
-rw-r--r-- | chrome/browser/history/url_database.h | 12 | ||||
-rw-r--r-- | chrome/browser/history/url_database_unittest.cc | 76 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 2 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 5 |
13 files changed, 1066 insertions, 38 deletions
diff --git a/chrome/browser/history/android/android_cache_database.cc b/chrome/browser/history/android/android_cache_database.cc index 926e099..4fb5422 100644 --- a/chrome/browser/history/android/android_cache_database.cc +++ b/chrome/browser/history/android/android_cache_database.cc @@ -30,6 +30,9 @@ sql::InitStatus AndroidCacheDatabase::InitAndroidCacheDatabase( if (!CreateBookmarkCacheTable()) return sql::INIT_FAILURE; + if (!CreateSearchTermsTable()) + return sql::INIT_FAILURE; + return sql::INIT_OK; } @@ -103,6 +106,71 @@ bool AndroidCacheDatabase::SetFaviconID(URLID url_id, FaviconID favicon_id) { return true; } +SearchTermID AndroidCacheDatabase::AddSearchTerm( + const string16& term, + const base::Time& last_visit_time) { + sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, + "INSERT INTO android_cache_db.search_terms (search, " + "date) VALUES (?, ?)")); + + statement.BindString16(0, term); + statement.BindInt64(1, ToMilliseconds(last_visit_time)); + + if (!statement.Run()) { + LOG(ERROR) << GetDB().GetErrorMessage(); + return 0; + } + + return GetDB().GetLastInsertRowId(); +} + +bool AndroidCacheDatabase::UpdateSearchTerm(SearchTermID id, + const SearchTermRow& row) { + sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, + "UPDATE android_cache_db.search_terms " + "SET search = ?, date = ? " + "WHERE _id = ?" + )); + statement.BindString16(0, row.term); + statement.BindInt64(1, ToMilliseconds(row.last_visit_time)); + statement.BindInt64(2, id); + + return statement.Run(); +} + +SearchTermID AndroidCacheDatabase::GetSearchTerm(const string16& term, + SearchTermRow* row) { + sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, + "SELECT _id, search, date " + "FROM android_cache_db.search_terms " + "WHERE search = ?" + )); + if (!statement.is_valid()) { + LOG(ERROR) << GetDB().GetErrorMessage(); + return 0; + } + statement.BindString16(0, term); + if (!statement.Step()) + return 0; + + if (row) { + row->id = statement.ColumnInt64(0); + row->term = statement.ColumnString16(1); + row->last_visit_time = MillisecondsToTime(statement.ColumnInt64(2)); + } + return statement.ColumnInt64(0); +} + +bool AndroidCacheDatabase::DeleteUnusedSearchTerms() { + sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, + "DELETE FROM android_cache_db.search_terms " + "WHERE search NOT IN (SELECT DISTINCT term FROM keyword_search_terms)" + )); + if (!statement.is_valid()) + return false; + return statement.Run(); +} + bool AndroidCacheDatabase::CreateDatabase(const FilePath& db_name) { db_name_ = db_name; if (file_util::PathExists(db_name_)) @@ -158,6 +226,33 @@ bool AndroidCacheDatabase::CreateBookmarkCacheTable() { return true; } +bool AndroidCacheDatabase::CreateSearchTermsTable() { + const char* name = "android_cache_db.search_terms"; + + // The table's column name matchs Android's definition. + std::string sql; + sql.append("CREATE TABLE "); + sql.append(name); + sql.append("(" + "_id INTEGER PRIMARY KEY," + "date INTEGER NOT NULL," // last visit time in millisecond. + "search LONGVARCHAR NOT NULL)"); // The actual search term. + + if (!GetDB().Execute(sql.c_str())) { + LOG(ERROR) << GetDB().GetErrorMessage(); + return false; + } + + sql.assign("CREATE INDEX " + "android_cache_db.search_terms_term_idx ON " + "search_terms(search)"); + if (!GetDB().Execute(sql.c_str())) { + LOG(ERROR) << GetDB().GetErrorMessage(); + return false; + } + return true; +} + bool AndroidCacheDatabase::Attach() { // Commit all open transactions to make attach succeed. if (GetDB().transaction_nesting()) diff --git a/chrome/browser/history/android/android_cache_database.h b/chrome/browser/history/android/android_cache_database.h index 7363e06..99e3451 100644 --- a/chrome/browser/history/android/android_cache_database.h +++ b/chrome/browser/history/android/android_cache_database.h @@ -23,17 +23,19 @@ class AndroidCacheDatabase { AndroidCacheDatabase(); virtual ~AndroidCacheDatabase(); - // Creates the database, deletes existing one if any; Also attach it to the + // Creates the database, deletes existing one if any; also attach it to the // database returned by GetDB(). Returns sql::INIT_OK on success, otherwise // sql::INIT_FAILURE returned. sql::InitStatus InitAndroidCacheDatabase(const FilePath& db_name); + // The bookmark_cache table ------------------------------------------------ + // // Adds a row to the bookmark_cache table. Returns true on success. bool AddBookmarkCacheRow(const base::Time& created_time, const base::Time& last_visit_time, URLID url_id); - // Clears all rows in the bookmark_cache table; Returns true on success. + // Clears all rows in the bookmark_cache table; returns true on success. bool ClearAllBookmarkCache(); // Marks the given |url_ids| as bookmarked; Returns true on success. @@ -43,20 +45,38 @@ class AndroidCacheDatabase { // success. bool SetFaviconID(URLID url_id, FaviconID favicon_id); + // The search_terms table ------------------------------------------------- + // + // Add a row in the search_term table with the given |term| and + // |last_visit_time|. Return the new row's id on success, otherwise 0 is + // returned. + SearchTermID AddSearchTerm(const string16& term, + const base::Time& last_visit_time); + + // Updates the |id|'s row with the given |row|; returns true on success. + bool UpdateSearchTerm(SearchTermID id, const SearchTermRow& row); + + // Get SearchTermRow of the given |term|; return the row id on success. + // otherwise 0 is returned. + // The found row is return in |row| if it is not NULL. + SearchTermID GetSearchTerm(const string16& term, SearchTermRow* row); + + // Delete the search terms which don't exist in keyword_search_terms table. + bool DeleteUnusedSearchTerms(); + protected: // Returns the database for the functions in this interface. The decendent of // this class implements these functions to return its objects. virtual sql::Connection& GetDB() = 0; private: - FRIEND_TEST_ALL_PREFIXES(AndroidCacheDatabaseTest, - CreateDatabase); + FRIEND_TEST_ALL_PREFIXES(AndroidCacheDatabaseTest, InitAndroidCacheDatabase); - // Creates the database and make it ready for attaching; Returns true on + // Creates the database and make it ready for attaching; returns true on // success. bool CreateDatabase(const FilePath& db_name); - // Creates the bookmark_cache table in attached DB; Returns true on success. + // Creates the bookmark_cache table in attached DB; returns true on success. // The created_time, last_visit_time, favicon_id and bookmark are stored. // // The created_time and last_visit_time are cached because Android use the @@ -71,7 +91,21 @@ class AndroidCacheDatabase { // Bookmark column is used to indicate whether the url is bookmarked. bool CreateBookmarkCacheTable(); - // Attachs to history database; Returns true on success. + // Creates the search_terms table in attached DB; returns true on success. + // This table has _id, search, and date fields which match the Android's + // definition. + // + // When Android Client require update the search term, the search term can't + // be updated as it always associated a URL. We simulate the update by + // deleting the old search term then inserting a new one, but the ID given + // to client can not be changed, so it appears to client as update. This + // table is used to mapping the ID given to client to the search term. + // + // The search term last visit time is stored in date as Android needs the time + // in milliseconds. + bool CreateSearchTermsTable(); + + // Attachs to history database; returns true on success. bool Attach(); // Does the real attach. Returns true on success. diff --git a/chrome/browser/history/android/android_cache_database_unittest.cc b/chrome/browser/history/android/android_cache_database_unittest.cc index 7256785..84faf63 100644 --- a/chrome/browser/history/android/android_cache_database_unittest.cc +++ b/chrome/browser/history/android/android_cache_database_unittest.cc @@ -5,27 +5,17 @@ #include "base/file_path.h" #include "base/file_util.h" #include "base/scoped_temp_dir.h" +#include "base/utf_string_conversions.h" #include "chrome/browser/history/android/android_cache_database.h" +#include "chrome/browser/history/android/android_time.h" #include "chrome/browser/history/history_database.h" #include "chrome/test/base/testing_profile.h" #include "testing/gtest/include/gtest/gtest.h" -namespace history { - -class SimpleAndroidCacheDatabase : public AndroidCacheDatabase { - public: - explicit SimpleAndroidCacheDatabase(sql::Connection* connection) - :db_(connection) { - } - - protected: - sql::Connection& GetDB() { - return *db_; - } +using base::Time; +using base::TimeDelta; - private: - sql::Connection* db_; -}; +namespace history { class AndroidCacheDatabaseTest : public testing::Test { public: @@ -38,25 +28,69 @@ class AndroidCacheDatabaseTest : public testing::Test { virtual void SetUp() { // Get a temporary directory for the test DB files. ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); - main_db_name_ = temp_dir_.path().AppendASCII("history.db"); + FilePath history_db_name_ = temp_dir_.path().AppendASCII("history.db"); android_cache_db_name_ = temp_dir_.path().AppendASCII( "TestAndroidCache.db"); + ASSERT_EQ(sql::INIT_OK, history_db_.Init(history_db_name_, + temp_dir_.path())); + ASSERT_EQ(sql::INIT_OK, + history_db_.InitAndroidCacheDatabase(android_cache_db_name_)); } ScopedTempDir temp_dir_; FilePath android_cache_db_name_; - FilePath main_db_name_; + HistoryDatabase history_db_; }; TEST_F(AndroidCacheDatabaseTest, InitAndroidCacheDatabase) { - sql::Connection connection; - ASSERT_TRUE(connection.Open(main_db_name_)); - SimpleAndroidCacheDatabase android_cache_db(&connection); - EXPECT_EQ(sql::INIT_OK, - android_cache_db.InitAndroidCacheDatabase(android_cache_db_name_)); - // Try to run a sql against the table to verify it exists. - EXPECT_TRUE(connection.Execute( + // Try to run a sql against the table to verify them exist. + AndroidCacheDatabase* cache_db = + static_cast<AndroidCacheDatabase*>(&history_db_); + EXPECT_TRUE(cache_db->GetDB().Execute( "DELETE FROM android_cache_db.bookmark_cache")); + EXPECT_TRUE(cache_db->GetDB().Execute( + "DELETE FROM android_cache_db.search_terms")); +} + +TEST_F(AndroidCacheDatabaseTest, SearchTermsTable) { + // Test AddSearchTerm. + Time search_time1 = Time::Now() - TimeDelta::FromDays(1); + string16 search_term1(UTF8ToUTF16("search term 1")); + SearchTermID id1 = history_db_.AddSearchTerm(search_term1, search_time1); + ASSERT_TRUE(id1); + SearchTermRow row1; + ASSERT_EQ(id1, history_db_.GetSearchTerm(search_term1, &row1)); + EXPECT_EQ(search_term1, row1.term); + EXPECT_EQ(ToMilliseconds(search_time1), + ToMilliseconds(row1.last_visit_time)); + EXPECT_EQ(id1, row1.id); + + // Test UpdateSearchTerm. + SearchTermRow update_row1; + update_row1.term = (UTF8ToUTF16("update search term1")); + update_row1.last_visit_time = Time::Now(); + ASSERT_TRUE(history_db_.UpdateSearchTerm(id1, update_row1)); + EXPECT_EQ(id1, history_db_.GetSearchTerm(update_row1.term, &row1)); + EXPECT_EQ(update_row1.term, row1.term); + EXPECT_EQ(ToMilliseconds(update_row1.last_visit_time), + ToMilliseconds(row1.last_visit_time)); + EXPECT_EQ(id1, row1.id); + + Time search_time2 = Time::Now() - TimeDelta::FromHours(1); + string16 search_term2(UTF8ToUTF16("search term 2")); + SearchTermID id2 = history_db_.AddSearchTerm(search_term2, search_time2); + ASSERT_TRUE(id2); + ASSERT_TRUE(history_db_.SetKeywordSearchTermsForURL(1, 1, search_term2)); + ASSERT_TRUE(history_db_.DeleteUnusedSearchTerms()); + + // The search_term1 was removed. + EXPECT_FALSE(history_db_.GetSearchTerm(update_row1.term, NULL)); + // The search_term2 should still in the table. + ASSERT_EQ(id2, history_db_.GetSearchTerm(search_term2, &row1)); + EXPECT_EQ(id2, row1.id); + EXPECT_EQ(ToMilliseconds(search_time2), + ToMilliseconds(row1.last_visit_time)); + EXPECT_EQ(search_term2, row1.term); } } // namespace history diff --git a/chrome/browser/history/android/android_history_types.cc b/chrome/browser/history/android/android_history_types.cc index 29339fd..42d82b0 100644 --- a/chrome/browser/history/android/android_history_types.cc +++ b/chrome/browser/history/android/android_history_types.cc @@ -116,6 +116,21 @@ SearchRow::SearchColumnID SearchRow::GetSearchColumnID( return i->second; } +AndroidURLRow::AndroidURLRow() + : id(0), + url_id(0) { +} + +AndroidURLRow::~AndroidURLRow() { +} + +SearchTermRow::SearchTermRow() + : id(0) { +} + +SearchTermRow::~SearchTermRow() { +} + AndroidStatement::AndroidStatement(sql::Statement* statement, int favicon_index) : statement_(statement), favicon_index_(favicon_index) { diff --git a/chrome/browser/history/android/android_history_types.h b/chrome/browser/history/android/android_history_types.h index ab99832..c09a5e1 100644 --- a/chrome/browser/history/android/android_history_types.h +++ b/chrome/browser/history/android/android_history_types.h @@ -19,6 +19,7 @@ class Statement; namespace history { typedef int64 AndroidURLID; +typedef int64 SearchTermID; // Wraps all columns needed to support android.provider.Browser.BookmarkColumns. // It is used in insert() and update() to specify the columns need to insert or @@ -206,6 +207,14 @@ class SearchRow { static SearchColumnID GetSearchColumnID(const std::string& name); + SearchTermID id() const { + return id_; + } + void set_id(SearchTermID id) { + set_value_explicitly(SearchRow::ID); + id_ = id; + } + const string16& search_term() const { return search_term_; } @@ -248,7 +257,7 @@ class SearchRow { values_set_.insert(id); } - int64 id_; + SearchTermID id_; string16 search_term_; base::Time search_time_; GURL url_; @@ -262,10 +271,8 @@ class SearchRow { // Defines the row stored in android_urls table. struct AndroidURLRow { - AndroidURLRow() - :id(0), - url_id(0) { - } + AndroidURLRow(); + ~AndroidURLRow(); // The unique id of the row AndroidURLID id; @@ -275,6 +282,19 @@ struct AndroidURLRow { std::string raw_url; }; +// Defines the row of keyword_cache table. +struct SearchTermRow { + SearchTermRow(); + ~SearchTermRow(); + + // The unique id of the row. + SearchTermID id; + // The keyword. + string16 term; + // The last visit time. + base::Time last_visit_time; +}; + // This class wraps the sql statement and favicon column index in statement if // any. It is returned by AndroidProviderBackend::Query(). // diff --git a/chrome/browser/history/android/android_provider_backend.cc b/chrome/browser/history/android/android_provider_backend.cc index 028427a..139e867 100644 --- a/chrome/browser/history/android/android_provider_backend.cc +++ b/chrome/browser/history/android/android_provider_backend.cc @@ -45,6 +45,12 @@ const char * kURLUpdateClause = "(SELECT url as visit_url, min(visit_time) as created_time" " FROM visits GROUP BY url) ON (visit_url = urls.id) "; +const char* kSearchTermUpdateClause = + "SELECT keyword_search_terms.term, max(urls.last_visit_time) " + "FROM keyword_search_terms JOIN urls ON " + "(keyword_search_terms.url_id = urls.id) " + "GROUP BY keyword_search_terms.term"; + void BindStatement(const std::vector<string16>& selection_args, sql::Statement* statement, int* col_index) { @@ -395,6 +401,165 @@ bool AndroidProviderBackend::DeleteBookmarks( return true; } +AndroidStatement* AndroidProviderBackend::QuerySearchTerms( + const std::vector<SearchRow::SearchColumnID>& projections, + const std::string& selection, + const std::vector<string16>& selection_args, + const std::string& sort_order) { + if (projections.empty()) + return NULL; + + if (!EnsureInitializedAndUpdated()) + return NULL; + + std::string sql; + sql.append("SELECT "); + AppendSearchResultColumn(projections, &sql); + sql.append(" FROM android_cache_db.search_terms "); + + if (!selection.empty()) { + sql.append(" WHERE "); + sql.append(selection); + } + + if (!sort_order.empty()) { + sql.append(" ORDER BY "); + sql.append(sort_order); + } + + scoped_ptr<sql::Statement> statement(new sql::Statement( + db_->GetUniqueStatement(sql.c_str()))); + int count = 0; + BindStatement(selection_args, statement.get(), &count); + if (!statement->is_valid()) { + LOG(ERROR) << db_->GetErrorMessage(); + return NULL; + } + sql::Statement* result = statement.release(); + return new AndroidStatement(result, -1); +} + +bool AndroidProviderBackend::UpdateSearchTerms( + const SearchRow& row, + const std::string& selection, + const std::vector<string16>& selection_args, + int* update_count) { + if (!EnsureInitializedAndUpdated()) + return false; + + SearchTerms search_terms; + if (!GetSelectedSearchTerms(selection, selection_args, &search_terms)) + return false; + + // We can not update search term if multiple row selected. + if (row.is_value_set_explicitly(SearchRow::SEARCH_TERM) && + search_terms.size() > 1) + return false; + + *update_count = search_terms.size(); + + if (search_terms.empty()) + return true; + + if (row.is_value_set_explicitly(SearchRow::SEARCH_TERM)) { + SearchTermRow search_term_row; + SearchRow search_row = row; + if (!history_db_->GetSearchTerm(search_terms[0], &search_term_row)) + return false; + + search_term_row.term = search_row.search_term(); + if (!search_row.is_value_set_explicitly(SearchRow::SEARCH_TIME)) + search_row.set_search_time(search_term_row.last_visit_time); + else + search_term_row.last_visit_time = search_row.search_time(); + + // Delete the original search term. + if (!history_db_->DeleteKeywordSearchTerm(search_terms[0])) + return false; + + // Add the new one. + if (!AddSearchTerm(search_row)) + return false; + + // Update the cache table so the id will not be changed. + if (!history_db_->UpdateSearchTerm(search_term_row.id, search_term_row)) + return false; + + return true; + } + + for (SearchTerms::const_iterator i = search_terms.begin(); + i != search_terms.end(); ++i) { + SearchTermRow search_term_row; + if (!history_db_->GetSearchTerm(*i, &search_term_row)) + return false; + + // Check whether the given search time less than the existing one. + if (search_term_row.last_visit_time > row.search_time()) + return false; + + std::vector<KeywordSearchTermRow> search_term_rows; + if (!history_db_->GetKeywordSearchTermRows(*i, &search_term_rows) || + search_term_rows.empty()) + return false; + + // Actually only search_time update. As there might multiple URLs + // asocciated with the keyword, Just update the first one's last_visit_time. + URLRow url_row; + if (!history_db_->GetURLRow(search_term_rows[0].url_id, &url_row)) + return false; + + BookmarkRow bookmark_row; + bookmark_row.set_last_visit_time(row.search_time()); + TableIDRow table_id_row; + table_id_row.url_id = url_row.id(); + TableIDRows table_id_rows; + table_id_rows.push_back(table_id_row); + if (!urls_handler_->Update(bookmark_row, table_id_rows)) + return false; + + if (!visit_handler_->Update(bookmark_row, table_id_rows)) + return false; + } + return true; +} + +SearchTermID AndroidProviderBackend::InsertSearchTerm( + const SearchRow& values) { + if (!EnsureInitializedAndUpdated()) + return 0; + + if (!AddSearchTerm(values)) + return 0; + + SearchTermID id = history_db_->GetSearchTerm(values.search_term(), NULL); + if (!id) + // Note the passed in Time() will be changed in UpdateSearchTermTable(). + id = history_db_->AddSearchTerm(values.search_term(), Time()); + return id; +} + +bool AndroidProviderBackend::DeleteSearchTerms( + const std::string& selection, + const std::vector<string16>& selection_args, + int * deleted_count) { + SearchTerms rows; + if (!GetSelectedSearchTerms(selection, selection_args, &rows)) + return false; + + *deleted_count = rows.size(); + if (rows.empty()) + return true; + + for (SearchTerms::const_iterator i = rows.begin(); i != rows.end(); ++i) + if (!history_db_->DeleteKeywordSearchTerm(*i)) + return false; + // We don't delete the rows in search_terms table, as once the + // search_terms table is updated with keyword_search_terms, all + // keyword cache not found in the keyword_search_terms will be removed. + return true; +} + bool AndroidProviderBackend::EnsureInitializedAndUpdated() { if (!initialized_) { if (!Init()) @@ -446,6 +611,11 @@ bool AndroidProviderBackend::UpdateTables() { LOG(ERROR) << "Update of the icons failed"; return false; } + + if (!UpdateSearchTermTable()) { + LOG(ERROR) << "Update of the search_terms failed"; + return false; + } return true; } @@ -531,6 +701,29 @@ bool AndroidProviderBackend::UpdateFavicon() { return true; } +bool AndroidProviderBackend::UpdateSearchTermTable() { + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, + kSearchTermUpdateClause)); + while (statement.Step()) { + string16 term = statement.ColumnString16(0); + Time last_visit_time = Time::FromInternalValue(statement.ColumnInt64(1)); + SearchTermRow search_term_row; + if (history_db_->GetSearchTerm(term, &search_term_row) && + search_term_row.last_visit_time != last_visit_time) { + search_term_row.last_visit_time = last_visit_time; + if (!history_db_->UpdateSearchTerm(search_term_row.id, search_term_row)) + return false; + } else { + if (!history_db_->AddSearchTerm(term, last_visit_time)) + return false; + } + } + if (!history_db_->DeleteUnusedSearchTerms()) + return false; + + return true; +} + int AndroidProviderBackend::AppendBookmarkResultColumn( const std::vector<BookmarkRow::BookmarkColumnID>& projections, std::string* result_column) { @@ -584,6 +777,46 @@ bool AndroidProviderBackend::GetSelectedURLs( return true; } +bool AndroidProviderBackend::GetSelectedSearchTerms( + const std::string& selection, + const std::vector<string16>& selection_args, + SearchTerms* rows) { + std::string sql("SELECT search " + "FROM android_cache_db.search_terms "); + if (!selection.empty()) { + sql.append(" WHERE "); + sql.append(selection); + } + sql::Statement statement(db_->GetUniqueStatement(sql.c_str())); + int count = 0; + BindStatement(selection_args, &statement, &count); + if (!statement.is_valid()) { + LOG(ERROR) << db_->GetErrorMessage(); + return false; + } + while (statement.Step()) { + rows->push_back(statement.ColumnString16(0)); + } + return true; +} + +void AndroidProviderBackend::AppendSearchResultColumn( + const std::vector<SearchRow::SearchColumnID>& projections, + std::string* result_column) { + bool first = true; + int index = 0; + for (std::vector<SearchRow::SearchColumnID>::const_iterator i = + projections.begin(); i != projections.end(); ++i) { + if (first) + first = false; + else + result_column->append(", "); + + result_column->append(SearchRow::GetAndroidName(*i)); + index++; + } +} + bool AndroidProviderBackend::SimulateUpdateURL( const BookmarkRow& row, const TableIDRows& ids, @@ -750,4 +983,55 @@ void AndroidProviderBackend::BroadcastNotifications( } } +bool AndroidProviderBackend::AddSearchTerm(const SearchRow& values) { + DCHECK(values.is_value_set_explicitly(SearchRow::SEARCH_TERM)); + DCHECK(values.is_value_set_explicitly(SearchRow::TEMPLATE_URL)); + DCHECK(values.is_value_set_explicitly(SearchRow::URL)); + + URLRow url_row; + BookmarkRow bookmark_row; + // Android CTS test BrowserTest.testAccessSearches allows insert the same + // seach term multiple times, and just search time need updated. + if (history_db_->GetRowForURL(values.url(), &url_row)) { + // Already exist, Add a visit. + if (values.is_value_set_explicitly(SearchRow::SEARCH_TIME)) + bookmark_row.set_last_visit_time(values.search_time()); + else + bookmark_row.set_visit_count(url_row.visit_count() + 1); + TableIDRows table_id_rows; + TableIDRow table_id_row; + table_id_row.url = values.url(); + table_id_row.url_id = url_row.id(); + table_id_rows.push_back(table_id_row); + if (!urls_handler_->Update(bookmark_row, table_id_rows)) + return false; + if (!visit_handler_->Update(bookmark_row, table_id_rows)) + return false; + + if (!history_db_->GetKeywordSearchTermRow(url_row.id(), NULL)) + if (!history_db_->SetKeywordSearchTermsForURL(url_row.id(), + values.template_url_id(), values.search_term())) + return false; + } else { + bookmark_row.set_raw_url(values.url().spec()); + bookmark_row.set_url(values.url()); + if (values.is_value_set_explicitly(SearchRow::SEARCH_TIME)) + bookmark_row.set_last_visit_time(values.search_time()); + + if (!urls_handler_->Insert(&bookmark_row)) + return false; + + if (!visit_handler_->Insert(&bookmark_row)) + return false; + + if (!android_urls_handler_->Insert(&bookmark_row)) + return false; + + if (!history_db_->SetKeywordSearchTermsForURL(bookmark_row.url_id(), + values.template_url_id(), values.search_term())) + return false; + } + return true; +} + } // namespace history diff --git a/chrome/browser/history/android/android_provider_backend.h b/chrome/browser/history/android/android_provider_backend.h index e8a4bed..a75a13f 100644 --- a/chrome/browser/history/android/android_provider_backend.h +++ b/chrome/browser/history/android/android_provider_backend.h @@ -92,11 +92,50 @@ class AndroidProviderBackend { const std::vector<string16>& selection_args, int* deleted_count); + // SearchTerms -------------------------------------------------------------- + // + // Returns the result of the given query. + // |projections| specifies the result columns, can not be empty, otherwise + // NULL is returned. + // |selection| is the SQL WHERE clause without 'WHERE'. + // |selection_args| is the arguments for WHERE clause. + // |sort_order| the SQL ORDER clause. + AndroidStatement* QuerySearchTerms( + const std::vector<SearchRow::SearchColumnID>& projections, + const std::string& selection, + const std::vector<string16>& selection_args, + const std::string& sort_order); + + // Runs the given update and returns the number of updated rows in + // |update_count| and return true, false returned if there is any error. + // + // |row| is the value need to update. + // |selection| is the SQL WHERE clause without 'WHERE'. + // |selection_args| is the arguments for WHERE clause. + bool UpdateSearchTerms(const SearchRow& row, + const std::string& selection, + const std::vector<string16>& selection_args, + int* update_count); + + // Inserts the given valus and return the SearchTermID of inserted row. + SearchTermID InsertSearchTerm(const SearchRow& values); + + // Deletes the matched rows and the number of deleted rows is returned in + // |deleted_count|. + // |selection| is the SQL WHERE clause without 'WHERE'. + // |selection_args| is the arguments for WHERE clause. + // + // if |selection| is empty all search be deleted. + bool DeleteSearchTerms(const std::string& selection, + const std::vector<string16>& selection_args, + int * deleted_count); + private: friend class AndroidProviderBackendTest; FRIEND_TEST_ALL_PREFIXES(AndroidProviderBackendTest, UpdateTables); FRIEND_TEST_ALL_PREFIXES(AndroidProviderBackendTest, UpdateBookmarks); + FRIEND_TEST_ALL_PREFIXES(AndroidProviderBackendTest, UpdateSearchTermTable); struct HistoryNotification { HistoryNotification(int type, HistoryDetails* detail); @@ -192,6 +231,9 @@ class AndroidProviderBackend { // Update the bookmark_cache table for favicon. bool UpdateFavicon(); + // Update the search_term table + bool UpdateSearchTermTable(); + // Append the specified result columns in |projections| to the given // |result_column|. // To support the lazy binding, the index of favicon column will be @@ -200,6 +242,12 @@ class AndroidProviderBackend { const std::vector<BookmarkRow::BookmarkColumnID>& projections, std::string* result_column); + // Append the specified search result columns in |projections| to the given + // |result_column|. + void AppendSearchResultColumn( + const std::vector<SearchRow::SearchColumnID>& projections, + std::string* result_column); + // Runs the given query on |virtual_table| and returns true if succeeds, the // selected URLID and url are returned in |rows|. bool GetSelectedURLs(const std::string& selection, @@ -207,6 +255,13 @@ class AndroidProviderBackend { const char* virtual_table, TableIDRows* rows); + // Runs the given query on search_terms table and returns true on success, + // The selected search term are returned in |rows|. + typedef std::vector<string16> SearchTerms; + bool GetSelectedSearchTerms(const std::string& selection, + const std::vector<string16>& selection_args, + SearchTerms* rows); + // Simulates update url by deleting the previous URL and creating a new one. // Return true on success. bool SimulateUpdateURL(const BookmarkRow& row, @@ -223,6 +278,11 @@ class AndroidProviderBackend { void BroadcastNotifications(const HistoryNotifications& notifications); + // Add the search term from the given |values|. It will add the values.url() + // in the urls table if it doesn't exist, insert visit in the visits table, + // also add keyword in keyword_search_term. + bool AddSearchTerm(const SearchRow& values); + // SQLHandlers for different tables. scoped_ptr<SQLHandler> urls_handler_; scoped_ptr<SQLHandler> visit_handler_; diff --git a/chrome/browser/history/android/android_provider_backend_unittest.cc b/chrome/browser/history/android/android_provider_backend_unittest.cc index 98bc89b..8c16df3 100644 --- a/chrome/browser/history/android/android_provider_backend_unittest.cc +++ b/chrome/browser/history/android/android_provider_backend_unittest.cc @@ -1081,4 +1081,365 @@ TEST_F(AndroidProviderBackendTest, UpdateFavicon) { NULL)); } +TEST_F(AndroidProviderBackendTest, UpdateSearchTermTable) { + ASSERT_EQ(sql::INIT_OK, history_db_.Init(history_db_name_, bookmark_temp_)); + ASSERT_EQ(sql::INIT_OK, thumbnail_db_.Init(thumbnail_db_name_, NULL, + &history_db_)); + scoped_ptr<AndroidProviderBackend> backend( + new AndroidProviderBackend(android_cache_db_name_, &history_db_, + &thumbnail_db_, &bookmark_model_, &delegate_)); + // Insert a keyword search item to verify if the update succeeds. + BookmarkRow row1; + row1.set_raw_url("cnn.com"); + row1.set_url(GURL("http://cnn.com")); + row1.set_last_visit_time(Time::Now() - TimeDelta::FromDays(1)); + row1.set_title(UTF8ToUTF16("cnn")); + ASSERT_TRUE(backend->InsertBookmark(row1)); + string16 term = UTF8ToUTF16("Search term 1"); + URLID url_id = history_db_.GetRowForURL(row1.url(), NULL); + ASSERT_TRUE(url_id); + ASSERT_TRUE(history_db_.SetKeywordSearchTermsForURL(url_id, 1, term)); + ASSERT_TRUE(backend->UpdateSearchTermTable()); + SearchTermRow keyword_cache; + SearchTermID id = history_db_.GetSearchTerm(term, &keyword_cache); + ASSERT_TRUE(id); + EXPECT_EQ(term, keyword_cache.term); + EXPECT_EQ(ToMilliseconds(row1.last_visit_time()), + ToMilliseconds(keyword_cache.last_visit_time)); + + // Add another row. + BookmarkRow row2; + row2.set_raw_url("google.com"); + row2.set_url(GURL("http://google.com")); + row2.set_last_visit_time(Time::Now() - TimeDelta::FromDays(2)); + row2.set_title(UTF8ToUTF16("cnn")); + ASSERT_TRUE(backend->InsertBookmark(row2)); + url_id = history_db_.GetRowForURL(row2.url(), NULL); + ASSERT_TRUE(url_id); + string16 term2 = UTF8ToUTF16("Search term 2"); + ASSERT_TRUE(history_db_.SetKeywordSearchTermsForURL(url_id, 1, term2)); + ASSERT_TRUE(backend->UpdateSearchTermTable()); + SearchTermID search_id1 = history_db_.GetSearchTerm(term, + &keyword_cache); + // The id shouldn't changed. + ASSERT_EQ(id, search_id1); + EXPECT_EQ(term, keyword_cache.term); + EXPECT_EQ(ToMilliseconds(row1.last_visit_time()), + ToMilliseconds(keyword_cache.last_visit_time)); + // Verify the row just inserted. + SearchTermID id2 = history_db_.GetSearchTerm(term2, &keyword_cache); + ASSERT_TRUE(id2); + EXPECT_EQ(term2, keyword_cache.term); + EXPECT_EQ(ToMilliseconds(row2.last_visit_time()), + ToMilliseconds(keyword_cache.last_visit_time)); + + // Add 3rd row and associate it with term. + BookmarkRow row3; + row3.set_raw_url("search.com"); + row3.set_url(GURL("http://search.com")); + row3.set_last_visit_time(Time::Now()); + row3.set_title(UTF8ToUTF16("search")); + ASSERT_TRUE(backend->InsertBookmark(row3)); + url_id = history_db_.GetRowForURL(row3.url(), NULL); + ASSERT_TRUE(url_id); + ASSERT_TRUE(history_db_.SetKeywordSearchTermsForURL(url_id, 1, term)); + ASSERT_TRUE(backend->UpdateSearchTermTable()); + // Verify id not changed and last_visit_time updated. + ASSERT_EQ(search_id1, history_db_.GetSearchTerm(term, &keyword_cache)); + EXPECT_EQ(ToMilliseconds(row3.last_visit_time()), + ToMilliseconds(keyword_cache.last_visit_time)); + // The id of term2 wasn't changed. + EXPECT_EQ(id2, history_db_.GetSearchTerm(term2, NULL)); + + // Remove the term. + ASSERT_TRUE(history_db_.DeleteKeywordSearchTerm(term)); + ASSERT_TRUE(backend->UpdateSearchTermTable()); + // The cache of term should removed. + ASSERT_FALSE(history_db_.GetSearchTerm(term, NULL)); + // The id of term2 wasn't changed. + EXPECT_EQ(id2, history_db_.GetSearchTerm(term2, NULL)); +} + +TEST_F(AndroidProviderBackendTest, QuerySearchTerms) { + ASSERT_EQ(sql::INIT_OK, history_db_.Init(history_db_name_, bookmark_temp_)); + ASSERT_EQ(sql::INIT_OK, thumbnail_db_.Init(thumbnail_db_name_, NULL, + &history_db_)); + scoped_ptr<AndroidProviderBackend> backend( + new AndroidProviderBackend(android_cache_db_name_, &history_db_, + &thumbnail_db_, &bookmark_model_, &delegate_)); + // Insert a keyword search item to verify if we can find it. + BookmarkRow row1; + row1.set_raw_url("cnn.com"); + row1.set_url(GURL("http://cnn.com")); + row1.set_last_visit_time(Time::Now() - TimeDelta::FromDays(1)); + row1.set_title(UTF8ToUTF16("cnn")); + ASSERT_TRUE(backend->InsertBookmark(row1)); + string16 term = UTF8ToUTF16("Search term 1"); + URLID url_id = history_db_.GetRowForURL(row1.url(), NULL); + ASSERT_TRUE(url_id); + ASSERT_TRUE(history_db_.SetKeywordSearchTermsForURL(url_id, 1, term)); + + std::vector<SearchRow::SearchColumnID> projections; + projections.push_back(SearchRow::ID); + projections.push_back(SearchRow::SEARCH_TERM); + projections.push_back(SearchRow::SEARCH_TIME); + scoped_ptr<AndroidStatement> statement(backend->QuerySearchTerms( + projections, std::string(), std::vector<string16>(), std::string())); + ASSERT_TRUE(statement.get()); + ASSERT_TRUE(statement->statement()->Step()); + EXPECT_TRUE(statement->statement()->ColumnInt64(0)); + EXPECT_EQ(term, statement->statement()->ColumnString16(1)); + EXPECT_EQ(ToMilliseconds(row1.last_visit_time()), + statement->statement()->ColumnInt64(2)); + EXPECT_FALSE(statement->statement()->Step()); +} + +TEST_F(AndroidProviderBackendTest, UpdateSearchTerms) { + ASSERT_EQ(sql::INIT_OK, history_db_.Init(history_db_name_, bookmark_temp_)); + ASSERT_EQ(sql::INIT_OK, thumbnail_db_.Init(thumbnail_db_name_, NULL, + &history_db_)); + scoped_ptr<AndroidProviderBackend> backend( + new AndroidProviderBackend(android_cache_db_name_, &history_db_, + &thumbnail_db_, &bookmark_model_, &delegate_)); + // Insert a keyword. + BookmarkRow row1; + row1.set_raw_url("cnn.com"); + row1.set_url(GURL("http://cnn.com")); + row1.set_last_visit_time(Time::Now() - TimeDelta::FromDays(1)); + row1.set_title(UTF8ToUTF16("cnn")); + ASSERT_TRUE(backend->InsertBookmark(row1)); + string16 term = UTF8ToUTF16("Search term 1"); + URLID url_id = history_db_.GetRowForURL(row1.url(), NULL); + ASSERT_TRUE(url_id); + ASSERT_TRUE(history_db_.SetKeywordSearchTermsForURL(url_id, 1, term)); + + // Get the SearchTermID of the row we just inserted. + std::vector<SearchRow::SearchColumnID> projections; + projections.push_back(SearchRow::ID); + projections.push_back(SearchRow::SEARCH_TIME); + projections.push_back(SearchRow::SEARCH_TERM); + std::vector<string16> args; + args.push_back(term); + scoped_ptr<AndroidStatement> statement(backend->QuerySearchTerms( + projections, "search = ?", args, std::string())); + ASSERT_TRUE(statement.get()); + ASSERT_TRUE(statement->statement()->Step()); + SearchTermID id = statement->statement()->ColumnInt64(0); + ASSERT_TRUE(id); + EXPECT_FALSE(statement->statement()->Step()); + + // Update the search term and time. + string16 update_term = UTF8ToUTF16("Update search term"); + args.clear(); + args.push_back(term); + SearchRow search_row; + search_row.set_search_term(update_term); + search_row.set_url(GURL("http://google.com")); + search_row.set_template_url_id(1); + search_row.set_search_time(Time::Now() - TimeDelta::FromHours(1)); + int update_count = 0; + ASSERT_TRUE(backend->UpdateSearchTerms(search_row, "search = ?", args, + &update_count)); + EXPECT_EQ(1, update_count); + + // Verify if the search term updated. + // The origin term should be removed. + std::vector<KeywordSearchTermRow> rows; + ASSERT_TRUE(history_db_.GetKeywordSearchTermRows(term, &rows)); + EXPECT_TRUE(rows.empty()); + // The new term should be inserted. + ASSERT_TRUE(history_db_.GetKeywordSearchTermRows(update_term, &rows)); + ASSERT_EQ(1u, rows.size()); + // The history of urls shouldn't be removed. + ASSERT_TRUE(history_db_.GetRowForURL(row1.url(), NULL)); + // The new URL is inserted. + ASSERT_TRUE(history_db_.GetRowForURL(search_row.url(), NULL)); + + // Verfiy the AndoridSearchID isn't changed. + args.clear(); + args.push_back(update_term); + statement.reset(backend->QuerySearchTerms(projections, "search = ?", args, + std::string())); + ASSERT_TRUE(statement.get()); + ASSERT_TRUE(statement->statement()->Step()); + // The id didn't change. + EXPECT_EQ(id, statement->statement()->ColumnInt64(0)); + // The search time was updated. + EXPECT_EQ(ToMilliseconds(search_row.search_time()), + statement->statement()->ColumnInt64(1)); + // The search term was updated. + EXPECT_EQ(update_term, statement->statement()->ColumnString16(2)); + EXPECT_FALSE(statement->statement()->Step()); + + // Only update the search time. + SearchRow update_time; + update_time.set_search_time(Time::Now()); + // Update it by id. + args.clear(); + std::ostringstream oss; + oss << id; + args.push_back(UTF8ToUTF16(oss.str())); + update_count = 0; + ASSERT_TRUE(backend->UpdateSearchTerms(update_time, "_id = ?", args, + &update_count)); + EXPECT_EQ(1, update_count); + + // Verify the update. + statement.reset(backend->QuerySearchTerms(projections, "_id = ?", args, + std::string())); + ASSERT_TRUE(statement.get()); + ASSERT_TRUE(statement->statement()->Step()); + // The id didn't change. + EXPECT_EQ(id, statement->statement()->ColumnInt64(0)); + // The search time was updated. + EXPECT_EQ(ToMilliseconds(update_time.search_time()), + statement->statement()->ColumnInt64(1)); + // The search term didn't change. + EXPECT_EQ(update_term, statement->statement()->ColumnString16(2)); + EXPECT_FALSE(statement->statement()->Step()); +} + +TEST_F(AndroidProviderBackendTest, DeleteSearchTerms) { + ASSERT_EQ(sql::INIT_OK, history_db_.Init(history_db_name_, bookmark_temp_)); + ASSERT_EQ(sql::INIT_OK, thumbnail_db_.Init(thumbnail_db_name_, NULL, + &history_db_)); + scoped_ptr<AndroidProviderBackend> backend( + new AndroidProviderBackend(android_cache_db_name_, &history_db_, + &thumbnail_db_, &bookmark_model_, &delegate_)); + // Insert a keyword. + BookmarkRow row1; + row1.set_raw_url("cnn.com"); + row1.set_url(GURL("http://cnn.com")); + row1.set_last_visit_time(Time::Now() - TimeDelta::FromDays(1)); + row1.set_title(UTF8ToUTF16("cnn")); + ASSERT_TRUE(backend->InsertBookmark(row1)); + string16 term = UTF8ToUTF16("Search term 1"); + URLID url_id = history_db_.GetRowForURL(row1.url(), NULL); + ASSERT_TRUE(url_id); + ASSERT_TRUE(history_db_.SetKeywordSearchTermsForURL(url_id, 1, term)); + + // Get the SearchTermID of the row we just inserted. + std::vector<SearchRow::SearchColumnID> projections; + projections.push_back(SearchRow::ID); + projections.push_back(SearchRow::SEARCH_TIME); + projections.push_back(SearchRow::SEARCH_TERM); + std::vector<string16> args; + args.push_back(term); + scoped_ptr<AndroidStatement> statement(backend->QuerySearchTerms( + projections, "search = ?", args, std::string())); + ASSERT_TRUE(statement.get()); + ASSERT_TRUE(statement->statement()->Step()); + SearchTermID id1 = statement->statement()->ColumnInt64(0); + ASSERT_TRUE(id1); + EXPECT_FALSE(statement->statement()->Step()); + + // Insert a keyword. + BookmarkRow row2; + row2.set_raw_url("google.com"); + row2.set_url(GURL("http://google.com")); + row2.set_last_visit_time(Time::Now() - TimeDelta::FromDays(1)); + row2.set_title(UTF8ToUTF16("google")); + ASSERT_TRUE(backend->InsertBookmark(row2)); + string16 term2 = UTF8ToUTF16("Search term 2"); + URLID url_id2 = history_db_.GetRowForURL(row2.url(), NULL); + ASSERT_TRUE(url_id2); + ASSERT_TRUE(history_db_.SetKeywordSearchTermsForURL(url_id2, 1, term2)); + + // Get the SearchTermID of the row we just inserted. + projections.clear(); + projections.push_back(SearchRow::ID); + projections.push_back(SearchRow::SEARCH_TIME); + projections.push_back(SearchRow::SEARCH_TERM); + args.clear(); + args.push_back(term2); + statement.reset(backend->QuerySearchTerms(projections, "search = ?", args, + std::string())); + ASSERT_TRUE(statement.get()); + ASSERT_TRUE(statement->statement()->Step()); + SearchTermID id2 = statement->statement()->ColumnInt64(0); + ASSERT_TRUE(id2); + EXPECT_FALSE(statement->statement()->Step()); + + // Delete the first one. + args.clear(); + args.push_back(term); + int deleted_count = 0; + ASSERT_TRUE(backend->DeleteSearchTerms("search = ?", args, &deleted_count)); + EXPECT_EQ(1, deleted_count); + std::vector<KeywordSearchTermRow> rows; + ASSERT_TRUE(history_db_.GetKeywordSearchTermRows(term, &rows)); + EXPECT_TRUE(rows.empty()); + // Verify we can't get the first term. + args.clear(); + std::ostringstream oss; + oss << id1; + args.push_back(UTF8ToUTF16(oss.str())); + statement.reset(backend->QuerySearchTerms(projections, "_id = ?", args, + std::string())); + ASSERT_TRUE(statement.get()); + EXPECT_FALSE(statement->statement()->Step()); + + // The second one is still there. + args.clear(); + std::ostringstream oss1; + oss1 << id2; + args.push_back(UTF8ToUTF16(oss1.str())); + statement.reset(backend->QuerySearchTerms(projections, "_id = ?", args, + std::string())); + ASSERT_TRUE(statement.get()); + EXPECT_TRUE(statement->statement()->Step()); + EXPECT_EQ(id2, statement->statement()->ColumnInt64(0)); + EXPECT_FALSE(statement->statement()->Step()); + + // Remove all search terms in no condition. + deleted_count = 0; + args.clear(); + ASSERT_TRUE(backend->DeleteSearchTerms(std::string(), args, &deleted_count)); + EXPECT_EQ(1, deleted_count); + + // Verify the second one was removed. + args.clear(); + args.push_back(UTF8ToUTF16(oss1.str())); + statement.reset(backend->QuerySearchTerms(projections, "_id = ?", args, + std::string())); + ASSERT_TRUE(statement.get()); + EXPECT_FALSE(statement->statement()->Step()); +} + +TEST_F(AndroidProviderBackendTest, InsertSearchTerm) { + ASSERT_EQ(sql::INIT_OK, history_db_.Init(history_db_name_, bookmark_temp_)); + ASSERT_EQ(sql::INIT_OK, thumbnail_db_.Init(thumbnail_db_name_, NULL, + &history_db_)); + scoped_ptr<AndroidProviderBackend> backend( + new AndroidProviderBackend(android_cache_db_name_, &history_db_, + &thumbnail_db_, &bookmark_model_, &delegate_)); + SearchRow search_row; + search_row.set_search_term(UTF8ToUTF16("google")); + search_row.set_url(GURL("http://google.com")); + search_row.set_template_url_id(1); + search_row.set_search_time(Time::Now() - TimeDelta::FromHours(1)); + + SearchTermID id = backend->InsertSearchTerm(search_row); + ASSERT_TRUE(id); + + std::vector<SearchRow::SearchColumnID> projections; + projections.push_back(SearchRow::ID); + projections.push_back(SearchRow::SEARCH_TIME); + projections.push_back(SearchRow::SEARCH_TERM); + std::vector<string16> args; + std::ostringstream oss; + oss << id; + args.push_back(UTF8ToUTF16(oss.str())); + scoped_ptr<AndroidStatement> statement(backend->QuerySearchTerms( + projections, "_id = ?", args, std::string())); + ASSERT_TRUE(statement.get()); + ASSERT_TRUE(statement->statement()->Step()); + EXPECT_EQ(id, statement->statement()->ColumnInt64(0)); + EXPECT_EQ(ToMilliseconds(search_row.search_time()), + statement->statement()->ColumnInt64(1)); + EXPECT_EQ(search_row.search_term(), + statement->statement()->ColumnString16(2)); + EXPECT_FALSE(statement->statement()->Step()); +} + } // namespace history diff --git a/chrome/browser/history/url_database.cc b/chrome/browser/history/url_database.cc index 2b4ba52..4436a4e 100644 --- a/chrome/browser/history/url_database.cc +++ b/chrome/browser/history/url_database.cc @@ -386,6 +386,12 @@ bool URLDatabase::CreateKeywordSearchTermsIndices() { return false; } + // For query or deletion by term. + if (!GetDB().Execute( + "CREATE INDEX IF NOT EXISTS keyword_search_terms_index3 ON " + "keyword_search_terms (term)")) { + return false; + } return true; } @@ -439,6 +445,26 @@ bool URLDatabase::GetKeywordSearchTermRow(URLID url_id, return true; } +bool URLDatabase::GetKeywordSearchTermRows( + const string16& term, + std::vector<KeywordSearchTermRow>* rows) { + sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, + "SELECT keyword_id, url_id FROM keyword_search_terms WHERE term=?")); + statement.BindString16(0, term); + + if (!statement.is_valid()) + return false; + + while (statement.Step()) { + KeywordSearchTermRow row; + row.url_id = statement.ColumnInt64(1); + row.keyword_id = statement.ColumnInt64(0); + row.term = term; + rows->push_back(row); + } + return true; +} + void URLDatabase::DeleteAllSearchTermsForKeyword( TemplateURLID keyword_id) { DCHECK(keyword_id); @@ -488,6 +514,14 @@ void URLDatabase::GetMostRecentKeywordSearchTerms( } } +bool URLDatabase::DeleteKeywordSearchTerm(const string16& term) { + sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, + "DELETE FROM keyword_search_terms WHERE term=?")); + statement.BindString16(0, term); + + return statement.Run(); +} + bool URLDatabase::DropStarredIDFromURLs() { if (!GetDB().DoesColumnExist("urls", "starred_id")) return true; // urls is already updated, no need to continue. diff --git a/chrome/browser/history/url_database.h b/chrome/browser/history/url_database.h index 2103ab77..57b7ec2 100644 --- a/chrome/browser/history/url_database.h +++ b/chrome/browser/history/url_database.h @@ -197,10 +197,15 @@ class URLDatabase { TemplateURLID keyword_id, const string16& term); - // Looks up a keyword search term given a url id. Fills row with the data. - // Returns true on success and false otherwise. + // Looks up a keyword search term given a url id. Returns all the search terms + // in |rows|. Returns true on success. bool GetKeywordSearchTermRow(URLID url_id, KeywordSearchTermRow* row); + // Looks up all keyword search terms given a term, Fills the rows with data. + // Returns true on success and false otherwise. + bool GetKeywordSearchTermRows(const string16& term, + std::vector<KeywordSearchTermRow>* rows); + // Deletes all search terms for the specified keyword that have been added by // way of SetKeywordSearchTermsForURL. void DeleteAllSearchTermsForKeyword(TemplateURLID keyword_id); @@ -213,6 +218,9 @@ class URLDatabase { int max_count, std::vector<KeywordSearchTermVisit>* matches); + // Deletes all searches matching |term|. + bool DeleteKeywordSearchTerm(const string16& term); + // Migration ----------------------------------------------------------------- // Do to a bug we were setting the favicon of about:blank. This forces diff --git a/chrome/browser/history/url_database_unittest.cc b/chrome/browser/history/url_database_unittest.cc index 6931963..485c940 100644 --- a/chrome/browser/history/url_database_unittest.cc +++ b/chrome/browser/history/url_database_unittest.cc @@ -267,4 +267,80 @@ TEST_F(URLDatabaseTest, IconMappingEnumerator) { ASSERT_FALSE(e.GetNextIconMapping(&icon_mapping)); } +// Test GetKeywordSearchTermRows and DeleteSearchTerm +TEST_F(URLDatabaseTest, GetAndDeleteKeywordSearchTermByTerm) { + URLRow url_info1(GURL("http://www.google.com/")); + url_info1.set_title(UTF8ToUTF16("Google")); + url_info1.set_visit_count(4); + url_info1.set_typed_count(2); + url_info1.set_last_visit(Time::Now() - TimeDelta::FromDays(1)); + url_info1.set_hidden(false); + URLID url_id1 = AddURL(url_info1); + ASSERT_NE(0, url_id1); + + // Add a keyword visit. + TemplateURLID keyword_id = 100; + string16 keyword = UTF8ToUTF16("visit"); + ASSERT_TRUE(SetKeywordSearchTermsForURL(url_id1, keyword_id, keyword)); + + URLRow url_info2(GURL("https://www.google.com/")); + url_info2.set_title(UTF8ToUTF16("Google")); + url_info2.set_visit_count(4); + url_info2.set_typed_count(2); + url_info2.set_last_visit(Time::Now() - TimeDelta::FromDays(1)); + url_info2.set_hidden(false); + URLID url_id2 = AddURL(url_info2); + ASSERT_NE(0, url_id2); + // Add the same keyword for url_info2. + ASSERT_TRUE(SetKeywordSearchTermsForURL(url_id2, keyword_id, keyword)); + + // Add another URL for different keyword. + URLRow url_info3(GURL("https://www.google.com/search")); + url_info3.set_title(UTF8ToUTF16("Google")); + url_info3.set_visit_count(4); + url_info3.set_typed_count(2); + url_info3.set_last_visit(Time::Now() - TimeDelta::FromDays(1)); + url_info3.set_hidden(false); + URLID url_id3 = AddURL(url_info3); + ASSERT_NE(0, url_id3); + string16 keyword2 = UTF8ToUTF16("Search"); + + ASSERT_TRUE(SetKeywordSearchTermsForURL(url_id3, keyword_id, keyword2)); + + // We should get 2 rows for |keyword|. + std::vector<KeywordSearchTermRow> rows; + ASSERT_TRUE(GetKeywordSearchTermRows(keyword, &rows)); + ASSERT_EQ(2u, rows.size()); + if (rows[0].url_id == url_id1) { + EXPECT_EQ(keyword, rows[0].term); + EXPECT_EQ(keyword, rows[1].term); + EXPECT_EQ(url_id2, rows[1].url_id); + } else { + EXPECT_EQ(keyword, rows[0].term); + EXPECT_EQ(url_id1, rows[1].url_id); + EXPECT_EQ(keyword, rows[1].term); + EXPECT_EQ(url_id2, rows[0].url_id); + } + + // We should get 1 row for |keyword2|. + rows.clear(); + ASSERT_TRUE(GetKeywordSearchTermRows(keyword2, &rows)); + ASSERT_EQ(1u, rows.size()); + EXPECT_EQ(keyword2, rows[0].term); + EXPECT_EQ(url_id3, rows[0].url_id); + + // Delete all rows have keyword. + ASSERT_TRUE(DeleteKeywordSearchTerm(keyword)); + rows.clear(); + // We should still find keyword2. + ASSERT_TRUE(GetKeywordSearchTermRows(keyword2, &rows)); + ASSERT_EQ(1u, rows.size()); + EXPECT_EQ(keyword2, rows[0].term); + EXPECT_EQ(url_id3, rows[0].url_id); + rows.clear(); + // No row for keyword. + ASSERT_TRUE(GetKeywordSearchTermRows(keyword, &rows)); + EXPECT_TRUE(rows.empty()); +} + } // namespace history diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 1b517af..7378563 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1129,6 +1129,8 @@ 'browser/history/android/bookmark_model_sql_handler.h', 'browser/history/android/favicon_sql_handler.cc', 'browser/history/android/favicon_sql_handler.h', + 'browser/history/android/sql_handler.cc', + 'browser/history/android/sql_handler.h', 'browser/history/android/urls_sql_handler.cc', 'browser/history/android/urls_sql_handler.h', 'browser/history/android/visit_sql_handler.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 510c48a..0214b1e 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1489,6 +1489,11 @@ 'browser/google/google_util_unittest.cc', 'browser/gpu_blacklist_unittest.cc', 'browser/gpu_util_unittest.cc', + 'browser/history/android/android_cache_database_unittest.cc', + 'browser/history/android/android_history_types_unittest.cc', + 'browser/history/android/android_provider_backend_unittest.cc', + 'browser/history/android/urls_sql_handler_unittest.cc', + 'browser/history/android/visit_sql_handler_unittest.cc', 'browser/history/expire_history_backend_unittest.cc', 'browser/history/history_backend_unittest.cc', 'browser/history/history_querying_unittest.cc', |