Roll src/third_party/WebKit d9c6159:8139f33 (svn 201974:201975)
[chromium-blink-merge.git] / components / password_manager / core / browser / affiliation_database.cc
blobbab8f36642207db092188730dd226ce824b2932f
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/password_manager/core/browser/affiliation_database.h"
7 #include "base/bind.h"
8 #include "base/files/file_path.h"
9 #include "sql/connection.h"
10 #include "sql/error_delegate_util.h"
11 #include "sql/meta_table.h"
12 #include "sql/statement.h"
13 #include "sql/transaction.h"
15 namespace password_manager {
17 namespace {
18 const int kVersion = 1;
19 const int kCompatibleVersion = 1;
20 } // namespace
22 AffiliationDatabase::AffiliationDatabase() {
25 AffiliationDatabase::~AffiliationDatabase() {
28 bool AffiliationDatabase::Init(const base::FilePath& path) {
29 sql_connection_.reset(new sql::Connection);
30 sql_connection_->set_histogram_tag("Affiliation");
31 sql_connection_->set_error_callback(base::Bind(
32 &AffiliationDatabase::SQLErrorCallback, base::Unretained(this)));
34 if (!sql_connection_->Open(path))
35 return false;
37 if (!sql_connection_->Execute("PRAGMA foreign_keys=1")) {
38 sql_connection_->Poison();
39 return false;
42 sql::MetaTable metatable;
43 if (!metatable.Init(sql_connection_.get(), kVersion, kCompatibleVersion)) {
44 sql_connection_->Poison();
45 return false;
48 if (metatable.GetCompatibleVersionNumber() > kVersion) {
49 LOG(WARNING) << "AffiliationDatabase is too new.";
50 sql_connection_->Poison();
51 return false;
54 if (!CreateTablesAndIndicesIfNeeded()) {
55 sql_connection_->Poison();
56 return false;
59 return true;
62 bool AffiliationDatabase::GetAffiliationsForFacet(
63 const FacetURI& facet_uri,
64 AffiliatedFacetsWithUpdateTime* result) const {
65 DCHECK(result);
66 result->facets.clear();
68 sql::Statement statement(sql_connection_->GetCachedStatement(
69 SQL_FROM_HERE,
70 "SELECT m2.facet_uri, c.last_update_time "
71 "FROM eq_class_members m1, eq_class_members m2, eq_classes c "
72 "WHERE m1.facet_uri = ? AND m1.set_id = m2.set_id AND m1.set_id = c.id"));
73 statement.BindString(0, facet_uri.canonical_spec());
75 while (statement.Step()) {
76 result->facets.push_back(
77 FacetURI::FromCanonicalSpec(statement.ColumnString(0)));
78 result->last_update_time =
79 base::Time::FromInternalValue(statement.ColumnInt64(1));
82 return !result->facets.empty();
85 void AffiliationDatabase::GetAllAffiliations(
86 std::vector<AffiliatedFacetsWithUpdateTime>* results) const {
87 DCHECK(results);
88 results->clear();
90 sql::Statement statement(sql_connection_->GetCachedStatement(
91 SQL_FROM_HERE,
92 "SELECT m.facet_uri, c.last_update_time, c.id "
93 "FROM eq_class_members m, eq_classes c "
94 "WHERE m.set_id = c.id "
95 "ORDER BY c.id"));
97 int64 last_eq_class_id = 0;
98 while (statement.Step()) {
99 int64 eq_class_id = statement.ColumnInt64(2);
100 if (results->empty() || eq_class_id != last_eq_class_id) {
101 results->push_back(AffiliatedFacetsWithUpdateTime());
102 last_eq_class_id = eq_class_id;
104 results->back().facets.push_back(
105 FacetURI::FromCanonicalSpec(statement.ColumnString(0)));
106 results->back().last_update_time =
107 base::Time::FromInternalValue(statement.ColumnInt64(1));
111 void AffiliationDatabase::DeleteAffiliationsForFacet(
112 const FacetURI& facet_uri) {
113 sql::Transaction transaction(sql_connection_.get());
114 if (!transaction.Begin())
115 return;
117 sql::Statement statement_lookup(sql_connection_->GetCachedStatement(
118 SQL_FROM_HERE,
119 "SELECT m.set_id FROM eq_class_members m "
120 "WHERE m.facet_uri = ?"));
121 statement_lookup.BindString(0, facet_uri.canonical_spec());
123 // No such |facet_uri|, nothing to do.
124 if (!statement_lookup.Step())
125 return;
127 int64 eq_class_id = statement_lookup.ColumnInt64(0);
129 // Children will get deleted due to 'ON DELETE CASCADE'.
130 sql::Statement statement_parent(sql_connection_->GetCachedStatement(
131 SQL_FROM_HERE, "DELETE FROM eq_classes WHERE eq_classes.id = ?"));
132 statement_parent.BindInt64(0, eq_class_id);
133 if (!statement_parent.Run())
134 return;
136 transaction.Commit();
139 void AffiliationDatabase::DeleteAffiliationsOlderThan(
140 const base::Time& cutoff_threshold) {
141 // Children will get deleted due to 'ON DELETE CASCADE'.
142 sql::Statement statement_parent(sql_connection_->GetCachedStatement(
143 SQL_FROM_HERE,
144 "DELETE FROM eq_classes "
145 "WHERE eq_classes.last_update_time < ?"));
146 statement_parent.BindInt64(0, cutoff_threshold.ToInternalValue());
147 statement_parent.Run();
150 void AffiliationDatabase::DeleteAllAffiliations() {
151 // Children will get deleted due to 'ON DELETE CASCADE'.
152 sql::Statement statement_parent(
153 sql_connection_->GetUniqueStatement("DELETE FROM eq_classes"));
154 statement_parent.Run();
157 bool AffiliationDatabase::Store(
158 const AffiliatedFacetsWithUpdateTime& affiliated_facets) {
159 DCHECK(!affiliated_facets.facets.empty());
161 sql::Statement statement_parent(sql_connection_->GetCachedStatement(
162 SQL_FROM_HERE, "INSERT INTO eq_classes(last_update_time) VALUES (?)"));
164 sql::Statement statement_child(sql_connection_->GetCachedStatement(
165 SQL_FROM_HERE,
166 "INSERT INTO eq_class_members(facet_uri, set_id) VALUES (?, ?)"));
168 sql::Transaction transaction(sql_connection_.get());
169 if (!transaction.Begin())
170 return false;
172 statement_parent.BindInt64(
173 0, affiliated_facets.last_update_time.ToInternalValue());
174 if (!statement_parent.Run())
175 return false;
177 int64 eq_class_id = sql_connection_->GetLastInsertRowId();
178 for (const FacetURI& uri : affiliated_facets.facets) {
179 statement_child.Reset(true);
180 statement_child.BindString(0, uri.canonical_spec());
181 statement_child.BindInt64(1, eq_class_id);
182 if (!statement_child.Run())
183 return false;
186 return transaction.Commit();
189 void AffiliationDatabase::StoreAndRemoveConflicting(
190 const AffiliatedFacetsWithUpdateTime& affiliation,
191 std::vector<AffiliatedFacetsWithUpdateTime>* removed_affiliations) {
192 DCHECK(!affiliation.facets.empty());
193 DCHECK(removed_affiliations);
194 removed_affiliations->clear();
196 sql::Transaction transaction(sql_connection_.get());
197 if (!transaction.Begin())
198 return;
200 for (const FacetURI& uri : affiliation.facets) {
201 AffiliatedFacetsWithUpdateTime old_affiliation;
202 if (GetAffiliationsForFacet(uri, &old_affiliation)) {
203 if (!AreEquivalenceClassesEqual(old_affiliation.facets,
204 affiliation.facets)) {
205 removed_affiliations->push_back(old_affiliation);
207 DeleteAffiliationsForFacet(uri);
211 if (!Store(affiliation))
212 NOTREACHED();
214 transaction.Commit();
217 // static
218 void AffiliationDatabase::Delete(const base::FilePath& path) {
219 bool success = sql::Connection::Delete(path);
220 DCHECK(success);
223 bool AffiliationDatabase::CreateTablesAndIndicesIfNeeded() {
224 if (!sql_connection_->Execute(
225 "CREATE TABLE IF NOT EXISTS eq_classes("
226 "id INTEGER PRIMARY KEY,"
227 "last_update_time INTEGER)")) {
228 return false;
231 if (!sql_connection_->Execute(
232 "CREATE TABLE IF NOT EXISTS eq_class_members("
233 "id INTEGER PRIMARY KEY,"
234 "facet_uri LONGVARCHAR UNIQUE NOT NULL,"
235 "set_id INTEGER NOT NULL"
236 " REFERENCES eq_classes(id) ON DELETE CASCADE)")) {
237 return false;
240 // An index on eq_class_members.facet_uri is automatically created due to the
241 // UNIQUE constraint, however, we must create one on eq_class_members.set_id
242 // manually (to prevent linear scan when joining).
243 return sql_connection_->Execute(
244 "CREATE INDEX IF NOT EXISTS index_on_eq_class_members_set_id ON "
245 "eq_class_members (set_id)");
248 void AffiliationDatabase::SQLErrorCallback(int error,
249 sql::Statement* statement) {
250 if (sql::IsErrorCatastrophic(error)) {
251 // Normally this will poison the database, causing any subsequent operations
252 // to silently fail without any side effects. However, if RazeAndClose() is
253 // called from the error callback in response to an error raised from within
254 // sql::Connection::Open, opening the now-razed database will be retried.
255 sql_connection_->RazeAndClose();
256 return;
259 // The default handling is to assert on debug and to ignore on release.
260 if (!sql::Connection::ShouldIgnoreSqliteError(error))
261 DLOG(FATAL) << sql_connection_->GetErrorMessage();
264 } // namespace password_manager