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"
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
{
18 const int kVersion
= 1;
19 const int kCompatibleVersion
= 1;
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
))
37 if (!sql_connection_
->Execute("PRAGMA foreign_keys=1"))
40 sql::MetaTable metatable
;
41 if (!metatable
.Init(sql_connection_
.get(), kVersion
, kCompatibleVersion
))
44 if (metatable
.GetCompatibleVersionNumber() > kVersion
) {
45 LOG(WARNING
) << "AffiliationDatabase is too new.";
49 return CreateTablesAndIndicesIfNeeded();
52 bool AffiliationDatabase::GetAffiliationsForFacet(
53 const FacetURI
& facet_uri
,
54 AffiliatedFacetsWithUpdateTime
* result
) const {
56 result
->facets
.clear();
58 sql::Statement
statement(sql_connection_
->GetCachedStatement(
60 "SELECT m2.facet_uri, c.last_update_time "
61 "FROM eq_class_members m1, eq_class_members m2, eq_classes c "
62 "WHERE m1.facet_uri = ? AND m1.set_id = m2.set_id AND m1.set_id = c.id"));
63 statement
.BindString(0, facet_uri
.canonical_spec());
65 while (statement
.Step()) {
66 result
->facets
.push_back(
67 FacetURI::FromCanonicalSpec(statement
.ColumnString(0)));
68 result
->last_update_time
=
69 base::Time::FromInternalValue(statement
.ColumnInt64(1));
72 return !result
->facets
.empty();
75 void AffiliationDatabase::GetAllAffiliations(
76 std::vector
<AffiliatedFacetsWithUpdateTime
>* results
) const {
80 sql::Statement
statement(sql_connection_
->GetCachedStatement(
82 "SELECT m.facet_uri, c.last_update_time, c.id "
83 "FROM eq_class_members m, eq_classes c "
84 "WHERE m.set_id = c.id "
87 int64 last_eq_class_id
= 0;
88 while (statement
.Step()) {
89 int64 eq_class_id
= statement
.ColumnInt64(2);
90 if (results
->empty() || eq_class_id
!= last_eq_class_id
) {
91 results
->push_back(AffiliatedFacetsWithUpdateTime());
92 last_eq_class_id
= eq_class_id
;
94 results
->back().facets
.push_back(
95 FacetURI::FromCanonicalSpec(statement
.ColumnString(0)));
96 results
->back().last_update_time
=
97 base::Time::FromInternalValue(statement
.ColumnInt64(1));
101 void AffiliationDatabase::DeleteAffiliationsForFacet(
102 const FacetURI
& facet_uri
) {
103 sql::Transaction
transaction(sql_connection_
.get());
104 if (!transaction
.Begin())
107 sql::Statement
statement_lookup(sql_connection_
->GetCachedStatement(
109 "SELECT m.set_id FROM eq_class_members m "
110 "WHERE m.facet_uri = ?"));
111 statement_lookup
.BindString(0, facet_uri
.canonical_spec());
113 // No such |facet_uri|, nothing to do.
114 if (!statement_lookup
.Step())
117 int64 eq_class_id
= statement_lookup
.ColumnInt64(0);
119 // Children will get deleted due to 'ON DELETE CASCADE'.
120 sql::Statement
statement_parent(sql_connection_
->GetCachedStatement(
121 SQL_FROM_HERE
, "DELETE FROM eq_classes WHERE eq_classes.id = ?"));
122 statement_parent
.BindInt64(0, eq_class_id
);
123 if (!statement_parent
.Run())
126 transaction
.Commit();
129 void AffiliationDatabase::DeleteAffiliationsOlderThan(
130 const base::Time
& cutoff_threshold
) {
131 // Children will get deleted due to 'ON DELETE CASCADE'.
132 sql::Statement
statement_parent(sql_connection_
->GetCachedStatement(
134 "DELETE FROM eq_classes "
135 "WHERE eq_classes.last_update_time < ?"));
136 statement_parent
.BindInt64(0, cutoff_threshold
.ToInternalValue());
137 statement_parent
.Run();
140 void AffiliationDatabase::DeleteAllAffiliations() {
141 // Children will get deleted due to 'ON DELETE CASCADE'.
142 sql::Statement
statement_parent(
143 sql_connection_
->GetUniqueStatement("DELETE FROM eq_classes"));
144 statement_parent
.Run();
147 bool AffiliationDatabase::Store(
148 const AffiliatedFacetsWithUpdateTime
& affiliated_facets
) {
149 DCHECK(!affiliated_facets
.facets
.empty());
151 sql::Statement
statement_parent(sql_connection_
->GetCachedStatement(
152 SQL_FROM_HERE
, "INSERT INTO eq_classes(last_update_time) VALUES (?)"));
154 sql::Statement
statement_child(sql_connection_
->GetCachedStatement(
156 "INSERT INTO eq_class_members(facet_uri, set_id) VALUES (?, ?)"));
158 sql::Transaction
transaction(sql_connection_
.get());
159 if (!transaction
.Begin())
162 statement_parent
.BindInt64(
163 0, affiliated_facets
.last_update_time
.ToInternalValue());
164 if (!statement_parent
.Run())
167 int64 eq_class_id
= sql_connection_
->GetLastInsertRowId();
168 for (const FacetURI
& uri
: affiliated_facets
.facets
) {
169 statement_child
.Reset(true);
170 statement_child
.BindString(0, uri
.canonical_spec());
171 statement_child
.BindInt64(1, eq_class_id
);
172 if (!statement_child
.Run())
176 return transaction
.Commit();
179 void AffiliationDatabase::StoreAndRemoveConflicting(
180 const AffiliatedFacetsWithUpdateTime
& affiliation
,
181 std::vector
<AffiliatedFacetsWithUpdateTime
>* removed_affiliations
) {
182 DCHECK(!affiliation
.facets
.empty());
183 DCHECK(removed_affiliations
);
184 removed_affiliations
->clear();
186 sql::Transaction
transaction(sql_connection_
.get());
187 if (!transaction
.Begin())
190 for (const FacetURI
& uri
: affiliation
.facets
) {
191 AffiliatedFacetsWithUpdateTime old_affiliation
;
192 if (GetAffiliationsForFacet(uri
, &old_affiliation
)) {
193 if (!AreEquivalenceClassesEqual(old_affiliation
.facets
,
194 affiliation
.facets
)) {
195 removed_affiliations
->push_back(old_affiliation
);
197 DeleteAffiliationsForFacet(uri
);
201 if (!Store(affiliation
))
204 transaction
.Commit();
207 bool AffiliationDatabase::CreateTablesAndIndicesIfNeeded() {
208 if (!sql_connection_
->Execute(
209 "CREATE TABLE IF NOT EXISTS eq_classes("
210 "id INTEGER PRIMARY KEY,"
211 "last_update_time INTEGER)")) {
215 if (!sql_connection_
->Execute(
216 "CREATE TABLE IF NOT EXISTS eq_class_members("
217 "id INTEGER PRIMARY KEY,"
218 "facet_uri LONGVARCHAR UNIQUE NOT NULL,"
219 "set_id INTEGER NOT NULL"
220 " REFERENCES eq_classes(id) ON DELETE CASCADE)")) {
224 // An index on eq_class_members.facet_uri is automatically created due to the
225 // UNIQUE constraint, however, we must create one on eq_class_members.set_id
226 // manually (to prevent linear scan when joining).
227 return sql_connection_
->Execute(
228 "CREATE INDEX IF NOT EXISTS index_on_eq_class_members_set_id ON "
229 "eq_class_members (set_id)");
232 void AffiliationDatabase::SQLErrorCallback(int error
,
233 sql::Statement
* statement
) {
234 if (sql::IsErrorCatastrophic(error
)) {
235 // Normally this will poison the database, causing any subsequent operations
236 // to silently fail without any side effects. However, if RazeAndClose() is
237 // called from the error callback in response to an error raised from within
238 // sql::Connection::Open, opening the now-razed database will be retried.
239 sql_connection_
->RazeAndClose();
243 // The default handling is to assert on debug and to ignore on release.
244 if (!sql::Connection::ShouldIgnoreSqliteError(error
))
245 DLOG(FATAL
) << sql_connection_
->GetErrorMessage();
248 } // namespace password_manager