diff --git a/DbSeeder/Data/Bogus/BogusGenerator.cs b/DbSeeder/Data/Bogus/BogusGeneratorDescriptor.cs similarity index 78% rename from DbSeeder/Data/Bogus/BogusGenerator.cs rename to DbSeeder/Data/Bogus/BogusGeneratorDescriptor.cs index 81702b0..26f8851 100644 --- a/DbSeeder/Data/Bogus/BogusGenerator.cs +++ b/DbSeeder/Data/Bogus/BogusGeneratorDescriptor.cs @@ -1,6 +1,6 @@ namespace DbSeeder.Data.Bogus; -public record BogusGenerator( +public record BogusGeneratorDescriptor( string Category, string GeneratorIdentifier, Type ReturnType, diff --git a/DbSeeder/Data/Bogus/BogusUtilities.cs b/DbSeeder/Data/Bogus/BogusUtilities.cs index e079b4b..0fdf426 100644 --- a/DbSeeder/Data/Bogus/BogusUtilities.cs +++ b/DbSeeder/Data/Bogus/BogusUtilities.cs @@ -17,9 +17,9 @@ public static class BogusUtilities } }; - public static Dictionary> GetBogusGenerators() + public static Dictionary> GetBogusGenerators() { - var generators = new Dictionary>(); + var generators = new Dictionary>(); var fakerType = typeof(Faker); @@ -44,7 +44,7 @@ public static Dictionary> GetBogusGenerators() foreach (var m in methods) { var methodParams = m.GetParameters().ToDictionary(x => x.Name!, x => x.ParameterType); - var generator = new BogusGenerator( + var generator = new BogusGeneratorDescriptor( generatorCategory, generatorCategory + m.Name.ToLower(), m.ReturnType, @@ -57,12 +57,12 @@ public static Dictionary> GetBogusGenerators() return generators; } - public static Dictionary> GetFiltersForReturnType( - this Dictionary> src, string returnType) + public static Dictionary> GetFiltersForReturnType( + this Dictionary> src, string returnType) { var allowed = AllowedTypes[returnType]; - var allowedGenerators = new Dictionary>(); + var allowedGenerators = new Dictionary>(); foreach (var (category, generators) in src) { allowedGenerators.Add(category, []); @@ -78,12 +78,12 @@ public static Dictionary> GetFiltersForReturnType( return allowedGenerators; } - public static dynamic? Generate(BogusGenerator generator) + public static dynamic? Generate(BogusGeneratorDescriptor generatorDescriptor) { var faker = new Faker(); - var generationMethod = generator.GeneratorIdentifier[generator.Category.Length..]; + var generationMethod = generatorDescriptor.GeneratorIdentifier[generatorDescriptor.Category.Length..]; - var generatorProperty = faker.GetType().GetProperty(generator.Category, + var generatorProperty = faker.GetType().GetProperty(generatorDescriptor.Category, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase); if (generatorProperty != null) { @@ -92,24 +92,24 @@ public static Dictionary> GetFiltersForReturnType( BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase); if (generatorMethod != null) { + var @params = (object[])null!; + var parameters = generatorMethod.GetParameters(); if (parameters.Length > 0) { - // TODO[#26]: Implement generators with params - throw new NotImplementedException("This feature is currently not implemented. " + - "Only parameterless generators can be used"); + @params = ParamsGenerator.GetParams(generatorDescriptor.GeneratorIdentifier); } // TODO[#27]: Implement constraints handling - var result = generatorMethod.Invoke(categoryGenerator, null); + var result = generatorMethod.Invoke(categoryGenerator, @params); return Convert.ChangeType(result, generatorMethod.ReturnType)!; } - Console.WriteLine($"Method '{generationMethod}' not found in '{generator.Category}' category"); + Console.WriteLine($"Method '{generationMethod}' not found in '{generatorDescriptor.Category}' category"); } else { - Console.WriteLine($"Category '{generator.Category}' not found on Faker object"); + Console.WriteLine($"Category '{generatorDescriptor.Category}' not found on Faker object"); } return null; diff --git a/DbSeeder/Data/Bogus/DataGeneratorsEvaluator.cs b/DbSeeder/Data/Bogus/DataGeneratorsEvaluator.cs index df205c0..964c719 100644 --- a/DbSeeder/Data/Bogus/DataGeneratorsEvaluator.cs +++ b/DbSeeder/Data/Bogus/DataGeneratorsEvaluator.cs @@ -4,23 +4,17 @@ namespace DbSeeder.Data.Bogus; public static class DataGeneratorsEvaluator { - public static List FindBestNGenerators( - this Dictionary> allGenerators, + public static List FindBestNGenerators( + this Dictionary> allGenerators, Column column, int n = 1) { - var weights = new Dictionary>(); + var weights = new Dictionary>(); foreach (var (_, generatorsCategory) in allGenerators) { foreach (var generator in generatorsCategory) { - if (generator.Params.Count != 0) - { - // TODO[#26]: Implement generators with params - continue; - } - var weight = CalculateLevenshteinDistance(column.Name, generator.GeneratorIdentifier); if (!weights.ContainsKey(weight)) { diff --git a/DbSeeder/Data/Bogus/ParamsGenerator.cs b/DbSeeder/Data/Bogus/ParamsGenerator.cs new file mode 100644 index 0000000..fb142b7 --- /dev/null +++ b/DbSeeder/Data/Bogus/ParamsGenerator.cs @@ -0,0 +1,226 @@ +using Bogus; +using Bogus.DataSets; + +namespace DbSeeder.Data.Bogus; + +public static class ParamsGenerator +{ + private const int MinNegativeNumber = int.MinValue; + private const int MaxNegativeNumber = -1; + private const int MinPositiveNumber = 0; + private const int MaxPositiveNumber = int.MaxValue; + + private static readonly Faker Faker = new(); + + public static object[] GetParams(string generatorId) + { + return GetRandomGeneratorParams(generatorId); + } + + private static object[] GetRandomGeneratorParams(string generatorId) + { + var minMaxInt = GetMinMaxIntValues(); + var minMaxDouble = GetMinMaxDoubleValues(); + + return generatorId switch + { + "randomodd" => [0, 100], + "randomeven" => [0, 100], + "randomnumber" => [minMaxInt[1]], + "randomdigits" => [GetRandomInt(1, 10), 0, 9], + "randomdecimal" => [minMaxDouble.Min, minMaxDouble.Max], + "randomfloat" => [minMaxDouble.Min, minMaxDouble.Max], + "randombytes" => [GetRandomInt(1, 100)], + "randomsbyte" => [MinNegativeNumber, MaxNegativeNumber], + "randomuint" => [MinPositiveNumber, MaxPositiveNumber], + "randomulong" => [0, ulong.MaxValue], + "randomlong" => [0, long.MaxValue], + "randomshort" => [short.MinValue, short.MaxValue], + "randomushort" => [ushort.MinValue, ushort.MaxValue], + "randomchar" => ['a', 'z'], + "randomchars" => ['a', 'z', GetRandomInt(1, 10)], + "randomstring" => [GetRandomInt(1, 100), 'a', 'z'], + "randomstring2" => [GetRandomInt(1, 100), "abcdef"], + "randomutf16string" => [GetRandomInt(1, 100), GetRandomInt(1, 100), true], + "randomhash" => [GetRandomInt(1, 100), true], + "randombool" => [0.5], + "randomarrayelement" => [GetRandomArray()], + "randomarrayelements" => [GetRandomArray(), GetRandomInt(1, 10)], + "randomlistitem" => [GetRandomList()], + "randomlistitems" => [GetRandomList(), GetRandomInt(1, 10)], + "randomcollectionitem" => [GetRandomCollection()], + "randomreplacenumbers" => ["###-##-####", '#'], + "randomreplacesymbols" => ["###-##-####", '#', (Func)(_ => '*')], + "randomreplace" => ["###-##-####"], + "randomclampstring" => ["sample", minMaxInt[0], minMaxInt[1]], + "randomenum" => [Enum.GetValues(typeof(DayOfWeek)).Cast().ToArray()], + "randomenumvalues" => [GetRandomInt(1, 7), Enum.GetValues(typeof(DayOfWeek)).Cast().ToArray()], + "randomshuffle" => [GetRandomList()], + "randomwords" => [GetRandomInt(1, 10)], + "randomwordsarray" => [GetRandomInt(1, 10)], + "randomalphanumeric" => [GetRandomInt(1, 100)], + "randomhexadecimal" => [GetRandomInt(1, 100), true], + "randomweightedrandom" => [GetRandomList(), GetRandomWeights()], + "phonephonenumber" => [GetPhoneNumberFormat()], + "phonephonenumberformat" => [GetRandomInt(0, 5)], + "namefirstname" => [GetRandomGender()], + "namelastname" => [GetRandomGender()], + "namefullname" => [GetRandomGender()], + "nameprefix" => [GetRandomGender()], + "namefindname" => [GetRandomFirstName(), GetRandomLastName(), true, false, GetRandomGender()], + "loremwords" => [GetRandomInt(1, 10)], + "loremletter" => [GetRandomInt(1, 26)], + "loremsentence" => [GetRandomInt(1, 20), GetRandomInt(1, 5)], + "loremsentences" => [GetRandomInt(1, 10), " "], + "loremparagraph" => [GetRandomInt(1, 5)], + "loremparagraphs" => [GetRandomInt(1, 10), " "], + "loremparagraphsminmax" => [GetRandomInt(1, 5), GetRandomInt(5, 10), " "], + "loremlines" => [GetRandomInt(1, 10), "\n"], + "loremslug" => [GetRandomInt(1, 10)], + "imagedatauri" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomColor()], + "imageplaceimgurl" => + [ + GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomCategory(), GetRandomFilter() + ], + "imagepicsumurl" => + [ + GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomInt(1, 10), + GetRandomInt(1, 1000) + ], + "imageplaceholderurl" => + [ + GetRandomInt(100, 1000), GetRandomInt(100, 1000), "Placeholder", GetRandomColor(), GetRandomColor(), + "png" + ], + "imageloremflickrurl" => + [ + GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomKeywords(), GetRandomBool(), GetRandomBool(), + GetRandomInt(1, 1000) + ], + "imagelorempixelurl" => + [ + GetRandomCategory(), GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool() + ], + "imageimage" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()], + "imageabstract" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()], + "imageanimals" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()], + "imagebusiness" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()], + "imagecats" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()], + "imagecity" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()], + "imagefood" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()], + "imagenightlife" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()], + "imagefashion" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()], + "imagepeople" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()], + "imagenature" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()], + "imagesports" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()], + "imagetechnics" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()], + "imagetransport" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()], + "financeaccount" => [GetRandomInt(1, 20)], + "financeamount" => [minMaxDouble.Min, minMaxDouble.Max, GetRandomInt(0, 5)], + "financecurrency" => [GetRandomBool()], + "financecreditcardnumber" => [GetRandomProvider()], + "financeiban" => [GetRandomBool(), GetRandomCountryCode()], + "addresszipcode" => [GetRandomFormat()], + "addressstreetaddress" => [GetRandomBool()], + "addresscountrycode" => [GetRandomFormat()], + "addresslatitude" => [minMaxDouble.Min, minMaxDouble.Max], + "addresslongitude" => [minMaxDouble.Min, minMaxDouble.Max], + "addressdirection" => [GetRandomBool()], + "addresscardinaldirection" => [GetRandomBool()], + "addressordinaldirection" => [GetRandomBool()], + "datepast" => [GetRandomInt(1, 100), DateTime.Now], + "datepastoffset" => [GetRandomInt(1, 100), DateTimeOffset.Now], + "datesoon" => [GetRandomInt(1, 100), DateTime.Now], + "datesoonoffset" => [GetRandomInt(1, 100), DateTimeOffset.Now], + "datefuture" => [GetRandomInt(1, 100), DateTime.Now], + "datefutureoffset" => [GetRandomInt(1, 100), DateTimeOffset.Now], + "datebetween" => [DateTime.Now.AddYears(-10), DateTime.Now.AddYears(10)], + "datebetweenoffset" => [DateTimeOffset.Now.AddYears(-10), DateTimeOffset.Now.AddYears(10)], + "daterecent" => [GetRandomInt(1, 10), DateTime.Now], + "daterecentoffset" => [GetRandomInt(1, 10), DateTimeOffset.Now], + "datetimespan" => [GetRandomInt(1, 100)], + "datemonth" => [GetRandomBool(), GetRandomBool()], + "dateweekday" => [GetRandomBool(), GetRandomBool()], + "datebetweendateonly" => [DateTime.Now.AddYears(-10).Date, DateTime.Now.AddYears(10).Date], + "datepastdateonly" => [GetRandomInt(1, 100), DateTime.Now.Date], + "datesoondateonly" => [GetRandomInt(1, 100), DateTime.Now.Date], + "datefuturedateonly" => [GetRandomInt(1, 100), DateTime.Now.Date], + "daterecentdateonly" => [GetRandomInt(1, 10), DateTime.Now.Date], + "datebetweentimeonly" => [DateTime.Now.TimeOfDay, DateTime.Now.AddHours(10).TimeOfDay], + "datesoontimeonly" => [GetRandomInt(1, 60), DateTime.Now.TimeOfDay], + "daterecenttimeonly" => [GetRandomInt(1, 60), DateTime.Now.TimeOfDay], + "companycompanyname" => [GetRandomInt(0, 5)], + "companycompanynameformat" => [GetRandomInt(0, 5)], + "internetemail" => [GetRandomFirstName(), GetRandomLastName(), GetRandomProvider(), GetRandomBool()], + "internetexampleemail" => [GetRandomFirstName(), GetRandomLastName()], + "internetusername" => [GetRandomFirstName(), GetRandomLastName()], + "internetusernameunicode" => [GetRandomFirstName(), GetRandomLastName()], + "internetmac" => [GetRandomSeparator()], + "internetpassword" => [GetRandomInt(6, 20), GetRandomBool(), "^[a-zA-Z0-9]+$", "prefix"], + "internetcolor" => + [ + GetRandomInt(0, 255), GetRandomInt(0, 255), GetRandomInt(0, 255), GetRandomBool(), "hex" + ], + "interneturlwithpath" => ["https", "example.com", "jpg"], + "interneturlrootedpath" => ["jpg"], + "commercedepartment" => [GetRandomInt(1, 10), GetRandomBool()], + "commerceprice" => [minMaxDouble.Min, minMaxDouble.Max, GetRandomInt(0, 5), "$"], + "commercecategories" => [GetRandomInt(1, 10)], + "systemfilename" => ["txt"], + "systemcommonfilename" => ["txt"], + "systemfileext" => ["text/plain"], + "rantreview" => ["product"], + "rantreviews" => ["product", GetRandomInt(1, 10)], + "vehiclevin" => [GetRandomBool()], + _ => throw new ArgumentException($"Unknown generatorId: {generatorId}") + }; + } + + private static int[] GetMinMaxIntValues() + { + var random = Random.Shared; + var num1 = random.Next(MinPositiveNumber, MaxPositiveNumber); + var num2 = random.Next(MinPositiveNumber, MaxPositiveNumber); + + var min = Math.Min(num1, num2); + var max = Math.Max(num1, num2); + + return [min, max]; + } + + private static (double Min, double Max) GetMinMaxDoubleValues() + { + var random = Random.Shared; + var num1 = random.NextDouble() * MaxPositiveNumber; + var num2 = random.NextDouble() * MaxPositiveNumber; + + var min = Math.Min(num1, num2); + var max = Math.Max(num1, num2); + + return (min, max); + } + + private static int GetRandomInt(int min, int max) => Faker.Random.Number(min, max); + private static object[] GetRandomArray() => GetRandomCollection().ToArray(); + private static List GetRandomList() => GetRandomCollection().ToList(); + + private static ICollection GetRandomCollection() + => Enumerable.Range(0, Faker.Random.Number(100)) + .Select(_ => (object)Guid.NewGuid().ToString()) + .ToList(); + + private static List GetRandomWeights() => Faker.Random.Digits(5).ToList(); + private static string GetPhoneNumberFormat() => "+1-###-###-####"; + private static Name.Gender GetRandomGender() => Faker.Random.ArrayElement([Name.Gender.Male, Name.Gender.Female]); + private static string GetRandomFirstName() => Faker.Person.FirstName; + private static string GetRandomLastName() => Faker.Person.LastName; + private static string GetRandomColor() => "#FF5733"; + private static string GetRandomCategory() => "nature"; + private static string GetRandomFilter() => "grayscale"; + private static bool GetRandomBool() => Faker.Random.Bool(0.5f); + private static string GetRandomProvider() => "Visa"; + private static string GetRandomCountryCode() => "US"; + private static string GetRandomFormat() => "#####"; + private static string[] GetRandomKeywords() => Faker.Lorem.Words(); + private static string GetRandomSeparator() => "-"; +} diff --git a/DbSeeder/Data/GeneratorFactory.cs b/DbSeeder/Data/GeneratorFactory.cs index 473b25b..522d1a2 100644 --- a/DbSeeder/Data/GeneratorFactory.cs +++ b/DbSeeder/Data/GeneratorFactory.cs @@ -1,4 +1,3 @@ -using System.Data; using DbSeeder.Data.Bogus; using DbSeeder.Schema; @@ -17,9 +16,9 @@ internal static class GeneratorFactory ["datetime", "smalldatetime", "date", "time", "datetime2", "datetimeoffset"]; private static readonly HashSet OtherTypes = - ["uniqueidentifier", "timestamp", "xml", "udt", "structured", "variant"]; + ["uniqueidentifier", "timestamp", "xml", "udt", "structured", "variant"]; - private static readonly Dictionary> Generators = BogusUtilities.GetBogusGenerators(); + private static readonly Dictionary> Generators = BogusUtilities.GetBogusGenerators(); public static object? GetGeneratorByColumnV2(Column col) { @@ -35,23 +34,9 @@ internal static class GeneratorFactory } // semantic filter - var generator = generators.FindBestNGenerators(col, n: 1).First(); - var generatedValue = (object?)BogusUtilities.Generate(generator); + var selectedGenerators = generators.FindBestNGenerators(col, n: 3); + var firstGenerator = selectedGenerators.First(); + var generatedValue = (object?)BogusUtilities.Generate(firstGenerator); return generatedValue; } - - public static (Type, Func) GetGeneratorByColumn(Column col) - { - if (StringTypes.Contains(col.DataType, StringComparer.OrdinalIgnoreCase)) - { - return (typeof(string), () => Guid.NewGuid().ToString("N")); - } - - if (NumeralTypes.Contains(col.DataType, StringComparer.OrdinalIgnoreCase)) // add type limits check - { - return (typeof(long), () => Random.Shared.NextInt64() * 17); - } - - throw new NotImplementedException($"{col.DataType} is not currently supported"); - } }