diff --git a/tooling/pipeline/CMakeLists.txt b/tooling/pipeline/CMakeLists.txt index 2716c490..220334bf 100644 --- a/tooling/pipeline/CMakeLists.txt +++ b/tooling/pipeline/CMakeLists.txt @@ -111,8 +111,9 @@ if(NOT BIERGARTEN_MOCK_ONLY) ) FetchContent_MakeAvailable(llama-cpp) endif() - + if (NOT APPLE) find_package(OpenMP REQUIRED) + endif() endif() # spdlog @@ -249,9 +250,10 @@ target_include_directories(${PROJECT_NAME} PRIVATE includes ) + target_link_libraries(${PROJECT_NAME} PRIVATE $<$>:llama> - $<$>:OpenMP::OpenMP_CXX> + $ boost::di Boost::json Boost::program_options diff --git a/tooling/pipeline/src/services/xeger/xeger_generator.cc b/tooling/pipeline/src/services/xeger/xeger_generator.cc index e812d898..82aca295 100644 --- a/tooling/pipeline/src/services/xeger/xeger_generator.cc +++ b/tooling/pipeline/src/services/xeger/xeger_generator.cc @@ -54,11 +54,11 @@ void XegerGenerator::Visit(const Alternation& node) { } void XegerGenerator::Visit(const Repeat& node) { - const int lo = node.min; - const int hi = (node.max < 0) ? node.min + unbounded_cap_ : node.max; - std::uniform_int_distribution dist(lo, hi); - const int count = dist(rng_); - for (int i = 0; i < count; ++i) { + const unsigned int low = node.min; + const unsigned int high = node.max.value_or(node.min + unbounded_cap_); + std::uniform_int_distribution dist(low, high); + const unsigned int count = dist(rng_); + for (unsigned int i = 0; i < count; ++i) { node.child->Accept(*this); } } diff --git a/web/backend/Database/Database.Seed/Database.Seed.csproj b/web/backend/Database/Database.Seed/Database.Seed.csproj index 0d9b350c..887c52c3 100644 --- a/web/backend/Database/Database.Seed/Database.Seed.csproj +++ b/web/backend/Database/Database.Seed/Database.Seed.csproj @@ -16,7 +16,7 @@ @@ -24,4 +24,10 @@ + + + + + diff --git a/web/backend/Database/Database.Seed/DatabaseHelpers.cs b/web/backend/Database/Database.Seed/DatabaseHelpers/ConnectionStrings.cs similarity index 73% rename from web/backend/Database/Database.Seed/DatabaseHelpers.cs rename to web/backend/Database/Database.Seed/DatabaseHelpers/ConnectionStrings.cs index 574296b4..6554b980 100644 --- a/web/backend/Database/Database.Seed/DatabaseHelpers.cs +++ b/web/backend/Database/Database.Seed/DatabaseHelpers/ConnectionStrings.cs @@ -1,19 +1,9 @@ using Microsoft.Data.SqlClient; -namespace Database.Seed; +namespace Database.Seed.DatabaseHelpers; public class ConnectionStrings { - /// - /// Builds a SQL Server connection string from the DB_SERVER, DB_NAME, - /// DB_USER, DB_PASSWORD, and DB_TRUST_SERVER_CERTIFICATE - /// environment variables. - /// - /// A fully built SQL Server connection string. - /// - /// Thrown when DB_SERVER, DB_NAME, DB_USER, or DB_PASSWORD - /// is not set. - /// private static string GetSqlServerConnectionString() { string server = @@ -48,7 +38,6 @@ public class ConnectionStrings return builder.ConnectionString; } - public static string SqlServerConnectionString => GetSqlServerConnectionString(); - public static string SqliteConnectionString => "Data Source=seed.sqlite"; + public static string SqliteConnectionString => "Data Source=SeedData/seed.sqlite"; } diff --git a/web/backend/Database/Database.Seed/PipelineData/BreweryAddress.cs b/web/backend/Database/Database.Seed/PipelineData/BreweryAddress.cs new file mode 100644 index 00000000..b6902bb1 --- /dev/null +++ b/web/backend/Database/Database.Seed/PipelineData/BreweryAddress.cs @@ -0,0 +1,8 @@ +namespace Database.Seed.PipelineData; + +public sealed record BreweryAddress +{ + public required City City { get; init; } + + public required string PostalCode { get; init; } +} diff --git a/web/backend/Database/Database.Seed/PipelineData/BreweryRecord.cs b/web/backend/Database/Database.Seed/PipelineData/BreweryRecord.cs new file mode 100644 index 00000000..93c67e9a --- /dev/null +++ b/web/backend/Database/Database.Seed/PipelineData/BreweryRecord.cs @@ -0,0 +1,8 @@ +namespace Database.Seed.PipelineData; + +public sealed record BreweryRecord +{ + public required BreweryAddress Address { get; init; } + + public required BreweryResult Brewery { get; init; } +} diff --git a/web/backend/Database/Database.Seed/PipelineData/BreweryResult.cs b/web/backend/Database/Database.Seed/PipelineData/BreweryResult.cs new file mode 100644 index 00000000..13d42f5f --- /dev/null +++ b/web/backend/Database/Database.Seed/PipelineData/BreweryResult.cs @@ -0,0 +1,12 @@ +namespace Database.Seed.PipelineData; + +public sealed record BreweryResult +{ + public required string NameEn { get; init; } + + public required string DescriptionEn { get; init; } + + public required string NameLocal { get; init; } + + public required string DescriptionLocal { get; init; } +} diff --git a/web/backend/Database/Database.Seed/PipelineData/City.cs b/web/backend/Database/Database.Seed/PipelineData/City.cs new file mode 100644 index 00000000..88de9e2e --- /dev/null +++ b/web/backend/Database/Database.Seed/PipelineData/City.cs @@ -0,0 +1,18 @@ +namespace Database.Seed.PipelineData; + +public sealed record City +{ + public required string CityName { get; init; } + + public required string StateProvince { get; init; } + + public required string Iso31662 { get; init; } + + public required string Country { get; init; } + + public required string Iso31661 { get; init; } + + public IReadOnlyList LocalLanguages { get; init; } = []; + + public required PostalCodeSpec PostalCode { get; init; } +} diff --git a/web/backend/Database/Database.Seed/PipelineData/PostalCodeSpec.cs b/web/backend/Database/Database.Seed/PipelineData/PostalCodeSpec.cs new file mode 100644 index 00000000..d367da6b --- /dev/null +++ b/web/backend/Database/Database.Seed/PipelineData/PostalCodeSpec.cs @@ -0,0 +1,10 @@ +namespace Database.Seed.PipelineData; + +public sealed record PostalCodeSpec +{ + public required string CountryFormatRegex { get; init; } + + public IReadOnlyList CityRegexes { get; init; } = []; + + public IReadOnlyList Examples { get; init; } = []; +} diff --git a/web/backend/Database/Database.Seed/PipelineData/UserAddress.cs b/web/backend/Database/Database.Seed/PipelineData/UserAddress.cs new file mode 100644 index 00000000..6b63a17c --- /dev/null +++ b/web/backend/Database/Database.Seed/PipelineData/UserAddress.cs @@ -0,0 +1,8 @@ +namespace Database.Seed.PipelineData; + +public sealed record UserAddress +{ + public required City City { get; init; } + + public required string PostalCode { get; init; } +} diff --git a/web/backend/Database/Database.Seed/PipelineData/UserRecord.cs b/web/backend/Database/Database.Seed/PipelineData/UserRecord.cs new file mode 100644 index 00000000..fa8f0dc2 --- /dev/null +++ b/web/backend/Database/Database.Seed/PipelineData/UserRecord.cs @@ -0,0 +1,12 @@ +namespace Database.Seed.PipelineData; + +public sealed record UserRecord +{ + public required UserAddress Address { get; init; } + + public required UserResult User { get; init; } + + public required string Email { get; init; } + + public required string DateOfBirth { get; init; } +} diff --git a/web/backend/Database/Database.Seed/PipelineData/UserResult.cs b/web/backend/Database/Database.Seed/PipelineData/UserResult.cs new file mode 100644 index 00000000..0e53881d --- /dev/null +++ b/web/backend/Database/Database.Seed/PipelineData/UserResult.cs @@ -0,0 +1,16 @@ +namespace Database.Seed.PipelineData; + +public sealed record UserResult +{ + public required string FirstName { get; init; } + + public required string LastName { get; init; } + + public required string Gender { get; init; } + + public required string Username { get; init; } + + public required string Bio { get; init; } + + public required float ActivityWeight { get; init; } +} diff --git a/web/backend/Database/Database.Seed/Program.cs b/web/backend/Database/Database.Seed/Program.cs new file mode 100644 index 00000000..6a025994 --- /dev/null +++ b/web/backend/Database/Database.Seed/Program.cs @@ -0,0 +1,53 @@ +using Database.Seed.DatabaseHelpers; +using Database.Seed.PipelineData; +using Database.Seed.Sqlite; +using Microsoft.Data.Sqlite; + +namespace Database.Seed; + +public class Program +{ + public static int Main() + { + IReadOnlyList breweries; + IReadOnlyList users; + try + { + PipelineSeedDataReader reader = new(ConnectionStrings.SqliteConnectionString); + breweries = reader.ReadBreweryRecords(); + users = reader.ReadUserRecords(); + } + catch (SqliteException ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"Error opening database connection: {ex.Message}"); + Console.ResetColor(); + return 1; + } + + + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine( + $"Loaded {breweries.Count} breweries." + ); + Console.ResetColor(); + + for (int i = 0; i < breweries.Count; i++) + { + BreweryRecord brewery = breweries[i]; + Console.WriteLine($"{i + 1}:\t{brewery.Brewery.NameEn}\t({brewery.Address.City.CityName}, {brewery.Address.City.Country})"); + } + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"Loaded {users.Count} users."); + Console.ResetColor(); + + for (int i = 0; i < users.Count; i++) + { + UserRecord user = users[i]; + Console.WriteLine($"{i + 1}:\t{user.User.FirstName} {user.User.LastName}\t ({user.Email})"); + } + + return 0; + + } +} diff --git a/web/backend/Database/Database.Seed/SeedData/seed.sqlite b/web/backend/Database/Database.Seed/SeedData/seed.sqlite new file mode 100644 index 00000000..06cd47a2 Binary files /dev/null and b/web/backend/Database/Database.Seed/SeedData/seed.sqlite differ diff --git a/web/backend/Database/Database.Seed/Sqlite/PipelineSeedDataReader.cs b/web/backend/Database/Database.Seed/Sqlite/PipelineSeedDataReader.cs new file mode 100644 index 00000000..8657d121 --- /dev/null +++ b/web/backend/Database/Database.Seed/Sqlite/PipelineSeedDataReader.cs @@ -0,0 +1,135 @@ +using System.Text.Json; +using Database.Seed.PipelineData; +using Microsoft.Data.Sqlite; + +namespace Database.Seed.Sqlite; + +public sealed class PipelineSeedDataReader +{ + private readonly string _connectionString; + + public PipelineSeedDataReader(string connectionString) + { + _connectionString = connectionString; + } + + public IReadOnlyList ReadBreweryRecords() + { + using SqliteConnection connection = new(_connectionString); + connection.Open(); + + IReadOnlyDictionary cities = ReadCities(connection); + + const string sql = """ + SELECT b.name_en, b.description_en, b.name_local, b.description_local, + ba.city_id, ba.postal_code + FROM breweries b + JOIN brewery_addresses ba ON ba.brewery_id = b.id; + """; + + using SqliteCommand command = new(sql, connection); + using SqliteDataReader reader = command.ExecuteReader(); + + List records = []; + while (reader.Read()) + records.Add( + new BreweryRecord + { + Brewery = new BreweryResult + { + NameEn = reader.GetString(0), + DescriptionEn = reader.GetString(1), + NameLocal = reader.GetString(2), + DescriptionLocal = reader.GetString(3), + }, + Address = new BreweryAddress + { + City = cities[reader.GetInt64(4)], + PostalCode = reader.GetString(5), + }, + } + ); + + return records; + } + + public IReadOnlyList ReadUserRecords() + { + using SqliteConnection connection = new(_connectionString); + connection.Open(); + + IReadOnlyDictionary cities = ReadCities(connection); + + const string sql = """ + SELECT u.first_name, u.last_name, u.gender, u.username, u.bio, + u.activity_weight, u.email, u.date_of_birth, + ua.city_id, ua.postal_code + FROM users u + JOIN user_addresses ua ON ua.user_id = u.id; + """; + + using SqliteCommand command = new(sql, connection); + using SqliteDataReader reader = command.ExecuteReader(); + + List records = []; + while (reader.Read()) + records.Add( + new UserRecord + { + User = new UserResult + { + FirstName = reader.GetString(0), + LastName = reader.GetString(1), + Gender = reader.GetString(2), + Username = reader.GetString(3), + Bio = reader.GetString(4), + ActivityWeight = (float)reader.GetDouble(5), + }, + Email = reader.GetString(6), + DateOfBirth = reader.GetString(7), + Address = new UserAddress + { + City = cities[reader.GetInt64(8)], + PostalCode = reader.GetString(9), + }, + } + ); + + return records; + } + + private static IReadOnlyDictionary ReadCities(SqliteConnection connection) + { + const string sql = """ + SELECT id, city, state_province, iso3166_2, country, iso3166_1, + local_languages_json, postal_code_country_format_regex, + postal_code_city_regex_json + FROM cities; + """; + + using SqliteCommand command = new(sql, connection); + using SqliteDataReader reader = command.ExecuteReader(); + + Dictionary cities = []; + while (reader.Read()) + cities[reader.GetInt64(0)] = new City + { + CityName = reader.GetString(1), + StateProvince = reader.GetString(2), + Iso31662 = reader.GetString(3), + Country = reader.GetString(4), + Iso31661 = reader.GetString(5), + LocalLanguages = DeserializeStringArray(reader.GetString(6)), + PostalCode = new PostalCodeSpec + { + CountryFormatRegex = reader.GetString(7), + CityRegexes = DeserializeStringArray(reader.GetString(8)), + }, + }; + + return cities; + } + + private static IReadOnlyList DeserializeStringArray(string json) => + JsonSerializer.Deserialize>(json) ?? []; +}