#include <limits>
#include <set>
#include <string>
#include "chrome/browser/history/text_database.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/string_number_conversions.h"
#include "base/stringprintf.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/diagnostics/sqlite_diagnostics.h"
#include "sql/statement.h"
#include "sql/transaction.h"
namespace history {
namespace {
static const int kCurrentVersionNumber = 2;
static const int kCompatibleVersionNumber = 2;
const char kTitleColumnIndex[] = "1";
const char kBodyColumnIndex[] = "2";
const FilePath::CharType kFilePrefix[] = FILE_PATH_LITERAL("History Index ");
}
TextDatabase::Match::Match() {}
TextDatabase::Match::~Match() {}
TextDatabase::TextDatabase(const FilePath& path,
DBIdent id,
bool allow_create)
: path_(path),
ident_(id),
allow_create_(allow_create) {
file_name_ = path_.Append(IDToFileName(ident_));
}
TextDatabase::~TextDatabase() {
}
const FilePath::CharType* TextDatabase::file_base() {
return kFilePrefix;
}
FilePath TextDatabase::IDToFileName(DBIdent id) {
FilePath::StringType filename(file_base());
base::StringAppendF(&filename, FILE_PATH_LITERAL("%d-%02d"),
id / 100, id % 100);
return FilePath(filename);
}
TextDatabase::DBIdent TextDatabase::FileNameToID(const FilePath& file_path) {
FilePath::StringType file_name = file_path.BaseName().value();
static const size_t kIDStringLength = 7;
if (file_name.length() < kIDStringLength)
return 0;
const FilePath::StringType suffix(
&file_name[file_name.length() - kIDStringLength]);
if (suffix.length() != kIDStringLength ||
suffix[4] != FILE_PATH_LITERAL('-')) {
return 0;
}
int year, month;
base::StringToInt(suffix.substr(0, 4), &year);
base::StringToInt(suffix.substr(5, 2), &month);
return year * 100 + month;
}
bool TextDatabase::Init() {
if (!allow_create_) {
if (!file_util::PathExists(file_name_))
return false;
}
db_.set_error_delegate(GetErrorHandlerForTextDb());
db_.set_page_size(4096);
db_.set_cache_size(512);
db_.set_exclusive_locking();
if (!db_.Open(file_name_))
return false;
if (!meta_table_.Init(&db_, kCurrentVersionNumber, kCompatibleVersionNumber))
return false;
if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) {
LOG(WARNING) << "Text database is too new.";
return false;
}
return CreateTables();
}
void TextDatabase::BeginTransaction() {
db_.BeginTransaction();
}
void TextDatabase::CommitTransaction() {
db_.CommitTransaction();
}
bool TextDatabase::CreateTables() {
if (!db_.DoesTableExist("pages")) {
if (!db_.Execute("CREATE VIRTUAL TABLE pages USING fts3("
"TOKENIZE icu,"
"url LONGVARCHAR,"
"title LONGVARCHAR,"
"body LONGVARCHAR)"))
return false;
}
if (!db_.DoesTableExist("info")) {
if (!db_.Execute("CREATE TABLE info(time INTEGER NOT NULL)"))
return false;
}
return db_.Execute("CREATE INDEX IF NOT EXISTS info_time ON info(time)");
}
bool TextDatabase::AddPageData(base::Time time,
const std::string& url,
const std::string& title,
const std::string& contents) {
sql::Transaction committer(&db_);
if (!committer.Begin())
return false;
sql::Statement add_to_pages(db_.GetCachedStatement(SQL_FROM_HERE,
"INSERT INTO pages (url, title, body) VALUES (?,?,?)"));
add_to_pages.BindString(0, url);
add_to_pages.BindString(1, title);
add_to_pages.BindString(2, contents);
if (!add_to_pages.Run())
return false;
int64 rowid = db_.GetLastInsertRowId();
sql::Statement add_to_info(db_.GetCachedStatement(SQL_FROM_HERE,
"INSERT INTO info (rowid, time) VALUES (?,?)"));
add_to_info.BindInt64(0, rowid);
add_to_info.BindInt64(1, time.ToInternalValue());
if (!add_to_info.Run())
return false;
return committer.Commit();
}
void TextDatabase::DeletePageData(base::Time time, const std::string& url) {
sql::Statement select_ids(db_.GetCachedStatement(SQL_FROM_HERE,
"SELECT info.rowid "
"FROM info JOIN pages ON info.rowid = pages.rowid "
"WHERE info.time=? AND pages.url=?"));
select_ids.BindInt64(0, time.ToInternalValue());
select_ids.BindString(1, url);
std::set<int64> rows_to_delete;
while (select_ids.Step())
rows_to_delete.insert(select_ids.ColumnInt64(0));
sql::Statement delete_page(db_.GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM pages WHERE rowid=?"));
for (std::set<int64>::const_iterator i = rows_to_delete.begin();
i != rows_to_delete.end(); ++i) {
delete_page.BindInt64(0, *i);
if (!delete_page.Run())
return;
delete_page.Reset(true);
}
sql::Statement delete_info(db_.GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM info WHERE rowid=?"));
for (std::set<int64>::const_iterator i = rows_to_delete.begin();
i != rows_to_delete.end(); ++i) {
delete_info.BindInt64(0, *i);
if (!delete_info.Run())
return;
delete_info.Reset(true);
}
}
void TextDatabase::Optimize() {
sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
"SELECT OPTIMIZE(pages) FROM pages LIMIT 1"));
statement.Run();
}
void TextDatabase::GetTextMatches(const std::string& query,
const QueryOptions& options,
std::vector<Match>* results,
URLSet* found_urls,
base::Time* first_time_searched) {
*first_time_searched = options.begin_time;
std::string sql = "SELECT url, title, time, offsets(pages), body FROM pages "
" LEFT OUTER JOIN info ON pages.rowid = info.rowid WHERE ";
sql += options.body_only ? "body " : "pages ";
sql += "MATCH ? AND time >= ? AND time < ? ORDER BY time DESC LIMIT ?";
sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, sql.c_str()));
int64 effective_begin_time = options.begin_time.is_null() ?
0 : options.begin_time.ToInternalValue();
int64 effective_end_time = options.end_time.is_null() ?
std::numeric_limits<int64>::max() : options.end_time.ToInternalValue();
int effective_max_count = options.max_count ?
options.max_count : std::numeric_limits<int>::max();
statement.BindString(0, query);
statement.BindInt64(1, effective_begin_time);
statement.BindInt64(2, effective_end_time);
statement.BindInt(3, effective_max_count);
while (statement.Step()) {
GURL url(statement.ColumnString(0));
URLSet::const_iterator found_url = found_urls->find(url);
if (found_url != found_urls->end())
continue;
results->resize(results->size() + 1);
Match& match = results->at(results->size() - 1);
match.url.Swap(&url);
match.title = statement.ColumnString16(1);
match.time = base::Time::FromInternalValue(statement.ColumnInt64(2));
std::string offsets_str = statement.ColumnString(3);
Snippet::ExtractMatchPositions(offsets_str, kTitleColumnIndex,
&match.title_match_positions);
Snippet::ConvertMatchPositionsToWide(statement.ColumnString(1),
&match.title_match_positions);
Snippet::MatchPositions match_positions;
Snippet::ExtractMatchPositions(offsets_str, kBodyColumnIndex,
&match_positions);
std::string body = statement.ColumnString(4);
match.snippet.ComputeSnippet(match_positions, body);
}
if (results->empty() ||
options.max_count == 0 ||
static_cast<int>(results->size()) < options.max_count) {
*first_time_searched = options.begin_time;
} else {
*first_time_searched = results->back().time;
}
statement.Reset(true);
}
}