diff --git a/.gitignore b/.gitignore index 50290793..63981ad1 100644 --- a/.gitignore +++ b/.gitignore @@ -276,3 +276,6 @@ __pycache__/ # Thumbs Thumbs.db + +# BenchmarkDotNet report +BenchmarkDotNet.Artifacts/ \ No newline at end of file diff --git a/QueryBuilder.Benchmarks/Infrastructure/TestCompiler.cs b/QueryBuilder.Benchmarks/Infrastructure/TestCompiler.cs new file mode 100644 index 00000000..171524ac --- /dev/null +++ b/QueryBuilder.Benchmarks/Infrastructure/TestCompiler.cs @@ -0,0 +1,9 @@ +using SqlKata.Compilers; + +namespace QueryBuilder.Benchmarks.Infrastructure; + +public class TestCompiler + : Compiler +{ + public override string EngineCode { get; } = "generic"; +} diff --git a/QueryBuilder.Benchmarks/Infrastructure/TestSupport.cs b/QueryBuilder.Benchmarks/Infrastructure/TestSupport.cs new file mode 100644 index 00000000..9023e6f1 --- /dev/null +++ b/QueryBuilder.Benchmarks/Infrastructure/TestSupport.cs @@ -0,0 +1,28 @@ +using SqlKata; +using SqlKata.Compilers; + +namespace QueryBuilder.Benchmarks.Infrastructure; + +public class TestSupport +{ + public static Compiler CreateCompiler(string engine) + { + return engine switch + { + EngineCodes.Firebird => new FirebirdCompiler(), + EngineCodes.MySql => new MySqlCompiler(), + EngineCodes.Oracle => new OracleCompiler + { + UseLegacyPagination = false + }, + EngineCodes.PostgreSql => new PostgresCompiler(), + EngineCodes.Sqlite => new SqliteCompiler(), + EngineCodes.SqlServer => new SqlServerCompiler + { + UseLegacyPagination = false + }, + EngineCodes.Generic => new TestCompiler(), + _ => throw new ArgumentException($"Unsupported engine type: {engine}", nameof(engine)), + }; + } +} diff --git a/QueryBuilder.Benchmarks/Program.cs b/QueryBuilder.Benchmarks/Program.cs new file mode 100644 index 00000000..e5c36886 --- /dev/null +++ b/QueryBuilder.Benchmarks/Program.cs @@ -0,0 +1,4 @@ +// BenchmarkDotNet: https://benchmarkdotnet.org/ +using BenchmarkDotNet.Running; + +BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); diff --git a/QueryBuilder.Benchmarks/QueryBuilder.Benchmarks.csproj b/QueryBuilder.Benchmarks/QueryBuilder.Benchmarks.csproj new file mode 100644 index 00000000..2960e8f2 --- /dev/null +++ b/QueryBuilder.Benchmarks/QueryBuilder.Benchmarks.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + enable + false + + + + + + + + + + + diff --git a/QueryBuilder.Benchmarks/REAEDME.MD b/QueryBuilder.Benchmarks/REAEDME.MD new file mode 100644 index 00000000..fdd30ecd --- /dev/null +++ b/QueryBuilder.Benchmarks/REAEDME.MD @@ -0,0 +1,30 @@ +# QueryBuilder.Benchmarks + +This project is a benchmark suite for measuring the performance of the SqlKata query builder library. It uses [BenchmarkDotNet](https://benchmarkdotnet.org/) to provide performance metrics for various query-building scenarios. + +## About + +- **Purpose:** Evaluate the performance of different SqlKata query operations. +- **Framework:** [BenchmarkDotNet](https://benchmarkdotnet.org/) is used for running and reporting benchmarks. +- **Scope:** Includes benchmarks for a few select scenarios. + +## How to Use + +1. **Build the Solution:** + Make sure the solution is built in Release mode for accurate results. + +2. **Run the Benchmarks:** + Execute the following command from the root of the repository: + + ```cmd + dotnet run -c Release --project .\QueryBuilder.Benchmarks\QueryBuilder.Benchmarks.csproj + ``` + +3. **Select Benchmarks:** + After running the command, a benchmark selector will appear. This is powered by BenchmarkDotNet's `BenchmarkSwitcher`, allowing you to choose which benchmarks to execute interactively. + +4. **View Results:** + BenchmarkDotNet will output the results to the console and generate detailed reports in the `BenchmarkDotNet.Artifacts` directory. + +## References +- [BenchmarkDotNet](https://benchmarkdotnet.org/) diff --git a/QueryBuilder.Benchmarks/SelectsBenchmark.cs b/QueryBuilder.Benchmarks/SelectsBenchmark.cs new file mode 100644 index 00000000..32d9f799 --- /dev/null +++ b/QueryBuilder.Benchmarks/SelectsBenchmark.cs @@ -0,0 +1,78 @@ +using BenchmarkDotNet.Attributes; +using QueryBuilder.Benchmarks.Infrastructure; +using SqlKata; +using SqlKata.Compilers; + +namespace QueryBuilder.Benchmarks; + +[MemoryDiagnoser] +public class SelectsBenchmark +{ + private Query selectSimple; + private Query selectGroupBy; + private Query selectWith; + + public Compiler compiler; + + [Params(EngineCodes.SqlServer)] + public string EngineCode { get; set; } + + [GlobalSetup] + public void Setup() + { + selectSimple = new Query("Products") + .Select("ProductID", "ProductName", "SupplierID", "CategoryID", "UnitPrice", "UnitsInStock", "UnitsOnOrder", + "ReorderLevel", "Discontinued") + .WhereIn("CategoryID", [1, 2, 3]) + .Where("SupplierID", 5) + .Where("UnitPrice", ">=", 10) + .Where("UnitPrice", "<=", 100) + .Take(10) + .Skip(20) + .OrderBy("UnitPrice", "ProductName"); + + + selectGroupBy = new Query("Products") + .Select("SupplierID", "CategoryID") + .SelectAvg("UnitPrice") + .SelectMin("UnitPrice") + .SelectMax("UnitPrice") + .Where("CategoryID", 123) + .GroupBy("SupplierID", "CategoryID") + .HavingRaw("MIN(UnitPrice) >= ?", 10) + .Take(10) + .Skip(20) + .OrderBy("SupplierID", "CategoryID"); + + var activePosts = new Query("Comments") + .Select("PostId") + .SelectRaw("count(1) as Count") + .GroupBy("PostId") + .HavingRaw("count(1) > 100"); + + selectWith = new Query("Posts") + .With("ActivePosts", activePosts) + .Join("ActivePosts", "ActivePosts.PostId", "Posts.Id") + .Select("Posts.*", "ActivePosts.Count"); + + compiler = TestSupport.CreateCompiler(EngineCode); + } + + [Benchmark] + public SqlResult SelectSimple() + { + return compiler.Compile(selectSimple); + } + + [Benchmark] + public SqlResult SelectGroupBy() + { + return compiler.Compile(selectGroupBy); + } + + [Benchmark] + public SqlResult SelectWith() + { + return compiler.Compile(selectWith); + } +} diff --git a/sqlkata.sln b/sqlkata.sln index ef91408a..2a3db5f6 100644 --- a/sqlkata.sln +++ b/sqlkata.sln @@ -14,6 +14,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QueryBuilder.Benchmarks", "QueryBuilder.Benchmarks\QueryBuilder.Benchmarks.csproj", "{4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -60,6 +62,30 @@ Global {B6DF0569-6040-4EAF-A38B-E4DEB8DC76E0}.Release|x64.Build.0 = Release|Any CPU {B6DF0569-6040-4EAF-A38B-E4DEB8DC76E0}.Release|x86.ActiveCfg = Release|Any CPU {B6DF0569-6040-4EAF-A38B-E4DEB8DC76E0}.Release|x86.Build.0 = Release|Any CPU + {5DEA7DBC-5B8A-44A9-A070-55E95881A4CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5DEA7DBC-5B8A-44A9-A070-55E95881A4CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5DEA7DBC-5B8A-44A9-A070-55E95881A4CF}.Debug|x64.ActiveCfg = Debug|Any CPU + {5DEA7DBC-5B8A-44A9-A070-55E95881A4CF}.Debug|x64.Build.0 = Debug|Any CPU + {5DEA7DBC-5B8A-44A9-A070-55E95881A4CF}.Debug|x86.ActiveCfg = Debug|Any CPU + {5DEA7DBC-5B8A-44A9-A070-55E95881A4CF}.Debug|x86.Build.0 = Debug|Any CPU + {5DEA7DBC-5B8A-44A9-A070-55E95881A4CF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5DEA7DBC-5B8A-44A9-A070-55E95881A4CF}.Release|Any CPU.Build.0 = Release|Any CPU + {5DEA7DBC-5B8A-44A9-A070-55E95881A4CF}.Release|x64.ActiveCfg = Release|Any CPU + {5DEA7DBC-5B8A-44A9-A070-55E95881A4CF}.Release|x64.Build.0 = Release|Any CPU + {5DEA7DBC-5B8A-44A9-A070-55E95881A4CF}.Release|x86.ActiveCfg = Release|Any CPU + {5DEA7DBC-5B8A-44A9-A070-55E95881A4CF}.Release|x86.Build.0 = Release|Any CPU + {4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}.Debug|x64.ActiveCfg = Debug|Any CPU + {4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}.Debug|x64.Build.0 = Debug|Any CPU + {4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}.Debug|x86.ActiveCfg = Debug|Any CPU + {4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}.Debug|x86.Build.0 = Debug|Any CPU + {4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}.Release|Any CPU.Build.0 = Release|Any CPU + {4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}.Release|x64.ActiveCfg = Release|Any CPU + {4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}.Release|x64.Build.0 = Release|Any CPU + {4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}.Release|x86.ActiveCfg = Release|Any CPU + {4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE