counting_policy.cc revision 424c4d7b64af9d0d8fd9624f381f469654d5e3d2
1// Copyright 2013 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// A policy for storing activity log data to a database that performs 6// aggregation to reduce the size of the database. The database layout is 7// nearly the same as FullStreamUIPolicy, which stores a complete log, with a 8// few changes: 9// - a "count" column is added to track how many log records were merged 10// together into this row 11// - the "time" column measures the most recent time that the current row was 12// updated 13// When writing a record, if a row already exists where all other columns 14// (extension_id, action_type, api_name, args, urls, etc.) all match, and the 15// previous time falls within today (the current time), then the count field on 16// the old row is incremented. Otherwise, a new row is written. 17// 18// For many text columns, repeated strings are compressed by moving string 19// storage to a separate table ("string_ids") and storing only an identifier in 20// the logging table. For example, if the api_name_x column contained the 21// value 4 and the string_ids table contained a row with primary key 4 and 22// value 'tabs.query', then the api_name field should be taken to have the 23// value 'tabs.query'. Each column ending with "_x" is compressed in this way. 24// All lookups are to the string_ids table, except for the page_url_x and 25// arg_url_x columns, which are converted via the url_ids table (this 26// separation of URL values is to help simplify history clearing). 27// 28// The activitylog_uncompressed view allows for simpler reading of the activity 29// log contents with identifiers already translated to string values. 30 31#include "chrome/browser/extensions/activity_log/counting_policy.h" 32 33#include <map> 34#include <string> 35#include <vector> 36 37#include "base/callback.h" 38#include "base/files/file_path.h" 39#include "base/json/json_reader.h" 40#include "base/json/json_string_value_serializer.h" 41#include "base/strings/string_util.h" 42#include "base/strings/stringprintf.h" 43#include "chrome/common/chrome_constants.h" 44 45using content::BrowserThread; 46 47namespace { 48 49// Delay between cleaning passes (to delete old action records) through the 50// database. 51const int kCleaningDelayInHours = 12; 52 53// We should log the arguments to these API calls. Be careful when 54// constructing this whitelist to not keep arguments that might compromise 55// privacy by logging too much data to the activity log. 56// 57// TODO(mvrable): The contents of this whitelist should be reviewed and 58// expanded as needed. 59const char* kAlwaysLog[] = {"extension.connect", "extension.sendMessage", 60 "tabs.executeScript", "tabs.insertCSS"}; 61 62// Columns in the main database table. See the file-level comment for a 63// discussion of how data is stored and the meanings of the _x columns. 64const char* kTableContentFields[] = { 65 "count", "extension_id_x", "time", "action_type", "api_name_x", "args_x", 66 "page_url_x", "page_title_x", "arg_url_x", "other_x"}; 67const char* kTableFieldTypes[] = { 68 "INTEGER NOT NULL DEFAULT 1", "INTEGER NOT NULL", "INTEGER", "INTEGER", 69 "INTEGER", "INTEGER", "INTEGER", "INTEGER", "INTEGER", 70 "INTEGER"}; 71 72// Miscellaneous SQL commands for initializing the database; these should be 73// idempotent. 74static const char kPolicyMiscSetup[] = 75 // The activitylog_uncompressed view performs string lookups for simpler 76 // access to the log data. 77 "DROP VIEW IF EXISTS activitylog_uncompressed;\n" 78 "CREATE VIEW activitylog_uncompressed AS\n" 79 "SELECT count,\n" 80 " x1.value AS extension_id,\n" 81 " time,\n" 82 " action_type,\n" 83 " x2.value AS api_name,\n" 84 " x3.value AS args,\n" 85 " x4.value AS page_url,\n" 86 " x5.value AS page_title,\n" 87 " x6.value AS arg_url,\n" 88 " x7.value AS other\n" 89 "FROM activitylog_compressed\n" 90 " LEFT JOIN string_ids AS x1 ON (x1.id = extension_id_x)\n" 91 " LEFT JOIN string_ids AS x2 ON (x2.id = api_name_x)\n" 92 " LEFT JOIN string_ids AS x3 ON (x3.id = args_x)\n" 93 " LEFT JOIN url_ids AS x4 ON (x4.id = page_url_x)\n" 94 " LEFT JOIN string_ids AS x5 ON (x5.id = page_title_x)\n" 95 " LEFT JOIN url_ids AS x6 ON (x6.id = arg_url_x)\n" 96 " LEFT JOIN string_ids AS x7 ON (x7.id = other_x);\n" 97 // An index on all fields except count and time: all the fields that aren't 98 // changed when incrementing a count. This should accelerate finding the 99 // rows to update (at worst several rows will need to be checked to find 100 // the one in the right time range). 101 "CREATE INDEX IF NOT EXISTS activitylog_compressed_index\n" 102 "ON activitylog_compressed(extension_id_x, action_type, api_name_x,\n" 103 " args_x, page_url_x, page_title_x, arg_url_x, other_x)"; 104 105// SQL statements to clean old, unused entries out of the string and URL id 106// tables. 107static const char kStringTableCleanup[] = 108 "DELETE FROM string_ids WHERE id NOT IN\n" 109 "(SELECT extension_id_x FROM activitylog_compressed\n" 110 " WHERE extension_id_x IS NOT NULL\n" 111 " UNION SELECT api_name_x FROM activitylog_compressed\n" 112 " WHERE api_name_x IS NOT NULL\n" 113 " UNION SELECT args_x FROM activitylog_compressed\n" 114 " WHERE args_x IS NOT NULL\n" 115 " UNION SELECT page_title_x FROM activitylog_compressed\n" 116 " WHERE page_title_x IS NOT NULL\n" 117 " UNION SELECT other_x FROM activitylog_compressed\n" 118 " WHERE other_x IS NOT NULL)"; 119static const char kUrlTableCleanup[] = 120 "DELETE FROM url_ids WHERE id NOT IN\n" 121 "(SELECT page_url_x FROM activitylog_compressed\n" 122 " WHERE page_url_x IS NOT NULL\n" 123 " UNION SELECT arg_url_x FROM activitylog_compressed\n" 124 " WHERE arg_url_x IS NOT NULL)"; 125 126} // namespace 127 128namespace extensions { 129 130// A specialized Action subclass which is used to represent an action read from 131// the database with a corresponding count. 132class CountedAction : public Action { 133 public: 134 CountedAction(const std::string& extension_id, 135 const base::Time& time, 136 const ActionType action_type, 137 const std::string& api_name) 138 : Action(extension_id, time, action_type, api_name), 139 count_(0) { 140 } 141 142 // Number of merged records for this action. 143 int count() const { return count_; } 144 void set_count(int count) { count_ = count; } 145 146 virtual std::string PrintForDebug() const OVERRIDE; 147 148 protected: 149 virtual ~CountedAction() {} 150 151 private: 152 int count_; 153}; 154 155std::string CountedAction::PrintForDebug() const { 156 return base::StringPrintf( 157 "%s COUNT=%d", Action::PrintForDebug().c_str(), count()); 158} 159 160const char* CountingPolicy::kTableName = "activitylog_compressed"; 161const char* CountingPolicy::kReadViewName = "activitylog_uncompressed"; 162 163CountingPolicy::CountingPolicy(Profile* profile) 164 : ActivityLogDatabasePolicy( 165 profile, 166 base::FilePath(chrome::kExtensionActivityLogFilename)), 167 string_table_("string_ids"), 168 url_table_("url_ids"), 169 retention_time_(base::TimeDelta::FromHours(60)) { 170 for (size_t i = 0; i < arraysize(kAlwaysLog); i++) { 171 api_arg_whitelist_.insert(kAlwaysLog[i]); 172 } 173} 174 175CountingPolicy::~CountingPolicy() {} 176 177bool CountingPolicy::InitDatabase(sql::Connection* db) { 178 if (!Util::DropObsoleteTables(db)) 179 return false; 180 181 if (!string_table_.Initialize(db)) 182 return false; 183 if (!url_table_.Initialize(db)) 184 return false; 185 186 // Create the unified activity log entry table. 187 if (!ActivityDatabase::InitializeTable(db, 188 kTableName, 189 kTableContentFields, 190 kTableFieldTypes, 191 arraysize(kTableContentFields))) 192 return false; 193 194 // Create a view for easily accessing the uncompressed form of the data, and 195 // any necessary indexes if needed. 196 return db->Execute(kPolicyMiscSetup); 197} 198 199void CountingPolicy::ProcessAction(scoped_refptr<Action> action) { 200 ScheduleAndForget(this, &CountingPolicy::QueueAction, action); 201} 202 203void CountingPolicy::QueueAction(scoped_refptr<Action> action) { 204 if (activity_database()->is_db_valid()) { 205 action = action->Clone(); 206 Util::StripPrivacySensitiveFields(action); 207 Util::StripArguments(api_arg_whitelist_, action); 208 209 // If the current action falls on a different date than the ones in the 210 // queue, flush the queue out now to prevent any false merging (actions 211 // from different days being merged). 212 base::Time new_date = action->time().LocalMidnight(); 213 if (new_date != queued_actions_date_) 214 activity_database()->AdviseFlush(ActivityDatabase::kFlushImmediately); 215 queued_actions_date_ = new_date; 216 217 ActionQueue::iterator queued_entry = queued_actions_.find(action); 218 if (queued_entry == queued_actions_.end()) { 219 queued_actions_[action] = 1; 220 } else { 221 // Update the timestamp in the key to be the latest time seen. Modifying 222 // the time is safe since that field is not involved in key comparisons 223 // in the map. 224 using std::max; 225 queued_entry->first->set_time( 226 max(queued_entry->first->time(), action->time())); 227 queued_entry->second++; 228 } 229 activity_database()->AdviseFlush(queued_actions_.size()); 230 } 231} 232 233bool CountingPolicy::FlushDatabase(sql::Connection* db) { 234 // Columns that must match exactly for database rows to be coalesced. 235 static const char* matched_columns[] = { 236 "extension_id_x", "action_type", "api_name_x", "args_x", "page_url_x", 237 "page_title_x", "arg_url_x", "other_x"}; 238 ActionQueue queue; 239 queue.swap(queued_actions_); 240 241 // Whether to clean old records out of the activity log database. Do this 242 // much less frequently than database flushes since it is expensive, but 243 // always check on the first database flush (since there might be a large 244 // amount of data to clear). 245 bool clean_database = (last_database_cleaning_time_.is_null() || 246 Now() - last_database_cleaning_time_ > 247 base::TimeDelta::FromHours(kCleaningDelayInHours)); 248 249 if (queue.empty() && !clean_database) 250 return true; 251 252 sql::Transaction transaction(db); 253 if (!transaction.Begin()) 254 return false; 255 256 std::string insert_str = 257 "INSERT INTO " + std::string(kTableName) + "(count, time"; 258 std::string update_str = 259 "UPDATE " + std::string(kTableName) + 260 " SET count = count + ?, time = max(?, time)" 261 " WHERE time >= ? AND time < ?"; 262 263 for (size_t i = 0; i < arraysize(matched_columns); i++) { 264 insert_str = 265 base::StringPrintf("%s, %s", insert_str.c_str(), matched_columns[i]); 266 update_str = base::StringPrintf( 267 "%s AND %s IS ?", update_str.c_str(), matched_columns[i]); 268 } 269 insert_str += ") VALUES (?, ?"; 270 for (size_t i = 0; i < arraysize(matched_columns); i++) { 271 insert_str += ", ?"; 272 } 273 insert_str += ")"; 274 275 for (ActionQueue::iterator i = queue.begin(); i != queue.end(); ++i) { 276 const Action& action = *i->first; 277 int count = i->second; 278 279 base::Time day_start = action.time().LocalMidnight(); 280 base::Time next_day = Util::AddDays(day_start, 1); 281 282 // The contents in values must match up with fields in matched_columns. A 283 // value of -1 is used to encode a null database value. 284 int64 id; 285 std::vector<int64> matched_values; 286 287 if (!string_table_.StringToInt(db, action.extension_id(), &id)) 288 return false; 289 matched_values.push_back(id); 290 291 matched_values.push_back(static_cast<int>(action.action_type())); 292 293 if (!string_table_.StringToInt(db, action.api_name(), &id)) 294 return false; 295 matched_values.push_back(id); 296 297 if (action.args()) { 298 std::string args = Util::Serialize(action.args()); 299 // TODO(mvrable): For now, truncate long argument lists. This is a 300 // workaround for excessively-long values coming from DOM logging. When 301 // the V8ValueConverter is fixed to return more reasonable values, we can 302 // drop the truncation. 303 if (args.length() > 10000) { 304 args = "[\"<too_large>\"]"; 305 } 306 if (!string_table_.StringToInt(db, args, &id)) 307 return false; 308 matched_values.push_back(id); 309 } else { 310 matched_values.push_back(-1); 311 } 312 313 std::string page_url_string = action.SerializePageUrl(); 314 if (!page_url_string.empty()) { 315 if (!url_table_.StringToInt(db, page_url_string, &id)) 316 return false; 317 matched_values.push_back(id); 318 } else { 319 matched_values.push_back(-1); 320 } 321 322 // TODO(mvrable): Create a title_table_? 323 if (!action.page_title().empty()) { 324 if (!string_table_.StringToInt(db, action.page_title(), &id)) 325 return false; 326 matched_values.push_back(id); 327 } else { 328 matched_values.push_back(-1); 329 } 330 331 std::string arg_url_string = action.SerializeArgUrl(); 332 if (!arg_url_string.empty()) { 333 if (!url_table_.StringToInt(db, arg_url_string, &id)) 334 return false; 335 matched_values.push_back(id); 336 } else { 337 matched_values.push_back(-1); 338 } 339 340 if (action.other()) { 341 if (!string_table_.StringToInt(db, Util::Serialize(action.other()), &id)) 342 return false; 343 matched_values.push_back(id); 344 } else { 345 matched_values.push_back(-1); 346 } 347 348 // Assume there is an existing row for this action, and try to update the 349 // count. 350 sql::Statement update_statement(db->GetCachedStatement( 351 sql::StatementID(SQL_FROM_HERE), update_str.c_str())); 352 update_statement.BindInt(0, count); 353 update_statement.BindInt64(1, action.time().ToInternalValue()); 354 update_statement.BindInt64(2, day_start.ToInternalValue()); 355 update_statement.BindInt64(3, next_day.ToInternalValue()); 356 for (size_t j = 0; j < matched_values.size(); j++) { 357 // A call to BindNull when matched_values contains -1 is likely not 358 // necessary as parameters default to null before they are explicitly 359 // bound. But to be completely clear, and in case a cached statement 360 // ever comes with some values already bound, we bind all parameters 361 // (even null ones) explicitly. 362 if (matched_values[j] == -1) 363 update_statement.BindNull(j + 4); 364 else 365 update_statement.BindInt64(j + 4, matched_values[j]); 366 } 367 if (!update_statement.Run()) 368 return false; 369 370 // Check if the update succeeded (was the count of updated rows non-zero)? 371 // If it failed because no matching row existed, fall back to inserting a 372 // new record. 373 if (db->GetLastChangeCount() > 0) { 374 if (db->GetLastChangeCount() > 1) { 375 LOG(WARNING) << "Found and updated multiple rows in the activity log " 376 << "database; counts may be off!"; 377 } 378 continue; 379 } 380 sql::Statement insert_statement(db->GetCachedStatement( 381 sql::StatementID(SQL_FROM_HERE), insert_str.c_str())); 382 insert_statement.BindInt(0, count); 383 insert_statement.BindInt64(1, action.time().ToInternalValue()); 384 for (size_t j = 0; j < matched_values.size(); j++) { 385 if (matched_values[j] == -1) 386 insert_statement.BindNull(j + 2); 387 else 388 insert_statement.BindInt64(j + 2, matched_values[j]); 389 } 390 if (!insert_statement.Run()) 391 return false; 392 } 393 394 if (clean_database) { 395 base::Time cutoff = (Now() - retention_time()).LocalMidnight(); 396 if (!CleanOlderThan(db, cutoff)) 397 return false; 398 last_database_cleaning_time_ = Now(); 399 } 400 401 if (!transaction.Commit()) 402 return false; 403 404 return true; 405} 406 407scoped_ptr<Action::ActionVector> CountingPolicy::DoReadFilteredData( 408 const std::string& extension_id, 409 const Action::ActionType type, 410 const std::string& api_name, 411 const std::string& page_url, 412 const std::string& arg_url) { 413 // Ensure data is flushed to the database first so that we query over all 414 // data. 415 activity_database()->AdviseFlush(ActivityDatabase::kFlushImmediately); 416 scoped_ptr<Action::ActionVector> actions(new Action::ActionVector()); 417 418 sql::Connection* db = GetDatabaseConnection(); 419 if (!db) 420 return actions.Pass(); 421 422 // Build up the query based on which parameters were specified. 423 std::string where_str = ""; 424 std::string where_next = ""; 425 if (!extension_id.empty()) { 426 where_str += "extension_id=?"; 427 where_next = " AND "; 428 } 429 if (!api_name.empty()) { 430 where_str += where_next + "api_name=?"; 431 where_next = " AND "; 432 } 433 if (type != Action::ACTION_ANY) { 434 where_str += where_next + "action_type=?"; 435 where_next = " AND "; 436 } 437 if (!page_url.empty()) { 438 where_str += where_next + "page_url LIKE ?"; 439 where_next = " AND "; 440 } 441 if (!arg_url.empty()) 442 where_str += where_next + "arg_url LIKE ?"; 443 std::string query_str = base::StringPrintf( 444 "SELECT extension_id,time, action_type, api_name, args, page_url," 445 "page_title, arg_url, other, count FROM %s WHERE %s ORDER BY time DESC", 446 kReadViewName, 447 where_str.c_str()); 448 sql::Statement query(db->GetUniqueStatement(query_str.c_str())); 449 int i = -1; 450 if (!extension_id.empty()) 451 query.BindString(++i, extension_id); 452 if (!api_name.empty()) 453 query.BindString(++i, api_name); 454 if (type != Action::ACTION_ANY) 455 query.BindInt(++i, static_cast<int>(type)); 456 if (!page_url.empty()) 457 query.BindString(++i, page_url + "%"); 458 if (!arg_url.empty()) 459 query.BindString(++i, arg_url + "%"); 460 461 // Execute the query and get results. 462 while (query.is_valid() && query.Step()) { 463 scoped_refptr<CountedAction> action = 464 new CountedAction(query.ColumnString(0), 465 base::Time::FromInternalValue(query.ColumnInt64(1)), 466 static_cast<Action::ActionType>(query.ColumnInt(2)), 467 query.ColumnString(3)); 468 469 if (query.ColumnType(4) != sql::COLUMN_TYPE_NULL) { 470 scoped_ptr<Value> parsed_value( 471 base::JSONReader::Read(query.ColumnString(4))); 472 if (parsed_value && parsed_value->IsType(Value::TYPE_LIST)) { 473 action->set_args( 474 make_scoped_ptr(static_cast<ListValue*>(parsed_value.release()))); 475 } 476 } 477 478 action->ParsePageUrl(query.ColumnString(5)); 479 action->set_page_title(query.ColumnString(6)); 480 action->ParseArgUrl(query.ColumnString(7)); 481 482 if (query.ColumnType(8) != sql::COLUMN_TYPE_NULL) { 483 scoped_ptr<Value> parsed_value( 484 base::JSONReader::Read(query.ColumnString(8))); 485 if (parsed_value && parsed_value->IsType(Value::TYPE_DICTIONARY)) { 486 action->set_other(make_scoped_ptr( 487 static_cast<DictionaryValue*>(parsed_value.release()))); 488 } 489 } 490 action->set_count(query.ColumnInt(9)); 491 actions->push_back(action); 492 } 493 494 return actions.Pass(); 495} 496 497scoped_ptr<Action::ActionVector> CountingPolicy::DoReadData( 498 const std::string& extension_id, 499 const int days_ago) { 500 // Ensure data is flushed to the database first so that we query over all 501 // data. 502 activity_database()->AdviseFlush(ActivityDatabase::kFlushImmediately); 503 504 DCHECK_GE(days_ago, 0); 505 scoped_ptr<Action::ActionVector> actions(new Action::ActionVector()); 506 507 sql::Connection* db = GetDatabaseConnection(); 508 if (!db) { 509 return actions.Pass(); 510 } 511 512 int64 early_bound; 513 int64 late_bound; 514 Util::ComputeDatabaseTimeBounds(Now(), days_ago, &early_bound, &late_bound); 515 std::string query_str = base::StringPrintf( 516 "SELECT time, action_type, api_name, args, page_url, page_title, " 517 "arg_url, other, count " 518 "FROM %s WHERE extension_id=? AND time>? AND time<=? " 519 "ORDER BY time DESC", 520 kReadViewName); 521 sql::Statement query(db->GetCachedStatement(SQL_FROM_HERE, 522 query_str.c_str())); 523 query.BindString(0, extension_id); 524 query.BindInt64(1, early_bound); 525 query.BindInt64(2, late_bound); 526 527 while (query.is_valid() && query.Step()) { 528 scoped_refptr<CountedAction> action = 529 new CountedAction(extension_id, 530 base::Time::FromInternalValue(query.ColumnInt64(0)), 531 static_cast<Action::ActionType>(query.ColumnInt(1)), 532 query.ColumnString(2)); 533 534 if (query.ColumnType(3) != sql::COLUMN_TYPE_NULL) { 535 scoped_ptr<Value> parsed_value( 536 base::JSONReader::Read(query.ColumnString(3))); 537 if (parsed_value && parsed_value->IsType(Value::TYPE_LIST)) { 538 action->set_args( 539 make_scoped_ptr(static_cast<ListValue*>(parsed_value.release()))); 540 } else { 541 LOG(WARNING) << "Unable to parse args: '" << query.ColumnString(3) 542 << "'"; 543 } 544 } 545 546 action->ParsePageUrl(query.ColumnString(4)); 547 action->set_page_title(query.ColumnString(5)); 548 action->ParseArgUrl(query.ColumnString(6)); 549 550 if (query.ColumnType(7) != sql::COLUMN_TYPE_NULL) { 551 scoped_ptr<Value> parsed_value( 552 base::JSONReader::Read(query.ColumnString(7))); 553 if (parsed_value && parsed_value->IsType(Value::TYPE_DICTIONARY)) { 554 action->set_other(make_scoped_ptr( 555 static_cast<DictionaryValue*>(parsed_value.release()))); 556 } else { 557 LOG(WARNING) << "Unable to parse other: '" << query.ColumnString(7) 558 << "'"; 559 } 560 } 561 562 action->set_count(query.ColumnInt(8)); 563 564 actions->push_back(action); 565 } 566 567 return actions.Pass(); 568} 569 570void CountingPolicy::DoRemoveURLs(const std::vector<GURL>& restrict_urls) { 571 sql::Connection* db = GetDatabaseConnection(); 572 if (!db) { 573 LOG(ERROR) << "Unable to connect to database"; 574 return; 575 } 576 577 // Flush data first so the URL clearing affects queued-up data as well. 578 activity_database()->AdviseFlush(ActivityDatabase::kFlushImmediately); 579 580 // If no restrictions then then all URLs need to be removed. 581 if (restrict_urls.empty()) { 582 std::string sql_str = base::StringPrintf( 583 "UPDATE %s SET page_url_x=NULL,page_title_x=NULL,arg_url_x=NULL", 584 kTableName); 585 586 sql::Statement statement; 587 statement.Assign(db->GetCachedStatement( 588 sql::StatementID(SQL_FROM_HERE), sql_str.c_str())); 589 590 if (!statement.Run()) { 591 LOG(ERROR) << "Removing all URLs from database failed: " 592 << statement.GetSQLStatement(); 593 return; 594 } 595 } 596 597 // If URLs are specified then restrict to only those URLs. 598 for (size_t i = 0; i < restrict_urls.size(); ++i) { 599 int64 url_id; 600 if (!restrict_urls[i].is_valid() || 601 !url_table_.StringToInt(db, restrict_urls[i].spec(), &url_id)) { 602 continue; 603 } 604 605 // Remove any that match the page_url. 606 std::string sql_str = base::StringPrintf( 607 "UPDATE %s SET page_url_x=NULL,page_title_x=NULL WHERE page_url_x IS ?", 608 kTableName); 609 610 sql::Statement statement; 611 statement.Assign(db->GetCachedStatement( 612 sql::StatementID(SQL_FROM_HERE), sql_str.c_str())); 613 statement.BindInt64(0, url_id); 614 615 if (!statement.Run()) { 616 LOG(ERROR) << "Removing page URL from database failed: " 617 << statement.GetSQLStatement(); 618 return; 619 } 620 621 // Remove any that match the arg_url. 622 sql_str = base::StringPrintf( 623 "UPDATE %s SET arg_url_x=NULL WHERE arg_url_x IS ?", kTableName); 624 625 statement.Assign(db->GetCachedStatement( 626 sql::StatementID(SQL_FROM_HERE), sql_str.c_str())); 627 statement.BindInt64(0, url_id); 628 629 if (!statement.Run()) { 630 LOG(ERROR) << "Removing arg URL from database failed: " 631 << statement.GetSQLStatement(); 632 return; 633 } 634 } 635 636 // Clean up unused strings from the strings and urls table to really delete 637 // the urls and page titles. Should be called even if an error occured when 638 // removing a URL as there may some things to clean up. 639 CleanStringTables(db); 640} 641 642void CountingPolicy::ReadData( 643 const std::string& extension_id, 644 const int day, 645 const base::Callback<void(scoped_ptr<Action::ActionVector>)>& callback) { 646 BrowserThread::PostTaskAndReplyWithResult( 647 BrowserThread::DB, 648 FROM_HERE, 649 base::Bind(&CountingPolicy::DoReadData, 650 base::Unretained(this), 651 extension_id, 652 day), 653 callback); 654} 655 656void CountingPolicy::ReadFilteredData( 657 const std::string& extension_id, 658 const Action::ActionType type, 659 const std::string& api_name, 660 const std::string& page_url, 661 const std::string& arg_url, 662 const base::Callback 663 <void(scoped_ptr<Action::ActionVector>)>& callback) { 664 BrowserThread::PostTaskAndReplyWithResult( 665 BrowserThread::DB, 666 FROM_HERE, 667 base::Bind(&CountingPolicy::DoReadFilteredData, 668 base::Unretained(this), 669 extension_id, 670 type, 671 api_name, 672 page_url, 673 arg_url), 674 callback); 675} 676 677void CountingPolicy::RemoveURLs(const std::vector<GURL>& restrict_urls) { 678 ScheduleAndForget(this, &CountingPolicy::DoRemoveURLs, restrict_urls); 679} 680 681void CountingPolicy::OnDatabaseFailure() { 682 queued_actions_.clear(); 683} 684 685void CountingPolicy::OnDatabaseClose() { 686 delete this; 687} 688 689// Cleans old records from the activity log database. 690bool CountingPolicy::CleanOlderThan(sql::Connection* db, 691 const base::Time& cutoff) { 692 std::string clean_statement = 693 "DELETE FROM " + std::string(kTableName) + " WHERE time < ?"; 694 sql::Statement cleaner(db->GetCachedStatement(sql::StatementID(SQL_FROM_HERE), 695 clean_statement.c_str())); 696 cleaner.BindInt64(0, cutoff.ToInternalValue()); 697 if (!cleaner.Run()) 698 return false; 699 return CleanStringTables(db); 700} 701 702// Cleans unused interned strings from the database. This should be run after 703// deleting rows from the main log table to clean out stale values. 704bool CountingPolicy::CleanStringTables(sql::Connection* db) { 705 sql::Statement cleaner1(db->GetCachedStatement( 706 sql::StatementID(SQL_FROM_HERE), kStringTableCleanup)); 707 if (!cleaner1.Run()) 708 return false; 709 if (db->GetLastChangeCount() > 0) 710 string_table_.ClearCache(); 711 712 sql::Statement cleaner2(db->GetCachedStatement( 713 sql::StatementID(SQL_FROM_HERE), kUrlTableCleanup)); 714 if (!cleaner2.Run()) 715 return false; 716 if (db->GetLastChangeCount() > 0) 717 url_table_.ClearCache(); 718 719 return true; 720} 721 722void CountingPolicy::Close() { 723 // The policy object should have never been created if there's no DB thread. 724 DCHECK(BrowserThread::IsMessageLoopValid(BrowserThread::DB)); 725 ScheduleAndForget(activity_database(), &ActivityDatabase::Close); 726} 727 728} // namespace extensions 729