Add user and brewery address tables

This commit is contained in:
Aaron Po
2026-07-13 19:59:18 -04:00
parent d52dba904c
commit 58e476afdb
13 changed files with 237 additions and 151 deletions

View File

@@ -7,6 +7,7 @@
* enriched data, and complete generation results. * enriched data, and complete generation results.
*/ */
#include <optional>
#include <string> #include <string>
#include "data_model/models.h" #include "data_model/models.h"
@@ -92,22 +93,40 @@ struct EnrichedCity {
}; };
/** /**
* @brief A brewery's street-level address, distinct from the shared `City` * @brief A brewery's address. Owns the `City` it belongs to alongside its
* it belongs to. Mirrors the backend's `BreweryPostLocation` shape. * street-level fields. Mirrors the `brewery_addresses` table and the
* backend's `BreweryPostLocation` shape.
* *
* Only `postal_code` is populated today -- street-address generation has no * Only `city` and `postal_code` are populated today -- street-address
* fixture data or service yet. * generation has no fixture data or service yet, so the address lines stay
* empty.
*/ */
struct Address { struct BreweryAddress {
City city{};
std::optional<std::string> address_line_1{};
std::optional<std::string> address_line_2{};
std::string postal_code{}; std::string postal_code{};
}; };
/**
* @brief A user's address. Owns the `City` it belongs to alongside its
* street-level fields. Mirrors the `user_addresses` table.
*
* Only `city` is populated today; the street-level columns exist for future
* enrichment.
*/
struct UserAddress {
City city{};
std::optional<std::string> address_line_1{};
std::optional<std::string> address_line_2{};
std::optional<std::string> postal_code{};
};
/** /**
* @brief Helper struct to store generated brewery data. * @brief Helper struct to store generated brewery data.
*/ */
struct BreweryRecord { struct BreweryRecord {
City location; BreweryAddress address;
Address address;
BreweryResult brewery; BreweryResult brewery;
}; };
@@ -119,7 +138,7 @@ struct BreweryRecord {
* consumer can register real accounts from the pipeline's SQLite export. * consumer can register real accounts from the pipeline's SQLite export.
*/ */
struct UserRecord { struct UserRecord {
City location; UserAddress address{};
UserResult user; UserResult user;
std::string email{}; std::string email{};
std::string date_of_birth{}; std::string date_of_birth{};

View File

@@ -60,16 +60,6 @@ struct City {
* @brief Local language codes in priority order. * @brief Local language codes in priority order.
*/ */
std::vector<std::string> local_languages{}; std::vector<std::string> local_languages{};
/**
* @brief Latitude in decimal degrees.
*/
double latitude{};
/**
* @brief Longitude in decimal degrees.
*/
double longitude{};
}; };
// ============================================================================ // ============================================================================

View File

@@ -63,7 +63,9 @@ class SqliteExportService final : public IExportService {
SqliteDatabaseHandle db_handle_; SqliteDatabaseHandle db_handle_;
SqliteStatementHandle insert_city_stmt_; SqliteStatementHandle insert_city_stmt_;
SqliteStatementHandle insert_brewery_stmt_; SqliteStatementHandle insert_brewery_stmt_;
SqliteStatementHandle insert_brewery_address_stmt_;
SqliteStatementHandle insert_user_stmt_; SqliteStatementHandle insert_user_stmt_;
SqliteStatementHandle insert_user_address_stmt_;
bool transaction_open_ = false; bool transaction_open_ = false;
std::unordered_map<std::string, sqlite3_int64> city_cache_; std::unordered_map<std::string, sqlite3_int64> city_cache_;
}; };

View File

@@ -27,9 +27,7 @@ CREATE TABLE IF NOT EXISTS cities (
country TEXT NOT NULL, country TEXT NOT NULL,
iso3166_1 TEXT NOT NULL, iso3166_1 TEXT NOT NULL,
local_languages_json TEXT NOT NULL, local_languages_json TEXT NOT NULL,
latitude REAL NOT NULL, UNIQUE(city, state_province, iso3166_2, country)
longitude REAL NOT NULL,
UNIQUE(city, state_province, iso3166_2, country, latitude, longitude)
); );
)sql"; )sql";
@@ -43,19 +41,16 @@ CREATE TABLE IF NOT EXISTS breweries (
description_en TEXT NOT NULL, description_en TEXT NOT NULL,
name_local TEXT NOT NULL, name_local TEXT NOT NULL,
description_local TEXT NOT NULL, description_local TEXT NOT NULL,
postal_code TEXT NOT NULL,
FOREIGN KEY(city_id) REFERENCES cities(id) ON DELETE CASCADE FOREIGN KEY(city_id) REFERENCES cities(id) ON DELETE CASCADE
); );
CREATE INDEX IF NOT EXISTS idx_breweries_city_id ON breweries(city_id); CREATE INDEX IF NOT EXISTS idx_breweries_city_id ON breweries(city_id);
)sql"; )sql";
inline constexpr std::string_view kCreateUsersTableSql = R"sql( inline constexpr std::string_view kCreateUsersTableSql = R"sql(
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
city_id INTEGER NOT NULL,
first_name TEXT NOT NULL, first_name TEXT NOT NULL,
last_name TEXT NOT NULL, last_name TEXT NOT NULL,
gender TEXT NOT NULL, gender TEXT NOT NULL,
@@ -63,12 +58,45 @@ CREATE TABLE IF NOT EXISTS users (
bio TEXT NOT NULL, bio TEXT NOT NULL,
activity_weight REAL NOT NULL, activity_weight REAL NOT NULL,
email TEXT NOT NULL UNIQUE, email TEXT NOT NULL UNIQUE,
date_of_birth TEXT NOT NULL, date_of_birth TEXT NOT NULL
);
)sql";
inline constexpr std::string_view kCreateBreweryAddressTableSql = R"sql(
CREATE TABLE IF NOT EXISTS brewery_addresses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
brewery_id INTEGER NOT NULL,
address_line_1 TEXT,
address_line_2 TEXT,
postal_code TEXT NOT NULL,
city_id INTEGER NOT NULL,
FOREIGN KEY(brewery_id) REFERENCES breweries(id) ON DELETE CASCADE,
FOREIGN KEY(city_id) REFERENCES cities(id) ON DELETE CASCADE FOREIGN KEY(city_id) REFERENCES cities(id) ON DELETE CASCADE
); );
CREATE INDEX IF NOT EXISTS idx_users_city_id ON users(city_id); CREATE INDEX IF NOT EXISTS idx_brewery_addresses_brewery_id
ON brewery_addresses(brewery_id);
CREATE INDEX IF NOT EXISTS idx_brewery_addresses_city_id
ON brewery_addresses(city_id);
)sql";
inline constexpr std::string_view kCreateUserAddressTableSql = R"sql(
CREATE TABLE IF NOT EXISTS user_addresses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
address_line_1 TEXT,
address_line_2 TEXT,
postal_code TEXT,
city_id INTEGER NOT NULL,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(city_id) REFERENCES cities(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_user_addresses_user_id
ON user_addresses(user_id);
CREATE INDEX IF NOT EXISTS idx_user_addresses_city_id
ON user_addresses(city_id);
)sql"; )sql";
inline constexpr std::string_view kInsertCitySql = R"sql( inline constexpr std::string_view kInsertCitySql = R"sql(
@@ -78,10 +106,8 @@ INSERT INTO cities (
iso3166_2, iso3166_2,
country, country,
iso3166_1, iso3166_1,
local_languages_json, local_languages_json
latitude, ) VALUES (?, ?, ?, ?, ?, ?);
longitude
) VALUES (?, ?, ?, ?, ?, ?, ?, ?);
)sql"; )sql";
inline constexpr std::string_view kInsertBrewerySql = R"sql( inline constexpr std::string_view kInsertBrewerySql = R"sql(
@@ -90,14 +116,12 @@ INSERT INTO breweries (
name_en, name_en,
description_en, description_en,
name_local, name_local,
description_local, description_local
postal_code ) VALUES (?, ?, ?, ?, ?);
) VALUES (?, ?, ?, ?, ?, ?);
)sql"; )sql";
inline constexpr std::string_view kInsertUserSql = R"sql( inline constexpr std::string_view kInsertUserSql = R"sql(
INSERT INTO users ( INSERT INTO users (
city_id,
first_name, first_name,
last_name, last_name,
gender, gender,
@@ -106,7 +130,22 @@ INSERT INTO users (
activity_weight, activity_weight,
email, email,
date_of_birth date_of_birth
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?); ) VALUES (?, ?, ?, ?, ?, ?, ?, ?);
)sql";
inline constexpr std::string_view kInsertBreweryAddressSql = R"sql(
INSERT INTO brewery_addresses (
brewery_id,
postal_code,
city_id
) VALUES (?, ?, ?);
)sql";
inline constexpr std::string_view kInsertUserAddressSql = R"sql(
INSERT INTO user_addresses (
user_id,
city_id
) VALUES (?, ?);
)sql"; )sql";
// sqlite3_bind_*() parameter indices are 1-based, matching the "?" // sqlite3_bind_*() parameter indices are 1-based, matching the "?"
@@ -118,8 +157,6 @@ enum CityBindIndex {
kCityCountryBindIndex, kCityCountryBindIndex,
kCityIso31661BindIndex, kCityIso31661BindIndex,
kCityLanguagesBindIndex, kCityLanguagesBindIndex,
kCityLatitudeBindIndex,
kCityLongitudeBindIndex,
}; };
enum BreweryBindIndex { enum BreweryBindIndex {
@@ -128,12 +165,10 @@ enum BreweryBindIndex {
kBreweryEnglishDescriptionBindIndex, kBreweryEnglishDescriptionBindIndex,
kBreweryLocalNameBindIndex, kBreweryLocalNameBindIndex,
kBreweryLocalDescriptionBindIndex, kBreweryLocalDescriptionBindIndex,
kBreweryPostalCodeBindIndex,
}; };
enum UserBindIndex { enum UserBindIndex {
kUserCityIdBindIndex = 1, kUserFirstNameBindIndex = 1,
kUserFirstNameBindIndex,
kUserLastNameBindIndex, kUserLastNameBindIndex,
kUserGenderBindIndex, kUserGenderBindIndex,
kUserUsernameBindIndex, kUserUsernameBindIndex,
@@ -143,6 +178,17 @@ enum UserBindIndex {
kUserDateOfBirthBindIndex, kUserDateOfBirthBindIndex,
}; };
enum BreweryAddressBindIndex {
kBreweryAddressBreweryIdBindIndex = 1,
kBreweryAddressPostalCodeBindIndex,
kBreweryAddressCityIdBindIndex,
};
enum UserAddressBindIndex {
kUserAddressUserIdBindIndex = 1,
kUserAddressCityIdBindIndex,
};
SqliteStatementHandle PrepareStatement(const SqliteDatabaseHandle& db_handle, SqliteStatementHandle PrepareStatement(const SqliteDatabaseHandle& db_handle,
std::string_view sql, std::string_view sql,
const char* action); const char* action);

View File

@@ -29,9 +29,10 @@ void BiergartenPipelineOrchestrator::GenerateBreweries(
generator_->GenerateBrewery(location, region_context); generator_->GenerateBrewery(location, region_context);
const std::string postal_code = const std::string postal_code =
postal_code_service_->GeneratePostalCode(location); postal_code_service_->GeneratePostalCode(location);
return BreweryRecord{.location = location, return BreweryRecord{
.address = Address{.postal_code = postal_code}, .address =
.brewery = brewery}; BreweryAddress{.city = location, .postal_code = postal_code},
.brewery = brewery};
} catch (const std::exception& e) { } catch (const std::exception& e) {
++skipped_count; ++skipped_count;
@@ -51,13 +52,13 @@ void BiergartenPipelineOrchestrator::GenerateBreweries(
exporter_->ProcessRecord(record); exporter_->ProcessRecord(record);
} catch (const std::exception& export_exception) { } catch (const std::exception& export_exception) {
++export_failed_count; ++export_failed_count;
logger_->Log( logger_->Log({.level = LogLevel::Warn,
{.level = LogLevel::Warn, .phase = PipelinePhase::BreweryAndBeerGeneration,
.phase = PipelinePhase::BreweryAndBeerGeneration, .message = std::format(
.message = std::format("[Pipeline] Generated brewery for '{}' ({}) " "[Pipeline] Generated brewery for '{}' ({}) "
"but SQLite export failed: {}", "but SQLite export failed: {}",
record.location.city, record.location.country, record.address.city.city, record.address.city.country,
export_exception.what())}); export_exception.what())});
} }
}; };

View File

@@ -137,7 +137,7 @@ void BiergartenPipelineOrchestrator::GenerateUsers(
generator_->GenerateUser(city, persona, sampled_name); generator_->GenerateUser(city, persona, sampled_name);
return UserRecord{ return UserRecord{
.location = city.location, .address = UserAddress{.city = city.location},
.user = user, .user = user,
.email = BuildEmail(sampled_name, used_email_local_parts), .email = BuildEmail(sampled_name, used_email_local_parts),
.date_of_birth = GenerateDateOfBirth(rng), .date_of_birth = GenerateDateOfBirth(rng),
@@ -161,13 +161,13 @@ void BiergartenPipelineOrchestrator::GenerateUsers(
} catch (const std::exception& export_exception) { } catch (const std::exception& export_exception) {
++export_failed_count; ++export_failed_count;
logger_->Log( logger_->Log({.level = LogLevel::Warn,
{.level = LogLevel::Warn, .phase = PipelinePhase::UserGeneration,
.phase = PipelinePhase::UserGeneration, .message = std::format(
.message = std::format("[Pipeline] Generated user for '{}' ({}) but " "[Pipeline] Generated user for '{}' ({}) but "
"SQLite export failed: {}", "SQLite export failed: {}",
record.location.city, record.location.country, record.address.city.city, record.address.city.country,
export_exception.what())}); export_exception.what())});
} }
}; };

View File

@@ -14,7 +14,8 @@
void BiergartenPipelineOrchestrator::LogResults() const { void BiergartenPipelineOrchestrator::LogResults() const {
boost::json::array brewery_output; boost::json::array brewery_output;
for (const auto& [location, address, brewery] : generated_breweries_) { for (const auto& [address, brewery] : generated_breweries_) {
const City& location = address.city;
brewery_output.push_back(boost::json::object{ brewery_output.push_back(boost::json::object{
{"name_en", brewery.name_en}, {"name_en", brewery.name_en},
{"description_en", brewery.description_en}, {"description_en", brewery.description_en},
@@ -26,8 +27,6 @@ void BiergartenPipelineOrchestrator::LogResults() const {
{"country", location.country}, {"country", location.country},
{"state_province", location.state_province}, {"state_province", location.state_province},
{"iso3166_2", location.iso3166_2}, {"iso3166_2", location.iso3166_2},
{"latitude", location.latitude},
{"longitude", location.longitude},
}}}); }}});
} }

View File

@@ -136,8 +136,7 @@ const CityList& CuratedJsonDataService::LoadCities() {
const boost::json::value* postal_code = object.if_contains("postal_code"); const boost::json::value* postal_code = object.if_contains("postal_code");
if (postal_code == nullptr || !postal_code->is_object()) { if (postal_code == nullptr || !postal_code->is_object()) {
throw std::runtime_error( throw std::runtime_error("Missing or invalid object field: postal_code");
"Missing or invalid object field: postal_code");
} }
locations.push_back(City{ locations.push_back(City{
@@ -147,8 +146,6 @@ const CityList& CuratedJsonDataService::LoadCities() {
.country = ReadRequiredString(object, "country"), .country = ReadRequiredString(object, "country"),
.iso3166_1 = ReadRequiredString(object, "iso3166_1"), .iso3166_1 = ReadRequiredString(object, "iso3166_1"),
.local_languages = ReadRequiredStringArray(object, "local_languages"), .local_languages = ReadRequiredStringArray(object, "local_languages"),
.latitude = ReadRequiredNumber(object, "latitude"),
.longitude = ReadRequiredNumber(object, "longitude"),
.postal_regex = .postal_regex =
ReadRequiredStringArray(postal_code->as_object(), "city_regex"), ReadRequiredStringArray(postal_code->as_object(), "city_regex"),
.postal_code_examples = .postal_code_examples =
@@ -159,13 +156,21 @@ const CityList& CuratedJsonDataService::LoadCities() {
for (auto location : locations) { for (auto location : locations) {
std::cout << "Location: " << location.city << ", " std::cout << "Location: " << location.city << ", "
<< location.state_province << ", " << location.iso3166_2 << ", " << location.state_province << ", " << location.iso3166_2 << ", "
<< location.country << ", " << location.iso3166_1 << ", " << location.country << ", " << location.iso3166_1 << "\n";
<< location.latitude << ", " << location.longitude << std::endl;
for (const auto& regex : location.postal_regex) { for (const auto& regex : location.postal_regex) {
std::cout << " Postal regex: " << regex << std::endl; std::cout << " Postal regex: " << regex << "\n";
}
for (const auto& example : location.postal_code_examples) {
std::cout << " Postal example: " << example << "\n";
}
for (const auto& lang : location.local_languages) {
std::cout << " Local language: " << lang << "\n";
} }
} }
cache_.locations = std::move(locations); cache_.locations = std::move(locations);
return cache_.locations; return cache_.locations;
} }

View File

@@ -7,46 +7,38 @@
MockCuratedDataService::MockCuratedDataService() MockCuratedDataService::MockCuratedDataService()
: locations_{ : locations_{
City{.city = "Portland", {.city = "Portland",
.state_province = "Oregon", .state_province = "Oregon",
.iso3166_2 = "US-OR", .iso3166_2 = "US-OR",
.country = "United States", .country = "United States",
.iso3166_1 = "US", .iso3166_1 = "US",
.local_languages = {"en"}, .local_languages = {"en"},
.latitude = 45.5152, .postal_regex = {"^972[0-9]{2}(?:-[0-9]{4})?$"},
.longitude = -122.6784, .postal_code_examples = {"97201", "97294"}},
.postal_regex = {"^972[0-9]{2}(?:-[0-9]{4})?$"}, {.city = "Munich",
.postal_code_examples = {"97201", "97294"}}, .state_province = "Bavaria",
City{.city = "Munich", .iso3166_2 = "DE-BY",
.state_province = "Bavaria", .country = "Germany",
.iso3166_2 = "DE-BY", .iso3166_1 = "DE",
.country = "Germany", .local_languages = {"de"},
.iso3166_1 = "DE", .postal_regex = {"^8[01][0-9]{3}$"},
.local_languages = {"de"}, .postal_code_examples = {"80331", "81929"}},
.latitude = 48.1351, {.city = "Lyon",
.longitude = 11.5820, .state_province = "Auvergne-Rhone-Alpes",
.postal_regex = {"^8[01][0-9]{3}$"}, .iso3166_2 = "FR-ARA",
.postal_code_examples = {"80331", "81929"}}, .country = "France",
City{.city = "Lyon", .iso3166_1 = "FR",
.state_province = "Auvergne-Rhone-Alpes", .local_languages = {"fr"},
.iso3166_2 = "FR-ARA", .postal_regex = {"^6900[1-9]$"},
.country = "France", .postal_code_examples = {"69001", "69009"}},
.iso3166_1 = "FR", {.city = "Brussels",
.local_languages = {"fr"}, .state_province = "Brussels-Capital",
.latitude = 45.7640, .iso3166_2 = "BE-BRU",
.longitude = 4.8357, .country = "Belgium",
.postal_regex = {"^6900[1-9]$"}, .iso3166_1 = "BE",
.postal_code_examples = {"69001", "69009"}}, .local_languages = {"nl", "fr"},
City{.city = "Brussels", .postal_regex = {"^1(?:0[0-9]{2}|1[0-9]{2}|20[0-9]|210)$"},
.state_province = "Brussels-Capital", .postal_code_examples = {"1000", "1210"}},
.iso3166_2 = "BE-BRU",
.country = "Belgium",
.iso3166_1 = "BE",
.local_languages = {"nl", "fr"},
.latitude = 50.8503,
.longitude = 4.3517,
.postal_regex = {"^1(?:0[0-9]{2}|1[0-9]{2}|20[0-9]|210)$"},
.postal_code_examples = {"1000", "1210"}},
}, },
personas_{ personas_{
UserPersona{.name = "Hophead Explorer", UserPersona{.name = "Hophead Explorer",
@@ -91,9 +83,7 @@ MockCuratedDataService::MockCuratedDataService()
{"BE", SurnameList{"Peeters", "Janssens"}}, {"BE", SurnameList{"Peeters", "Janssens"}},
} {} } {}
const CityList& MockCuratedDataService::LoadCities() { const CityList& MockCuratedDataService::LoadCities() { return locations_; }
return locations_;
}
const PersonasList& MockCuratedDataService::LoadPersonas() { return personas_; } const PersonasList& MockCuratedDataService::LoadPersonas() { return personas_; }

View File

@@ -14,7 +14,9 @@ void SqliteExportService::Finalize() {
} }
try { try {
insert_user_address_stmt_.reset();
insert_user_stmt_.reset(); insert_user_stmt_.reset();
insert_brewery_address_stmt_.reset();
insert_brewery_stmt_.reset(); insert_brewery_stmt_.reset();
insert_city_stmt_.reset(); insert_city_stmt_.reset();
if (transaction_open_) { if (transaction_open_) {

View File

@@ -36,6 +36,12 @@ void SqliteExportService::InitializeSchema() const {
sqlite_export_service_internal::ExecSql( sqlite_export_service_internal::ExecSql(
db_handle_, sqlite_export_service_internal::kCreateUsersTableSql, db_handle_, sqlite_export_service_internal::kCreateUsersTableSql,
"Failed to create SQLite users table"); "Failed to create SQLite users table");
sqlite_export_service_internal::ExecSql(
db_handle_, sqlite_export_service_internal::kCreateBreweryAddressTableSql,
"Failed to create SQLite brewery addresses table");
sqlite_export_service_internal::ExecSql(
db_handle_, sqlite_export_service_internal::kCreateUserAddressTableSql,
"Failed to create SQLite user addresses table");
} }
void SqliteExportService::PrepareStatements() { void SqliteExportService::PrepareStatements() {
@@ -45,9 +51,16 @@ void SqliteExportService::PrepareStatements() {
insert_brewery_stmt_ = sqlite_export_service_internal::PrepareStatement( insert_brewery_stmt_ = sqlite_export_service_internal::PrepareStatement(
db_handle_, sqlite_export_service_internal::kInsertBrewerySql, db_handle_, sqlite_export_service_internal::kInsertBrewerySql,
"Failed to prepare SQLite brewery insert statement"); "Failed to prepare SQLite brewery insert statement");
insert_brewery_address_stmt_ =
sqlite_export_service_internal::PrepareStatement(
db_handle_, sqlite_export_service_internal::kInsertBreweryAddressSql,
"Failed to prepare SQLite brewery address insert statement");
insert_user_stmt_ = sqlite_export_service_internal::PrepareStatement( insert_user_stmt_ = sqlite_export_service_internal::PrepareStatement(
db_handle_, sqlite_export_service_internal::kInsertUserSql, db_handle_, sqlite_export_service_internal::kInsertUserSql,
"Failed to prepare SQLite user insert statement"); "Failed to prepare SQLite user insert statement");
insert_user_address_stmt_ = sqlite_export_service_internal::PrepareStatement(
db_handle_, sqlite_export_service_internal::kInsertUserAddressSql,
"Failed to prepare SQLite user address insert statement");
} }
void SqliteExportService::RollbackAndCloseNoThrow() noexcept { void SqliteExportService::RollbackAndCloseNoThrow() noexcept {
@@ -60,7 +73,9 @@ void SqliteExportService::RollbackAndCloseNoThrow() noexcept {
transaction_open_ = false; transaction_open_ = false;
} }
insert_user_address_stmt_.reset();
insert_user_stmt_.reset(); insert_user_stmt_.reset();
insert_brewery_address_stmt_.reset();
insert_brewery_stmt_.reset(); insert_brewery_stmt_.reset();
insert_city_stmt_.reset(); insert_city_stmt_.reset();
db_handle_.reset(); db_handle_.reset();

View File

@@ -4,7 +4,6 @@
* and the shared city-resolution helper. * and the shared city-resolution helper.
*/ */
#include <iomanip>
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
@@ -12,8 +11,6 @@
#include "services/database/sqlite_export_service.h" #include "services/database/sqlite_export_service.h"
#include "services/database/sqlite_export_service_helpers.h" #include "services/database/sqlite_export_service_helpers.h"
constexpr int kLocationPrecision = 17;
std::string SqliteExportService::BuildCityKey(const City& location) { std::string SqliteExportService::BuildCityKey(const City& location) {
std::ostringstream key_stream; std::ostringstream key_stream;
key_stream << location.city << '\n' key_stream << location.city << '\n'
@@ -21,10 +18,6 @@ std::string SqliteExportService::BuildCityKey(const City& location) {
<< location.iso3166_2 << '\n' << location.iso3166_2 << '\n'
<< location.country << '\n' << location.country << '\n'
<< location.iso3166_1 << '\n' << location.iso3166_1 << '\n'
<< std::setprecision(kLocationPrecision) << location.latitude
<< '\n'
<< std::setprecision(kLocationPrecision) << location.longitude
<< '\n'
<< sqlite_export_service_internal::SerializeVector( << sqlite_export_service_internal::SerializeVector(
location.local_languages); location.local_languages);
return key_stream.str(); return key_stream.str();
@@ -49,8 +42,7 @@ sqlite3_int64 SqliteExportService::ResolveCityId(const City& location) {
sqlite_export_service_internal::Bind( sqlite_export_service_internal::Bind(
insert_city_stmt_, insert_city_stmt_,
sqlite_export_service_internal::BoundParam<std::string_view>{ sqlite_export_service_internal::BoundParam<std::string_view>{
.index = .index = sqlite_export_service_internal::kCityStateProvinceBindIndex,
sqlite_export_service_internal::kCityStateProvinceBindIndex,
.value = location.state_province, .value = location.state_province,
.action = "Failed to bind SQLite city state/province"}); .action = "Failed to bind SQLite city state/province"});
sqlite_export_service_internal::Bind( sqlite_export_service_internal::Bind(
@@ -77,22 +69,9 @@ sqlite3_int64 SqliteExportService::ResolveCityId(const City& location) {
.index = sqlite_export_service_internal::kCityLanguagesBindIndex, .index = sqlite_export_service_internal::kCityLanguagesBindIndex,
.value = local_languages_json, .value = local_languages_json,
.action = "Failed to bind SQLite city languages"}); .action = "Failed to bind SQLite city languages"});
sqlite_export_service_internal::Bind(
insert_city_stmt_,
sqlite_export_service_internal::BoundParam{
.index = sqlite_export_service_internal::kCityLatitudeBindIndex,
.value = location.latitude,
.action = "Failed to bind SQLite city latitude"});
sqlite_export_service_internal::Bind(
insert_city_stmt_,
sqlite_export_service_internal::BoundParam{
.index = sqlite_export_service_internal::kCityLongitudeBindIndex,
.value = location.longitude,
.action = "Failed to bind SQLite city longitude"});
sqlite_export_service_internal::StepStatement( sqlite_export_service_internal::StepStatement(
db_handle_, insert_city_stmt_, db_handle_, insert_city_stmt_, "Failed to insert SQLite city row");
"Failed to insert SQLite city row");
const sqlite3_int64 city_id = const sqlite3_int64 city_id =
sqlite_export_service_internal::LastInsertRowId(db_handle_); sqlite_export_service_internal::LastInsertRowId(db_handle_);
@@ -107,7 +86,7 @@ uint64_t SqliteExportService::ProcessRecord(const BreweryRecord& brewery) {
throw std::runtime_error("SQLite export service is not initialized"); throw std::runtime_error("SQLite export service is not initialized");
} }
const sqlite3_int64 city_id = ResolveCityId(brewery.location); const sqlite3_int64 city_id = ResolveCityId(brewery.address.city);
sqlite_export_service_internal::Bind( sqlite_export_service_internal::Bind(
insert_brewery_stmt_, insert_brewery_stmt_,
@@ -146,17 +125,40 @@ uint64_t SqliteExportService::ProcessRecord(const BreweryRecord& brewery) {
.value = brewery.brewery.description_local, .value = brewery.brewery.description_local,
.action = "Failed to bind SQLite brewery local description"}); .action = "Failed to bind SQLite brewery local description"});
sqlite_export_service_internal::Bind(
insert_brewery_stmt_,
sqlite_export_service_internal::BoundParam<std::string_view>{
.index = sqlite_export_service_internal::kBreweryPostalCodeBindIndex,
.value = brewery.address.postal_code,
.action = "Failed to bind SQLite brewery postal code"});
sqlite_export_service_internal::StepStatement( sqlite_export_service_internal::StepStatement(
db_handle_, insert_brewery_stmt_, "Failed to insert SQLite brewery row"); db_handle_, insert_brewery_stmt_, "Failed to insert SQLite brewery row");
const sqlite3_int64 brewery_id =
sqlite_export_service_internal::LastInsertRowId(db_handle_);
sqlite_export_service_internal::ResetStatement(insert_brewery_stmt_); sqlite_export_service_internal::ResetStatement(insert_brewery_stmt_);
return sqlite_export_service_internal::LastInsertRowId(db_handle_); sqlite_export_service_internal::Bind(
insert_brewery_address_stmt_,
sqlite_export_service_internal::BoundParam<sqlite3_int64>{
.index =
sqlite_export_service_internal::kBreweryAddressBreweryIdBindIndex,
.value = brewery_id,
.action = "Failed to bind SQLite brewery address brewery id"});
sqlite_export_service_internal::Bind(
insert_brewery_address_stmt_,
sqlite_export_service_internal::BoundParam<std::string_view>{
.index = sqlite_export_service_internal::
kBreweryAddressPostalCodeBindIndex,
.value = brewery.address.postal_code,
.action = "Failed to bind SQLite brewery address postal code"});
sqlite_export_service_internal::Bind(
insert_brewery_address_stmt_,
sqlite_export_service_internal::BoundParam<sqlite3_int64>{
.index =
sqlite_export_service_internal::kBreweryAddressCityIdBindIndex,
.value = city_id,
.action = "Failed to bind SQLite brewery address city id"});
sqlite_export_service_internal::StepStatement(
db_handle_, insert_brewery_address_stmt_,
"Failed to insert SQLite brewery address row");
sqlite_export_service_internal::ResetStatement(insert_brewery_address_stmt_);
return brewery_id;
} }

View File

@@ -13,14 +13,8 @@ uint64_t SqliteExportService::ProcessRecord(const UserRecord& user) {
throw std::runtime_error("SQLite export service is not initialized"); throw std::runtime_error("SQLite export service is not initialized");
} }
const sqlite3_int64 city_id = ResolveCityId(user.location); const sqlite3_int64 city_id = ResolveCityId(user.address.city);
sqlite_export_service_internal::Bind(
insert_user_stmt_,
sqlite_export_service_internal::BoundParam<sqlite3_int64>{
.index = sqlite_export_service_internal::kUserCityIdBindIndex,
.value = city_id,
.action = "Failed to bind SQLite user city id"});
sqlite_export_service_internal::Bind( sqlite_export_service_internal::Bind(
insert_user_stmt_, insert_user_stmt_,
sqlite_export_service_internal::BoundParam<std::string_view>{ sqlite_export_service_internal::BoundParam<std::string_view>{
@@ -73,7 +67,28 @@ uint64_t SqliteExportService::ProcessRecord(const UserRecord& user) {
sqlite_export_service_internal::StepStatement( sqlite_export_service_internal::StepStatement(
db_handle_, insert_user_stmt_, "Failed to insert SQLite user row"); db_handle_, insert_user_stmt_, "Failed to insert SQLite user row");
const sqlite3_int64 user_id =
sqlite_export_service_internal::LastInsertRowId(db_handle_);
sqlite_export_service_internal::ResetStatement(insert_user_stmt_); sqlite_export_service_internal::ResetStatement(insert_user_stmt_);
return sqlite_export_service_internal::LastInsertRowId(db_handle_); sqlite_export_service_internal::Bind(
insert_user_address_stmt_,
sqlite_export_service_internal::BoundParam<sqlite3_int64>{
.index = sqlite_export_service_internal::kUserAddressUserIdBindIndex,
.value = user_id,
.action = "Failed to bind SQLite user address user id"});
sqlite_export_service_internal::Bind(
insert_user_address_stmt_,
sqlite_export_service_internal::BoundParam<sqlite3_int64>{
.index = sqlite_export_service_internal::kUserAddressCityIdBindIndex,
.value = city_id,
.action = "Failed to bind SQLite user address city id"});
sqlite_export_service_internal::StepStatement(
db_handle_, insert_user_address_stmt_,
"Failed to insert SQLite user address row");
sqlite_export_service_internal::ResetStatement(insert_user_address_stmt_);
return user_id;
} }