summaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
authorisherman@chromium.org <isherman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-02-26 07:04:15 +0000
committerisherman@chromium.org <isherman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-02-26 07:04:15 +0000
commitd81e1fd783cf736bbec9f1f97e728f5b2f7f463e (patch)
tree3760d35955628081c92e9dedcb3b2c4883d6e44b /components
parent5cf4f1f5098478abbb2503b2d425153eaa96f6da (diff)
downloadchromium_src-d81e1fd783cf736bbec9f1f97e728f5b2f7f463e.zip
chromium_src-d81e1fd783cf736bbec9f1f97e728f5b2f7f463e.tar.gz
chromium_src-d81e1fd783cf736bbec9f1f97e728f5b2f7f463e.tar.bz2
[Autofill] Merge the autofill_dates table into the autofill table.
We already only store two timestamps per autocomplete entry: creation and last use. However, we used to store an arbitrary number of timestamps, and the table structure was designed to support that. Now that that's no longer the case, we can dramatically simplify the code. The one cost to only keeping two timestamps, rather than all of them, is that it's no longer possible to perfectly undo changes in response to a user clearing Autofill data for e.g. the last week. In practice, users are more likely to care about having less data stored on disk and traversing the Sync pipes than about having perfect frequency information after partially clearing the Autofill data. BUG=118696 TEST=unit_tests R=estade@chromium.org Review URL: https://codereview.chromium.org/166313002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@253380 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'components')
-rw-r--r--components/autofill/core/browser/webdata/autofill_table.cc708
-rw-r--r--components/autofill/core/browser/webdata/autofill_table.h78
-rw-r--r--components/autofill/core/browser/webdata/autofill_table_unittest.cc251
-rw-r--r--components/test/data/web_database/version_21.sql40
-rw-r--r--components/test/data/web_database/version_54.sql40
-rw-r--r--components/webdata/common/web_database.cc4
-rw-r--r--components/webdata/common/web_database_migration_unittest.cc280
7 files changed, 879 insertions, 522 deletions
diff --git a/components/autofill/core/browser/webdata/autofill_table.cc b/components/autofill/core/browser/webdata/autofill_table.cc
index 039942f..23c5b05 100644
--- a/components/autofill/core/browser/webdata/autofill_table.cc
+++ b/components/autofill/core/browser/webdata/autofill_table.cc
@@ -5,6 +5,7 @@
#include "components/autofill/core/browser/webdata/autofill_table.h"
#include <algorithm>
+#include <cmath>
#include <limits>
#include <map>
#include <set>
@@ -14,6 +15,7 @@
#include "base/guid.h"
#include "base/i18n/case_conversion.h"
#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
@@ -38,14 +40,27 @@ using base::Time;
namespace autofill {
namespace {
-typedef std::vector<Tuple3<int64, base::string16, base::string16> >
- AutofillElementList;
-
template<typename T>
T* address_of(T& v) {
return &v;
}
+// Helper struct for AutofillTable::RemoveFormElementsAddedBetween().
+// Contains all the necessary fields to update a row in the 'autofill' table.
+struct AutofillUpdate {
+ base::string16 name;
+ base::string16 value;
+ time_t date_created;
+ time_t date_last_used;
+ int count;
+};
+
+// Rounds a positive floating point number to the nearest integer.
+int Round(float f) {
+ DCHECK_GE(f, 0.f);
+ return base::checked_cast<int>(std::floor(f + 0.5f));
+}
+
// Returns the |data_model|'s value corresponding to the |type|, trimmed to the
// maximum length that can be stored in a column of the Autofill database.
base::string16 GetInfo(const AutofillDataModel& data_model,
@@ -364,10 +379,9 @@ WebDatabaseTable::TypeKey AutofillTable::GetTypeKey() const {
bool AutofillTable::Init(sql::Connection* db, sql::MetaTable* meta_table) {
WebDatabaseTable::Init(db, meta_table);
- return (InitMainTable() && InitCreditCardsTable() && InitDatesTable() &&
- InitProfilesTable() && InitProfileNamesTable() &&
- InitProfileEmailsTable() && InitProfilePhonesTable() &&
- InitProfileTrashTable());
+ return (InitMainTable() && InitCreditCardsTable() && InitProfilesTable() &&
+ InitProfileNamesTable() && InitProfileEmailsTable() &&
+ InitProfilePhonesTable() && InitProfileTrashTable());
}
bool AutofillTable::IsSyncable() {
@@ -379,7 +393,7 @@ bool AutofillTable::MigrateToVersion(int version,
// Migrate if necessary.
switch (version) {
case 22:
- return ClearAutofillEmptyValueElements();
+ return MigrateToVersion22ClearAutofillEmptyValueElements();
case 23:
return MigrateToVersion23AddCardNumberEncryptedColumn();
case 24:
@@ -423,6 +437,9 @@ bool AutofillTable::MigrateToVersion(int version,
case 54:
*update_compatible_version = true;
return MigrateToVersion54AddI18nFieldsAndRemoveDeprecatedFields();
+ case 55:
+ *update_compatible_version = true;
+ return MigrateToVersion55MergeAutofillDatesTable();
}
return true;
}
@@ -479,8 +496,7 @@ bool AutofillTable::GetFormValuesForElementName(
}
bool AutofillTable::HasFormElements() {
- sql::Statement s(db_->GetUniqueStatement(
- "SELECT COUNT(*) FROM autofill"));
+ sql::Statement s(db_->GetUniqueStatement("SELECT COUNT(*) FROM autofill"));
if (!s.Step()) {
NOTREACHED();
return false;
@@ -492,264 +508,139 @@ bool AutofillTable::RemoveFormElementsAddedBetween(
const Time& delete_begin,
const Time& delete_end,
std::vector<AutofillChange>* changes) {
- DCHECK(changes);
- // Query for the pair_id, name, and value of all form elements that
- // were used between the given times.
+ const time_t delete_begin_time_t = delete_begin.ToTimeT();
+ const time_t delete_end_time_t = GetEndTime(delete_end);
+
+ // Query for the name, value, count, and access dates of all form elements
+ // that were used between the given times.
sql::Statement s(db_->GetUniqueStatement(
- "SELECT DISTINCT a.pair_id, a.name, a.value "
- "FROM autofill_dates ad JOIN autofill a ON ad.pair_id = a.pair_id "
- "WHERE ad.date_created >= ? AND ad.date_created < ?"));
- s.BindInt64(0, delete_begin.ToTimeT());
- s.BindInt64(1,
- (delete_end.is_null() || delete_end == base::Time::Max()) ?
- std::numeric_limits<int64>::max() :
- delete_end.ToTimeT());
-
- AutofillElementList elements;
+ "SELECT name, value, count, date_created, date_last_used FROM autofill "
+ "WHERE (date_created >= ? AND date_created < ?) OR "
+ " (date_last_used >= ? AND date_last_used < ?)"));
+ s.BindInt64(0, delete_begin_time_t);
+ s.BindInt64(1, delete_end_time_t);
+ s.BindInt64(2, delete_begin_time_t);
+ s.BindInt64(3, delete_end_time_t);
+
+ std::vector<AutofillUpdate> updates;
+ std::vector<AutofillChange> tentative_changes;
while (s.Step()) {
- elements.push_back(MakeTuple(s.ColumnInt64(0),
- s.ColumnString16(1),
- s.ColumnString16(2)));
+ base::string16 name = s.ColumnString16(0);
+ base::string16 value = s.ColumnString16(1);
+ int count = s.ColumnInt(2);
+ time_t date_created_time_t = s.ColumnInt64(3);
+ time_t date_last_used_time_t = s.ColumnInt64(4);
+
+ // If *all* uses of the element were between |delete_begin| and
+ // |delete_end|, then delete the element. Otherwise, update the use
+ // timestamps and use count.
+ AutofillChange::Type change_type;
+ if (date_created_time_t >= delete_begin_time_t &&
+ date_last_used_time_t < delete_end_time_t) {
+ change_type = AutofillChange::REMOVE;
+ } else {
+ change_type = AutofillChange::UPDATE;
+
+ // For all updated elements, set either date_created or date_last_used so
+ // that the range [date_created, date_last_used] no longer overlaps with
+ // [delete_begin, delete_end). Update the count by interpolating.
+ // Precisely, compute the average amount of time between increments to the
+ // count in the original range [date_created, date_last_used]:
+ // avg_delta = (date_last_used_orig - date_created_orig) / (count - 1)
+ // The count can be exressed as
+ // count = 1 + (date_last_used - date_created) / avg_delta
+ // Hence, update the count to
+ // count_new = 1 + (date_last_used_new - date_created_new) / avg_delta
+ // = 1 + ((count - 1) *
+ // (date_last_used_new - date_created_new) /
+ // (date_last_used_orig - date_created_orig))
+ // Interpolating might not give a result that completely accurately
+ // reflects the user's history, but it's the best that can be done given
+ // the information in the database.
+ AutofillUpdate updated_entry;
+ updated_entry.name = name;
+ updated_entry.value = value;
+ updated_entry.date_created =
+ date_created_time_t < delete_begin_time_t ?
+ date_created_time_t :
+ delete_end_time_t;
+ updated_entry.date_last_used =
+ date_last_used_time_t >= delete_end_time_t ?
+ date_last_used_time_t :
+ delete_begin_time_t - 1;
+ updated_entry.count =
+ 1 +
+ Round(1.0 * (count - 1) *
+ (updated_entry.date_last_used - updated_entry.date_created) /
+ (date_last_used_time_t - date_created_time_t));
+ updates.push_back(updated_entry);
+ }
+
+ tentative_changes.push_back(
+ AutofillChange(change_type, AutofillKey(name, value)));
}
if (!s.Succeeded())
return false;
- for (AutofillElementList::iterator itr = elements.begin();
- itr != elements.end(); ++itr) {
- int how_many = 0;
- if (!RemoveFormElementForTimeRange(itr->a, delete_begin, delete_end,
- &how_many)) {
+ // As a single transaction, remove or update the elements appropriately.
+ sql::Statement s_delete(db_->GetUniqueStatement(
+ "DELETE FROM autofill WHERE date_created >= ? AND date_last_used < ?"));
+ s_delete.BindInt64(0, delete_begin_time_t);
+ s_delete.BindInt64(1, delete_end_time_t);
+ sql::Transaction transaction(db_);
+ if (!transaction.Begin())
+ return false;
+ if (!s_delete.Run())
+ return false;
+ for (size_t i = 0; i < updates.size(); ++i) {
+ sql::Statement s_update(db_->GetUniqueStatement(
+ "UPDATE autofill SET date_created = ?, date_last_used = ?, count = ?"
+ "WHERE name = ? AND value = ?"));
+ s_update.BindInt64(0, updates[i].date_created);
+ s_update.BindInt64(1, updates[i].date_last_used);
+ s_update.BindInt(2, updates[i].count);
+ s_update.BindString16(3, updates[i].name);
+ s_update.BindString16(4, updates[i].value);
+ if (!s_update.Run())
return false;
- }
- // We store at most 2 time stamps. If we remove both of them we should
- // delete the corresponding data. If we delete only one it could still be
- // the last timestamp for the data, so check how many timestamps do remain.
- bool should_remove = (CountTimestampsData(itr->a) == 0);
- if (should_remove) {
- if (!RemoveFormElementForID(itr->a))
- return false;
- } else {
- if (!AddToCountOfFormElement(itr->a, -how_many))
- return false;
- }
- AutofillChange::Type change_type =
- should_remove ? AutofillChange::REMOVE : AutofillChange::UPDATE;
- changes->push_back(AutofillChange(change_type,
- AutofillKey(itr->b, itr->c)));
}
+ if (!transaction.Commit())
+ return false;
+ *changes = tentative_changes;
return true;
}
bool AutofillTable::RemoveExpiredFormElements(
std::vector<AutofillChange>* changes) {
- DCHECK(changes);
+ base::Time expiration_time = AutofillEntry::ExpirationTime();
- base::Time delete_end = AutofillEntry::ExpirationTime();
- // Query for the pair_id, name, and value of all form elements that
- // were last used before the |delete_end|.
+ // Query for the name and value of all form elements that were last used
+ // before the |expiration_time|.
sql::Statement select_for_delete(db_->GetUniqueStatement(
- "SELECT DISTINCT pair_id, name, value "
- "FROM autofill WHERE pair_id NOT IN "
- "(SELECT DISTINCT pair_id "
- "FROM autofill_dates WHERE date_created >= ?)"));
- select_for_delete.BindInt64(0, delete_end.ToTimeT());
- AutofillElementList entries_to_delete;
+ "SELECT name, value FROM autofill WHERE date_last_used < ?"));
+ select_for_delete.BindInt64(0, expiration_time.ToTimeT());
+ std::vector<AutofillChange> tentative_changes;
while (select_for_delete.Step()) {
- entries_to_delete.push_back(MakeTuple(select_for_delete.ColumnInt64(0),
- select_for_delete.ColumnString16(1),
- select_for_delete.ColumnString16(2)));
+ base::string16 name = select_for_delete.ColumnString16(0);
+ base::string16 value = select_for_delete.ColumnString16(1);
+ tentative_changes.push_back(
+ AutofillChange(AutofillChange::REMOVE, AutofillKey(name, value)));
}
if (!select_for_delete.Succeeded())
return false;
sql::Statement delete_data_statement(db_->GetUniqueStatement(
- "DELETE FROM autofill WHERE pair_id NOT IN ("
- "SELECT pair_id FROM autofill_dates WHERE date_created >= ?)"));
- delete_data_statement.BindInt64(0, delete_end.ToTimeT());
+ "DELETE FROM autofill WHERE date_last_used < ?"));
+ delete_data_statement.BindInt64(0, expiration_time.ToTimeT());
if (!delete_data_statement.Run())
return false;
- sql::Statement delete_times_statement(db_->GetUniqueStatement(
- "DELETE FROM autofill_dates WHERE pair_id NOT IN ("
- "SELECT pair_id FROM autofill_dates WHERE date_created >= ?)"));
- delete_times_statement.BindInt64(0, delete_end.ToTimeT());
- if (!delete_times_statement.Run())
- return false;
-
- // Cull remaining entries' timestamps.
- std::vector<AutofillEntry> entries;
- if (!GetAllAutofillEntries(&entries))
- return false;
- sql::Statement cull_date_entry(db_->GetUniqueStatement(
- "DELETE FROM autofill_dates "
- "WHERE pair_id == (SELECT pair_id FROM autofill "
- "WHERE name = ? and value = ?)"
- "AND date_created != ? AND date_created != ?"));
- for (size_t i = 0; i < entries.size(); ++i) {
- cull_date_entry.BindString16(0, entries[i].key().name());
- cull_date_entry.BindString16(1, entries[i].key().value());
- cull_date_entry.BindInt64(2,
- entries[i].timestamps().empty() ? 0 :
- entries[i].timestamps().front().ToTimeT());
- cull_date_entry.BindInt64(3,
- entries[i].timestamps().empty() ? 0 :
- entries[i].timestamps().back().ToTimeT());
- if (!cull_date_entry.Run())
- return false;
- cull_date_entry.Reset(true);
- }
-
- changes->clear();
- changes->reserve(entries_to_delete.size());
-
- for (AutofillElementList::iterator it = entries_to_delete.begin();
- it != entries_to_delete.end(); ++it) {
- changes->push_back(AutofillChange(
- AutofillChange::REMOVE, AutofillKey(it->b, it->c)));
- }
+ *changes = tentative_changes;
return true;
}
-bool AutofillTable::RemoveFormElementForTimeRange(int64 pair_id,
- const Time& delete_begin,
- const Time& delete_end,
- int* how_many) {
- sql::Statement s(db_->GetUniqueStatement(
- "DELETE FROM autofill_dates WHERE pair_id = ? AND "
- "date_created >= ? AND date_created < ?"));
- s.BindInt64(0, pair_id);
- s.BindInt64(1, delete_begin.is_null() ? 0 : delete_begin.ToTimeT());
- s.BindInt64(2, delete_end.is_null() ? std::numeric_limits<int64>::max() :
- delete_end.ToTimeT());
-
- bool result = s.Run();
- if (how_many)
- *how_many = db_->GetLastChangeCount();
-
- return result;
-}
-
-int AutofillTable::CountTimestampsData(int64 pair_id) {
- sql::Statement s(db_->GetUniqueStatement(
- "SELECT COUNT(*) FROM autofill_dates WHERE pair_id = ?"));
- s.BindInt64(0, pair_id);
- if (!s.Step()) {
- NOTREACHED();
- return 0;
- } else {
- return s.ColumnInt(0);
- }
-}
-
-bool AutofillTable::AddToCountOfFormElement(int64 pair_id,
- int delta) {
- int count = 0;
-
- if (!GetCountOfFormElement(pair_id, &count))
- return false;
-
- if (count + delta == 0) {
- // Should remove the element earlier in the code.
- NOTREACHED();
- return false;
- } else {
- if (!SetCountOfFormElement(pair_id, count + delta))
- return false;
- }
- return true;
-}
-
-bool AutofillTable::GetIDAndCountOfFormElement(
- const FormFieldData& element,
- int64* pair_id,
- int* count) {
- DCHECK(pair_id);
- DCHECK(count);
-
- sql::Statement s(db_->GetUniqueStatement(
- "SELECT pair_id, count FROM autofill "
- "WHERE name = ? AND value = ?"));
- s.BindString16(0, element.name);
- s.BindString16(1, element.value);
-
- if (!s.is_valid())
- return false;
-
- *pair_id = 0;
- *count = 0;
-
- if (s.Step()) {
- *pair_id = s.ColumnInt64(0);
- *count = s.ColumnInt(1);
- }
-
- return true;
-}
-
-bool AutofillTable::GetCountOfFormElement(int64 pair_id, int* count) {
- DCHECK(count);
- sql::Statement s(db_->GetUniqueStatement(
- "SELECT count FROM autofill WHERE pair_id = ?"));
- s.BindInt64(0, pair_id);
-
- if (s.Step()) {
- *count = s.ColumnInt(0);
- return true;
- }
- return false;
-}
-
-bool AutofillTable::SetCountOfFormElement(int64 pair_id, int count) {
- sql::Statement s(db_->GetUniqueStatement(
- "UPDATE autofill SET count = ? WHERE pair_id = ?"));
- s.BindInt(0, count);
- s.BindInt64(1, pair_id);
-
- return s.Run();
-}
-
-bool AutofillTable::InsertFormElement(const FormFieldData& element,
- int64* pair_id) {
- DCHECK(pair_id);
- sql::Statement s(db_->GetUniqueStatement(
- "INSERT INTO autofill (name, value, value_lower) VALUES (?,?,?)"));
- s.BindString16(0, element.name);
- s.BindString16(1, element.value);
- s.BindString16(2, base::i18n::ToLower(element.value));
-
- if (!s.Run())
- return false;
-
- *pair_id = db_->GetLastInsertRowId();
- return true;
-}
-
-bool AutofillTable::InsertPairIDAndDate(int64 pair_id,
- const Time& date_created) {
- sql::Statement s(db_->GetUniqueStatement(
- "INSERT INTO autofill_dates "
- "(pair_id, date_created) VALUES (?, ?)"));
- s.BindInt64(0, pair_id);
- s.BindInt64(1, date_created.ToTimeT());
-
- return s.Run();
-}
-
-bool AutofillTable::DeleteLastAccess(int64 pair_id) {
- // Inner SELECT selects the newest |date_created| for a given |pair_id|.
- // DELETE deletes only that entry.
- sql::Statement s(db_->GetUniqueStatement(
- "DELETE FROM autofill_dates WHERE pair_id = ? and date_created IN "
- "(SELECT date_created FROM autofill_dates WHERE pair_id = ? "
- "ORDER BY date_created DESC LIMIT 1)"));
- s.BindInt64(0, pair_id);
- s.BindInt64(1, pair_id);
-
- return s.Run();
-}
-
bool AutofillTable::AddFormFieldValuesTime(
const std::vector<FormFieldData>& elements,
std::vector<AutofillChange>* changes,
@@ -771,75 +662,20 @@ bool AutofillTable::AddFormFieldValuesTime(
return result;
}
-bool AutofillTable::ClearAutofillEmptyValueElements() {
- sql::Statement s(db_->GetUniqueStatement(
- "SELECT pair_id FROM autofill WHERE TRIM(value)= \"\""));
- if (!s.is_valid())
- return false;
-
- std::set<int64> ids;
- while (s.Step())
- ids.insert(s.ColumnInt64(0));
- if (!s.Succeeded())
- return false;
-
- bool success = true;
- for (std::set<int64>::const_iterator iter = ids.begin(); iter != ids.end();
- ++iter) {
- if (!RemoveFormElementForID(*iter))
- success = false;
- }
-
- return success;
-}
-
bool AutofillTable::GetAllAutofillEntries(std::vector<AutofillEntry>* entries) {
- DCHECK(entries);
sql::Statement s(db_->GetUniqueStatement(
- "SELECT name, value, date_created FROM autofill a JOIN "
- "autofill_dates ad ON a.pair_id=ad.pair_id"));
-
- bool first_entry = true;
- AutofillKey* current_key_ptr = NULL;
- std::vector<Time>* timestamps_ptr = NULL;
- base::string16 name, value;
- Time time;
- while (s.Step()) {
- name = s.ColumnString16(0);
- value = s.ColumnString16(1);
- time = Time::FromTimeT(s.ColumnInt64(2));
-
- if (first_entry) {
- current_key_ptr = new AutofillKey(name, value);
-
- timestamps_ptr = new std::vector<Time>;
- timestamps_ptr->push_back(time);
-
- first_entry = false;
- } else {
- // we've encountered the next entry
- if (current_key_ptr->name().compare(name) != 0 ||
- current_key_ptr->value().compare(value) != 0) {
- AutofillEntry entry(*current_key_ptr, *timestamps_ptr);
- entries->push_back(entry);
-
- delete current_key_ptr;
- delete timestamps_ptr;
+ "SELECT name, value, date_created, date_last_used FROM autofill"));
- current_key_ptr = new AutofillKey(name, value);
- timestamps_ptr = new std::vector<Time>;
- }
- timestamps_ptr->push_back(time);
- }
- }
+ while (s.Step()) {
+ base::string16 name = s.ColumnString16(0);
+ base::string16 value = s.ColumnString16(1);
- // If there is at least one result returned, first_entry will be false.
- // For this case we need to do a final cleanup step.
- if (!first_entry) {
- AutofillEntry entry(*current_key_ptr, *timestamps_ptr);
- entries->push_back(entry);
- delete current_key_ptr;
- delete timestamps_ptr;
+ std::vector<Time> timestamps;
+ timestamps.push_back(Time::FromTimeT(s.ColumnInt64(2)));
+ timestamps.push_back(Time::FromTimeT(s.ColumnInt64(3)));
+ if (timestamps.front() == timestamps.back())
+ timestamps.pop_back();
+ entries->push_back(AutofillEntry(AutofillKey(name, value), timestamps));
}
return s.Succeeded();
@@ -848,18 +684,22 @@ bool AutofillTable::GetAllAutofillEntries(std::vector<AutofillEntry>* entries) {
bool AutofillTable::GetAutofillTimestamps(const base::string16& name,
const base::string16& value,
std::vector<Time>* timestamps) {
- DCHECK(timestamps);
sql::Statement s(db_->GetUniqueStatement(
- "SELECT date_created FROM autofill a JOIN "
- "autofill_dates ad ON a.pair_id=ad.pair_id "
- "WHERE a.name = ? AND a.value = ?"));
+ "SELECT date_created, date_last_used FROM autofill "
+ "WHERE name = ? AND value = ?"));
s.BindString16(0, name);
s.BindString16(1, value);
+ if (!s.Step())
+ return false;
- while (s.Step())
- timestamps->push_back(Time::FromTimeT(s.ColumnInt64(0)));
+ timestamps->clear();
+ timestamps->push_back(Time::FromTimeT(s.ColumnInt64(0)));
+ timestamps->push_back(Time::FromTimeT(s.ColumnInt64(1)));
+ if (timestamps->front() == timestamps->back())
+ timestamps->pop_back();
- return s.Succeeded();
+ DCHECK(!s.Step());
+ return true;
}
bool AutofillTable::UpdateAutofillEntries(
@@ -868,24 +708,17 @@ bool AutofillTable::UpdateAutofillEntries(
return true;
// Remove all existing entries.
- for (size_t i = 0; i < entries.size(); i++) {
- std::string sql = "SELECT pair_id FROM autofill "
- "WHERE name = ? AND value = ?";
- sql::Statement s(db_->GetUniqueStatement(sql.c_str()));
+ for (size_t i = 0; i < entries.size(); ++i) {
+ sql::Statement s(db_->GetUniqueStatement(
+ "DELETE FROM autofill WHERE name = ? AND value = ?"));
s.BindString16(0, entries[i].key().name());
s.BindString16(1, entries[i].key().value());
-
- if (!s.is_valid())
+ if (!s.Run())
return false;
-
- if (s.Step()) {
- if (!RemoveFormElementForID(s.ColumnInt64(0)))
- return false;
- }
}
// Insert all the supplied autofill entries.
- for (size_t i = 0; i < entries.size(); i++) {
+ for (size_t i = 0; i < entries.size(); ++i) {
if (!InsertAutofillEntry(entries[i]))
return false;
}
@@ -894,69 +727,86 @@ bool AutofillTable::UpdateAutofillEntries(
}
bool AutofillTable::InsertAutofillEntry(const AutofillEntry& entry) {
- std::string sql = "INSERT INTO autofill (name, value, value_lower, count) "
- "VALUES (?, ?, ?, ?)";
- sql::Statement s(db_->GetUniqueStatement(sql.c_str()));
- s.BindString16(0, entry.key().name());
- s.BindString16(1, entry.key().value());
- s.BindString16(2, base::i18n::ToLower(entry.key().value()));
- s.BindInt(3, entry.timestamps().size());
-
- if (!s.Run())
+ if (entry.timestamps().empty())
return false;
- int64 pair_id = db_->GetLastInsertRowId();
- for (size_t i = 0; i < entry.timestamps().size(); i++) {
- if (!InsertPairIDAndDate(pair_id, entry.timestamps()[i]))
- return false;
+ Time date_created = entry.timestamps().front();
+ Time date_last_used = date_created;
+ for (size_t i = 1; i < entry.timestamps().size(); ++i) {
+ const Time& timestamp = entry.timestamps()[i];
+ date_created = std::min(date_created, timestamp);
+ date_last_used = std::max(date_last_used, timestamp);
}
- return true;
+ std::string sql =
+ "INSERT INTO autofill "
+ "(name, value, value_lower, date_created, date_last_used, count) "
+ "VALUES (?, ?, ?, ?, ?, ?)";
+ sql::Statement s(db_->GetUniqueStatement(sql.c_str()));
+ s.BindString16(0, entry.key().name());
+ s.BindString16(1, entry.key().value());
+ s.BindString16(2, base::i18n::ToLower(entry.key().value()));
+ s.BindInt64(3, date_created.ToTimeT());
+ s.BindInt64(4, date_last_used.ToTimeT());
+ // TODO(isherman): The counts column is currently synced implicitly as the
+ // number of timestamps. Sync the value explicitly instead, since the DB now
+ // only saves the first and last timestamp, which makes counting timestamps
+ // completely meaningless as a way to track frequency of usage.
+ s.BindInt(5, entry.timestamps().size());
+ return s.Run();
}
bool AutofillTable::AddFormFieldValueTime(const FormFieldData& element,
std::vector<AutofillChange>* changes,
Time time) {
- int count = 0;
- int64 pair_id;
-
- if (!GetIDAndCountOfFormElement(element, &pair_id, &count))
- return false;
-
- if (count == 0 && !InsertFormElement(element, &pair_id))
- return false;
-
- if (!SetCountOfFormElement(pair_id, count + 1))
+ sql::Statement s_exists(db_->GetUniqueStatement(
+ "SELECT COUNT(*) FROM autofill WHERE name = ? AND value = ?"));
+ s_exists.BindString16(0, element.name);
+ s_exists.BindString16(1, element.value);
+ if (!s_exists.Step())
return false;
- // If we already have more than 2 times delete last one, before adding new
- // one.
- if (count >= 2 && !DeleteLastAccess(pair_id))
- return false;
-
- if (!InsertPairIDAndDate(pair_id, time))
- return false;
+ bool already_exists = s_exists.ColumnInt(0) > 0;
+ if (already_exists) {
+ sql::Statement s(db_->GetUniqueStatement(
+ "UPDATE autofill SET date_last_used = ?, count = count + 1 "
+ "WHERE name = ? AND value = ?"));
+ s.BindInt64(0, time.ToTimeT());
+ s.BindString16(1, element.name);
+ s.BindString16(2, element.value);
+ if (!s.Run())
+ return false;
+ } else {
+ time_t time_as_time_t = time.ToTimeT();
+ sql::Statement s(db_->GetUniqueStatement(
+ "INSERT INTO autofill "
+ "(name, value, value_lower, date_created, date_last_used, count) "
+ "VALUES (?, ?, ?, ?, ?, ?)"));
+ s.BindString16(0, element.name);
+ s.BindString16(1, element.value);
+ s.BindString16(2, base::i18n::ToLower(element.value));
+ s.BindInt64(3, time_as_time_t);
+ s.BindInt64(4, time_as_time_t);
+ s.BindInt(5, 1);
+ if (!s.Run())
+ return false;
+ }
AutofillChange::Type change_type =
- count == 0 ? AutofillChange::ADD : AutofillChange::UPDATE;
+ already_exists ? AutofillChange::UPDATE : AutofillChange::ADD;
changes->push_back(
- AutofillChange(change_type,
- AutofillKey(element.name, element.value)));
+ AutofillChange(change_type, AutofillKey(element.name, element.value)));
return true;
}
bool AutofillTable::RemoveFormElement(const base::string16& name,
const base::string16& value) {
- // Find the id for that pair.
sql::Statement s(db_->GetUniqueStatement(
- "SELECT pair_id FROM autofill WHERE name = ? AND value= ?"));
+ "DELETE FROM autofill WHERE name = ? AND value= ?"));
s.BindString16(0, name);
s.BindString16(1, value);
-
- if (s.Step())
- return RemoveFormElementForID(s.ColumnInt64(0));
- return false;
+ return s.Run();
}
bool AutofillTable::AddAutofillProfile(const AutofillProfile& profile) {
@@ -1355,17 +1205,6 @@ bool AutofillTable::EmptyAutofillProfilesTrash() {
}
-bool AutofillTable::RemoveFormElementForID(int64 pair_id) {
- sql::Statement s(db_->GetUniqueStatement(
- "DELETE FROM autofill WHERE pair_id = ?"));
- s.BindInt64(0, pair_id);
-
- if (s.Run())
- return RemoveFormElementForTimeRange(pair_id, Time(), Time(), NULL);
-
- return false;
-}
-
bool AutofillTable::AddAutofillGUIDToTrash(const std::string& guid) {
sql::Statement s(db_->GetUniqueStatement(
"INSERT INTO autofill_profiles_trash"
@@ -1400,16 +1239,12 @@ bool AutofillTable::InitMainTable() {
"name VARCHAR, "
"value VARCHAR, "
"value_lower VARCHAR, "
- "pair_id INTEGER PRIMARY KEY, "
- "count INTEGER DEFAULT 1)")) {
- NOTREACHED();
- return false;
- }
- if (!db_->Execute("CREATE INDEX autofill_name ON autofill (name)")) {
- NOTREACHED();
- return false;
- }
- if (!db_->Execute("CREATE INDEX autofill_name_value_lower ON "
+ "date_created INTEGER DEFAULT 0, "
+ "date_last_used INTEGER DEFAULT 0, "
+ "count INTEGER DEFAULT 1, "
+ "PRIMARY KEY (name, value))") ||
+ !db_->Execute("CREATE INDEX autofill_name ON autofill (name)") ||
+ !db_->Execute("CREATE INDEX autofill_name_value_lower ON "
"autofill (name, value_lower)")) {
NOTREACHED();
return false;
@@ -1436,23 +1271,6 @@ bool AutofillTable::InitCreditCardsTable() {
return true;
}
-bool AutofillTable::InitDatesTable() {
- if (!db_->DoesTableExist("autofill_dates")) {
- if (!db_->Execute("CREATE TABLE autofill_dates ( "
- "pair_id INTEGER DEFAULT 0, "
- "date_created INTEGER DEFAULT 0)")) {
- NOTREACHED();
- return false;
- }
- if (!db_->Execute("CREATE INDEX autofill_dates_pair_id ON "
- "autofill_dates (pair_id)")) {
- NOTREACHED();
- return false;
- }
- }
- return true;
-}
-
bool AutofillTable::InitProfilesTable() {
if (!db_->DoesTableExist("autofill_profiles")) {
if (!db_->Execute("CREATE TABLE autofill_profiles ( "
@@ -1523,6 +1341,33 @@ bool AutofillTable::InitProfileTrashTable() {
return true;
}
+bool AutofillTable::MigrateToVersion22ClearAutofillEmptyValueElements() {
+ sql::Statement s(db_->GetUniqueStatement(
+ "SELECT pair_id FROM autofill WHERE TRIM(value) = \"\""));
+ if (!s.is_valid())
+ return false;
+
+ std::set<int64> ids;
+ while (s.Step())
+ ids.insert(s.ColumnInt64(0));
+ if (!s.Succeeded())
+ return false;
+
+ if (!db_->Execute("DELETE FROM autofill WHERE TRIM(value) = \"\""))
+ return false;
+
+ for (std::set<int64>::const_iterator it = ids.begin(); it != ids.end();
+ ++it) {
+ sql::Statement s(db_->GetUniqueStatement(
+ "DELETE FROM autofill_dates WHERE pair_id = ?"));
+ s.BindInt64(0, *it);
+ if (!s.Run())
+ return false;
+ }
+
+ return true;
+}
+
// Add the card_number_encrypted column if credit card table was not
// created in this build (otherwise the column already exists).
// WARNING: Do not change the order of the execution of the SQL
@@ -2291,4 +2136,65 @@ bool AutofillTable::MigrateToVersion54AddI18nFieldsAndRemoveDeprecatedFields() {
return transaction.Commit();
}
+bool AutofillTable::MigrateToVersion55MergeAutofillDatesTable() {
+ sql::Transaction transaction(db_);
+ if (!transaction.Begin())
+ return false;
+
+ if (db_->DoesTableExist("autofill_temp") ||
+ !db_->Execute("CREATE TABLE autofill_temp ("
+ "name VARCHAR, "
+ "value VARCHAR, "
+ "value_lower VARCHAR, "
+ "date_created INTEGER DEFAULT 0, "
+ "date_last_used INTEGER DEFAULT 0, "
+ "count INTEGER DEFAULT 1, "
+ "PRIMARY KEY (name, value))")) {
+ return false;
+ }
+
+ // Slurp up the data from the existing table and write it to the new table.
+ sql::Statement s(db_->GetUniqueStatement(
+ "SELECT name, value, value_lower, count, MIN(date_created),"
+ " MAX(date_created) "
+ "FROM autofill a JOIN autofill_dates ad ON a.pair_id=ad.pair_id "
+ "GROUP BY name, value, value_lower, count"));
+ while (s.Step()) {
+ sql::Statement s_insert(db_->GetUniqueStatement(
+ "INSERT INTO autofill_temp "
+ "(name, value, value_lower, count, date_created, date_last_used) "
+ "VALUES (?, ?, ?, ?, ?, ?)"));
+ s_insert.BindString16(0, s.ColumnString16(0));
+ s_insert.BindString16(1, s.ColumnString16(1));
+ s_insert.BindString16(2, s.ColumnString16(2));
+ s_insert.BindInt(3, s.ColumnInt(3));
+ s_insert.BindInt64(4, s.ColumnInt64(4));
+ s_insert.BindInt64(5, s.ColumnInt64(5));
+ if (!s_insert.Run())
+ return false;
+ }
+
+ if (!s.Succeeded())
+ return false;
+
+ // Delete the existing (version 54) tables and replace them with the contents
+ // of the temporary table.
+ if (!db_->Execute("DROP TABLE autofill") ||
+ !db_->Execute("DROP TABLE autofill_dates") ||
+ !db_->Execute("ALTER TABLE autofill_temp "
+ "RENAME TO autofill")) {
+ return false;
+ }
+
+ // Create indices on the new table, for fast lookups.
+ if (!db_->Execute("CREATE INDEX autofill_name ON autofill (name)") ||
+ !db_->Execute("CREATE INDEX autofill_name_value_lower ON "
+ "autofill (name, value_lower)")) {
+ return false;
+ }
+
+
+ return transaction.Commit();
+}
+
} // namespace autofill
diff --git a/components/autofill/core/browser/webdata/autofill_table.h b/components/autofill/core/browser/webdata/autofill_table.h
index bcacdcd..83aded5 100644
--- a/components/autofill/core/browser/webdata/autofill_table.h
+++ b/components/autofill/core/browser/webdata/autofill_table.h
@@ -38,17 +38,13 @@ struct FormFieldData;
// name The name of the input as specified in the html.
// value The literal contents of the text field.
// value_lower The contents of the text field made lower_case.
-// pair_id An ID number unique to the row in the table.
+// date_created The date on which the user first entered the string
+// |value| into a field of name |name|.
+// date_last_used The date on which the user last entered the string
+// |value| into a field of name |name|.
// count How many times the user has entered the string |value|
// in a field of name |name|.
//
-// autofill_dates This table associates a row to each separate time the
-// user submits a form containing a certain name/value
-// pair. The |pair_id| should match the |pair_id| field
-// in the appropriate row of the autofill table.
-// pair_id
-// date_created
-//
// autofill_profiles This table contains Autofill profile data added by the
// user with the Autofill dialog. Most of the columns are
// standard entries in a contact information form.
@@ -176,48 +172,6 @@ class AutofillTable : public WebDatabaseTable {
// culling in future versions.
bool RemoveExpiredFormElements(std::vector<AutofillChange>* changes);
- // Removes from autofill_dates rows with given pair_id where date_created lies
- // between |delete_begin| and |delete_end|.
- bool RemoveFormElementForTimeRange(int64 pair_id,
- const base::Time& delete_begin,
- const base::Time& delete_end,
- int* how_many);
-
- // Increments the count in the row corresponding to |pair_id| by |delta|.
- bool AddToCountOfFormElement(int64 pair_id, int delta);
-
- // Counts how many timestamp data rows are in the |autofill_dates| table for
- // a given |pair_id|. GetCountOfFormElement() on the other hand gives the
- // |count| property for a given id.
- int CountTimestampsData(int64 pair_id);
-
- // Gets the pair_id and count entries from name and value specified in
- // |element|. Sets *pair_id and *count to 0 if there is no such row in
- // the table.
- bool GetIDAndCountOfFormElement(const FormFieldData& element,
- int64* pair_id,
- int* count);
-
- // Gets the count only given the pair_id.
- bool GetCountOfFormElement(int64 pair_id, int* count);
-
- // Updates the count entry in the row corresponding to |pair_id| to |count|.
- bool SetCountOfFormElement(int64 pair_id, int count);
-
- // Adds a new row to the autofill table with name and value given in
- // |element|. Sets *pair_id to the pair_id of the new row.
- bool InsertFormElement(const FormFieldData& element,
- int64* pair_id);
-
- // Adds a new row to the autofill_dates table.
- bool InsertPairIDAndDate(int64 pair_id, const base::Time& date_created);
-
- // Deletes last access to the Autofill data from the autofill_dates table.
- bool DeleteLastAccess(int64 pair_id);
-
- // Removes row from the autofill tables given |pair_id|.
- bool RemoveFormElementForID(int64 pair_id);
-
// Removes row from the autofill tables for the given |name| |value| pair.
virtual bool RemoveFormElement(const base::string16& name,
const base::string16& value);
@@ -299,10 +253,6 @@ class AutofillTable : public WebDatabaseTable {
// Empties the Autofill profiles "trash can".
bool EmptyAutofillProfilesTrash();
- // Removes empty values for autofill that were incorrectly stored in the DB
- // See bug http://crbug.com/6111
- bool ClearAutofillEmptyValueElements();
-
// Retrieves all profiles in the database that have been deleted since last
// "empty" of the trash.
bool AddAutofillGUIDToTrash(const std::string& guid);
@@ -311,6 +261,9 @@ class AutofillTable : public WebDatabaseTable {
bool ClearAutofillProfiles();
// Table migration functions.
+ // Removes empty values for autofill that were incorrectly stored in the DB
+ // See bug http://crbug.com/6111
+ bool MigrateToVersion22ClearAutofillEmptyValueElements();
bool MigrateToVersion23AddCardNumberEncryptedColumn();
bool MigrateToVersion24CleanupOversizedStringFields();
bool MigrateToVersion27UpdateLegacyCreditCards();
@@ -323,6 +276,7 @@ class AutofillTable : public WebDatabaseTable {
bool MigrateToVersion37MergeAndCullOlderProfiles();
bool MigrateToVersion51AddOriginColumn();
bool MigrateToVersion54AddI18nFieldsAndRemoveDeprecatedFields();
+ bool MigrateToVersion55MergeAutofillDatesTable();
// Max data length saved in the table;
static const size_t kMaxDataLength;
@@ -331,8 +285,22 @@ class AutofillTable : public WebDatabaseTable {
FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, Autofill);
FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, Autofill_AddChanges);
FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, Autofill_RemoveBetweenChanges);
-
FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, Autofill_UpdateDontReplace);
+ FRIEND_TEST_ALL_PREFIXES(
+ AutofillTableTest,
+ Autofill_RemoveFormElementsAddedBetween_UsedOnlyBefore);
+ FRIEND_TEST_ALL_PREFIXES(
+ AutofillTableTest,
+ Autofill_RemoveFormElementsAddedBetween_UsedOnlyAfter);
+ FRIEND_TEST_ALL_PREFIXES(
+ AutofillTableTest,
+ Autofill_RemoveFormElementsAddedBetween_UsedOnlyDuring);
+ FRIEND_TEST_ALL_PREFIXES(
+ AutofillTableTest,
+ Autofill_RemoveFormElementsAddedBetween_UsedBeforeAndDuring);
+ FRIEND_TEST_ALL_PREFIXES(
+ AutofillTableTest,
+ Autofill_RemoveFormElementsAddedBetween_UsedDuringAndAfter);
FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, Autofill_AddFormFieldValues);
FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, AutofillProfile);
FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, UpdateAutofillProfile);
diff --git a/components/autofill/core/browser/webdata/autofill_table_unittest.cc b/components/autofill/core/browser/webdata/autofill_table_unittest.cc
index 649eec3..daa0a63 100644
--- a/components/autofill/core/browser/webdata/autofill_table_unittest.cc
+++ b/components/autofill/core/browser/webdata/autofill_table_unittest.cc
@@ -108,6 +108,17 @@ void CompareAutofillEntrySets(const AutofillEntrySet& actual,
EXPECT_EQ(actual.size(), count);
}
+int GetAutofillEntryCount(const base::string16& name,
+ const base::string16& value,
+ WebDatabase* db) {
+ sql::Statement s(db->GetSQLConnection()->GetUniqueStatement(
+ "SELECT count FROM autofill WHERE name = ? AND value = ?"));
+ s.BindString16(0, name);
+ s.BindString16(1, value);
+ s.Step();
+ return s.ColumnInt(0);
+}
+
} // namespace
class AutofillTableTest : public testing::Test {
@@ -170,27 +181,17 @@ TEST_F(AutofillTableTest, Autofill) {
now + i * two_seconds));
}
- int count = 0;
- int64 pair_id = 0;
-
- // We have added the name Clark Kent 5 times, so count should be 5 and pair_id
- // should be somthing non-zero.
- field.name = ASCIIToUTF16("Name");
- field.value = ASCIIToUTF16("Clark Kent");
- EXPECT_TRUE(table_->GetIDAndCountOfFormElement(field, &pair_id, &count));
- EXPECT_EQ(5, count);
- EXPECT_NE(0, pair_id);
+ // We have added the name Clark Kent 5 times, so count should be 5.
+ EXPECT_EQ(5, GetAutofillEntryCount(ASCIIToUTF16("Name"),
+ ASCIIToUTF16("Clark Kent"), db_.get()));
// Storing in the data base should be case sensitive, so there should be no
// database entry for clark kent lowercase.
- field.value = ASCIIToUTF16("clark kent");
- EXPECT_TRUE(table_->GetIDAndCountOfFormElement(field, &pair_id, &count));
- EXPECT_EQ(0, count);
+ EXPECT_EQ(0, GetAutofillEntryCount(ASCIIToUTF16("Name"),
+ ASCIIToUTF16("clark kent"), db_.get()));
- field.name = ASCIIToUTF16("Favorite Color");
- field.value = ASCIIToUTF16("Green");
- EXPECT_TRUE(table_->GetIDAndCountOfFormElement(field, &pair_id, &count));
- EXPECT_EQ(2, count);
+ EXPECT_EQ(2, GetAutofillEntryCount(ASCIIToUTF16("Favorite Color"),
+ ASCIIToUTF16("Green"), db_.get()));
// This is meant to get a list of suggestions for Name. The empty prefix
// in the second argument means it should return all suggestions for a name
@@ -248,10 +249,8 @@ TEST_F(AutofillTableTest, Autofill) {
EXPECT_EQ(kExpectedChanges[i], changes[i]);
}
- field.name = ASCIIToUTF16("Name");
- field.value = ASCIIToUTF16("Clark Kent");
- EXPECT_TRUE(table_->GetIDAndCountOfFormElement(field, &pair_id, &count));
- EXPECT_EQ(0, count);
+ EXPECT_EQ(0, GetAutofillEntryCount(ASCIIToUTF16("Name"),
+ ASCIIToUTF16("Clark Kent"), db_.get()));
EXPECT_TRUE(table_->GetFormValuesForElementName(
ASCIIToUTF16("Name"), base::string16(), &v, 6));
@@ -278,16 +277,6 @@ TEST_F(AutofillTableTest, Autofill) {
EXPECT_TRUE(table_->GetFormValuesForElementName(
ASCIIToUTF16("blank"), base::string16(), &v, 10));
EXPECT_EQ(4U, v.size());
-
- // Now we'll check that ClearAutofillEmptyValueElements() works as expected.
- table_->ClearAutofillEmptyValueElements();
-
- v.clear();
- EXPECT_TRUE(table_->GetFormValuesForElementName(
- ASCIIToUTF16("blank"), base::string16(), &v, 10));
- ASSERT_EQ(1U, v.size());
-
- EXPECT_EQ(kValue, v[0]);
}
TEST_F(AutofillTableTest, Autofill_RemoveBetweenChanges) {
@@ -352,14 +341,8 @@ TEST_F(AutofillTableTest, Autofill_UpdateOneWithOneTimestamp) {
entries.push_back(entry);
ASSERT_TRUE(table_->UpdateAutofillEntries(entries));
- FormFieldData field;
- field.name = ASCIIToUTF16("foo");
- field.value = ASCIIToUTF16("bar");
- int64 pair_id;
- int count;
- ASSERT_TRUE(table_->GetIDAndCountOfFormElement(field, &pair_id, &count));
- EXPECT_LE(0, pair_id);
- EXPECT_EQ(1, count);
+ EXPECT_EQ(1, GetAutofillEntryCount(ASCIIToUTF16("foo"), ASCIIToUTF16("bar"),
+ db_.get()));
std::vector<AutofillEntry> all_entries;
ASSERT_TRUE(table_->GetAllAutofillEntries(&all_entries));
@@ -373,14 +356,8 @@ TEST_F(AutofillTableTest, Autofill_UpdateOneWithTwoTimestamps) {
entries.push_back(entry);
ASSERT_TRUE(table_->UpdateAutofillEntries(entries));
- FormFieldData field;
- field.name = ASCIIToUTF16("foo");
- field.value = ASCIIToUTF16("bar");
- int64 pair_id;
- int count;
- ASSERT_TRUE(table_->GetIDAndCountOfFormElement(field, &pair_id, &count));
- EXPECT_LE(0, pair_id);
- EXPECT_EQ(2, count);
+ EXPECT_EQ(2, GetAutofillEntryCount(ASCIIToUTF16("foo"), ASCIIToUTF16("bar"),
+ db_.get()));
std::vector<AutofillEntry> all_entries;
ASSERT_TRUE(table_->GetAllAutofillEntries(&all_entries));
@@ -411,21 +388,10 @@ TEST_F(AutofillTableTest, Autofill_UpdateTwo) {
entries.push_back(entry1);
ASSERT_TRUE(table_->UpdateAutofillEntries(entries));
- FormFieldData field0;
- field0.name = ASCIIToUTF16("foo");
- field0.value = ASCIIToUTF16("bar0");
- int64 pair_id;
- int count;
- ASSERT_TRUE(table_->GetIDAndCountOfFormElement(field0, &pair_id, &count));
- EXPECT_LE(0, pair_id);
- EXPECT_EQ(1, count);
-
- FormFieldData field1;
- field1.name = ASCIIToUTF16("foo");
- field1.value = ASCIIToUTF16("bar1");
- ASSERT_TRUE(table_->GetIDAndCountOfFormElement(field1, &pair_id, &count));
- EXPECT_LE(0, pair_id);
- EXPECT_EQ(2, count);
+ EXPECT_EQ(1, GetAutofillEntryCount(ASCIIToUTF16("foo"), ASCIIToUTF16("bar0"),
+ db_.get()));
+ EXPECT_EQ(2, GetAutofillEntryCount(ASCIIToUTF16("foo"), ASCIIToUTF16("bar1"),
+ db_.get()));
}
TEST_F(AutofillTableTest, Autofill_UpdateReplace) {
@@ -513,6 +479,167 @@ TEST_F(AutofillTableTest, Autofill_AddFormFieldValues) {
ASSERT_EQ(2U, all_entries.size());
}
+TEST_F(AutofillTableTest,
+ Autofill_RemoveFormElementsAddedBetween_UsedOnlyBefore) {
+ // Add an entry used only before the targetted range.
+ AutofillChangeList changes;
+ FormFieldData field;
+ field.name = ASCIIToUTF16("Name");
+ field.value = ASCIIToUTF16("Superman");
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(10)));
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(20)));
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(30)));
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(40)));
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(50)));
+
+ EXPECT_EQ(5, GetAutofillEntryCount(field.name, field.value, db_.get()));
+
+ changes.clear();
+ EXPECT_TRUE(table_->RemoveFormElementsAddedBetween(base::Time::FromTimeT(51),
+ base::Time::FromTimeT(60),
+ &changes));
+ EXPECT_TRUE(changes.empty());
+ EXPECT_EQ(5, GetAutofillEntryCount(field.name, field.value, db_.get()));
+}
+
+TEST_F(AutofillTableTest,
+ Autofill_RemoveFormElementsAddedBetween_UsedOnlyAfter) {
+ // Add an entry used only after the targetted range.
+ AutofillChangeList changes;
+ FormFieldData field;
+ field.name = ASCIIToUTF16("Name");
+ field.value = ASCIIToUTF16("Superman");
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(50)));
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(60)));
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(70)));
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(80)));
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(90)));
+
+ EXPECT_EQ(5, GetAutofillEntryCount(field.name, field.value, db_.get()));
+
+ changes.clear();
+ EXPECT_TRUE(table_->RemoveFormElementsAddedBetween(base::Time::FromTimeT(40),
+ base::Time::FromTimeT(50),
+ &changes));
+ EXPECT_TRUE(changes.empty());
+ EXPECT_EQ(5, GetAutofillEntryCount(field.name, field.value, db_.get()));
+}
+
+TEST_F(AutofillTableTest,
+ Autofill_RemoveFormElementsAddedBetween_UsedOnlyDuring) {
+ // Add an entry used entirely during the targetted range.
+ AutofillChangeList changes;
+ FormFieldData field;
+ field.name = ASCIIToUTF16("Name");
+ field.value = ASCIIToUTF16("Superman");
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(10)));
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(20)));
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(30)));
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(40)));
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(50)));
+
+ EXPECT_EQ(5, GetAutofillEntryCount(field.name, field.value, db_.get()));
+
+ changes.clear();
+ EXPECT_TRUE(table_->RemoveFormElementsAddedBetween(base::Time::FromTimeT(10),
+ base::Time::FromTimeT(51),
+ &changes));
+ ASSERT_EQ(1U, changes.size());
+ EXPECT_EQ(AutofillChange(AutofillChange::REMOVE,
+ AutofillKey(field.name, field.value)),
+ changes[0]);
+ EXPECT_EQ(0, GetAutofillEntryCount(field.name, field.value, db_.get()));
+}
+
+TEST_F(AutofillTableTest,
+ Autofill_RemoveFormElementsAddedBetween_UsedBeforeAndDuring) {
+ // Add an entry used both before and during the targetted range.
+ AutofillChangeList changes;
+ FormFieldData field;
+ field.name = ASCIIToUTF16("Name");
+ field.value = ASCIIToUTF16("Superman");
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(10)));
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(20)));
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(30)));
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(40)));
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(50)));
+
+ EXPECT_EQ(5, GetAutofillEntryCount(field.name, field.value, db_.get()));
+
+ changes.clear();
+ EXPECT_TRUE(table_->RemoveFormElementsAddedBetween(base::Time::FromTimeT(40),
+ base::Time::FromTimeT(60),
+ &changes));
+ ASSERT_EQ(1U, changes.size());
+ EXPECT_EQ(AutofillChange(AutofillChange::UPDATE,
+ AutofillKey(field.name, field.value)),
+ changes[0]);
+ EXPECT_EQ(4, GetAutofillEntryCount(field.name, field.value, db_.get()));
+ std::vector<base::Time> timestamps;
+ EXPECT_TRUE(
+ table_->GetAutofillTimestamps(field.name, field.value, &timestamps));
+ ASSERT_EQ(2U, timestamps.size());
+ EXPECT_EQ(base::Time::FromTimeT(10), timestamps[0]);
+ EXPECT_EQ(base::Time::FromTimeT(39), timestamps[1]);
+}
+
+TEST_F(AutofillTableTest,
+ Autofill_RemoveFormElementsAddedBetween_UsedDuringAndAfter) {
+ // Add an entry used both during and after the targetted range.
+ AutofillChangeList changes;
+ FormFieldData field;
+ field.name = ASCIIToUTF16("Name");
+ field.value = ASCIIToUTF16("Superman");
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(50)));
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(60)));
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(70)));
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(80)));
+ EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes,
+ base::Time::FromTimeT(90)));
+
+ EXPECT_EQ(5, GetAutofillEntryCount(field.name, field.value, db_.get()));
+
+ changes.clear();
+ EXPECT_TRUE(table_->RemoveFormElementsAddedBetween(base::Time::FromTimeT(40),
+ base::Time::FromTimeT(80),
+ &changes));
+ ASSERT_EQ(1U, changes.size());
+ EXPECT_EQ(AutofillChange(AutofillChange::UPDATE,
+ AutofillKey(field.name, field.value)),
+ changes[0]);
+ EXPECT_EQ(2, GetAutofillEntryCount(field.name, field.value, db_.get()));
+ std::vector<base::Time> timestamps;
+ EXPECT_TRUE(
+ table_->GetAutofillTimestamps(field.name, field.value, &timestamps));
+ ASSERT_EQ(2U, timestamps.size());
+ EXPECT_EQ(base::Time::FromTimeT(80), timestamps[0]);
+ EXPECT_EQ(base::Time::FromTimeT(90), timestamps[1]);
+}
+
TEST_F(AutofillTableTest, AutofillProfile) {
// Add a 'Home' profile.
AutofillProfile home_profile;
diff --git a/components/test/data/web_database/version_21.sql b/components/test/data/web_database/version_21.sql
new file mode 100644
index 0000000..a77ab7e
--- /dev/null
+++ b/components/test/data/web_database/version_21.sql
@@ -0,0 +1,40 @@
+PRAGMA foreign_keys=OFF;
+BEGIN TRANSACTION;
+CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY, value LONGVARCHAR);
+INSERT INTO "meta" VALUES('version','21');
+INSERT INTO "meta" VALUES('last_compatible_version','21');
+INSERT INTO "meta" VALUES('Builtin Keyword Version','27');
+INSERT INTO "meta" VALUES('Default Search Provider ID','7');
+CREATE TABLE keywords (id INTEGER PRIMARY KEY,short_name VARCHAR NOT NULL, keyword VARCHAR NOT NULL,favicon_url VARCHAR NOT NULL,url VARCHAR NOT NULL,show_in_default_list INTEGER,safe_for_autoreplace INTEGER,originating_url VARCHAR,date_created INTEGER DEFAULT 0,usage_count INTEGER DEFAULT 0,input_encodings VARCHAR,suggest_url VARCHAR,prepopulate_id INTEGER DEFAULT 0,autogenerate_keyword INTEGER DEFAULT 0);
+INSERT INTO "keywords" VALUES(2,'Google','google.com','http://www.google.com/favicon.ico','{google:baseURL}search?{google:RLZ}{google:acceptedSuggestion}{google:originalQueryForSuggestion}sourceid=chrome&ie={inputEncoding}&q={searchTerms}',1,1,'',0,0,'UTF-8','{google:baseSuggestURL}search?client=chrome&hl={language}&q={searchTerms}',1,1);
+INSERT INTO "keywords" VALUES(3,'Yahoo!','yahoo.com','http://search.yahoo.com/favicon.ico','http://search.yahoo.com/search?ei={inputEncoding}&fr=crmas&p={searchTerms}',1,1,'',0,0,'UTF-8','http://ff.search.yahoo.com/gossip?output=fxjson&command={searchTerms}',2,0);
+INSERT INTO "keywords" VALUES(4,'Bing','bing.com','http://www.bing.com/s/wlflag.ico','http://www.bing.com/search?setmkt=en-US&q={searchTerms}',1,1,'',0,0,'UTF-8','http://api.bing.com/osjson.aspx?query={searchTerms}&language={language}',3,0);
+INSERT INTO "keywords" VALUES(5,'Wikipedia (en)','en.wikipedia.org','','http://en.wikipedia.org/w/index.php?title=Special:Search&search={searchTerms}',1,0,'',1283287335,0,'','',0,0);
+INSERT INTO "keywords" VALUES(6,'NYTimes','query.nytimes.com','','http://query.nytimes.com/gst/handler.html?query={searchTerms}&opensearch=1',1,0,'',1283287335,0,'','',0,0);
+INSERT INTO "keywords" VALUES(7,'eBay','rover.ebay.com','','http://rover.ebay.com/rover/1/711-43047-14818-1/4?satitle={searchTerms}',1,0,'',1283287335,0,'','',0,0);
+INSERT INTO "keywords" VALUES(8,'ff','ff','','http://ff{searchTerms}',0,0,'',1283287356,0,'','',0,0);
+CREATE TABLE logins (origin_url VARCHAR NOT NULL, action_url VARCHAR, username_element VARCHAR, username_value VARCHAR, password_element VARCHAR, password_value BLOB, submit_element VARCHAR,signon_realm VARCHAR NOT NULL,ssl_valid INTEGER NOT NULL,preferred INTEGER NOT NULL,date_created INTEGER NOT NULL,blacklisted_by_user INTEGER NOT NULL,scheme INTEGER NOT NULL,UNIQUE (origin_url, username_element, username_value, password_element, submit_element, signon_realm));
+CREATE TABLE ie7_logins (url_hash VARCHAR NOT NULL, password_value BLOB, date_created INTEGER NOT NULL,UNIQUE (url_hash));
+CREATE TABLE web_app_icons (url LONGVARCHAR,width int,height int,image BLOB, UNIQUE (url, width, height));
+CREATE TABLE web_apps (url LONGVARCHAR UNIQUE,has_all_images INTEGER NOT NULL);
+CREATE TABLE autofill (name VARCHAR, value VARCHAR, value_lower VARCHAR, pair_id INTEGER PRIMARY KEY, count INTEGER DEFAULT 1);
+INSERT INTO "autofill" VALUES('Name','John Doe','john doe',10,1);
+INSERT INTO "autofill" VALUES('Name','','',11,1);
+INSERT INTO "autofill" VALUES('Email','jane@example.com','jane@example.com',20,3);
+INSERT INTO "autofill" VALUES('Email','','',21,4);
+CREATE TABLE autofill_dates ( pair_id INTEGER DEFAULT 0,date_created INTEGER DEFAULT 0);
+INSERT INTO "autofill_dates" VALUES(10,1384299100);
+INSERT INTO "autofill_dates" VALUES(11,1384299200);
+INSERT INTO "autofill_dates" VALUES(20,1384299300);
+INSERT INTO "autofill_dates" VALUES(20,1384299301);
+INSERT INTO "autofill_dates" VALUES(21,1384299401);
+INSERT INTO "autofill_dates" VALUES(21,1384299400);
+INSERT INTO "autofill_dates" VALUES(21,1384299403);
+INSERT INTO "autofill_dates" VALUES(21,1384299402);
+CREATE INDEX logins_signon ON logins (signon_realm);
+CREATE INDEX ie7_logins_hash ON ie7_logins (url_hash);
+CREATE INDEX web_apps_url_index ON web_apps (url);
+CREATE INDEX autofill_name ON autofill (name);
+CREATE INDEX autofill_name_value_lower ON autofill (name, value_lower);
+CREATE INDEX autofill_dates_pair_id ON autofill_dates (pair_id);
+COMMIT;
diff --git a/components/test/data/web_database/version_54.sql b/components/test/data/web_database/version_54.sql
new file mode 100644
index 0000000..17e5b68
--- /dev/null
+++ b/components/test/data/web_database/version_54.sql
@@ -0,0 +1,40 @@
+PRAGMA foreign_keys=OFF;
+BEGIN TRANSACTION;
+CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY, value LONGVARCHAR);
+INSERT INTO "meta" VALUES('version','54');
+INSERT INTO "meta" VALUES('last_compatible_version','54');
+INSERT INTO "meta" VALUES('Builtin Keyword Version','69');
+INSERT INTO "meta" VALUES('Default Search Provider ID','7');
+CREATE TABLE keywords (id INTEGER PRIMARY KEY,short_name VARCHAR NOT NULL,keyword VARCHAR NOT NULL,favicon_url VARCHAR NOT NULL,url VARCHAR NOT NULL,safe_for_autoreplace INTEGER,originating_url VARCHAR,date_created INTEGER DEFAULT 0,usage_count INTEGER DEFAULT 0,input_encodings VARCHAR,show_in_default_list INTEGER,suggest_url VARCHAR,prepopulate_id INTEGER DEFAULT 0,created_by_policy INTEGER DEFAULT 0,instant_url VARCHAR,last_modified INTEGER DEFAULT 0,sync_guid VARCHAR,alternate_urls VARCHAR,search_terms_replacement_key VARCHAR,image_url VARCHAR,search_url_post_params VARCHAR,suggest_url_post_params VARCHAR,instant_url_post_params VARCHAR,image_url_post_params VARCHAR,new_tab_url VARCHAR);
+CREATE TABLE token_service (service VARCHAR PRIMARY KEY NOT NULL,encrypted_token BLOB);
+CREATE TABLE web_app_icons (url LONGVARCHAR,width int,height int,image BLOB, UNIQUE (url, width, height));
+CREATE TABLE web_apps (url LONGVARCHAR UNIQUE,has_all_images INTEGER NOT NULL);
+CREATE TABLE web_intents ( service_url LONGVARCHAR, action VARCHAR, type VARCHAR, title LONGVARCHAR, disposition VARCHAR, scheme VARCHAR, UNIQUE (service_url, action, scheme, type));
+CREATE TABLE web_intents_defaults ( action VARCHAR, type VARCHAR, url_pattern LONGVARCHAR, user_date INTEGER, suppression INTEGER, service_url LONGVARCHAR, scheme VARCHAR, UNIQUE (action, scheme, type, url_pattern));
+CREATE TABLE autofill (name VARCHAR, value VARCHAR, value_lower VARCHAR, pair_id INTEGER PRIMARY KEY, count INTEGER DEFAULT 1);
+INSERT INTO "autofill" VALUES('Name','John Doe','john doe',10,1);
+INSERT INTO "autofill" VALUES('Name','john doe','john doe',11,1);
+INSERT INTO "autofill" VALUES('Email','jane@example.com','jane@example.com',20,3);
+INSERT INTO "autofill" VALUES('Email','jane.doe@example.org','jane.doe@example.org',21,4);
+CREATE TABLE credit_cards ( guid VARCHAR PRIMARY KEY, name_on_card VARCHAR, expiration_month INTEGER, expiration_year INTEGER, card_number_encrypted BLOB, date_modified INTEGER NOT NULL DEFAULT 0, origin VARCHAR DEFAULT '');
+CREATE TABLE autofill_dates ( pair_id INTEGER DEFAULT 0, date_created INTEGER DEFAULT 0);
+INSERT INTO "autofill_dates" VALUES(10,1384299100);
+INSERT INTO "autofill_dates" VALUES(11,1384299200);
+INSERT INTO "autofill_dates" VALUES(20,1384299300);
+INSERT INTO "autofill_dates" VALUES(20,1384299301);
+INSERT INTO "autofill_dates" VALUES(21,1384299401);
+INSERT INTO "autofill_dates" VALUES(21,1384299400);
+INSERT INTO "autofill_dates" VALUES(21,1384299403);
+INSERT INTO "autofill_dates" VALUES(21,1384299402);
+CREATE TABLE autofill_profiles ( guid VARCHAR PRIMARY KEY, company_name VARCHAR, address_line_1 VARCHAR, address_line_2 VARCHAR, city VARCHAR, state VARCHAR, zipcode VARCHAR, country VARCHAR, country_code VARCHAR, date_modified INTEGER NOT NULL DEFAULT 0, origin VARCHAR DEFAULT '');
+CREATE TABLE autofill_profile_names ( guid VARCHAR, first_name VARCHAR, middle_name VARCHAR, last_name VARCHAR);
+CREATE TABLE autofill_profile_emails ( guid VARCHAR, email VARCHAR);
+CREATE TABLE autofill_profile_phones ( guid VARCHAR, type INTEGER DEFAULT 0, number VARCHAR);
+CREATE TABLE autofill_profiles_trash ( guid VARCHAR);
+CREATE INDEX web_apps_url_index ON web_apps (url);
+CREATE INDEX web_intents_index ON web_intents (action);
+CREATE INDEX web_intents_default_index ON web_intents_defaults (action);
+CREATE INDEX autofill_name ON autofill (name);
+CREATE INDEX autofill_name_value_lower ON autofill (name, value_lower);
+CREATE INDEX autofill_dates_pair_id ON autofill_dates (pair_id);
+COMMIT;
diff --git a/components/webdata/common/web_database.cc b/components/webdata/common/web_database.cc
index 44b6790..230eae1 100644
--- a/components/webdata/common/web_database.cc
+++ b/components/webdata/common/web_database.cc
@@ -14,11 +14,11 @@
// corresponding changes must happen in the unit tests, and new migration test
// added. See |WebDatabaseMigrationTest::kCurrentTestedVersionNumber|.
// static
-const int WebDatabase::kCurrentVersionNumber = 54;
+const int WebDatabase::kCurrentVersionNumber = 55;
namespace {
-const int kCompatibleVersionNumber = 54;
+const int kCompatibleVersionNumber = 55;
// Change the version number and possibly the compatibility version of
// |meta_table_|.
diff --git a/components/webdata/common/web_database_migration_unittest.cc b/components/webdata/common/web_database_migration_unittest.cc
index 0f22cb5..a72140c 100644
--- a/components/webdata/common/web_database_migration_unittest.cc
+++ b/components/webdata/common/web_database_migration_unittest.cc
@@ -248,7 +248,7 @@ class WebDatabaseMigrationTest : public testing::Test {
DISALLOW_COPY_AND_ASSIGN(WebDatabaseMigrationTest);
};
-const int WebDatabaseMigrationTest::kCurrentTestedVersionNumber = 54;
+const int WebDatabaseMigrationTest::kCurrentTestedVersionNumber = 55;
void WebDatabaseMigrationTest::LoadDatabase(
const base::FilePath::StringType& file) {
@@ -275,7 +275,9 @@ TEST_F(WebDatabaseMigrationTest, MigrateEmptyToCurrent) {
// Check that expected tables are present.
EXPECT_TRUE(connection.DoesTableExist("autofill"));
- EXPECT_TRUE(connection.DoesTableExist("autofill_dates"));
+ // The autofill_dates table is obsolete. (It's been merged into the autofill
+ // table.)
+ EXPECT_FALSE(connection.DoesTableExist("autofill_dates"));
EXPECT_TRUE(connection.DoesTableExist("autofill_profiles"));
EXPECT_TRUE(connection.DoesTableExist("credit_cards"));
EXPECT_TRUE(connection.DoesTableExist("keywords"));
@@ -290,6 +292,125 @@ TEST_F(WebDatabaseMigrationTest, MigrateEmptyToCurrent) {
}
}
+// Tests that rows with empty values get removed from the autofill tables.
+TEST_F(WebDatabaseMigrationTest, MigrateVersion21ToCurrent) {
+ ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_21.sql")));
+
+ // Verify pre-conditions.
+ {
+ sql::Connection connection;
+ ASSERT_TRUE(connection.Open(GetDatabasePath()));
+
+ // Both empty and non-empty values are allowed in a version 21 database.
+ sql::Statement s_autofill(connection.GetUniqueStatement(
+ "SELECT name, value, value_lower, pair_id, count FROM autofill"));
+ sql::Statement s_dates(connection.GetUniqueStatement(
+ "SELECT pair_id, date_created FROM autofill_dates"));
+
+ // An entry with a non-empty value.
+ ASSERT_TRUE(s_autofill.Step());
+ EXPECT_EQ(ASCIIToUTF16("Name"), s_autofill.ColumnString16(0));
+ EXPECT_EQ(ASCIIToUTF16("John Doe"), s_autofill.ColumnString16(1));
+ EXPECT_EQ(ASCIIToUTF16("john doe"), s_autofill.ColumnString16(2));
+ EXPECT_EQ(10, s_autofill.ColumnInt(3));
+ EXPECT_EQ(1, s_autofill.ColumnInt(4));
+ ASSERT_TRUE(s_dates.Step());
+ EXPECT_EQ(10, s_dates.ColumnInt(0));
+ EXPECT_EQ(1384299100, s_dates.ColumnInt64(1));
+
+ // An entry with an empty value.
+ ASSERT_TRUE(s_autofill.Step());
+ EXPECT_EQ(ASCIIToUTF16("Name"), s_autofill.ColumnString16(0));
+ EXPECT_EQ(base::string16(), s_autofill.ColumnString16(1));
+ EXPECT_EQ(base::string16(), s_autofill.ColumnString16(2));
+ EXPECT_EQ(11, s_autofill.ColumnInt(3));
+ EXPECT_EQ(1, s_autofill.ColumnInt(4));
+ ASSERT_TRUE(s_dates.Step());
+ EXPECT_EQ(11, s_dates.ColumnInt(0));
+ EXPECT_EQ(1384299200, s_dates.ColumnInt64(1));
+
+ // Another entry with a non-empty value.
+ ASSERT_TRUE(s_autofill.Step());
+ EXPECT_EQ(ASCIIToUTF16("Email"), s_autofill.ColumnString16(0));
+ EXPECT_EQ(ASCIIToUTF16("jane@example.com"), s_autofill.ColumnString16(1));
+ EXPECT_EQ(ASCIIToUTF16("jane@example.com"), s_autofill.ColumnString16(2));
+ EXPECT_EQ(20, s_autofill.ColumnInt(3));
+ EXPECT_EQ(3, s_autofill.ColumnInt(4));
+ ASSERT_TRUE(s_dates.Step());
+ EXPECT_EQ(20, s_dates.ColumnInt(0));
+ EXPECT_EQ(1384299300, s_dates.ColumnInt64(1));
+ ASSERT_TRUE(s_dates.Step());
+ EXPECT_EQ(20, s_dates.ColumnInt(0));
+ EXPECT_EQ(1384299301, s_dates.ColumnInt64(1));
+
+ // Another entry with an empty value.
+ ASSERT_TRUE(s_autofill.Step());
+ EXPECT_EQ(ASCIIToUTF16("Email"), s_autofill.ColumnString16(0));
+ EXPECT_EQ(base::string16(), s_autofill.ColumnString16(1));
+ EXPECT_EQ(base::string16(), s_autofill.ColumnString16(2));
+ EXPECT_EQ(21, s_autofill.ColumnInt(3));
+ EXPECT_EQ(4, s_autofill.ColumnInt(4));
+ ASSERT_TRUE(s_dates.Step());
+ EXPECT_EQ(21, s_dates.ColumnInt(0));
+ EXPECT_EQ(1384299401, s_dates.ColumnInt64(1));
+ ASSERT_TRUE(s_dates.Step());
+ EXPECT_EQ(21, s_dates.ColumnInt(0));
+ EXPECT_EQ(1384299400, s_dates.ColumnInt64(1));
+ ASSERT_TRUE(s_dates.Step());
+ EXPECT_EQ(21, s_dates.ColumnInt(0));
+ EXPECT_EQ(1384299403, s_dates.ColumnInt64(1));
+ ASSERT_TRUE(s_dates.Step());
+ EXPECT_EQ(21, s_dates.ColumnInt(0));
+ EXPECT_EQ(1384299402, s_dates.ColumnInt64(1));
+
+ // No more entries expected.
+ ASSERT_FALSE(s_autofill.Step());
+ ASSERT_FALSE(s_dates.Step());
+ }
+
+ DoMigration();
+
+ // Verify post-conditions. These are expectations for current version of the
+ // database.
+ {
+ sql::Connection connection;
+ ASSERT_TRUE(connection.Open(GetDatabasePath()));
+
+ // Check version.
+ EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection));
+
+ // Entries with empty values should have been dropped. The remaining
+ // entries should have been preserved.
+ sql::Statement s(
+ connection.GetUniqueStatement(
+ "SELECT name, value, value_lower, date_created, date_last_used,"
+ " count "
+ "FROM autofill "
+ "ORDER BY name, value ASC"));
+
+ // "jane@example.com"
+ ASSERT_TRUE(s.Step());
+ EXPECT_EQ(ASCIIToUTF16("Email"), s.ColumnString16(0));
+ EXPECT_EQ(ASCIIToUTF16("jane@example.com"), s.ColumnString16(1));
+ EXPECT_EQ(ASCIIToUTF16("jane@example.com"), s.ColumnString16(2));
+ EXPECT_EQ(1384299300, s.ColumnInt64(3));
+ EXPECT_EQ(1384299301, s.ColumnInt64(4));
+ EXPECT_EQ(3, s.ColumnInt(5));
+
+ // "John Doe"
+ ASSERT_TRUE(s.Step());
+ EXPECT_EQ(ASCIIToUTF16("Name"), s.ColumnString16(0));
+ EXPECT_EQ(ASCIIToUTF16("John Doe"), s.ColumnString16(1));
+ EXPECT_EQ(ASCIIToUTF16("john doe"), s.ColumnString16(2));
+ EXPECT_EQ(1384299100, s.ColumnInt64(3));
+ EXPECT_EQ(1384299100, s.ColumnInt64(4));
+ EXPECT_EQ(1, s.ColumnInt(5));
+
+ // No more entries expected.
+ ASSERT_FALSE(s.Step());
+ }
+}
+
// Tests that the |credit_card| table gets added to the schema for a version 22
// database.
TEST_F(WebDatabaseMigrationTest, MigrateVersion22ToCurrent) {
@@ -2270,3 +2391,158 @@ TEST_F(WebDatabaseMigrationTest, MigrateVersion53ToCurrent) {
EXPECT_FALSE(s_phones.Step());
}
}
+
+// Tests that migrating from version 54 to version 55 drops the autofill_dates
+// table, and merges the appropriate dates into the autofill table.
+TEST_F(WebDatabaseMigrationTest, MigrateVersion54ToCurrent) {
+ ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_54.sql")));
+
+ // Verify pre-conditions. These are expectations for version 54 of the
+ // database.
+ {
+ sql::Connection connection;
+ ASSERT_TRUE(connection.Open(GetDatabasePath()));
+
+ EXPECT_TRUE(connection.DoesTableExist("autofill_dates"));
+ EXPECT_FALSE(connection.DoesColumnExist("autofill", "date_created"));
+ EXPECT_FALSE(connection.DoesColumnExist("autofill", "date_last_used"));
+
+ // Verify the incoming data.
+ sql::Statement s_autofill(connection.GetUniqueStatement(
+ "SELECT name, value, value_lower, pair_id, count FROM autofill"));
+ sql::Statement s_dates(connection.GetUniqueStatement(
+ "SELECT pair_id, date_created FROM autofill_dates"));
+
+ // An entry with one timestamp.
+ ASSERT_TRUE(s_autofill.Step());
+ EXPECT_EQ(ASCIIToUTF16("Name"), s_autofill.ColumnString16(0));
+ EXPECT_EQ(ASCIIToUTF16("John Doe"), s_autofill.ColumnString16(1));
+ EXPECT_EQ(ASCIIToUTF16("john doe"), s_autofill.ColumnString16(2));
+ EXPECT_EQ(10, s_autofill.ColumnInt(3));
+ EXPECT_EQ(1, s_autofill.ColumnInt(4));
+ ASSERT_TRUE(s_dates.Step());
+ EXPECT_EQ(10, s_dates.ColumnInt(0));
+ EXPECT_EQ(1384299100, s_dates.ColumnInt64(1));
+
+ // Another entry with one timestamp, differing from the previous one in case
+ // only.
+ ASSERT_TRUE(s_autofill.Step());
+ EXPECT_EQ(ASCIIToUTF16("Name"), s_autofill.ColumnString16(0));
+ EXPECT_EQ(ASCIIToUTF16("john doe"), s_autofill.ColumnString16(1));
+ EXPECT_EQ(ASCIIToUTF16("john doe"), s_autofill.ColumnString16(2));
+ EXPECT_EQ(11, s_autofill.ColumnInt(3));
+ EXPECT_EQ(1, s_autofill.ColumnInt(4));
+ ASSERT_TRUE(s_dates.Step());
+ EXPECT_EQ(11, s_dates.ColumnInt(0));
+ EXPECT_EQ(1384299200, s_dates.ColumnInt64(1));
+
+ // An entry with two timestamps (with count > 2; this is realistic).
+ ASSERT_TRUE(s_autofill.Step());
+ EXPECT_EQ(ASCIIToUTF16("Email"), s_autofill.ColumnString16(0));
+ EXPECT_EQ(ASCIIToUTF16("jane@example.com"), s_autofill.ColumnString16(1));
+ EXPECT_EQ(ASCIIToUTF16("jane@example.com"), s_autofill.ColumnString16(2));
+ EXPECT_EQ(20, s_autofill.ColumnInt(3));
+ EXPECT_EQ(3, s_autofill.ColumnInt(4));
+ ASSERT_TRUE(s_dates.Step());
+ EXPECT_EQ(20, s_dates.ColumnInt(0));
+ EXPECT_EQ(1384299300, s_dates.ColumnInt64(1));
+ ASSERT_TRUE(s_dates.Step());
+ EXPECT_EQ(20, s_dates.ColumnInt(0));
+ EXPECT_EQ(1384299301, s_dates.ColumnInt64(1));
+
+ // An entry with more than two timestamps, which are stored out of order.
+ ASSERT_TRUE(s_autofill.Step());
+ EXPECT_EQ(ASCIIToUTF16("Email"), s_autofill.ColumnString16(0));
+ EXPECT_EQ(ASCIIToUTF16("jane.doe@example.org"),
+ s_autofill.ColumnString16(1));
+ EXPECT_EQ(ASCIIToUTF16("jane.doe@example.org"),
+ s_autofill.ColumnString16(2));
+ EXPECT_EQ(21, s_autofill.ColumnInt(3));
+ EXPECT_EQ(4, s_autofill.ColumnInt(4));
+ ASSERT_TRUE(s_dates.Step());
+ EXPECT_EQ(21, s_dates.ColumnInt(0));
+ EXPECT_EQ(1384299401, s_dates.ColumnInt64(1));
+ ASSERT_TRUE(s_dates.Step());
+ EXPECT_EQ(21, s_dates.ColumnInt(0));
+ EXPECT_EQ(1384299400, s_dates.ColumnInt64(1));
+ ASSERT_TRUE(s_dates.Step());
+ EXPECT_EQ(21, s_dates.ColumnInt(0));
+ EXPECT_EQ(1384299403, s_dates.ColumnInt64(1));
+ ASSERT_TRUE(s_dates.Step());
+ EXPECT_EQ(21, s_dates.ColumnInt(0));
+ EXPECT_EQ(1384299402, s_dates.ColumnInt64(1));
+
+ // No more entries expected.
+ ASSERT_FALSE(s_autofill.Step());
+ ASSERT_FALSE(s_dates.Step());
+ }
+
+ DoMigration();
+
+ // Verify post-conditions. These are expectations for current version of the
+ // database.
+ {
+ sql::Connection connection;
+ ASSERT_TRUE(connection.Open(GetDatabasePath()));
+ ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection));
+
+ // Check version.
+ EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection));
+
+ // The autofill_dates table should have been dropped, and its columns should
+ // have been migrated to the autofill table.
+ EXPECT_FALSE(connection.DoesTableExist("autofill_dates"));
+ EXPECT_TRUE(connection.DoesColumnExist("autofill", "date_created"));
+ EXPECT_TRUE(connection.DoesColumnExist("autofill", "date_last_used"));
+
+ // Data should have been preserved. Note that it appears out of order
+ // relative to the previous table, as it's been alphabetized. That's ok.
+ sql::Statement s(
+ connection.GetUniqueStatement(
+ "SELECT name, value, value_lower, date_created, date_last_used,"
+ " count "
+ "FROM autofill "
+ "ORDER BY name, value ASC"));
+
+ // "jane.doe@example.org": Timestamps should be parsed correctly, and only
+ // the first and last should be kept.
+ ASSERT_TRUE(s.Step());
+ EXPECT_EQ(ASCIIToUTF16("Email"), s.ColumnString16(0));
+ EXPECT_EQ(ASCIIToUTF16("jane.doe@example.org"), s.ColumnString16(1));
+ EXPECT_EQ(ASCIIToUTF16("jane.doe@example.org"), s.ColumnString16(2));
+ EXPECT_EQ(1384299400, s.ColumnInt64(3));
+ EXPECT_EQ(1384299403, s.ColumnInt64(4));
+ EXPECT_EQ(4, s.ColumnInt(5));
+
+ // "jane@example.com": Timestamps should be parsed correctly.
+ ASSERT_TRUE(s.Step());
+ EXPECT_EQ(ASCIIToUTF16("Email"), s.ColumnString16(0));
+ EXPECT_EQ(ASCIIToUTF16("jane@example.com"), s.ColumnString16(1));
+ EXPECT_EQ(ASCIIToUTF16("jane@example.com"), s.ColumnString16(2));
+ EXPECT_EQ(1384299300, s.ColumnInt64(3));
+ EXPECT_EQ(1384299301, s.ColumnInt64(4));
+ EXPECT_EQ(3, s.ColumnInt(5));
+
+ // "John Doe": The single timestamp should be assigned as both the creation
+ // and the last use timestamp.
+ ASSERT_TRUE(s.Step());
+ EXPECT_EQ(ASCIIToUTF16("Name"), s.ColumnString16(0));
+ EXPECT_EQ(ASCIIToUTF16("John Doe"), s.ColumnString16(1));
+ EXPECT_EQ(ASCIIToUTF16("john doe"), s.ColumnString16(2));
+ EXPECT_EQ(1384299100, s.ColumnInt64(3));
+ EXPECT_EQ(1384299100, s.ColumnInt64(4));
+ EXPECT_EQ(1, s.ColumnInt(5));
+
+ // "john doe": Should not be merged with "John Doe" (case-sensitivity).
+ ASSERT_TRUE(s.Step());
+ EXPECT_EQ(ASCIIToUTF16("Name"), s.ColumnString16(0));
+ EXPECT_EQ(ASCIIToUTF16("john doe"), s.ColumnString16(1));
+ EXPECT_EQ(ASCIIToUTF16("john doe"), s.ColumnString16(2));
+ EXPECT_EQ(1384299200, s.ColumnInt64(3));
+ EXPECT_EQ(1384299200, s.ColumnInt64(4));
+ EXPECT_EQ(1, s.ColumnInt(5));
+
+ // No more entries expected.
+ ASSERT_FALSE(s.Step());
+ }
+}