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