| OLD | NEW |
| (Empty) |
| 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. | |
| 4 | |
| 5 #include "components/password_manager/core/browser/affiliation_database.h" | |
| 6 | |
| 7 #include <stdint.h> | |
| 8 | |
| 9 #include "base/bind.h" | |
| 10 #include "base/files/file_path.h" | |
| 11 #include "sql/connection.h" | |
| 12 #include "sql/error_delegate_util.h" | |
| 13 #include "sql/meta_table.h" | |
| 14 #include "sql/statement.h" | |
| 15 #include "sql/transaction.h" | |
| 16 | |
| 17 namespace password_manager { | |
| 18 | |
| 19 namespace { | |
| 20 const int kVersion = 1; | |
| 21 const int kCompatibleVersion = 1; | |
| 22 } // namespace | |
| 23 | |
| 24 AffiliationDatabase::AffiliationDatabase() { | |
| 25 } | |
| 26 | |
| 27 AffiliationDatabase::~AffiliationDatabase() { | |
| 28 } | |
| 29 | |
| 30 bool AffiliationDatabase::Init(const base::FilePath& path) { | |
| 31 sql_connection_.reset(new sql::Connection); | |
| 32 sql_connection_->set_histogram_tag("Affiliation"); | |
| 33 sql_connection_->set_error_callback(base::Bind( | |
| 34 &AffiliationDatabase::SQLErrorCallback, base::Unretained(this))); | |
| 35 | |
| 36 if (!sql_connection_->Open(path)) | |
| 37 return false; | |
| 38 | |
| 39 if (!sql_connection_->Execute("PRAGMA foreign_keys=1")) { | |
| 40 sql_connection_->Poison(); | |
| 41 return false; | |
| 42 } | |
| 43 | |
| 44 sql::MetaTable metatable; | |
| 45 if (!metatable.Init(sql_connection_.get(), kVersion, kCompatibleVersion)) { | |
| 46 sql_connection_->Poison(); | |
| 47 return false; | |
| 48 } | |
| 49 | |
| 50 if (metatable.GetCompatibleVersionNumber() > kVersion) { | |
| 51 LOG(WARNING) << "AffiliationDatabase is too new."; | |
| 52 sql_connection_->Poison(); | |
| 53 return false; | |
| 54 } | |
| 55 | |
| 56 if (!CreateTablesAndIndicesIfNeeded()) { | |
| 57 sql_connection_->Poison(); | |
| 58 return false; | |
| 59 } | |
| 60 | |
| 61 return true; | |
| 62 } | |
| 63 | |
| 64 bool AffiliationDatabase::GetAffiliationsForFacet( | |
| 65 const FacetURI& facet_uri, | |
| 66 AffiliatedFacetsWithUpdateTime* result) const { | |
| 67 DCHECK(result); | |
| 68 result->facets.clear(); | |
| 69 | |
| 70 sql::Statement statement(sql_connection_->GetCachedStatement( | |
| 71 SQL_FROM_HERE, | |
| 72 "SELECT m2.facet_uri, c.last_update_time " | |
| 73 "FROM eq_class_members m1, eq_class_members m2, eq_classes c " | |
| 74 "WHERE m1.facet_uri = ? AND m1.set_id = m2.set_id AND m1.set_id = c.id")); | |
| 75 statement.BindString(0, facet_uri.canonical_spec()); | |
| 76 | |
| 77 while (statement.Step()) { | |
| 78 result->facets.push_back( | |
| 79 FacetURI::FromCanonicalSpec(statement.ColumnString(0))); | |
| 80 result->last_update_time = | |
| 81 base::Time::FromInternalValue(statement.ColumnInt64(1)); | |
| 82 } | |
| 83 | |
| 84 return !result->facets.empty(); | |
| 85 } | |
| 86 | |
| 87 void AffiliationDatabase::GetAllAffiliations( | |
| 88 std::vector<AffiliatedFacetsWithUpdateTime>* results) const { | |
| 89 DCHECK(results); | |
| 90 results->clear(); | |
| 91 | |
| 92 sql::Statement statement(sql_connection_->GetCachedStatement( | |
| 93 SQL_FROM_HERE, | |
| 94 "SELECT m.facet_uri, c.last_update_time, c.id " | |
| 95 "FROM eq_class_members m, eq_classes c " | |
| 96 "WHERE m.set_id = c.id " | |
| 97 "ORDER BY c.id")); | |
| 98 | |
| 99 int64_t last_eq_class_id = 0; | |
| 100 while (statement.Step()) { | |
| 101 int64_t eq_class_id = statement.ColumnInt64(2); | |
| 102 if (results->empty() || eq_class_id != last_eq_class_id) { | |
| 103 results->push_back(AffiliatedFacetsWithUpdateTime()); | |
| 104 last_eq_class_id = eq_class_id; | |
| 105 } | |
| 106 results->back().facets.push_back( | |
| 107 FacetURI::FromCanonicalSpec(statement.ColumnString(0))); | |
| 108 results->back().last_update_time = | |
| 109 base::Time::FromInternalValue(statement.ColumnInt64(1)); | |
| 110 } | |
| 111 } | |
| 112 | |
| 113 void AffiliationDatabase::DeleteAffiliationsForFacet( | |
| 114 const FacetURI& facet_uri) { | |
| 115 sql::Transaction transaction(sql_connection_.get()); | |
| 116 if (!transaction.Begin()) | |
| 117 return; | |
| 118 | |
| 119 sql::Statement statement_lookup(sql_connection_->GetCachedStatement( | |
| 120 SQL_FROM_HERE, | |
| 121 "SELECT m.set_id FROM eq_class_members m " | |
| 122 "WHERE m.facet_uri = ?")); | |
| 123 statement_lookup.BindString(0, facet_uri.canonical_spec()); | |
| 124 | |
| 125 // No such |facet_uri|, nothing to do. | |
| 126 if (!statement_lookup.Step()) | |
| 127 return; | |
| 128 | |
| 129 int64_t eq_class_id = statement_lookup.ColumnInt64(0); | |
| 130 | |
| 131 // Children will get deleted due to 'ON DELETE CASCADE'. | |
| 132 sql::Statement statement_parent(sql_connection_->GetCachedStatement( | |
| 133 SQL_FROM_HERE, "DELETE FROM eq_classes WHERE eq_classes.id = ?")); | |
| 134 statement_parent.BindInt64(0, eq_class_id); | |
| 135 if (!statement_parent.Run()) | |
| 136 return; | |
| 137 | |
| 138 transaction.Commit(); | |
| 139 } | |
| 140 | |
| 141 void AffiliationDatabase::DeleteAffiliationsOlderThan( | |
| 142 const base::Time& cutoff_threshold) { | |
| 143 // Children will get deleted due to 'ON DELETE CASCADE'. | |
| 144 sql::Statement statement_parent(sql_connection_->GetCachedStatement( | |
| 145 SQL_FROM_HERE, | |
| 146 "DELETE FROM eq_classes " | |
| 147 "WHERE eq_classes.last_update_time < ?")); | |
| 148 statement_parent.BindInt64(0, cutoff_threshold.ToInternalValue()); | |
| 149 statement_parent.Run(); | |
| 150 } | |
| 151 | |
| 152 void AffiliationDatabase::DeleteAllAffiliations() { | |
| 153 // Children will get deleted due to 'ON DELETE CASCADE'. | |
| 154 sql::Statement statement_parent( | |
| 155 sql_connection_->GetUniqueStatement("DELETE FROM eq_classes")); | |
| 156 statement_parent.Run(); | |
| 157 } | |
| 158 | |
| 159 bool AffiliationDatabase::Store( | |
| 160 const AffiliatedFacetsWithUpdateTime& affiliated_facets) { | |
| 161 DCHECK(!affiliated_facets.facets.empty()); | |
| 162 | |
| 163 sql::Statement statement_parent(sql_connection_->GetCachedStatement( | |
| 164 SQL_FROM_HERE, "INSERT INTO eq_classes(last_update_time) VALUES (?)")); | |
| 165 | |
| 166 sql::Statement statement_child(sql_connection_->GetCachedStatement( | |
| 167 SQL_FROM_HERE, | |
| 168 "INSERT INTO eq_class_members(facet_uri, set_id) VALUES (?, ?)")); | |
| 169 | |
| 170 sql::Transaction transaction(sql_connection_.get()); | |
| 171 if (!transaction.Begin()) | |
| 172 return false; | |
| 173 | |
| 174 statement_parent.BindInt64( | |
| 175 0, affiliated_facets.last_update_time.ToInternalValue()); | |
| 176 if (!statement_parent.Run()) | |
| 177 return false; | |
| 178 | |
| 179 int64_t eq_class_id = sql_connection_->GetLastInsertRowId(); | |
| 180 for (const FacetURI& uri : affiliated_facets.facets) { | |
| 181 statement_child.Reset(true); | |
| 182 statement_child.BindString(0, uri.canonical_spec()); | |
| 183 statement_child.BindInt64(1, eq_class_id); | |
| 184 if (!statement_child.Run()) | |
| 185 return false; | |
| 186 } | |
| 187 | |
| 188 return transaction.Commit(); | |
| 189 } | |
| 190 | |
| 191 void AffiliationDatabase::StoreAndRemoveConflicting( | |
| 192 const AffiliatedFacetsWithUpdateTime& affiliation, | |
| 193 std::vector<AffiliatedFacetsWithUpdateTime>* removed_affiliations) { | |
| 194 DCHECK(!affiliation.facets.empty()); | |
| 195 DCHECK(removed_affiliations); | |
| 196 removed_affiliations->clear(); | |
| 197 | |
| 198 sql::Transaction transaction(sql_connection_.get()); | |
| 199 if (!transaction.Begin()) | |
| 200 return; | |
| 201 | |
| 202 for (const FacetURI& uri : affiliation.facets) { | |
| 203 AffiliatedFacetsWithUpdateTime old_affiliation; | |
| 204 if (GetAffiliationsForFacet(uri, &old_affiliation)) { | |
| 205 if (!AreEquivalenceClassesEqual(old_affiliation.facets, | |
| 206 affiliation.facets)) { | |
| 207 removed_affiliations->push_back(old_affiliation); | |
| 208 } | |
| 209 DeleteAffiliationsForFacet(uri); | |
| 210 } | |
| 211 } | |
| 212 | |
| 213 if (!Store(affiliation)) | |
| 214 NOTREACHED(); | |
| 215 | |
| 216 transaction.Commit(); | |
| 217 } | |
| 218 | |
| 219 // static | |
| 220 void AffiliationDatabase::Delete(const base::FilePath& path) { | |
| 221 bool success = sql::Connection::Delete(path); | |
| 222 DCHECK(success); | |
| 223 } | |
| 224 | |
| 225 bool AffiliationDatabase::CreateTablesAndIndicesIfNeeded() { | |
| 226 if (!sql_connection_->Execute( | |
| 227 "CREATE TABLE IF NOT EXISTS eq_classes(" | |
| 228 "id INTEGER PRIMARY KEY," | |
| 229 "last_update_time INTEGER)")) { | |
| 230 return false; | |
| 231 } | |
| 232 | |
| 233 if (!sql_connection_->Execute( | |
| 234 "CREATE TABLE IF NOT EXISTS eq_class_members(" | |
| 235 "id INTEGER PRIMARY KEY," | |
| 236 "facet_uri LONGVARCHAR UNIQUE NOT NULL," | |
| 237 "set_id INTEGER NOT NULL" | |
| 238 " REFERENCES eq_classes(id) ON DELETE CASCADE)")) { | |
| 239 return false; | |
| 240 } | |
| 241 | |
| 242 // An index on eq_class_members.facet_uri is automatically created due to the | |
| 243 // UNIQUE constraint, however, we must create one on eq_class_members.set_id | |
| 244 // manually (to prevent linear scan when joining). | |
| 245 return sql_connection_->Execute( | |
| 246 "CREATE INDEX IF NOT EXISTS index_on_eq_class_members_set_id ON " | |
| 247 "eq_class_members (set_id)"); | |
| 248 } | |
| 249 | |
| 250 void AffiliationDatabase::SQLErrorCallback(int error, | |
| 251 sql::Statement* statement) { | |
| 252 if (sql::IsErrorCatastrophic(error)) { | |
| 253 // Normally this will poison the database, causing any subsequent operations | |
| 254 // to silently fail without any side effects. However, if RazeAndClose() is | |
| 255 // called from the error callback in response to an error raised from within | |
| 256 // sql::Connection::Open, opening the now-razed database will be retried. | |
| 257 sql_connection_->RazeAndClose(); | |
| 258 return; | |
| 259 } | |
| 260 | |
| 261 // The default handling is to assert on debug and to ignore on release. | |
| 262 if (!sql::Connection::IsExpectedSqliteError(error)) | |
| 263 DLOG(FATAL) << sql_connection_->GetErrorMessage(); | |
| 264 } | |
| 265 | |
| 266 } // namespace password_manager | |
| OLD | NEW |