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