- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / activity_log / counting_policy.cc
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