blob: 167e77f54f9e0c80487107b1f47cbdc5a9ae12e9 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/segmentation_platform/internal/database/ukm_metrics_table.h"
#include <inttypes.h>
#include <cstdint>
#include "base/check_op.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "sql/database.h"
#include "sql/statement.h"
namespace segmentation_platform {
UkmMetricsTable::UkmMetricsTable(sql::Database* db) : db_(db) {
DETACH_FROM_SEQUENCE(sequence_checker_);
DCHECK(db_);
}
UkmMetricsTable::~UkmMetricsTable() = default;
bool UkmMetricsTable::InitTable() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
static constexpr char kCreateTableQuery[] =
// clang-format off
"CREATE TABLE IF NOT EXISTS metrics("
"id INTEGER PRIMARY KEY NOT NULL,"
"event_timestamp INTEGER NOT NULL,"
"ukm_source_id INTEGER NOT NULL,"
"url_id INTEGER NOT NULL,"
"event_id INTEGER NOT NULL,"
"event_hash TEXT NOT NULL,"
"metric_hash TEXT NOT NULL,"
"metric_value INTEGER NOT NULL)";
// clang-format on
if (!db_->Execute(kCreateTableQuery)) {
return false;
}
// Accelerates get entries for a range of event_timestamps.
if (!db_->Execute("CREATE INDEX IF NOT EXISTS event_timestamp_index ON "
"metrics(event_timestamp)")) {
return false;
}
// Accelerates update entries for a given ukm_source_id.
if (!db_->Execute("CREATE INDEX IF NOT EXISTS ukm_source_id_index ON "
"metrics(ukm_source_id)")) {
return false;
}
// Accelerates find metrics for a given url_id, join with URL table using
// url_id.
if (!db_->Execute("CREATE INDEX IF NOT EXISTS url_id_index ON "
"metrics(url_id)")) {
return false;
}
// Accelerates find value for a given event_hash and metric_hash.
if (!db_->Execute("CREATE INDEX IF NOT EXISTS event_hash_index ON "
"metrics(event_hash)")) {
return false;
}
// Other common queries using the metrics table:
// * Join metrics table with itself using event_id, to find metrics of the
// same event.
//
// Metric hashes should not be used to query on the database unless event hash
// is also used as filter. So, just having an index for event_hash is good
// enough. Event ID is also used only after filtering the table by the
// event_hash. So, event_id does not need an index.
return true;
}
bool UkmMetricsTable::AddUkmEvent(const UkmMetricsTable::MetricsRow& row) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!row.id);
// Verify visit row.
if (row.metric_hash.is_null() || row.event_hash.is_null()) {
return false;
}
if (row.event_timestamp.is_null() ||
row.event_timestamp < base::Time::Now() - kNumDaysToKeepUkm) {
return false;
}
static constexpr char kInsertQuery[] =
// clang-format off
"INSERT INTO metrics(event_timestamp,ukm_source_id,url_id,event_id,"
"event_hash,metric_hash,metric_value) "
"VALUES (?,?,?,?,?,?,?)";
// clang-format on
sql::Statement statement(
db_->GetCachedStatement(SQL_FROM_HERE, kInsertQuery));
statement.BindTime(0, row.event_timestamp);
statement.BindInt64(1, row.source_id);
statement.BindInt64(2, row.url_id.GetUnsafeValue());
statement.BindInt64(3, row.event_id.GetUnsafeValue());
uint64_t event_hash = row.event_hash.GetUnsafeValue();
statement.BindString(4, base::StringPrintf("%" PRIX64, event_hash));
uint64_t metric_hash = row.metric_hash.GetUnsafeValue();
statement.BindString(5, base::StringPrintf("%" PRIX64, metric_hash));
statement.BindInt64(6, row.metric_value);
return statement.Run();
}
std::string HashToHexString(uint64_t hash) {
return base::StringPrintf("%" PRIX64, hash);
}
bool UkmMetricsTable::UpdateUrlIdForSource(ukm::SourceId source_id,
UrlId url_id) {
DCHECK_NE(source_id, ukm::kInvalidSourceId);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
static constexpr char kUpdateUrlIdQuery[] =
"UPDATE metrics SET url_id = ? WHERE ukm_source_id = ?";
sql::Statement statement(
db_->GetCachedStatement(SQL_FROM_HERE, kUpdateUrlIdQuery));
statement.BindInt64(0, url_id.GetUnsafeValue());
statement.BindInt64(1, source_id);
return statement.Run();
}
bool UkmMetricsTable::DeleteEventsForUrls(const std::vector<UrlId>& urls) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
static constexpr char kDeleteForUrl[] =
"DELETE FROM metrics WHERE url_id = ?";
bool success = true;
sql::Statement statement(
db_->GetCachedStatement(SQL_FROM_HERE, kDeleteForUrl));
for (const UrlId& url_id : urls) {
statement.Reset(/*clear_bound_vars=*/true);
statement.BindInt64(0, url_id.GetUnsafeValue());
if (!statement.Run()) {
success = false;
}
}
return success;
}
std::vector<UrlId> UkmMetricsTable::DeleteEventsBeforeTimestamp(
base::Time time) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::vector<UrlId> url_list;
// Get a list of URL IDs of the metrics that will be removed.
static constexpr char kGetOldEntries[] =
"SELECT DISTINCT url_id FROM metrics WHERE event_timestamp<=? ORDER BY "
"url_id";
sql::Statement find_statement(
db_->GetCachedStatement(SQL_FROM_HERE, kGetOldEntries));
find_statement.BindTime(0, time);
while (find_statement.Step()) {
url_list.push_back(UrlId::FromUnsafeValue(find_statement.ColumnInt64(0)));
}
// Delete the metrics.
static constexpr char kDeleteoldEntries[] =
"DELETE FROM metrics WHERE event_timestamp<=?";
sql::Statement statement(
db_->GetCachedStatement(SQL_FROM_HERE, kDeleteoldEntries));
statement.BindTime(0, time);
if (!statement.Run()) {
return {};
}
// Find the list of URL IDs that are no longer needed in the URL table by
// checking if there are any other metrics referring to the removed URL IDs.
for (auto it = url_list.begin(); it != url_list.end();) {
if (HasEntriesWithUrl(*it)) {
it = url_list.erase(it);
} else {
it++;
}
}
return url_list;
}
bool UkmMetricsTable::HasEntriesWithUrl(UrlId url_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
static constexpr char kGetUrlQuery[] =
"SELECT 1 FROM metrics WHERE url_id=? LIMIT 1";
sql::Statement statement(
db_->GetCachedStatement(SQL_FROM_HERE, kGetUrlQuery));
statement.BindInt64(0, url_id.GetUnsafeValue());
return statement.Step();
}
} // namespace segmentation_platform