blob: 52dc20600a17fd0816b5bd8484ed22a0462038f0 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/interest_group/interest_group_storage.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <limits>
#include <memory>
#include <utility>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/format_macros.h"
#include "base/functional/bind.h"
#include "base/json/json_string_value_serializer.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/rand_util.h"
#include "base/sequence_checker.h"
#include "base/strings/escape.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/time/time.h"
#include "content/browser/interest_group/interest_group_ad.pb.h"
#include "content/browser/interest_group/interest_group_k_anonymity_manager.h"
#include "content/browser/interest_group/interest_group_update.h"
#include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "sql/database.h"
#include "sql/error_delegate_util.h"
#include "sql/meta_table.h"
#include "sql/recovery.h"
#include "sql/statement.h"
#include "sql/transaction.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/interest_group/interest_group.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/boringssl/src/include/openssl/curve25519.h"
#include "third_party/sqlite/sqlite3.h"
#include "url/origin.h"
namespace content {
namespace {
using auction_worklet::mojom::BiddingBrowserSignalsPtr;
using auction_worklet::mojom::PreviousWinPtr;
using SellerCapabilitiesType = blink::SellerCapabilitiesType;
const base::FilePath::CharType kDatabasePath[] =
FILE_PATH_LITERAL("InterestGroups");
// Version number of the database.
//
// Version 1 - 2021/03 - crrev.com/c/2757425
// Version 2 - 2021/08 - crrev.com/c/3097715
// Version 3 - 2021/09 - crrev.com/c/3165576
// Version 4 - 2021/10 - crrev.com/c/3172863
// Version 5 - 2021/10 - crrev.com/c/3067804
// Version 6 - 2021/12 - crrev.com/c/3330516
// Version 7 - 2022/03 - crrev.com/c/3517534
// Version 8 - 2022/06 - crrev.com/c/3696265
// Version 9 - 2022/07 - crrev.com/c/3780305
// Version 10 - 2022/08 - crrev.com/c/3818142
// Version 13 - 2023/01 - crrev.com/c/4167800
// Version 14 - 2023/08 - crrev.com/c/4739632
// Version 15 - 2023/08 - crrev.com/c/4808727
// Version 16 - 2023/08 - crrev.com/c/4822944
//
// Version 1 adds a table for interest groups.
// Version 2 adds a column for rate limiting interest group updates.
// Version 3 adds a field for ad components.
// Version 4 adds joining origin and url.
// Version 5 adds k-anonymity tables and fields.
// Version 6 adds WebAssembly helper url.
// Version 7 changes an index, adds interest group priority.
// Version 8 adds the execution_mode field to interest groups.
// Version 9 changes bid_history and join_history to daily counts.
// Version 10 changes k-anonymity table so it doesn't split by type.
// Version 11 adds priority vector support and time a group was joined.
// Version 12 adds seller capabilities fields.
// Version 13 adds ad size-related fields (ad_sizes & size_groups).
// Version 14 adds auction server request flags.
// Version 15 adds an additional bid key field.
// Version 16 changes the ads and ad component columns of the interest group
// table to protobuf format.
const int kCurrentVersionNumber = 16;
// Earliest version of the code which can use a |kCurrentVersionNumber|
// database without failing.
const int kCompatibleVersionNumber = 16;
// Latest version of the database that cannot be upgraded to
// |kCurrentVersionNumber| without razing the database.
const int kDeprecatedVersionNumber = 5;
std::string Serialize(base::ValueView value_view) {
std::string json_output;
JSONStringValueSerializer serializer(&json_output);
serializer.Serialize(value_view);
return json_output;
}
std::unique_ptr<base::Value> DeserializeValue(
const std::string& serialized_value) {
if (serialized_value.empty()) {
return {};
}
JSONStringValueDeserializer deserializer(serialized_value);
return deserializer.Deserialize(/*error_code=*/nullptr,
/*error_message=*/nullptr);
}
std::string Serialize(const url::Origin& origin) {
return origin.Serialize();
}
url::Origin DeserializeOrigin(const std::string& serialized_origin) {
return url::Origin::Create(GURL(serialized_origin));
}
std::string Serialize(const absl::optional<GURL>& url) {
if (!url) {
return std::string();
}
return url->spec();
}
absl::optional<GURL> DeserializeURL(const std::string& serialized_url) {
GURL result(serialized_url);
if (result.is_empty()) {
return absl::nullopt;
}
return result;
}
blink::InterestGroup::Ad FromInterestGroupAdValue(const base::Value::Dict& dict,
bool for_components) {
blink::InterestGroup::Ad result;
const std::string* maybe_url = dict.FindString("url");
if (maybe_url) {
result.render_url = GURL(*maybe_url);
}
const std::string* maybe_size_group = dict.FindString("size_group");
if (maybe_size_group) {
result.size_group = *maybe_size_group;
}
if (!for_components) {
const std::string* maybe_buyer_reporting_id =
dict.FindString("buyer_reporting_id");
if (maybe_buyer_reporting_id) {
result.buyer_reporting_id = *maybe_buyer_reporting_id;
}
const std::string* maybe_buyer_and_seller_reporting_id =
dict.FindString("buyer_and_seller_reporting_id");
if (maybe_buyer_and_seller_reporting_id) {
result.buyer_and_seller_reporting_id =
*maybe_buyer_and_seller_reporting_id;
}
const auto* maybe_allowed_reporting_origins =
dict.FindList("allowed_reporting_origins");
if (maybe_allowed_reporting_origins) {
std::vector<url::Origin> allowed_reporting_origins_vector;
for (const auto& origin : *maybe_allowed_reporting_origins) {
const std::string* origin_str = origin.GetIfString();
DCHECK(origin_str);
allowed_reporting_origins_vector.emplace_back(
DeserializeOrigin(*origin_str));
}
result.allowed_reporting_origins =
std::move(allowed_reporting_origins_vector);
}
}
const std::string* maybe_metadata = dict.FindString("metadata");
if (maybe_metadata) {
result.metadata = *maybe_metadata;
}
const std::string* maybe_ad_render_id = dict.FindString("ad_render_id");
if (maybe_ad_render_id) {
result.ad_render_id = *maybe_ad_render_id;
}
return result;
}
std::string Serialize(
const absl::optional<base::flat_map<std::string, double>>& flat_map) {
if (!flat_map) {
return std::string();
}
base::Value::Dict dict;
for (const auto& key_value_pair : *flat_map) {
dict.Set(key_value_pair.first, key_value_pair.second);
}
return Serialize(base::Value(std::move(dict)));
}
absl::optional<base::flat_map<std::string, double>> DeserializeStringDoubleMap(
const std::string& serialized_flat_map) {
std::unique_ptr<base::Value> flat_map_value =
DeserializeValue(serialized_flat_map);
if (!flat_map_value || !flat_map_value->is_dict()) {
return absl::nullopt;
}
// Extract all key/values pairs to a vector before writing to a flat_map,
// since flat_map insertion is O(n).
std::vector<std::pair<std::string, double>> pairs;
for (const auto pair : flat_map_value->GetDict()) {
if (!pair.second.is_double()) {
return absl::nullopt;
}
pairs.emplace_back(pair.first, pair.second.GetDouble());
}
return base::flat_map<std::string, double>(std::move(pairs));
}
AdProtos GetAdProtosFromAds(std::vector<blink::InterestGroup::Ad> ads) {
AdProtos ad_protos;
for (blink::InterestGroup::Ad ad : ads) {
AdProtos_AdProto* ad_proto = ad_protos.add_ads();
ad_proto->set_render_url(ad.render_url.spec());
if (ad.size_group.has_value()) {
ad_proto->set_size_group(*ad.size_group);
}
if (ad.metadata.has_value()) {
ad_proto->set_metadata(*ad.metadata);
}
if (ad.buyer_reporting_id.has_value()) {
ad_proto->set_buyer_reporting_id(*ad.buyer_reporting_id);
}
if (ad.buyer_and_seller_reporting_id.has_value()) {
ad_proto->set_buyer_and_seller_reporting_id(
*ad.buyer_and_seller_reporting_id);
}
if (ad.ad_render_id.has_value()) {
ad_proto->set_ad_render_id(*ad.ad_render_id);
}
if (ad.allowed_reporting_origins.has_value()) {
for (auto allowed_reporting_origin : *ad.allowed_reporting_origins) {
ad_proto->add_allowed_reporting_origins(
allowed_reporting_origin.Serialize());
}
}
}
return ad_protos;
}
std::string Serialize(
const absl::optional<std::vector<blink::InterestGroup::Ad>>& ads) {
std::string serialized_ads;
AdProtos ad_protos =
ads.has_value() ? GetAdProtosFromAds(ads.value()) : AdProtos();
ad_protos.SerializeToString(&serialized_ads);
return serialized_ads;
}
absl::optional<std::vector<blink::InterestGroup::Ad>>
DeserializeInterestGroupAdVectorJson(const std::string& serialized_ads,
bool for_components) {
std::unique_ptr<base::Value> ads_value = DeserializeValue(serialized_ads);
if (!ads_value || !ads_value->is_list()) {
return absl::nullopt;
}
std::vector<blink::InterestGroup::Ad> result;
for (const auto& ad_value : ads_value->GetList()) {
const base::Value::Dict* dict = ad_value.GetIfDict();
if (dict) {
result.emplace_back(FromInterestGroupAdValue(*dict, for_components));
}
}
return result;
}
absl::optional<std::vector<blink::InterestGroup::Ad>>
DeserializeInterestGroupAdVectorProto(const std::string& serialized_ads) {
AdProtos ad_protos;
bool success = ad_protos.ParseFromString(serialized_ads);
if (not success || ad_protos.ads().empty()) {
return absl::nullopt;
}
std::vector<blink::InterestGroup::Ad> out;
for (const auto& ad_proto : ad_protos.ads()) {
blink::InterestGroup::Ad ad;
ad.render_url = GURL(ad_proto.render_url());
if (ad_proto.has_size_group()) {
ad.size_group = ad_proto.size_group();
}
if (ad_proto.has_metadata()) {
ad.metadata = ad_proto.metadata();
}
if (ad_proto.has_buyer_reporting_id()) {
ad.buyer_reporting_id = ad_proto.buyer_reporting_id();
}
if (ad_proto.has_buyer_and_seller_reporting_id()) {
ad.buyer_and_seller_reporting_id =
ad_proto.buyer_and_seller_reporting_id();
}
if (ad_proto.has_ad_render_id()) {
ad.ad_render_id = ad_proto.ad_render_id();
}
if (!ad_proto.allowed_reporting_origins().empty()) {
std::vector<url::Origin> allowed_reporting_origins_vector;
for (std::string allowed_reporting_origin :
ad_proto.allowed_reporting_origins()) {
allowed_reporting_origins_vector.emplace_back(
DeserializeOrigin(allowed_reporting_origin));
}
ad.allowed_reporting_origins =
std::move(allowed_reporting_origins_vector);
}
out.push_back(ad);
}
return out;
}
std::string Serialize(
const absl::optional<base::flat_map<std::string, blink::AdSize>>&
ad_sizes) {
if (!ad_sizes) {
return std::string();
}
base::Value::Dict dict;
for (const auto& key_value_pair : *ad_sizes) {
base::Value::Dict size_dict;
size_dict.Set("width", key_value_pair.second.width);
size_dict.Set("width_units",
static_cast<int>(key_value_pair.second.width_units));
size_dict.Set("height", key_value_pair.second.height);
size_dict.Set("height_units",
static_cast<int>(key_value_pair.second.height_units));
dict.Set(key_value_pair.first,
Serialize(base::Value(std::move(size_dict))));
}
return Serialize(base::Value(std::move(dict)));
}
absl::optional<base::flat_map<std::string, blink::AdSize>>
DeserializeStringSizeMap(const std::string& serialized_sizes) {
std::unique_ptr<base::Value> dict = DeserializeValue(serialized_sizes);
if (!dict || !dict->is_dict()) {
return absl::nullopt;
}
std::vector<std::pair<std::string, blink::AdSize>> result;
for (std::pair<const std::string&, base::Value&> entry : dict->GetDict()) {
std::unique_ptr<base::Value> ads_size =
DeserializeValue(entry.second.GetString());
const base::Value::Dict* size_dict = ads_size->GetIfDict();
DCHECK(size_dict);
const base::Value* width_val = size_dict->Find("width");
const base::Value* width_units_val = size_dict->Find("width_units");
const base::Value* height_val = size_dict->Find("height");
const base::Value* height_units_val = size_dict->Find("height_units");
if (!width_val || !width_units_val || !height_val || !height_units_val) {
return absl::nullopt;
}
result.emplace_back(entry.first,
blink::AdSize(width_val->GetDouble(),
static_cast<blink::AdSize::LengthUnit>(
width_units_val->GetInt()),
height_val->GetDouble(),
static_cast<blink::AdSize::LengthUnit>(
height_units_val->GetInt())));
}
return result;
}
std::string Serialize(
const absl::optional<base::flat_map<std::string, std::vector<std::string>>>&
size_groups) {
if (!size_groups) {
return std::string();
}
base::Value::Dict dict;
for (const auto& key_value_pair : *size_groups) {
base::Value::List list;
for (const auto& s : key_value_pair.second) {
list.Append(s);
}
dict.Set(key_value_pair.first, Serialize(list));
}
return Serialize(base::Value(std::move(dict)));
}
absl::optional<base::flat_map<std::string, std::vector<std::string>>>
DeserializeStringStringVectorMap(const std::string& serialized_groups) {
std::unique_ptr<base::Value> dict = DeserializeValue(serialized_groups);
if (!dict || !dict->is_dict()) {
return absl::nullopt;
}
std::vector<std::pair<std::string, std::vector<std::string>>> result;
for (std::pair<const std::string&, base::Value&> entry : dict->GetDict()) {
std::unique_ptr<base::Value> list =
DeserializeValue(entry.second.GetString());
DCHECK(list && list->is_list());
std::vector<std::string> result_sizes;
for (base::Value& size : list->GetList()) {
result_sizes.emplace_back(size.GetString());
}
result.emplace_back(entry.first, result_sizes);
}
return result;
}
std::string Serialize(const absl::optional<std::vector<std::string>>& strings) {
if (!strings) {
return std::string();
}
base::Value::List list;
for (const auto& s : strings.value()) {
list.Append(s);
}
return Serialize(list);
}
absl::optional<std::vector<std::string>> DeserializeStringVector(
const std::string& serialized_vector) {
std::unique_ptr<base::Value> list = DeserializeValue(serialized_vector);
if (!list || !list->is_list()) {
return absl::nullopt;
}
std::vector<std::string> result;
for (const auto& value : list->GetList()) {
result.push_back(value.GetString());
}
return result;
}
int64_t Serialize(SellerCapabilitiesType capabilities) {
uint64_t result = capabilities.ToEnumBitmask();
// Supporting 64 or more seller capabilities will require a different
// serialization format.
DCHECK(result <= std::numeric_limits<int64_t>::max());
return static_cast<int64_t>(result);
}
SellerCapabilitiesType DeserializeSellerCapabilities(int64_t serialized) {
DCHECK(serialized >= 0);
return SellerCapabilitiesType::FromEnumBitmask(serialized);
}
std::string Serialize(
const absl::optional<base::flat_map<url::Origin, SellerCapabilitiesType>>&
flat_map) {
if (!flat_map) {
return std::string();
}
base::Value::Dict dict;
for (const auto& key_value_pair : *flat_map) {
dict.Set(Serialize(key_value_pair.first),
base::NumberToString(Serialize(key_value_pair.second)));
}
return Serialize(base::Value(std::move(dict)));
}
absl::optional<base::flat_map<url::Origin, SellerCapabilitiesType>>
DeserializeSellerCapabilitiesMap(const std::string& serialized) {
std::unique_ptr<base::Value> dict = DeserializeValue(serialized);
if (!dict || !dict->is_dict()) {
return absl::nullopt;
}
std::vector<std::pair<url::Origin, SellerCapabilitiesType>> result;
for (std::pair<const std::string&, base::Value&> entry : dict->GetDict()) {
std::string* value_string = entry.second.GetIfString();
if (!value_string) {
return absl::nullopt;
}
int64_t value_bitmask;
if (!base::StringToInt64(*value_string, &value_bitmask)) {
return absl::nullopt;
}
result.emplace_back(DeserializeOrigin(entry.first),
DeserializeSellerCapabilities(value_bitmask));
}
return result;
}
int64_t Serialize(blink::AuctionServerRequestFlags flags) {
// Supporting 64 or more auction server request flags will require a different
// serialization format. That check is done in EnumSet at compile time, so we
// don't need to duplicate it here.
return flags.ToEnumBitmask();
}
blink::AuctionServerRequestFlags DeserializeAuctionServerRequestFlags(
int64_t serialized) {
return blink::AuctionServerRequestFlags::FromEnumBitmask(serialized);
}
std::vector<uint8_t> Serialize(
absl::optional<blink::InterestGroup::AdditionalBidKey> key) {
if (!key || key->empty()) {
return std::vector<uint8_t>();
}
return std::vector<uint8_t>(key->begin(), key->end());
}
absl::optional<blink::InterestGroup::AdditionalBidKey>
DeserializeAdditionalBidKey(const base::span<const uint8_t>& serialized) {
if (serialized.size() != ED25519_PUBLIC_KEY_LEN) {
return absl::nullopt;
}
blink::InterestGroup::AdditionalBidKey deserialized;
std::copy(serialized.begin(), serialized.end(), deserialized.begin());
return deserialized;
}
StorageInterestGroup::KAnonymityData DefaultKAnonymityData(
const std::string& key) {
return {key, /*is_k_anonymous=*/false, /*last_updated=*/base::Time::Min()};
}
// Merges new `priority_signals_overrides` received from an update with an
// existing set of overrides store with an interest group. Populates `overrides`
// if it was previously null.
void MergePrioritySignalsOverrides(
const base::flat_map<std::string, absl::optional<double>>& update_data,
absl::optional<base::flat_map<std::string, double>>&
priority_signals_overrides) {
if (!priority_signals_overrides) {
priority_signals_overrides.emplace();
}
for (const auto& pair : update_data) {
if (!pair.second.has_value()) {
priority_signals_overrides->erase(pair.first);
continue;
}
priority_signals_overrides->insert_or_assign(pair.first, *pair.second);
}
}
// Same as above, but takes a map with PrioritySignalsDoublePtrs instead of
// absl::optional<double>s. This isn't much more code than it takes to convert
// the flat_map of PrioritySignalsDoublePtr to one of optionals, so just
// duplicate the logic.
void MergePrioritySignalsOverrides(
const base::flat_map<std::string,
auction_worklet::mojom::PrioritySignalsDoublePtr>&
update_data,
absl::optional<base::flat_map<std::string, double>>&
priority_signals_overrides) {
if (!priority_signals_overrides) {
priority_signals_overrides.emplace();
}
for (const auto& pair : update_data) {
if (!pair.second) {
priority_signals_overrides->erase(pair.first);
continue;
}
priority_signals_overrides->insert_or_assign(pair.first,
pair.second->value);
}
}
// Adds indices to the `interest_group` table. Called after the table has been
// created.
bool CreateInterestGroupIndices(sql::Database& db) {
// Index on group expiration. Owner and Name are only here to speed up
// queries that don't need the full group.
DCHECK(!db.DoesIndexExist("interest_group_expiration"));
static const char kInterestGroupExpirationIndexSql[] =
// clang-format off
"CREATE INDEX interest_group_expiration"
" ON interest_groups(expiration DESC, owner, name)";
// clang-format on
if (!db.Execute(kInterestGroupExpirationIndexSql)) {
return false;
}
// Index on group expiration by owner.
DCHECK(!db.DoesIndexExist("interest_group_owner"));
static const char kInterestGroupOwnerIndexSql[] =
// clang-format off
"CREATE INDEX interest_group_owner"
" ON interest_groups(owner,expiration DESC,next_update_after ASC,name)";
// clang-format on
if (!db.Execute(kInterestGroupOwnerIndexSql)) {
return false;
}
// Index on group expiration by joining origin. Owner and Name are only here
// to speed up queries that don't need the full group.
DCHECK(!db.DoesIndexExist("interest_group_joining_origin"));
static const char kInterestGroupJoiningOriginIndexSql[] =
// clang-format off
"CREATE INDEX interest_group_joining_origin"
" ON interest_groups(joining_origin, expiration DESC, owner, name)";
// clang-format on
if (!db.Execute(kInterestGroupJoiningOriginIndexSql)) {
return false;
}
return true;
}
// Initializes the tables, returning true on success.
// The tables cannot exist when calling this function.
bool CreateV16Schema(sql::Database& db) {
// IMPORTANT: If you add or remove fields, you need to update
// `ClearExcessiveStorage()` to consider the size of added/removed fields for
// storage usage calculations.
DCHECK(!db.DoesTableExist("interest_groups"));
static const char kInterestGroupTableSql[] =
// clang-format off
"CREATE TABLE interest_groups("
"expiration INTEGER NOT NULL,"
"last_updated INTEGER NOT NULL,"
"next_update_after INTEGER NOT NULL,"
"owner TEXT NOT NULL,"
"joining_origin TEXT NOT NULL,"
// Most recent time the interest group was joined. Called
// `exact_join_time` to differentiate it from `join_time` in the join
// history table, which is a day, and isn't looked up on InterestGroup
// load.
"exact_join_time INTEGER NOT NULL,"
"name TEXT NOT NULL,"
"priority DOUBLE NOT NULL,"
"enable_bidding_signals_prioritization INTEGER NOT NULL,"
"priority_vector TEXT NOT NULL,"
"priority_signals_overrides TEXT NOT NULL,"
"seller_capabilities TEXT NOT NULL,"
"all_sellers_capabilities INTEGER NOT NULL,"
"execution_mode INTEGER NOT NULL,"
"joining_url TEXT NOT NULL,"
"bidding_url TEXT NOT NULL,"
"bidding_wasm_helper_url TEXT NOT NULL,"
"update_url TEXT NOT NULL,"
"trusted_bidding_signals_url TEXT NOT NULL,"
"trusted_bidding_signals_keys TEXT NOT NULL,"
"user_bidding_signals TEXT,"
"ads_pb BLOB NOT NULL,"
"ad_components_pb BLOB NOT NULL,"
"ad_sizes TEXT NOT NULL,"
"size_groups TEXT NOT NULL,"
"auction_server_request_flags INTEGER NOT NULL,"
"additional_bid_key BLOB NOT NULL,"
"PRIMARY KEY(owner,name))";
// clang-format on
if (!db.Execute(kInterestGroupTableSql)) {
return false;
}
if (!CreateInterestGroupIndices(db)) {
return false;
}
DCHECK(!db.DoesTableExist("k_anon"));
static const char kInterestGroupKAnonTableSql[] =
// clang-format off
"CREATE TABLE k_anon("
"last_referenced_time INTEGER NOT NULL,"
"key TEXT NOT NULL,"
"is_k_anon INTEGER NOT NULL,"
"last_k_anon_updated_time INTEGER NOT NULL,"
"last_reported_to_anon_server_time INTEGER NOT NULL,"
"PRIMARY KEY(key))";
// clang-format on
if (!db.Execute(kInterestGroupKAnonTableSql)) {
return false;
}
// Index on kanon last_referenced_time.
DCHECK(!db.DoesIndexExist("k_anon_last_referenced_time"));
static const char kInterestGroupKAnonLastRefIndexSql[] =
// clang-format off
"CREATE INDEX k_anon_last_referenced_time"
" ON k_anon(last_referenced_time DESC)";
// clang-format on
if (!db.Execute(kInterestGroupKAnonLastRefIndexSql)) {
return false;
}
DCHECK(!db.DoesTableExist("join_history"));
static const char kJoinHistoryTableSql[] =
// clang-format off
"CREATE TABLE join_history("
"owner TEXT NOT NULL,"
"name TEXT NOT NULL,"
"join_time INTEGER NOT NULL,"
"count INTEGER NOT NULL,"
"PRIMARY KEY(owner, name, join_time) "
"FOREIGN KEY(owner,name) REFERENCES interest_groups)";
// clang-format on
if (!db.Execute(kJoinHistoryTableSql)) {
return false;
}
DCHECK(!db.DoesTableExist("bid_history"));
static const char kBidHistoryTableSql[] =
// clang-format off
"CREATE TABLE bid_history("
"owner TEXT NOT NULL,"
"name TEXT NOT NULL,"
"bid_time INTEGER NOT NULL,"
"count INTEGER NOT NULL,"
"PRIMARY KEY(owner, name, bid_time) "
"FOREIGN KEY(owner,name) REFERENCES interest_groups)";
// clang-format on
if (!db.Execute(kBidHistoryTableSql)) {
return false;
}
// We can't use the interest group and win time as primary keys since
// auctions on separate pages may occur at the same time.
DCHECK(!db.DoesTableExist("win_history"));
static const char kWinHistoryTableSQL[] =
// clang-format off
"CREATE TABLE win_history("
"owner TEXT NOT NULL,"
"name TEXT NOT NULL,"
"win_time INTEGER NOT NULL,"
"ad TEXT NOT NULL,"
"FOREIGN KEY(owner,name) REFERENCES interest_groups)";
// clang-format on
if (!db.Execute(kWinHistoryTableSQL)) {
return false;
}
DCHECK(!db.DoesIndexExist("win_history_index"));
static const char kWinHistoryIndexSQL[] =
// clang-format off
"CREATE INDEX win_history_index "
"ON win_history(owner,name,win_time DESC)";
// clang-format on
if (!db.Execute(kWinHistoryIndexSQL)) {
return false;
}
return true;
}
bool UpgradeV15SchemaToV16(sql::Database& db, sql::MetaTable& meta_table) {
static const char kInterestGroupTableSql[] =
// clang-format off
"CREATE TABLE new_interest_groups("
"expiration INTEGER NOT NULL,"
"last_updated INTEGER NOT NULL,"
"next_update_after INTEGER NOT NULL,"
"owner TEXT NOT NULL,"
"joining_origin TEXT NOT NULL,"
"exact_join_time INTEGER NOT NULL,"
"name TEXT NOT NULL,"
"priority DOUBLE NOT NULL,"
"enable_bidding_signals_prioritization INTEGER NOT NULL,"
"priority_vector TEXT NOT NULL,"
"priority_signals_overrides TEXT NOT NULL,"
"seller_capabilities TEXT NOT NULL,"
"all_sellers_capabilities INTEGER NOT NULL,"
"execution_mode INTEGER NOT NULL,"
"joining_url TEXT NOT NULL,"
"bidding_url TEXT NOT NULL,"
"bidding_wasm_helper_url TEXT NOT NULL,"
"update_url TEXT NOT NULL,"
"trusted_bidding_signals_url TEXT NOT NULL,"
"trusted_bidding_signals_keys TEXT NOT NULL,"
"user_bidding_signals TEXT,"
"ads_pb BLOB NOT NULL,"
"ad_components_pb BLOB NOT NULL,"
"ad_sizes TEXT NOT NULL,"
"size_groups TEXT NOT NULL,"
"auction_server_request_flags INTEGER NOT NULL,"
"additional_bid_key BLOB NOT NULL,"
"PRIMARY KEY(owner,name))";
// clang-format on
if (!db.Execute(kInterestGroupTableSql)) {
return false;
}
// clang-format off
sql::Statement kCopyInterestGroupTableWithEmptyAdPBsSql(
db.GetCachedStatement(SQL_FROM_HERE,
"INSERT INTO new_interest_groups "
"SELECT expiration,"
"last_updated,"
"next_update_after,"
"owner,"
"joining_origin,"
"exact_join_time,"
"name,"
"priority,"
"enable_bidding_signals_prioritization,"
"priority_vector,"
"priority_signals_overrides,"
"seller_capabilities,"
"all_sellers_capabilities,"
"execution_mode,"
"joining_url,"
"bidding_url,"
"bidding_wasm_helper_url,"
"update_url,"
"trusted_bidding_signals_url,"
"trusted_bidding_signals_keys,"
"user_bidding_signals,"
"?," // ads_pb
"?," // ad_components_pb
"ad_sizes,"
"size_groups,"
"auction_server_request_flags,"
"additional_bid_key "
"FROM interest_groups"));
// clang-format on
std::string empty_ad_proto_value;
AdProtos().SerializeToString(&empty_ad_proto_value);
kCopyInterestGroupTableWithEmptyAdPBsSql.BindBlob(0, empty_ad_proto_value);
kCopyInterestGroupTableWithEmptyAdPBsSql.BindBlob(1, empty_ad_proto_value);
if (!kCopyInterestGroupTableWithEmptyAdPBsSql.Run()) {
return false;
}
sql::Statement kSelectIGsWithAds(db.GetCachedStatement(
SQL_FROM_HERE,
"SELECT owner, name, ads, ad_components from interest_groups"));
while (kSelectIGsWithAds.Step()) {
std::string owner = kSelectIGsWithAds.ColumnString(0);
std::string name = kSelectIGsWithAds.ColumnString(1);
absl::optional<std::vector<blink::InterestGroup::Ad>> ads =
DeserializeInterestGroupAdVectorJson(kSelectIGsWithAds.ColumnString(2),
/*for_components=*/false);
absl::optional<std::vector<blink::InterestGroup::Ad>> ad_components =
DeserializeInterestGroupAdVectorJson(kSelectIGsWithAds.ColumnString(3),
/*for_components=*/true);
std::string serialized_ads = Serialize(ads);
std::string serialized_ad_components = Serialize(ad_components);
sql::Statement insert_value_into_IG(db.GetCachedStatement(
SQL_FROM_HERE,
"UPDATE new_interest_groups SET ads_pb = ?, ad_components_pb = ? "
"WHERE owner = ? AND name = ?"));
insert_value_into_IG.BindBlob(0, serialized_ads);
insert_value_into_IG.BindBlob(1, serialized_ad_components);
insert_value_into_IG.BindString(2, owner);
insert_value_into_IG.BindString(3, name);
if (!insert_value_into_IG.Run()) {
return false;
}
}
static const char kDropInterestGroupTableSql[] = "DROP TABLE interest_groups";
if (!db.Execute(kDropInterestGroupTableSql)) {
return false;
}
static const char kRenameInterestGroupTableSql[] =
// clang-format off
"ALTER TABLE new_interest_groups "
"RENAME TO interest_groups";
// clang-format on
if (!db.Execute(kRenameInterestGroupTableSql)) {
return false;
}
return CreateInterestGroupIndices(db);
}
bool UpgradeV14SchemaToV15(sql::Database& db, sql::MetaTable& meta_table) {
static const char kInterestGroupTableSql[] =
// clang-format off
"CREATE TABLE new_interest_groups("
"expiration INTEGER NOT NULL,"
"last_updated INTEGER NOT NULL,"
"next_update_after INTEGER NOT NULL,"
"owner TEXT NOT NULL,"
"joining_origin TEXT NOT NULL,"
"exact_join_time INTEGER NOT NULL,"
"name TEXT NOT NULL,"
"priority DOUBLE NOT NULL,"
"enable_bidding_signals_prioritization INTEGER NOT NULL,"
"priority_vector TEXT NOT NULL,"
"priority_signals_overrides TEXT NOT NULL,"
"seller_capabilities TEXT NOT NULL,"
"all_sellers_capabilities INTEGER NOT NULL,"
"execution_mode INTEGER NOT NULL,"
"joining_url TEXT NOT NULL,"
"bidding_url TEXT NOT NULL,"
"bidding_wasm_helper_url TEXT NOT NULL,"
"update_url TEXT NOT NULL,"
"trusted_bidding_signals_url TEXT NOT NULL,"
"trusted_bidding_signals_keys TEXT NOT NULL,"
"user_bidding_signals TEXT,"
"ads TEXT NOT NULL,"
"ad_components TEXT NOT NULL,"
"ad_sizes TEXT NOT NULL,"
"size_groups TEXT NOT NULL,"
"auction_server_request_flags INTEGER NOT NULL,"
"additional_bid_key BLOB NOT NULL,"
"PRIMARY KEY(owner,name))";
// clang-format on
if (!db.Execute(kInterestGroupTableSql)) {
return false;
}
static const char kCopyInterestGroupTableSql[] =
// clang-format off
"INSERT INTO new_interest_groups "
"SELECT expiration,"
"last_updated,"
"next_update_after,"
"owner,"
"joining_origin,"
"exact_join_time,"
"name,"
"priority,"
"enable_bidding_signals_prioritization,"
"priority_vector,"
"priority_signals_overrides,"
"seller_capabilities,"
"all_sellers_capabilities,"
"execution_mode,"
"joining_url,"
"bidding_url,"
"bidding_wasm_helper_url,"
"update_url,"
"trusted_bidding_signals_url,"
"trusted_bidding_signals_keys,"
"user_bidding_signals,"
"ads,"
"ad_components,"
"ad_sizes,"
"size_groups,"
"auction_server_request_flags, "
"X'' " // additional_bid_key
"FROM interest_groups";
// clang-format on
if (!db.Execute(kCopyInterestGroupTableSql)) {
return false;
}
static const char kDropInterestGroupTableSql[] = "DROP TABLE interest_groups";
if (!db.Execute(kDropInterestGroupTableSql)) {
return false;
}
static const char kRenameInterestGroupTableSql[] =
// clang-format off
"ALTER TABLE new_interest_groups "
"RENAME TO interest_groups";
// clang-format on
if (!db.Execute(kRenameInterestGroupTableSql)) {
return false;
}
return CreateInterestGroupIndices(db);
}
bool UpgradeV13SchemaToV14(sql::Database& db, sql::MetaTable& meta_table) {
static const char kInterestGroupTableSql[] =
// clang-format off
"CREATE TABLE new_interest_groups("
"expiration INTEGER NOT NULL,"
"last_updated INTEGER NOT NULL,"
"next_update_after INTEGER NOT NULL,"
"owner TEXT NOT NULL,"
"joining_origin TEXT NOT NULL,"
"exact_join_time INTEGER NOT NULL,"
"name TEXT NOT NULL,"
"priority DOUBLE NOT NULL,"
"enable_bidding_signals_prioritization INTEGER NOT NULL,"
"priority_vector TEXT NOT NULL,"
"priority_signals_overrides TEXT NOT NULL,"
"seller_capabilities TEXT NOT NULL,"
"all_sellers_capabilities INTEGER NOT NULL,"
"execution_mode INTEGER NOT NULL,"
"joining_url TEXT NOT NULL,"
"bidding_url TEXT NOT NULL,"
"bidding_wasm_helper_url TEXT NOT NULL,"
"update_url TEXT NOT NULL,"
"trusted_bidding_signals_url TEXT NOT NULL,"
"trusted_bidding_signals_keys TEXT NOT NULL,"
"user_bidding_signals TEXT,"
"ads TEXT NOT NULL,"
"ad_components TEXT NOT NULL,"
"ad_sizes TEXT NOT NULL,"
"size_groups TEXT NOT NULL,"
"auction_server_request_flags INTEGER NOT NULL,"
"PRIMARY KEY(owner,name))";
// clang-format on
if (!db.Execute(kInterestGroupTableSql)) {
return false;
}
static const char kCopyInterestGroupTableSql[] =
// clang-format off
"INSERT INTO new_interest_groups "
"SELECT expiration,"
"last_updated,"
"next_update_after,"
"owner,"
"joining_origin,"
"exact_join_time,"
"name,"
"priority,"
"enable_bidding_signals_prioritization,"
"priority_vector,"
"priority_signals_overrides,"
"seller_capabilities,"
"all_sellers_capabilities,"
"execution_mode,"
"joining_url,"
"bidding_url,"
"bidding_wasm_helper_url,"
"update_url,"
"trusted_bidding_signals_url,"
"trusted_bidding_signals_keys,"
"user_bidding_signals,"
"ads,"
"ad_components,"
"ad_sizes,"
"size_groups,"
"0 " // auction_server_request_flags
"FROM interest_groups";
// clang-format on
if (!db.Execute(kCopyInterestGroupTableSql)) {
return false;
}
static const char kDropInterestGroupTableSql[] = "DROP TABLE interest_groups";
if (!db.Execute(kDropInterestGroupTableSql)) {
return false;
}
static const char kRenameInterestGroupTableSql[] =
// clang-format off
"ALTER TABLE new_interest_groups "
"RENAME TO interest_groups";
// clang-format on
if (!db.Execute(kRenameInterestGroupTableSql)) {
return false;
}
return CreateInterestGroupIndices(db);
}
bool UpgradeV12SchemaToV13(sql::Database& db, sql::MetaTable& meta_table) {
static const char kInterestGroupTableSql[] =
// clang-format off
"CREATE TABLE new_interest_groups("
"expiration INTEGER NOT NULL,"
"last_updated INTEGER NOT NULL,"
"next_update_after INTEGER NOT NULL,"
"owner TEXT NOT NULL,"
"joining_origin TEXT NOT NULL,"
"exact_join_time INTEGER NOT NULL,"
"name TEXT NOT NULL,"
"priority DOUBLE NOT NULL,"
"enable_bidding_signals_prioritization INTEGER NOT NULL,"
"priority_vector TEXT NOT NULL,"
"priority_signals_overrides TEXT NOT NULL,"
"seller_capabilities TEXT NOT NULL,"
"all_sellers_capabilities INTEGER NOT NULL,"
"execution_mode INTEGER NOT NULL,"
"joining_url TEXT NOT NULL,"
"bidding_url TEXT NOT NULL,"
"bidding_wasm_helper_url TEXT NOT NULL,"
"update_url TEXT NOT NULL,"
"trusted_bidding_signals_url TEXT NOT NULL,"
"trusted_bidding_signals_keys TEXT NOT NULL,"
"user_bidding_signals TEXT,"
"ads TEXT NOT NULL,"
"ad_components TEXT NOT NULL,"
"ad_sizes TEXT NOT NULL,"
"size_groups TEXT NOT NULL,"
"PRIMARY KEY(owner,name))";
// clang-format on
if (!db.Execute(kInterestGroupTableSql)) {
return false;
}
static const char kCopyInterestGroupTableSql[] =
// clang-format off
"INSERT INTO new_interest_groups "
"SELECT expiration,"
"last_updated,"
"next_update_after,"
"owner,"
"joining_origin,"
"exact_join_time,"
"name,"
"priority,"
"enable_bidding_signals_prioritization,"
"priority_vector,"
"priority_signals_overrides,"
"seller_capabilities,"
"all_sellers_capabilities,"
"execution_mode,"
"joining_url,"
"bidding_url,"
"bidding_wasm_helper_url,"
"update_url,"
"trusted_bidding_signals_url,"
"trusted_bidding_signals_keys,"
"user_bidding_signals,"
"ads,"
"ad_components,"
"''," // ad_sizes
"''" // size_groups
"FROM interest_groups";
// clang-format on
if (!db.Execute(kCopyInterestGroupTableSql)) {
return false;
}
static const char kDropInterestGroupTableSql[] = "DROP TABLE interest_groups";
if (!db.Execute(kDropInterestGroupTableSql)) {
return false;
}
static const char kRenameInterestGroupTableSql[] =
// clang-format off
"ALTER TABLE new_interest_groups "
"RENAME TO interest_groups";
// clang-format on
if (!db.Execute(kRenameInterestGroupTableSql)) {
return false;
}
return CreateInterestGroupIndices(db);
}
bool UpgradeV11SchemaToV12(sql::Database& db, sql::MetaTable& meta_table) {
static const char kInterestGroupTableSql[] =
// clang-format off
"CREATE TABLE new_interest_groups("
"expiration INTEGER NOT NULL,"
"last_updated INTEGER NOT NULL,"
"next_update_after INTEGER NOT NULL,"
"owner TEXT NOT NULL,"
"joining_origin TEXT NOT NULL,"
"exact_join_time INTEGER NOT NULL,"
"name TEXT NOT NULL,"
"priority DOUBLE NOT NULL,"
"enable_bidding_signals_prioritization INTEGER NOT NULL,"
"priority_vector TEXT NOT NULL,"
"priority_signals_overrides TEXT NOT NULL,"
"seller_capabilities TEXT NOT NULL,"
"all_sellers_capabilities INTEGER NOT NULL,"
"execution_mode INTEGER NOT NULL,"
"joining_url TEXT NOT NULL,"
"bidding_url TEXT NOT NULL,"
"bidding_wasm_helper_url TEXT NOT NULL,"
"update_url TEXT NOT NULL,"
"trusted_bidding_signals_url TEXT NOT NULL,"
"trusted_bidding_signals_keys TEXT NOT NULL,"
"user_bidding_signals TEXT,"
"ads TEXT NOT NULL,"
"ad_components TEXT NOT NULL,"
"PRIMARY KEY(owner,name))";
// clang-format on
if (!db.Execute(kInterestGroupTableSql)) {
return false;
}
static const char kCopyInterestGroupTableSql[] =
// clang-format off
"INSERT INTO new_interest_groups "
"SELECT expiration,"
"last_updated,"
"next_update_after,"
"owner,"
"joining_origin,"
"exact_join_time,"
"name,"
"priority,"
"enable_bidding_signals_prioritization,"
"priority_vector,"
"priority_signals_overrides,"
"''," // seller_capabilities
"0," // all_sellers_capabilities
"execution_mode,"
"joining_url,"
"bidding_url,"
"bidding_wasm_helper_url,"
"update_url,"
"trusted_bidding_signals_url,"
"trusted_bidding_signals_keys,"
"user_bidding_signals,"
"ads,"
"ad_components "
"FROM interest_groups";
// clang-format on
if (!db.Execute(kCopyInterestGroupTableSql)) {
return false;
}
static const char kDropInterestGroupTableSql[] = "DROP TABLE interest_groups";
if (!db.Execute(kDropInterestGroupTableSql)) {
return false;
}
static const char kRenameInterestGroupTableSql[] =
// clang-format off
"ALTER TABLE new_interest_groups "
"RENAME TO interest_groups";
// clang-format on
if (!db.Execute(kRenameInterestGroupTableSql)) {
return false;
}
return CreateInterestGroupIndices(db);
}
bool UpgradeV10SchemaToV11(sql::Database& db, sql::MetaTable& meta_table) {
static const char kInterestGroupTableSql[] =
// clang-format off
"CREATE TABLE new_interest_groups("
"expiration INTEGER NOT NULL,"
"last_updated INTEGER NOT NULL,"
"next_update_after INTEGER NOT NULL,"
"owner TEXT NOT NULL,"
"joining_origin TEXT NOT NULL,"
"exact_join_time INTEGER NOT NULL,"
"name TEXT NOT NULL,"
"priority DOUBLE NOT NULL,"
"enable_bidding_signals_prioritization INTEGER NOT NULL,"
"priority_vector TEXT NOT NULL,"
"priority_signals_overrides TEXT NOT NULL,"
"execution_mode INTEGER NOT NULL,"
"joining_url TEXT NOT NULL,"
"bidding_url TEXT NOT NULL,"
"bidding_wasm_helper_url TEXT NOT NULL,"
"update_url TEXT NOT NULL,"
"trusted_bidding_signals_url TEXT NOT NULL,"
"trusted_bidding_signals_keys TEXT NOT NULL,"
"user_bidding_signals TEXT,"
"ads TEXT NOT NULL,"
"ad_components TEXT NOT NULL,"
"PRIMARY KEY(owner,name))";
// clang-format on
if (!db.Execute(kInterestGroupTableSql)) {
return false;
}
static const char kCopyInterestGroupTableSql[] =
// clang-format off
"INSERT INTO new_interest_groups "
"SELECT expiration,"
"last_updated,"
"next_update_after,"
"owner,"
"joining_origin,"
"last_updated," // exact_join_time
"name,"
"priority,"
"0," // enable_bidding_signals_prioritization
"''," // priority_vector
"''," // priority_signals_overrides
"execution_mode,"
"joining_url,"
"bidding_url,"
"bidding_wasm_helper_url,"
"update_url,"
"trusted_bidding_signals_url,"
"trusted_bidding_signals_keys,"
"user_bidding_signals,"
"ads,"
"ad_components "
"FROM interest_groups";
// clang-format on
if (!db.Execute(kCopyInterestGroupTableSql)) {
return false;
}
static const char kDropInterestGroupTableSql[] = "DROP TABLE interest_groups";
if (!db.Execute(kDropInterestGroupTableSql)) {
return false;
}
static const char kRenameInterestGroupTableSql[] =
// clang-format off
"ALTER TABLE new_interest_groups "
"RENAME TO interest_groups";
// clang-format on
if (!db.Execute(kRenameInterestGroupTableSql)) {
return false;
}
return CreateInterestGroupIndices(db);
}
bool UpgradeV9SchemaToV10(sql::Database& db, sql::MetaTable& meta_table) {
static const char kInterestGroupKAnonTableSql[] =
// clang-format off
"CREATE TABLE k_anon("
"last_referenced_time INTEGER NOT NULL,"
"key TEXT NOT NULL,"
"is_k_anon INTEGER NOT NULL,"
"last_k_anon_updated_time INTEGER NOT NULL,"
"last_reported_to_anon_server_time INTEGER NOT NULL,"
"PRIMARY KEY(key))";
// clang-format on
if (!db.Execute(kInterestGroupKAnonTableSql)) {
return false;
}
static const char kInterestGroupKAnonLastRefIndexSql[] =
// clang-format off
"CREATE INDEX k_anon_last_referenced_time"
" ON k_anon(last_referenced_time DESC)";
// clang-format on
if (!db.Execute(kInterestGroupKAnonLastRefIndexSql)) {
return false;
}
static const char kCopyKAnonTableSql[] =
// clang-format off
"INSERT INTO k_anon "
"SELECT last_referenced_time,"
"key,"
"k_anon_count > 50 AS is_k_anon,"
"last_k_anon_updated_time,"
"last_reported_to_anon_server_time "
"FROM kanon";
// clang-format on
if (!db.Execute(kCopyKAnonTableSql)) {
return false;
}
static const char kDropKanonTableSql[] = "DROP TABLE kanon";
if (!db.Execute(kDropKanonTableSql)) {
return false;
}
return true;
}
bool UpgradeV8SchemaToV9(sql::Database& db, sql::MetaTable& meta_table) {
static const char kJoinHistoryTableSql[] =
// clang-format off
"CREATE TABLE join_history2("
"owner TEXT NOT NULL,"
"name TEXT NOT NULL,"
"join_time INTEGER NOT NULL,"
"count INTEGER NOT NULL,"
"PRIMARY KEY(owner, name, join_time) "
"FOREIGN KEY(owner,name) REFERENCES interest_groups)";
// clang-format on
if (!db.Execute(kJoinHistoryTableSql)) {
return false;
}
// Consolidate old join records into a single record that
// counts the number joins in a day (86400000000 microseconds)
static const char kCopyJoinHistoryTableSql[] =
// clang-format off
"INSERT INTO join_history2 "
"SELECT owner,"
"name,"
"(join_time-(join_time%86400000000)) as join_time2,"
"COUNT() as count "
"FROM join_history "
"GROUP BY owner,name,join_time2";
// clang-format on
if (!db.Execute(kCopyJoinHistoryTableSql)) {
return false;
}
static const char kDropJoinHistoryTableSql[] = "DROP TABLE join_history";
if (!db.Execute(kDropJoinHistoryTableSql)) {
return false;
}
static const char kRenameJoinHistoryTableSql[] =
// clang-format off
"ALTER TABLE join_history2 "
"RENAME TO join_history";
// clang-format on
if (!db.Execute(kRenameJoinHistoryTableSql)) {
return false;
}
static const char kBidHistoryTableSql[] =
// clang-format off
"CREATE TABLE bid_history2("
"owner TEXT NOT NULL,"
"name TEXT NOT NULL,"
"bid_time INTEGER NOT NULL,"
"count INTEGER NOT NULL,"
"PRIMARY KEY(owner, name, bid_time) "
"FOREIGN KEY(owner,name) REFERENCES interest_groups)";
// clang-format on
if (!db.Execute(kBidHistoryTableSql)) {
return false;
}
// Consolidate old bid records into a single record that
// counts the number bids in a day (86400000000 microseconds)
static const char kCopyBidHistoryTableSql[] =
// clang-format off
"INSERT INTO bid_history2 "
"SELECT owner,"
"name,"
"(bid_time-(bid_time%86400000000)) as bid_time2,"
"COUNT() as count "
"FROM bid_history "
"GROUP BY owner,name,bid_time2";
// clang-format on
if (!db.Execute(kCopyBidHistoryTableSql)) {
return false;
}
static const char kDropBidHistoryTableSql[] = "DROP TABLE bid_history";
if (!db.Execute(kDropBidHistoryTableSql)) {
return false;
}
static const char kRenameBidHistoryTableSql[] =
// clang-format off
"ALTER TABLE bid_history2 "
"RENAME TO bid_history";
// clang-format on
if (!db.Execute(kRenameBidHistoryTableSql)) {
return false;
}
return true;
}
bool UpgradeV7SchemaToV8(sql::Database& db, sql::MetaTable& meta_table) {
static const char kInterestGroupsAddExecutionModeSql[] =
"ALTER TABLE interest_groups ADD COLUMN execution_mode INTEGER DEFAULT 0";
if (!db.Execute(kInterestGroupsAddExecutionModeSql)) {
return false;
}
return true;
}
bool UpgradeV6SchemaToV7(sql::Database& db, sql::MetaTable& meta_table) {
// Index on group expiration by owner.
DCHECK(db.DoesIndexExist("interest_group_owner"));
static const char kRemoveInterestGroupOwnerIndexSql[] =
// clang-format off
"DROP INDEX interest_group_owner";
// clang-format on
if (!db.Execute(kRemoveInterestGroupOwnerIndexSql)) {
return false;
}
DCHECK(!db.DoesIndexExist("interest_group_owner"));
static const char kInterestGroupOwnerIndexSql[] =
// clang-format off
"CREATE INDEX interest_group_owner"
" ON interest_groups(owner,expiration DESC,next_update_after ASC,name)";
// clang-format on
if (!db.Execute(kInterestGroupOwnerIndexSql)) {
return false;
}
// Update interest_group table.
static const char kInterestGroupsAddPrioritySql[] =
"ALTER TABLE interest_groups ADD COLUMN priority DOUBLE DEFAULT 0";
if (!db.Execute(kInterestGroupsAddPrioritySql)) {
return false;
}
return true;
}
bool MaybeCreateKAnonEntry(sql::Database& db,
const std::string& key,
const base::Time& now) {
base::Time distant_past = base::Time::Min();
// clang-format off
sql::Statement maybe_insert_kanon(
db.GetCachedStatement(SQL_FROM_HERE,
"INSERT OR IGNORE INTO k_anon("
"last_referenced_time,"
"key,"
"is_k_anon,"
"last_k_anon_updated_time,"
"last_reported_to_anon_server_time) "
"VALUES(?,?,0,?,?)"
));
// clang-format on
if (!maybe_insert_kanon.is_valid()) {
return false;
}
maybe_insert_kanon.Reset(true);
maybe_insert_kanon.BindTime(0, now);
maybe_insert_kanon.BindString(1, key);
maybe_insert_kanon.BindTime(2, distant_past);
maybe_insert_kanon.BindTime(3, distant_past);
return maybe_insert_kanon.Run();
}
bool RemoveJoinHistory(sql::Database& db,
const blink::InterestGroupKey& group_key) {
sql::Statement remove_join_history(
db.GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM join_history "
"WHERE owner=? AND name=?"));
if (!remove_join_history.is_valid()) {
return false;
}
remove_join_history.Reset(true);
remove_join_history.BindString(0, Serialize(group_key.owner));
remove_join_history.BindString(1, group_key.name);
return remove_join_history.Run();
}
bool RemoveBidHistory(sql::Database& db,
const blink::InterestGroupKey& group_key) {
sql::Statement remove_bid_history(
db.GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM bid_history "
"WHERE owner=? AND name=?"));
if (!remove_bid_history.is_valid()) {
return false;
}
remove_bid_history.Reset(true);
remove_bid_history.BindString(0, Serialize(group_key.owner));
remove_bid_history.BindString(1, group_key.name);
return remove_bid_history.Run();
}
bool RemoveWinHistory(sql::Database& db,
const blink::InterestGroupKey& group_key) {
sql::Statement remove_win_history(
db.GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM win_history "
"WHERE owner=? AND name=?"));
if (!remove_win_history.is_valid()) {
return false;
}
remove_win_history.Reset(true);
remove_win_history.BindString(0, Serialize(group_key.owner));
remove_win_history.BindString(1, group_key.name);
return remove_win_history.Run();
}
bool DoRemoveInterestGroup(sql::Database& db,
const blink::InterestGroupKey& group_key) {
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return false;
}
// These tables have foreign keys that reference the interest group table.
if (!RemoveJoinHistory(db, group_key)) {
return false;
}
if (!RemoveBidHistory(db, group_key)) {
return false;
}
if (!RemoveWinHistory(db, group_key)) {
return false;
}
sql::Statement remove_group(
db.GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM interest_groups "
"WHERE owner=? AND name=?"));
if (!remove_group.is_valid()) {
return false;
}
remove_group.Reset(true);
remove_group.BindString(0, Serialize(group_key.owner));
remove_group.BindString(1, group_key.name);
return remove_group.Run() && transaction.Commit();
}
bool DoClearClusteredBiddingGroups(sql::Database& db,
const url::Origin owner,
const url::Origin main_frame) {
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return false;
}
// clang-format off
sql::Statement same_cluster_groups(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT name "
"FROM interest_groups "
"WHERE owner = ? AND joining_origin = ? AND execution_mode = ?"));
// clang-format on
if (!same_cluster_groups.is_valid()) {
return false;
}
same_cluster_groups.Reset(true);
same_cluster_groups.BindString(0, Serialize(owner));
same_cluster_groups.BindString(1, Serialize(main_frame));
same_cluster_groups.BindInt(
2, static_cast<int>(
blink::InterestGroup::ExecutionMode::kGroupedByOriginMode));
while (same_cluster_groups.Step()) {
if (!DoRemoveInterestGroup(
db, blink::InterestGroupKey(owner,
same_cluster_groups.ColumnString(0)))) {
return false;
}
}
return transaction.Commit();
}
bool DoLoadInterestGroup(sql::Database& db,
const blink::InterestGroupKey& group_key,
blink::InterestGroup& group,
url::Origin* joining_origin = nullptr,
base::Time* exact_join_time = nullptr,
base::Time* last_updated = nullptr) {
// clang-format off
sql::Statement load(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT expiration,"
"joining_origin,"
"exact_join_time,"
"last_updated,"
"priority,"
"enable_bidding_signals_prioritization,"
"priority_vector,"
"priority_signals_overrides,"
"seller_capabilities,"
"all_sellers_capabilities,"
"execution_mode,"
"bidding_url,"
"bidding_wasm_helper_url,"
"update_url,"
"trusted_bidding_signals_url,"
"trusted_bidding_signals_keys,"
"user_bidding_signals," // opaque data
"ads_pb,"
"ad_components_pb,"
"ad_sizes,"
"size_groups,"
"auction_server_request_flags,"
"additional_bid_key "
"FROM interest_groups "
"WHERE owner = ? AND name = ? "));
// clang-format on
if (!load.is_valid()) {
return false;
}
load.Reset(true);
load.BindString(0, Serialize(group_key.owner));
load.BindString(1, group_key.name);
if (!load.Step() || !load.Succeeded()) {
return false;
}
group.expiry = load.ColumnTime(0);
group.owner = group_key.owner;
group.name = group_key.name;
if (joining_origin) {
*joining_origin = DeserializeOrigin(load.ColumnString(1));
}
if (exact_join_time) {
*exact_join_time = load.ColumnTime(2);
}
if (last_updated) {
*last_updated = load.ColumnTime(3);
}
group.priority = load.ColumnDouble(4);
group.enable_bidding_signals_prioritization = load.ColumnBool(5);
group.priority_vector = DeserializeStringDoubleMap(load.ColumnString(6));
group.priority_signals_overrides =
DeserializeStringDoubleMap(load.ColumnString(7));
group.seller_capabilities =
DeserializeSellerCapabilitiesMap(load.ColumnString(8));
group.all_sellers_capabilities =
DeserializeSellerCapabilities(load.ColumnInt64(9));
group.execution_mode =
static_cast<blink::InterestGroup::ExecutionMode>(load.ColumnInt(10));
group.bidding_url = DeserializeURL(load.ColumnString(11));
group.bidding_wasm_helper_url = DeserializeURL(load.ColumnString(12));
group.update_url = DeserializeURL(load.ColumnString(13));
group.trusted_bidding_signals_url = DeserializeURL(load.ColumnString(14));
group.trusted_bidding_signals_keys =
DeserializeStringVector(load.ColumnString(15));
if (load.GetColumnType(16) != sql::ColumnType::kNull) {
group.user_bidding_signals = load.ColumnString(16);
}
group.ads = DeserializeInterestGroupAdVectorProto(load.ColumnString(17));
group.ad_components =
DeserializeInterestGroupAdVectorProto(load.ColumnString(18));
group.ad_sizes = DeserializeStringSizeMap(load.ColumnString(19));
group.size_groups = DeserializeStringStringVectorMap(load.ColumnString(20));
group.auction_server_request_flags =
DeserializeAuctionServerRequestFlags(load.ColumnInt64(21));
group.additional_bid_key = DeserializeAdditionalBidKey(load.ColumnBlob(22));
return true;
}
bool DoRecordInterestGroupJoin(sql::Database& db,
const url::Origin& owner,
const std::string& name,
base::Time join_time) {
// This flow basically emulates SQLite's UPSERT feature which is disabled in
// Chrome. Although there are two statements executed, we don't need to
// enclose them in a transaction since only one will actually modify the
// database.
int64_t join_day = join_time.ToDeltaSinceWindowsEpoch()
.FloorToMultiple(base::Days(1))
.InMicroseconds();
// clang-format off
sql::Statement insert_join_hist(
db.GetCachedStatement(SQL_FROM_HERE,
"INSERT OR IGNORE INTO join_history(owner,name,join_time,count) "
"VALUES(?,?,?,1)"));
// clang-format on
if (!insert_join_hist.is_valid()) {
return false;
}
insert_join_hist.Reset(true);
insert_join_hist.BindString(0, Serialize(owner));
insert_join_hist.BindString(1, name);
insert_join_hist.BindInt64(2, join_day);
if (!insert_join_hist.Run()) {
return false;
}
// If the insert changed the database return early.
if (db.GetLastChangeCount() > 0) {
return true;
}
// clang-format off
sql::Statement update_join_hist(
db.GetCachedStatement(SQL_FROM_HERE,
"UPDATE join_history "
"SET count=count+1 "
"WHERE owner=? AND name=? AND join_time=?"));
// clang-format on
if (!update_join_hist.is_valid()) {
return false;
}
update_join_hist.Reset(true);
update_join_hist.BindString(0, Serialize(owner));
update_join_hist.BindString(1, name);
update_join_hist.BindInt64(2, join_day);
return update_join_hist.Run();
}
bool DoJoinInterestGroup(sql::Database& db,
const blink::InterestGroup& data,
const GURL& joining_url,
base::Time exact_join_time,
base::Time last_updated,
base::Time next_update_after) {
DCHECK(data.IsValid());
url::Origin joining_origin = url::Origin::Create(joining_url);
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return false;
}
blink::InterestGroup old_group;
url::Origin old_joining_origin;
blink::InterestGroupKey interest_group_key(data.owner, data.name);
if (DoLoadInterestGroup(db, interest_group_key, old_group,
&old_joining_origin,
/*exact_join_time=*/nullptr,
/*last_updated=*/nullptr)) {
if (old_group.expiry <= base::Time::Now()) {
// If there's a matching old interest group that's expired but that hasn't
// yet been cleaned up, delete it. This removes its associated tables,
// which should expire at the same time as the old interest group.
if (!DoRemoveInterestGroup(db, interest_group_key)) {
return false;
}
} else if (old_group.execution_mode ==
blink::InterestGroup::ExecutionMode::kGroupedByOriginMode &&
joining_origin != old_joining_origin) {
// Clear all interest groups with same owner and mode GroupedByOriginMode
// and same `old_joining_origin`.
if (!DoClearClusteredBiddingGroups(db, data.owner, old_joining_origin)) {
return false;
}
}
}
// clang-format off
sql::Statement join_group(
db.GetCachedStatement(SQL_FROM_HERE,
"INSERT OR REPLACE INTO interest_groups("
"expiration,"
"last_updated,"
"next_update_after,"
"owner,"
"joining_origin,"
"exact_join_time,"
"name,"
"priority,"
"enable_bidding_signals_prioritization,"
"priority_vector,"
"priority_signals_overrides,"
"seller_capabilities,"
"all_sellers_capabilities,"
"execution_mode,"
"joining_url,"
"bidding_url,"
"bidding_wasm_helper_url,"
"update_url,"
"trusted_bidding_signals_url,"
"trusted_bidding_signals_keys,"
"user_bidding_signals," // opaque data
"ads_pb,"
"ad_components_pb,"
"ad_sizes,"
"size_groups,"
"auction_server_request_flags,"
"additional_bid_key) "
"VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"));
// clang-format on
if (!join_group.is_valid()) {
return false;
}
join_group.Reset(true);
join_group.BindTime(0, data.expiry);
join_group.BindTime(1, last_updated);
join_group.BindTime(2, next_update_after);
join_group.BindString(3, Serialize(data.owner));
join_group.BindString(4, Serialize(joining_origin));
join_group.BindTime(5, exact_join_time);
join_group.BindString(6, data.name);
join_group.BindDouble(7, data.priority);
join_group.BindBool(8, data.enable_bidding_signals_prioritization);
join_group.BindString(9, Serialize(data.priority_vector));
join_group.BindString(10, Serialize(data.priority_signals_overrides));
join_group.BindString(11, Serialize(data.seller_capabilities));
join_group.BindInt64(12, Serialize(data.all_sellers_capabilities));
join_group.BindInt(13, static_cast<int>(data.execution_mode));
join_group.BindString(14, Serialize(joining_url));
join_group.BindString(15, Serialize(data.bidding_url));
join_group.BindString(16, Serialize(data.bidding_wasm_helper_url));
join_group.BindString(17, Serialize(data.update_url));
join_group.BindString(18, Serialize(data.trusted_bidding_signals_url));
join_group.BindString(19, Serialize(data.trusted_bidding_signals_keys));
if (data.user_bidding_signals) {
join_group.BindString(20, data.user_bidding_signals.value());
} else {
join_group.BindNull(20);
}
join_group.BindBlob(21, Serialize(data.ads));
join_group.BindBlob(22, Serialize(data.ad_components));
join_group.BindString(23, Serialize(data.ad_sizes));
join_group.BindString(24, Serialize(data.size_groups));
join_group.BindInt64(25, Serialize(data.auction_server_request_flags));
join_group.BindBlob(26, Serialize(data.additional_bid_key));
if (!join_group.Run()) {
return false;
}
if (!DoRecordInterestGroupJoin(db, data.owner, data.name, last_updated)) {
return false;
}
return transaction.Commit();
}
bool DoStoreInterestGroupUpdate(sql::Database& db,
const blink::InterestGroup& group,
base::Time now) {
// clang-format off
sql::Statement store_group(
db.GetCachedStatement(SQL_FROM_HERE,
"UPDATE interest_groups "
"SET last_updated=?,"
"next_update_after=?,"
"priority=?,"
"enable_bidding_signals_prioritization=?,"
"priority_vector=?,"
"priority_signals_overrides=?,"
"seller_capabilities=?,"
"all_sellers_capabilities=?,"
"execution_mode=?,"
"bidding_url=?,"
"bidding_wasm_helper_url=?,"
"update_url=?,"
"trusted_bidding_signals_url=?,"
"trusted_bidding_signals_keys=?,"
"ads_pb=?,"
"ad_components_pb=?,"
"ad_sizes=?,"
"size_groups=?,"
"auction_server_request_flags=?, "
"additional_bid_key=? "
"WHERE owner=? AND name=?"));
// clang-format on
if (!store_group.is_valid()) {
return false;
}
store_group.Reset(true);
store_group.BindTime(0, now);
store_group.BindTime(
1, now + InterestGroupStorage::kUpdateSucceededBackoffPeriod);
store_group.BindDouble(2, group.priority);
store_group.BindBool(3, group.enable_bidding_signals_prioritization);
store_group.BindString(4, Serialize(group.priority_vector));
store_group.BindString(5, Serialize(group.priority_signals_overrides));
store_group.BindString(6, Serialize(group.seller_capabilities));
store_group.BindInt64(7, Serialize(group.all_sellers_capabilities));
store_group.BindInt(8, static_cast<int>(group.execution_mode));
store_group.BindString(9, Serialize(group.bidding_url));
store_group.BindString(10, Serialize(group.bidding_wasm_helper_url));
store_group.BindString(11, Serialize(group.update_url));
store_group.BindString(12, Serialize(group.trusted_bidding_signals_url));
store_group.BindString(13, Serialize(group.trusted_bidding_signals_keys));
store_group.BindBlob(14, Serialize(group.ads));
store_group.BindBlob(15, Serialize(group.ad_components));
store_group.BindString(16, Serialize(group.ad_sizes));
store_group.BindString(17, Serialize(group.size_groups));
store_group.BindInt64(18, Serialize(group.auction_server_request_flags));
store_group.BindBlob(19, Serialize(group.additional_bid_key));
store_group.BindString(20, Serialize(group.owner));
store_group.BindString(21, group.name);
return store_group.Run();
}
bool DoUpdateInterestGroup(sql::Database& db,
const blink::InterestGroupKey& group_key,
InterestGroupUpdate update,
base::Time now) {
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return false;
}
// Unlike Join() operations, for Update() operations, values that aren't
// specified in the JSON returned by servers (Serialize()'d below as empty
// strings) aren't modified in the database -- in this sense, new data is
// merged with old data.
//
// Since we need to verify this results in a valid interest group, we have to
// first read the interest group from the DB, apply the changes and then
// verify the interest group is valid before writing it to the database.
blink::InterestGroup stored_group;
if (!DoLoadInterestGroup(db, group_key, stored_group,
/*joining_origin=*/nullptr,
/*exact_join_time=*/nullptr,
/*last_updated=*/nullptr)) {
return false;
}
// (Optimization) Don't do anything for expired interest groups.
if (stored_group.expiry <= now) {
return false;
}
if (update.priority) {
stored_group.priority = *update.priority;
}
if (update.enable_bidding_signals_prioritization) {
stored_group.enable_bidding_signals_prioritization =
*update.enable_bidding_signals_prioritization;
}
if (update.priority_vector) {
stored_group.priority_vector = update.priority_vector;
}
if (update.priority_signals_overrides) {
MergePrioritySignalsOverrides(*update.priority_signals_overrides,
stored_group.priority_signals_overrides);
}
if (update.seller_capabilities) {
stored_group.seller_capabilities = update.seller_capabilities;
}
if (update.all_sellers_capabilities) {
stored_group.all_sellers_capabilities = *update.all_sellers_capabilities;
}
if (update.execution_mode) {
stored_group.execution_mode = *update.execution_mode;
}
if (update.daily_update_url) {
stored_group.update_url = std::move(update.daily_update_url);
}
if (update.bidding_url) {
stored_group.bidding_url = std::move(update.bidding_url);
}
if (update.bidding_wasm_helper_url) {
stored_group.bidding_wasm_helper_url =
std::move(update.bidding_wasm_helper_url);
}
if (update.trusted_bidding_signals_url) {
stored_group.trusted_bidding_signals_url =
std::move(update.trusted_bidding_signals_url);
}
if (update.trusted_bidding_signals_keys) {
stored_group.trusted_bidding_signals_keys =
std::move(update.trusted_bidding_signals_keys);
}
if (update.ads) {
stored_group.ads = std::move(update.ads);
}
if (update.ad_components) {
stored_group.ad_components = std::move(update.ad_components);
}
if (update.ad_sizes) {
stored_group.ad_sizes = std::move(update.ad_sizes);
}
if (update.size_groups) {
stored_group.size_groups = std::move(update.size_groups);
}
if (update.auction_server_request_flags) {
stored_group.auction_server_request_flags =
*update.auction_server_request_flags;
}
if (update.additional_bid_key) {
stored_group.additional_bid_key = std::move(update.additional_bid_key);
}
if (!stored_group.IsValid()) {
// TODO(behamilton): Report errors to devtools.
return false;
}
if (!DoStoreInterestGroupUpdate(db, stored_group, now)) {
return false;
}
return transaction.Commit();
}
bool DoReportUpdateFailed(sql::Database& db,
const blink::InterestGroupKey& group_key,
bool parse_failure,
base::Time now) {
sql::Statement update_group(db.GetCachedStatement(SQL_FROM_HERE, R"(
UPDATE interest_groups SET
next_update_after=?
WHERE owner=? AND name=?)"));
if (!update_group.is_valid()) {
return false;
}
update_group.Reset(true);
if (parse_failure) {
// Non-network failures delay the same amount of time as successful updates.
update_group.BindTime(
0, now + InterestGroupStorage::kUpdateSucceededBackoffPeriod);
} else {
update_group.BindTime(
0, now + InterestGroupStorage::kUpdateFailedBackoffPeriod);
}
update_group.BindString(1, Serialize(group_key.owner));
update_group.BindString(2, group_key.name);
return update_group.Run();
}
bool DoRecordInterestGroupBid(sql::Database& db,
const blink::InterestGroupKey& group_key,
base::Time bid_time) {
// This flow basically emulates SQLite's UPSERT feature which is disabled in
// Chrome. Although there are two statements executed, we don't need to
// enclose them in a transaction since only one will actually modify the
// database.
int64_t bid_day = bid_time.ToDeltaSinceWindowsEpoch()
.FloorToMultiple(base::Days(1))
.InMicroseconds();
// clang-format off
sql::Statement insert_bid_hist(
db.GetCachedStatement(SQL_FROM_HERE,
"INSERT OR IGNORE INTO bid_history(owner,name,bid_time,count) "
"VALUES(?,?,?,1)"));
// clang-format on
if (!insert_bid_hist.is_valid()) {
return false;
}
insert_bid_hist.Reset(true);
insert_bid_hist.BindString(0, Serialize(group_key.owner));
insert_bid_hist.BindString(1, group_key.name);
insert_bid_hist.BindInt64(2, bid_day);
if (!insert_bid_hist.Run()) {
return false;
}
// If the insert changed the database return early.
if (db.GetLastChangeCount() > 0) {
return true;
}
// clang-format off
sql::Statement update_bid_hist(
db.GetCachedStatement(SQL_FROM_HERE,
"UPDATE bid_history "
"SET count=count+1 "
"WHERE owner=? AND name=? AND bid_time=?"));
// clang-format on
if (!update_bid_hist.is_valid()) {
return false;
}
update_bid_hist.Reset(true);
update_bid_hist.BindString(0, Serialize(group_key.owner));
update_bid_hist.BindString(1, group_key.name);
update_bid_hist.BindInt64(2, bid_day);
return update_bid_hist.Run();
}
bool DoRecordInterestGroupBids(sql::Database& db,
const blink::InterestGroupSet& group_keys,
base::Time bid_time) {
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return false;
}
for (const auto& group_key : group_keys) {
if (!DoRecordInterestGroupBid(db, group_key, bid_time)) {
return false;
}
}
return transaction.Commit();
}
bool DoRecordInterestGroupWin(sql::Database& db,
const blink::InterestGroupKey& group_key,
const std::string& ad_json,
base::Time win_time) {
// Record the win. It should be unique since auctions should be serialized.
// If it is not unique we should just keep the first one.
// clang-format off
sql::Statement win_hist(
db.GetCachedStatement(SQL_FROM_HERE,
"INSERT INTO win_history(owner,name,win_time,ad) "
"VALUES(?,?,?,?)"));
// clang-format on
if (!win_hist.is_valid()) {
return false;
}
win_hist.Reset(true);
win_hist.BindString(0, Serialize(group_key.owner));
win_hist.BindString(1, group_key.name);
win_hist.BindTime(2, win_time);
win_hist.BindString(3, ad_json);
return win_hist.Run();
}
bool DoUpdateKAnonymity(sql::Database& db,
const StorageInterestGroup::KAnonymityData& data,
base::Time now) {
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return false;
}
if (!MaybeCreateKAnonEntry(db, data.key, now)) {
return false;
}
// clang-format off
sql::Statement update(
db.GetCachedStatement(SQL_FROM_HERE,
"UPDATE k_anon "
"SET is_k_anon=?,"
"last_k_anon_updated_time=?,"
"last_referenced_time=? "
"WHERE key=?"));
// clang-format on
if (!update.is_valid()) {
return false;
}
update.Reset(true);
update.BindInt(0, data.is_k_anonymous);
update.BindTime(1, data.last_updated);
update.BindTime(2, now);
update.BindString(3, data.key);
if (!update.Run()) {
return false;
}
return transaction.Commit();
}
absl::optional<base::Time> DoGetLastKAnonymityReported(sql::Database& db,
const std::string& key) {
const base::Time distant_past = base::Time::Min();
sql::Statement get_reported(db.GetCachedStatement(
SQL_FROM_HERE,
"SELECT last_reported_to_anon_server_time FROM k_anon WHERE key=?"));
if (!get_reported.is_valid()) {
DLOG(ERROR) << "GetLastKAnonymityReported SQL statement did not compile: "
<< db.GetErrorMessage();
return absl::nullopt;
}
get_reported.Reset(true);
get_reported.BindString(0, key);
if (!get_reported.Step()) {
return distant_past;
}
if (!get_reported.Succeeded()) {
return absl::nullopt;
}
return get_reported.ColumnTime(0);
}
void DoUpdateLastKAnonymityReported(sql::Database& db,
const std::string& key,
base::Time now) {
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return;
}
if (!MaybeCreateKAnonEntry(db, key, now)) {
return;
}
// clang-format off
sql::Statement set_reported(db.GetCachedStatement(
SQL_FROM_HERE,
"UPDATE k_anon "
"SET last_reported_to_anon_server_time=?,"
"last_referenced_time=? "
"WHERE key=?"));
// clang-format on
if (!set_reported.is_valid()) {
DLOG(ERROR)
<< "DoUpdateLastKAnonymityReported SQL statement did not compile: "
<< db.GetErrorMessage();
return;
}
set_reported.Reset(true);
set_reported.BindTime(0, now);
set_reported.BindTime(1, now);
set_reported.BindString(2, key);
if (!set_reported.Run()) {
return;
}
transaction.Commit();
}
absl::optional<std::vector<url::Origin>> DoGetAllInterestGroupOwners(
sql::Database& db,
base::Time expiring_after) {
std::vector<url::Origin> result;
sql::Statement load(db.GetCachedStatement(SQL_FROM_HERE,
"SELECT DISTINCT owner "
"FROM interest_groups "
"WHERE expiration>? "
"ORDER BY expiration DESC"));
if (!load.is_valid()) {
DLOG(ERROR) << "LoadAllInterestGroups SQL statement did not compile: "
<< db.GetErrorMessage();
return absl::nullopt;
}
load.Reset(true);
load.BindTime(0, expiring_after);
while (load.Step()) {
result.push_back(DeserializeOrigin(load.ColumnString(0)));
}
if (!load.Succeeded()) {
return absl::nullopt;
}
return result;
}
absl::optional<std::vector<url::Origin>> DoGetAllInterestGroupJoiningOrigins(
sql::Database& db,
base::Time expiring_after) {
std::vector<url::Origin> result;
sql::Statement load(db.GetCachedStatement(SQL_FROM_HERE,
"SELECT DISTINCT joining_origin "
"FROM interest_groups "
"WHERE expiration>?"));
if (!load.is_valid()) {
DLOG(ERROR)
<< "LoadAllInterestGroupJoiningOrigins SQL statement did not compile: "
<< db.GetErrorMessage();
return absl::nullopt;
}
load.Reset(true);
load.BindTime(0, expiring_after);
while (load.Step()) {
result.push_back(DeserializeOrigin(load.ColumnString(0)));
}
if (!load.Succeeded()) {
return absl::nullopt;
}
return result;
}
bool DoRemoveInterestGroupsMatchingOwnerAndJoiner(sql::Database& db,
url::Origin owner,
url::Origin joining_origin,
base::Time expiring_after) {
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return false;
}
std::vector<std::string> owner_joiner_names;
sql::Statement load(db.GetCachedStatement(
SQL_FROM_HERE,
"SELECT name "
"FROM interest_groups "
"WHERE owner=? AND joining_origin=? AND expiration>?"));
if (!load.is_valid()) {
return false;
}
load.Reset(true);
load.BindString(0, owner.Serialize());
load.BindString(1, joining_origin.Serialize());
load.BindTime(2, expiring_after);
while (load.Step()) {
owner_joiner_names.emplace_back(load.ColumnString(0));
}
for (const auto& name : owner_joiner_names) {
if (!DoRemoveInterestGroup(db, blink::InterestGroupKey{owner, name})) {
return false;
}
}
return transaction.Commit();
}
absl::optional<std::vector<std::pair<url::Origin, url::Origin>>>
DoGetAllInterestGroupOwnerJoinerPairs(sql::Database& db,
base::Time expiring_after) {
std::vector<std::pair<url::Origin, url::Origin>> result;
sql::Statement load(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT DISTINCT owner,joining_origin "
"FROM interest_groups "
"WHERE expiration>?"));
if (!load.is_valid()) {
DLOG(ERROR) << "LoadAllInterestGroupOwnerJoinerPairs SQL statement did not "
"compile: "
<< db.GetErrorMessage();
return absl::nullopt;
}
load.Reset(true);
load.BindTime(0, expiring_after);
while (load.Step()) {
result.emplace_back(DeserializeOrigin(load.ColumnString(0)),
DeserializeOrigin(load.ColumnString(1)));
}
if (!load.Succeeded()) {
return absl::nullopt;
}
return result;
}
bool DoGetKAnonymity(
sql::Database& db,
const std::string& key,
absl::optional<StorageInterestGroup::KAnonymityData>& output) {
// clang-format off
sql::Statement interest_group_kanon(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT is_k_anon, last_k_anon_updated_time "
"FROM k_anon "
"WHERE key=?"
));
// clang-format on
if (!interest_group_kanon.is_valid()) {
DLOG(ERROR)
<< "GetInterestGroupsForOwner interest_group_kanon SQL statement did "
"not compile: "
<< db.GetErrorMessage();
return false;
}
interest_group_kanon.Reset(true);
interest_group_kanon.BindString(0, key);
if (!interest_group_kanon.Step()) {
// Not in the table, so return the defaults.
output = DefaultKAnonymityData(key);
return true;
}
output = {key, /*is_k_anonymous=*/interest_group_kanon.ColumnInt(0) > 0,
/*last_updated=*/interest_group_kanon.ColumnTime(1)};
return interest_group_kanon.Succeeded();
}
bool GetPreviousWins(sql::Database& db,
const blink::InterestGroupKey& group_key,
base::Time win_time_after,
BiddingBrowserSignalsPtr& output) {
// clang-format off
sql::Statement prev_wins(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT win_time, ad "
"FROM win_history "
"WHERE owner = ? AND name = ? AND win_time >= ? "
"ORDER BY win_time DESC"));
// clang-format on
if (!prev_wins.is_valid()) {
DLOG(ERROR) << "GetInterestGroupsForOwner win_history SQL statement did "
"not compile: "
<< db.GetErrorMessage();
return false;
}
prev_wins.Reset(true);
prev_wins.BindString(0, Serialize(group_key.owner));
prev_wins.BindString(1, group_key.name);
prev_wins.BindTime(2, win_time_after);
while (prev_wins.Step()) {
PreviousWinPtr prev_win = auction_worklet::mojom::PreviousWin::New(
/*time=*/prev_wins.ColumnTime(0),
/*ad_json=*/prev_wins.ColumnString(1));
output->prev_wins.push_back(std::move(prev_win));
}
return prev_wins.Succeeded();
}
bool GetJoinCount(sql::Database& db,
const blink::InterestGroupKey& group_key,
base::Time joined_after,
BiddingBrowserSignalsPtr& output) {
// clang-format off
sql::Statement join_count(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT SUM(count) "
"FROM join_history "
"WHERE owner = ? AND name = ? AND join_time >=?"));
// clang-format on
if (!join_count.is_valid()) {
DLOG(ERROR) << "GetJoinCount SQL statement did not compile: "
<< db.GetErrorMessage();
return false;
}
join_count.Reset(true);
join_count.BindString(0, Serialize(group_key.owner));
join_count.BindString(1, group_key.name);
join_count.BindTime(2, joined_after);
while (join_count.Step()) {
output->join_count = join_count.ColumnInt64(0);
}
return join_count.Succeeded();
}
bool GetBidCount(sql::Database& db,
const blink::InterestGroupKey& group_key,
base::Time now,
BiddingBrowserSignalsPtr& output) {
// clang-format off
sql::Statement bid_count(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT SUM(count) "
"FROM bid_history "
"WHERE owner = ? AND name = ? AND bid_time >= ?"));
// clang-format on
if (!bid_count.is_valid()) {
DLOG(ERROR) << "GetBidCount SQL statement did not compile: "
<< db.GetErrorMessage();
return false;
}
bid_count.Reset(true);
bid_count.BindString(0, Serialize(group_key.owner));
bid_count.BindString(1, group_key.name);
bid_count.BindTime(2, now - InterestGroupStorage::kHistoryLength);
while (bid_count.Step()) {
output->bid_count = bid_count.ColumnInt64(0);
}
return bid_count.Succeeded();
}
absl::optional<std::vector<std::string>> DoGetInterestGroupNamesForOwner(
sql::Database& db,
const url::Origin& owner,
base::Time now,
base::Time next_update_after) {
// clang-format off
sql::Statement get_names(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT name "
"FROM interest_groups "
"WHERE owner=? AND expiration>? AND ?>=next_update_after "
"ORDER BY expiration DESC"));
// clang-format on
if (!get_names.is_valid()) {
return absl::nullopt;
}
get_names.Reset(true);
get_names.BindString(0, Serialize(owner));
get_names.BindTime(1, now);
get_names.BindTime(2, next_update_after);
std::vector<std::string> result;
while (get_names.Step()) {
result.push_back(get_names.ColumnString(0));
}
if (!get_names.Succeeded()) {
return absl::nullopt;
}
return result;
}
absl::optional<StorageInterestGroup> DoGetStoredInterestGroup(
sql::Database& db,
const blink::InterestGroupKey& group_key,
base::Time now) {
StorageInterestGroup db_interest_group;
if (!DoLoadInterestGroup(db, group_key, db_interest_group.interest_group,
&db_interest_group.joining_origin,
&db_interest_group.join_time,
&db_interest_group.last_updated)) {
return absl::nullopt;
}
if (db_interest_group.interest_group.bidding_url) {
if (db_interest_group.interest_group.ads) {
for (auto& ad : db_interest_group.interest_group.ads.value()) {
absl::optional<StorageInterestGroup::KAnonymityData> ad_kanon;
if (!DoGetKAnonymity(
db,
blink::KAnonKeyForAdBid(db_interest_group.interest_group,
ad.render_url),
ad_kanon)) {
return absl::nullopt;
}
if (!ad_kanon) {
continue;
}
db_interest_group.bidding_ads_kanon.push_back(
std::move(ad_kanon).value());
absl::optional<StorageInterestGroup::KAnonymityData> ad_name_kanon;
if (!DoGetKAnonymity(db,
blink::KAnonKeyForAdNameReporting(
db_interest_group.interest_group, ad),
ad_name_kanon)) {
return absl::nullopt;
}
if (!ad_name_kanon) {
continue;
}
db_interest_group.reporting_ads_kanon.push_back(
std::move(ad_name_kanon).value());
}
}
if (db_interest_group.interest_group.ad_components) {
for (auto& ad : db_interest_group.interest_group.ad_components.value()) {
absl::optional<StorageInterestGroup::KAnonymityData> ad_kanon;
if (!DoGetKAnonymity(db,
blink::KAnonKeyForAdComponentBid(ad.render_url),
ad_kanon)) {
return absl::nullopt;
}
if (!ad_kanon) {
continue;
}
db_interest_group.component_ads_kanon.push_back(
std::move(ad_kanon).value());
}
}
}
db_interest_group.bidding_browser_signals =
auction_worklet::mojom::BiddingBrowserSignals::New();
if (!GetJoinCount(db, group_key, now - InterestGroupStorage::kHistoryLength,
db_interest_group.bidding_browser_signals)) {
return absl::nullopt;
}
if (!GetBidCount(db, group_key, now - InterestGroupStorage::kHistoryLength,
db_interest_group.bidding_browser_signals)) {
return absl::nullopt;
}
if (!GetPreviousWins(db, group_key,
now - InterestGroupStorage::kHistoryLength,
db_interest_group.bidding_browser_signals)) {
return absl::nullopt;
}
return db_interest_group;
}
absl::optional<std::vector<StorageInterestGroup>> DoGetInterestGroupsForOwner(
sql::Database& db,
const url::Origin& owner,
base::Time now,
bool get_groups_for_update = false) {
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return absl::nullopt;
}
base::Time next_update_after =
(get_groups_for_update ? now : base::Time::Max());
absl::optional<std::vector<std::string>> group_names =
DoGetInterestGroupNamesForOwner(db, owner, now, next_update_after);
if (!group_names) {
return absl::nullopt;
}
std::vector<StorageInterestGroup> result;
for (const std::string& name : *group_names) {
absl::optional<StorageInterestGroup> db_interest_group =
DoGetStoredInterestGroup(db, blink::InterestGroupKey(owner, name), now);
if (!db_interest_group) {
return absl::nullopt;
}
result.push_back(std::move(db_interest_group).value());
}
if (!transaction.Commit()) {
return absl::nullopt;
}
return result;
}
absl::optional<std::vector<blink::InterestGroupKey>>
DoGetInterestGroupNamesForJoiningOrigin(sql::Database& db,
const url::Origin& joining_origin,
base::Time now) {
std::vector<blink::InterestGroupKey> result;
// clang-format off
sql::Statement load(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT owner,name "
"FROM interest_groups "
"WHERE joining_origin=? AND expiration>?"));
// clang-format on
if (!load.is_valid()) {
DLOG(ERROR) << "GetInterestGroupNamesForJoiningOrigin SQL statement did "
"not compile: "
<< db.GetErrorMessage();
return absl::nullopt;
}
load.Reset(true);
load.BindString(0, Serialize(joining_origin));
load.BindTime(1, now);
while (load.Step()) {
result.emplace_back(DeserializeOrigin(load.ColumnString(0)),
load.ColumnString(1));
}
if (!load.Succeeded()) {
return absl::nullopt;
}
return result;
}
bool DoDeleteInterestGroupData(
sql::Database& db,
StoragePartition::StorageKeyMatcherFunction storage_key_matcher) {
const base::Time distant_past = base::Time::Min();
const base::Time distant_future = base::Time::Max();
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return false;
}
std::vector<url::Origin> affected_origins;
absl::optional<std::vector<url::Origin>> maybe_all_origins =
DoGetAllInterestGroupOwners(db, distant_past);
if (!maybe_all_origins) {
return false;
}
for (const url::Origin& origin : maybe_all_origins.value()) {
if (storage_key_matcher.is_null() ||
storage_key_matcher.Run(blink::StorageKey::CreateFirstParty(origin))) {
affected_origins.push_back(origin);
}
}
for (const auto& affected_origin : affected_origins) {
absl::optional<std::vector<std::string>> maybe_group_names =
DoGetInterestGroupNamesForOwner(db, affected_origin, distant_past,
distant_future);
if (!maybe_group_names) {
return false;
}
for (const auto& group_name : maybe_group_names.value()) {
if (!DoRemoveInterestGroup(
db, blink::InterestGroupKey(affected_origin, group_name))) {
return false;
}
}
}
affected_origins.clear();
maybe_all_origins = DoGetAllInterestGroupJoiningOrigins(db, distant_past);
if (!maybe_all_origins) {
return false;
}
for (const url::Origin& origin : maybe_all_origins.value()) {
if (storage_key_matcher.is_null() ||
storage_key_matcher.Run(blink::StorageKey::CreateFirstParty(origin))) {
affected_origins.push_back(origin);
}
}
for (const auto& affected_origin : affected_origins) {
absl::optional<std::vector<blink::InterestGroupKey>> maybe_group_names =
DoGetInterestGroupNamesForJoiningOrigin(db, affected_origin,
distant_past);
if (!maybe_group_names) {
return false;
}
for (const auto& interest_group_key : maybe_group_names.value()) {
if (!DoRemoveInterestGroup(db, interest_group_key)) {
return false;
}
}
}
return transaction.Commit();
}
bool DoSetInterestGroupPriority(sql::Database& db,
const blink::InterestGroupKey& group_key,
double priority) {
// clang-format off
sql::Statement set_priority_sql(
db.GetCachedStatement(SQL_FROM_HERE,
"UPDATE interest_groups "
"SET priority=? "
"WHERE owner=? AND name=?"));
// clang-format on
if (!set_priority_sql.is_valid()) {
DLOG(ERROR) << "SetPriority SQL statement did not compile.";
return false;
}
set_priority_sql.Reset(true);
set_priority_sql.BindDouble(0, priority);
set_priority_sql.BindString(1, Serialize(group_key.owner));
set_priority_sql.BindString(2, group_key.name);
return set_priority_sql.Run();
}
bool DoSetInterestGroupPrioritySignalsOverrides(
sql::Database& db,
const blink::InterestGroupKey& group_key,
const absl::optional<base::flat_map<std::string, double>>&
priority_signals_overrides) {
// clang-format off
sql::Statement update_priority_signals_overrides_sql(
db.GetCachedStatement(SQL_FROM_HERE,
"UPDATE interest_groups "
"SET priority_signals_overrides=? "
"WHERE owner=? AND name=?"));
// clang-format on
if (!update_priority_signals_overrides_sql.is_valid()) {
DLOG(ERROR) << "SetPrioritySignalsOverrides SQL statement did not compile.";
return false;
}
update_priority_signals_overrides_sql.Reset(true);
update_priority_signals_overrides_sql.BindString(
0, Serialize(priority_signals_overrides));
update_priority_signals_overrides_sql.BindString(1,
Serialize(group_key.owner));
update_priority_signals_overrides_sql.BindString(2, group_key.name);
return update_priority_signals_overrides_sql.Run();
}
bool DeleteOldJoins(sql::Database& db, base::Time cutoff) {
sql::Statement del_join_history(db.GetCachedStatement(
SQL_FROM_HERE, "DELETE FROM join_history WHERE join_time <= ?"));
if (!del_join_history.is_valid()) {
DLOG(ERROR) << "DeleteOldJoins SQL statement did not compile.";
return false;
}
del_join_history.Reset(true);
del_join_history.BindTime(0, cutoff);
if (!del_join_history.Run()) {
DLOG(ERROR) << "Could not delete old join_history.";
return false;
}
return true;
}
bool DeleteOldBids(sql::Database& db, base::Time cutoff) {
sql::Statement del_bid_history(db.GetCachedStatement(
SQL_FROM_HERE, "DELETE FROM bid_history WHERE bid_time <= ?"));
if (!del_bid_history.is_valid()) {
DLOG(ERROR) << "DeleteOldBids SQL statement did not compile.";
return false;
}
del_bid_history.Reset(true);
del_bid_history.BindTime(0, cutoff);
if (!del_bid_history.Run()) {
DLOG(ERROR) << "Could not delete old bid_history.";
return false;
}
return true;
}
bool DeleteOldWins(sql::Database& db, base::Time cutoff) {
sql::Statement del_win_history(db.GetCachedStatement(
SQL_FROM_HERE, "DELETE FROM win_history WHERE win_time <= ?"));
if (!del_win_history.is_valid()) {
DLOG(ERROR) << "DeleteOldWins SQL statement did not compile.";
return false;
}
del_win_history.Reset(true);
del_win_history.BindTime(0, cutoff);
if (!del_win_history.Run()) {
DLOG(ERROR) << "Could not delete old win_history.";
return false;
}
return true;
}
bool ClearExcessInterestGroups(sql::Database& db,
size_t max_owners,
size_t max_owner_interest_groups) {
const base::Time distant_past = base::Time::Min();
const base::Time distant_future = base::Time::Max();
const absl::optional<std::vector<url::Origin>> maybe_all_origins =
DoGetAllInterestGroupOwners(db, distant_past);
if (!maybe_all_origins) {
return false;
}
for (size_t owner_idx = 0; owner_idx < maybe_all_origins.value().size();
owner_idx++) {
const url::Origin& affected_origin = maybe_all_origins.value()[owner_idx];
const absl::optional<std::vector<std::string>> maybe_interest_groups =
DoGetInterestGroupNamesForOwner(db, affected_origin, distant_past,
distant_future);
if (!maybe_interest_groups) {
return false;
}
size_t first_idx = max_owner_interest_groups;
if (owner_idx >= max_owners) {
first_idx = 0;
}
for (size_t group_idx = first_idx;
group_idx < maybe_interest_groups.value().size(); group_idx++) {
if (!DoRemoveInterestGroup(
db,
blink::InterestGroupKey(
affected_origin, maybe_interest_groups.value()[group_idx]))) {
return false;
}
}
}
return true;
}
bool ClearExpiredInterestGroups(sql::Database& db,
base::Time expiration_before) {
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return false;
}
sql::Statement expired_interest_group(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT owner, name "
"FROM interest_groups "
"WHERE expiration<=?"));
if (!expired_interest_group.is_valid()) {
DLOG(ERROR) << "ClearExpiredInterestGroups SQL statement did not compile.";
return false;
}
expired_interest_group.Reset(true);
expired_interest_group.BindTime(0, expiration_before);
std::vector<blink::InterestGroupKey> expired_groups;
while (expired_interest_group.Step()) {
expired_groups.emplace_back(
DeserializeOrigin(expired_interest_group.ColumnString(0)),
expired_interest_group.ColumnString(1));
}
if (!expired_interest_group.Succeeded()) {
DLOG(ERROR) << "ClearExpiredInterestGroups could not get expired groups.";
// Keep going so we can clear any groups that we did get.
}
for (const auto& interest_group : expired_groups) {
if (!DoRemoveInterestGroup(db, interest_group)) {
return false;
}
}
return transaction.Commit();
}
// Removes interest groups so that per-owner limit is respected. Note that we're
// intentionally not trying to keep this in sync with
// `blink::InterestGroup::EstimateSize()`. There's not a compelling reason to
// keep those exactly aligned and keeping them in sync would require a
// significant amount of extra work.
bool ClearExcessiveStorage(sql::Database& db, size_t max_owner_storage_size) {
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return false;
}
// We go through groups for each owner, starting with the ones that expire
// later, accumulating the stored size. If the accumulated size is too much,
// we start marking groups for deletion. This means that the interest groups
// expiring soonest will be deleted if we need to free up space.
// clang-format off
sql::Statement excessive_storage_groups(db.GetCachedStatement(
SQL_FROM_HERE,
"SELECT owner, name, "
"(LENGTH(interest_groups.owner)+"
"LENGTH(interest_groups.joining_origin)+"
"LENGTH(interest_groups.name)+"
"LENGTH(interest_groups.priority_vector)+"
"LENGTH(interest_groups.priority_signals_overrides)+"
"LENGTH(interest_groups.seller_capabilities)+"
"LENGTH(interest_groups.joining_url)+"
"LENGTH(interest_groups.bidding_url)+"
"LENGTH(interest_groups.bidding_wasm_helper_url)+"
"LENGTH(interest_groups.update_url)+"
"LENGTH(interest_groups.trusted_bidding_signals_url)+"
"LENGTH(interest_groups.trusted_bidding_signals_keys)+"
"LENGTH(interest_groups.user_bidding_signals)+"
"LENGTH(interest_groups.ads_pb)+"
"LENGTH(interest_groups.ad_components_pb)+"
"LENGTH(interest_groups.ad_sizes)+"
"LENGTH(interest_groups.size_groups)+"
"LENGTH(interest_groups.additional_bid_key)+"
"40) " // other fields are fixed at 40 bytes
"AS cum_size "
"FROM interest_groups "
"ORDER BY owner, expiration DESC"
));
// clang-format on
if (!excessive_storage_groups.is_valid()) {
return false;
}
excessive_storage_groups.Reset(true);
std::vector<blink::InterestGroupKey> groups_to_remove;
absl::optional<url::Origin> previous;
size_t cum_size;
while (excessive_storage_groups.Step()) {
url::Origin group_owner =
DeserializeOrigin(excessive_storage_groups.ColumnString(0));
std::string group_name = excessive_storage_groups.ColumnString(1);
size_t group_size = excessive_storage_groups.ColumnInt64(2);
if (!previous || *previous != group_owner) {
previous = group_owner;
cum_size = group_size;
continue;
}
cum_size += group_size;
if (cum_size > max_owner_storage_size) {
groups_to_remove.emplace_back(std::move(group_owner),
std::move(group_name));
}
}
if (!excessive_storage_groups.Succeeded()) {
DLOG(ERROR) << "ClearExcessiveStorage could not get expired groups.";
// Keep going so we can clear any groups that we did get.
}
for (const auto& interest_group : groups_to_remove) {
if (!DoRemoveInterestGroup(db, interest_group)) {
return false;
}
}
return transaction.Commit();
}
bool ClearExpiredKAnon(sql::Database& db, base::Time cutoff) {
sql::Statement expired_kanon(
db.GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM k_anon "
"WHERE last_referenced_time <= ?"));
if (!expired_kanon.is_valid()) {
DLOG(ERROR) << "ClearExpiredKAnon SQL statement did not compile.";
return false;
}
expired_kanon.Reset(true);
expired_kanon.BindTime(0, cutoff);
return expired_kanon.Run();
}
bool DoPerformDatabaseMaintenance(sql::Database& db,
base::Time now,
size_t max_owners,
size_t max_owner_storage_size,
size_t max_owner_interest_groups) {
SCOPED_UMA_HISTOGRAM_TIMER_MICROS("Storage.InterestGroup.DBMaintenanceTime");
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return false;
}
if (!ClearExcessInterestGroups(db, max_owners, max_owner_interest_groups)) {
return false;
}
if (!ClearExpiredInterestGroups(db, now)) {
return false;
}
if (!ClearExcessiveStorage(db, max_owner_storage_size)) {
return false;
}
if (!DeleteOldJoins(db, now - InterestGroupStorage::kHistoryLength)) {
return false;
}
if (!DeleteOldBids(db, now - InterestGroupStorage::kHistoryLength)) {
return false;
}
if (!DeleteOldWins(db, now - InterestGroupStorage::kHistoryLength)) {
return false;
}
if (!ClearExpiredKAnon(db, now - InterestGroupStorage::kHistoryLength)) {
return false;
}
return transaction.Commit();
}
base::FilePath DBPath(const base::FilePath& base) {
if (base.empty()) {
return base;
}
return base.Append(kDatabasePath);
}
} // namespace
constexpr base::TimeDelta InterestGroupStorage::kHistoryLength;
constexpr base::TimeDelta InterestGroupStorage::kMaintenanceInterval;
constexpr base::TimeDelta InterestGroupStorage::kIdlePeriod;
constexpr base::TimeDelta InterestGroupStorage::kUpdateSucceededBackoffPeriod;
constexpr base::TimeDelta InterestGroupStorage::kUpdateFailedBackoffPeriod;
InterestGroupStorage::InterestGroupStorage(const base::FilePath& path)
: path_to_database_(DBPath(path)),
max_owners_(blink::features::kInterestGroupStorageMaxOwners.Get()),
max_owner_interest_groups_(
blink::features::kInterestGroupStorageMaxGroupsPerOwner.Get()),
max_owner_storage_size_(
blink::features::kInterestGroupStorageMaxStoragePerOwner.Get()),
max_ops_before_maintenance_(
blink::features::kInterestGroupStorageMaxOpsBeforeMaintenance.Get()),
db_(std::make_unique<sql::Database>(sql::DatabaseOptions{})),
db_maintenance_timer_(FROM_HERE,
kIdlePeriod,
this,
&InterestGroupStorage::PerformDBMaintenance) {
DETACH_FROM_SEQUENCE(sequence_checker_);
}
InterestGroupStorage::~InterestGroupStorage() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
bool InterestGroupStorage::EnsureDBInitialized() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::Time now = base::Time::Now();
if (now > last_maintenance_time_ + kMaintenanceInterval) {
// Schedule maintenance for next idle period. If maintenance already
// scheduled this delays it further (we're not idle).
db_maintenance_timer_.Reset();
}
// Force maintenance even if we're busy if the database may have changed too
// much.
if (ops_since_last_maintenance_++ > max_ops_before_maintenance_) {
PerformDBMaintenance();
}
last_access_time_ = now;
if (db_ && db_->is_open()) {
return true;
}
return InitializeDB();
}
bool InterestGroupStorage::InitializeDB() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
db_ = std::make_unique<sql::Database>(sql::DatabaseOptions{});
db_->set_error_callback(base::BindRepeating(
&InterestGroupStorage::DatabaseErrorCallback, base::Unretained(this)));
db_->set_histogram_tag("InterestGroups");
if (path_to_database_.empty()) {
if (!db_->OpenInMemory()) {
DLOG(ERROR) << "Failed to create in-memory interest group database: "
<< db_->GetErrorMessage();
return false;
}
} else {
const base::FilePath dir = path_to_database_.DirName();
if (!base::CreateDirectory(dir)) {
DLOG(ERROR) << "Failed to create directory for interest group database";
return false;
}
if (db_->Open(path_to_database_) == false) {
DLOG(ERROR) << "Failed to open interest group database: "
<< db_->GetErrorMessage();
return false;
}
}
if (!InitializeSchema()) {
db_->Close();
return false;
}
DCHECK(sql::MetaTable::DoesTableExist(db_.get()));
DCHECK(db_->DoesTableExist("interest_groups"));
DCHECK(db_->DoesTableExist("join_history"));
DCHECK(db_->DoesTableExist("bid_history"));
DCHECK(db_->DoesTableExist("win_history"));
DCHECK(db_->DoesTableExist("k_anon"));
return true;
}
bool InterestGroupStorage::InitializeSchema() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!db_) {
return false;
}
if (!sql::MetaTable::RazeIfIncompatible(
db_.get(), /*lowest_supported_version=*/kDeprecatedVersionNumber + 1,
kCurrentVersionNumber)) {
return false;
}
sql::MetaTable meta_table;
bool has_metatable = meta_table.DoesTableExist(db_.get());
if (!has_metatable && db_->DoesTableExist("interest_groups")) {
// Existing DB with no meta table. We have no idea what version the schema
// is so we should remove it and start fresh.
db_->Raze();
}
const bool new_db = !has_metatable;
if (!meta_table.Init(db_.get(), kCurrentVersionNumber,
kCompatibleVersionNumber)) {
return false;
}
if (new_db) {
return CreateV16Schema(*db_);
}
const int db_version = meta_table.GetVersionNumber();
if (db_version >= kCurrentVersionNumber) {
// Getting past RazeIfIncompatible implies that
// kCurrentVersionNumber >= meta_table.GetCompatibleVersionNumber
// So DB is either the current database version or a future version that is
// back-compatible with this version of Chrome.
return true;
}
// Older versions - should be migrated.
// db_version < kCurrentVersionNumber
// db_version > kDeprecatedVersionNumber
{
sql::Transaction transaction(db_.get());
if (!transaction.Begin()) {
return false;
}
switch (db_version) {
case 6:
if (!UpgradeV6SchemaToV7(*db_, meta_table)) {
return false;
}
ABSL_FALLTHROUGH_INTENDED;
case 7:
if (!UpgradeV7SchemaToV8(*db_, meta_table)) {
return false;
}
ABSL_FALLTHROUGH_INTENDED;
case 8:
if (!UpgradeV8SchemaToV9(*db_, meta_table)) {
return false;
}
ABSL_FALLTHROUGH_INTENDED;
case 9:
if (!UpgradeV9SchemaToV10(*db_, meta_table)) {
return false;
}
ABSL_FALLTHROUGH_INTENDED;
case 10:
if (!UpgradeV10SchemaToV11(*db_, meta_table)) {
return false;
}
ABSL_FALLTHROUGH_INTENDED;
case 11:
if (!UpgradeV11SchemaToV12(*db_, meta_table)) {
return false;
}
ABSL_FALLTHROUGH_INTENDED;
case 12:
if (!UpgradeV12SchemaToV13(*db_, meta_table)) {
return false;
}
ABSL_FALLTHROUGH_INTENDED;
case 13:
if (!UpgradeV13SchemaToV14(*db_, meta_table)) {
return false;
}
ABSL_FALLTHROUGH_INTENDED;
case 14:
if (!UpgradeV14SchemaToV15(*db_, meta_table)) {
return false;
}
ABSL_FALLTHROUGH_INTENDED;
case 15:
if (!UpgradeV15SchemaToV16(*db_, meta_table)) {
return false;
}
if (!meta_table.SetVersionNumber(kCurrentVersionNumber)) {
return false;
}
}
return transaction.Commit();
}
NOTREACHED(); // Only versions 6 up to the current version should have passed
// RazeIfIncompatible.
return false;
}
void InterestGroupStorage::JoinInterestGroup(
const blink::InterestGroup& group,
const GURL& main_frame_joining_url) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
base::Time now = base::Time::Now();
if (!DoJoinInterestGroup(*db_, group, main_frame_joining_url,
/*exact_join_time=*/now,
/*last_updated=*/now,
/*next_update_after=*/base::Time::Min())) {
DLOG(ERROR) << "Could not join interest group: " << db_->GetErrorMessage();
}
}
void InterestGroupStorage::LeaveInterestGroup(
const blink::InterestGroupKey& group_key,
const url::Origin& main_frame) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
blink::InterestGroup old_group;
url::Origin old_joining_origin;
if (DoLoadInterestGroup(*db_, group_key, old_group, &old_joining_origin,
/*exact_join_time=*/nullptr,
/*last_updated=*/nullptr) &&
old_group.execution_mode ==
blink::InterestGroup::ExecutionMode::kGroupedByOriginMode &&
main_frame != old_joining_origin) {
// Clear all interest groups with same owner and mode GroupedByOriginMode
// and same old_joining_origin.
if (!DoClearClusteredBiddingGroups(*db_, group_key.owner,
old_joining_origin)) {
DLOG(ERROR) << "Could not leave interest group: "
<< db_->GetErrorMessage();
}
return;
}
if (!DoRemoveInterestGroup(*db_, group_key)) {
DLOG(ERROR) << "Could not leave interest group: " << db_->GetErrorMessage();
}
}
bool InterestGroupStorage::UpdateInterestGroup(
const blink::InterestGroupKey& group_key,
InterestGroupUpdate update) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return false;
}
bool success =
DoUpdateInterestGroup(*db_, group_key, update, base::Time::Now());
if (!success) {
DLOG(ERROR) << "Could not update interest group: "
<< db_->GetErrorMessage();
}
return success;
}
void InterestGroupStorage::ReportUpdateFailed(
const blink::InterestGroupKey& group_key,
bool parse_failure) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
NOTREACHED(); // We already fetched interest groups to update...
return;
}
if (!DoReportUpdateFailed(*db_, group_key, parse_failure,
base::Time::Now())) {
DLOG(ERROR) << "Couldn't update next_update_after: "
<< db_->GetErrorMessage();
}
}
void InterestGroupStorage::RecordInterestGroupBids(
const blink::InterestGroupSet& group_keys) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
if (!DoRecordInterestGroupBids(*db_, group_keys, base::Time::Now())) {
DLOG(ERROR) << "Could not record win for interest group: "
<< db_->GetErrorMessage();
}
}
void InterestGroupStorage::RecordInterestGroupWin(
const blink::InterestGroupKey& group_key,
const std::string& ad_json) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
if (!DoRecordInterestGroupWin(*db_, group_key, ad_json, base::Time::Now())) {
DLOG(ERROR) << "Could not record bid for interest group: "
<< db_->GetErrorMessage();
}
}
void InterestGroupStorage::UpdateKAnonymity(
const StorageInterestGroup::KAnonymityData& data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
if (!DoUpdateKAnonymity(*db_, data, base::Time::Now())) {
DLOG(ERROR) << "Could not update k-anonymity: " << db_->GetErrorMessage();
}
}
absl::optional<base::Time> InterestGroupStorage::GetLastKAnonymityReported(
const std::string& key) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return {};
}
return DoGetLastKAnonymityReported(*db_, key);
}
void InterestGroupStorage::UpdateLastKAnonymityReported(
const std::string& key) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
DoUpdateLastKAnonymityReported(*db_, key, base::Time::Now());
}
absl::optional<StorageInterestGroup> InterestGroupStorage::GetInterestGroup(
const blink::InterestGroupKey& group_key) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return absl::nullopt;
}
return DoGetStoredInterestGroup(*db_, group_key, base::Time::Now());
}
std::vector<url::Origin> InterestGroupStorage::GetAllInterestGroupOwners() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return {};
}
absl::optional<std::vector<url::Origin>> maybe_result =
DoGetAllInterestGroupOwners(*db_, base::Time::Now());
if (!maybe_result) {
return {};
}
return std::move(maybe_result.value());
}
std::vector<StorageInterestGroup>
InterestGroupStorage::GetInterestGroupsForOwner(const url::Origin& owner) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return {};
}
absl::optional<std::vector<StorageInterestGroup>> maybe_result =
DoGetInterestGroupsForOwner(*db_, owner, base::Time::Now());
if (!maybe_result) {
return {};
}
base::UmaHistogramCounts1000("Storage.InterestGroup.PerSiteCount",
maybe_result->size());
return std::move(maybe_result.value());
}
std::vector<StorageInterestGroup>
InterestGroupStorage::GetInterestGroupsForUpdate(const url::Origin& owner,
size_t groups_limit) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return {};
}
absl::optional<std::vector<StorageInterestGroup>> maybe_result =
DoGetInterestGroupsForOwner(*db_, owner, base::Time::Now(),
/*get_groups_for_update=*/true);
if (!maybe_result) {
return {};
}
base::RandomShuffle(maybe_result->begin(), maybe_result->end());
maybe_result->resize(std::min(maybe_result->size(), groups_limit));
return std::move(maybe_result.value());
}
std::vector<url::Origin>
InterestGroupStorage::GetAllInterestGroupJoiningOrigins() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return {};
}
absl::optional<std::vector<url::Origin>> maybe_result =
DoGetAllInterestGroupJoiningOrigins(*db_, base::Time::Now());
if (!maybe_result) {
return {};
}
return std::move(maybe_result.value());
}
std::vector<std::pair<url::Origin, url::Origin>>
InterestGroupStorage::GetAllInterestGroupOwnerJoinerPairs() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return {};
}
absl::optional<std::vector<std::pair<url::Origin, url::Origin>>>
maybe_result =
DoGetAllInterestGroupOwnerJoinerPairs(*db_, base::Time::Now());
if (!maybe_result) {
return {};
}
return std::move(maybe_result.value());
}
void InterestGroupStorage::RemoveInterestGroupsMatchingOwnerAndJoiner(
url::Origin owner,
url::Origin joining_origin) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
if (!DoRemoveInterestGroupsMatchingOwnerAndJoiner(*db_, owner, joining_origin,
base::Time::Now())) {
DLOG(ERROR)
<< "Could not remove interest groups matching owner and joiner: "
<< db_->GetErrorMessage();
}
return;
}
void InterestGroupStorage::DeleteInterestGroupData(
StoragePartition::StorageKeyMatcherFunction storage_key_matcher) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
if (!DoDeleteInterestGroupData(*db_, storage_key_matcher)) {
DLOG(ERROR) << "Could not delete interest group data: "
<< db_->GetErrorMessage();
}
}
void InterestGroupStorage::DeleteAllInterestGroupData() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
db_->RazeAndPoison();
db_.reset();
}
void InterestGroupStorage::SetInterestGroupPriority(
const blink::InterestGroupKey& group_key,
double priority) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
if (!DoSetInterestGroupPriority(*db_, group_key, priority)) {
DLOG(ERROR) << "Could not set interest group priority: "
<< db_->GetErrorMessage();
}
}
void InterestGroupStorage::UpdateInterestGroupPriorityOverrides(
const blink::InterestGroupKey& group_key,
base::flat_map<std::string,
auction_worklet::mojom::PrioritySignalsDoublePtr>
update_priority_signals_overrides) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
blink::InterestGroup group;
if (!DoLoadInterestGroup(*db_, group_key, group)) {
return;
}
MergePrioritySignalsOverrides(update_priority_signals_overrides,
group.priority_signals_overrides);
if (!group.IsValid()) {
// TODO(mmenke): Report errors to devtools.
return;
}
if (!DoSetInterestGroupPrioritySignalsOverrides(
*db_, group_key, group.priority_signals_overrides)) {
DLOG(ERROR) << "Could not set interest group priority signals overrides: "
<< db_->GetErrorMessage();
}
}
void InterestGroupStorage::PerformDBMaintenance() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
last_maintenance_time_ = base::Time::Now();
ops_since_last_maintenance_ = 0;
int64_t db_size;
if (base::GetFileSize(path_to_database_, &db_size)) {
UMA_HISTOGRAM_MEMORY_KB("Storage.InterestGroup.DBSize", db_size / 1024);
}
if (EnsureDBInitialized()) {
DoPerformDatabaseMaintenance(
*db_, last_maintenance_time_, /*max_owners=*/max_owners_,
/*max_owner_storage_size=*/max_owner_storage_size_,
/*max_owner_interest_groups=*/max_owner_interest_groups_);
}
}
std::vector<StorageInterestGroup>
InterestGroupStorage::GetAllInterestGroupsUnfilteredForTesting() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return {};
}
const base::Time distant_past = base::Time::Min();
std::vector<StorageInterestGroup> result;
absl::optional<std::vector<url::Origin>> maybe_owners =
DoGetAllInterestGroupOwners(*db_, distant_past);
if (!maybe_owners) {
return {};
}
for (const auto& owner : *maybe_owners) {
absl::optional<std::vector<StorageInterestGroup>> maybe_owner_results =
DoGetInterestGroupsForOwner(*db_, owner, distant_past);
DCHECK(maybe_owner_results) << owner;
std::move(maybe_owner_results->begin(), maybe_owner_results->end(),
std::back_inserter(result));
}
return result;
}
base::Time InterestGroupStorage::GetLastMaintenanceTimeForTesting() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return last_maintenance_time_;
}
void InterestGroupStorage::DatabaseErrorCallback(int extended_error,
sql::Statement* stmt) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Only save the basic error code (not extended) to UMA.
base::UmaHistogramExactLinear("Storage.InterestGroup.DBErrors",
extended_error & 0xFF,
/*sqlite error max+1*/ SQLITE_WARNING + 1);
if (sql::IsErrorCatastrophic(extended_error)) {
// Normally this will poison the database, causing any subsequent operations
// to silently fail without any side effects. However, if RazeAndPoison() is
// called from the error callback in response to an error raised from within
// sql::Database::Open, opening the now-razed database will be retried.
db_->RazeAndPoison();
return;
}
// The default handling is to assert on debug and to ignore on release.
if (!sql::Database::IsExpectedSqliteError(extended_error)) {
DLOG(FATAL) << db_->GetErrorMessage();
}
}
} // namespace content