Update To 11.40.268.0
[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_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* const 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* const 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* const 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.get();
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