Skip to content

Commit b7a29cf

Browse files
committed
Add benchmarks for QueryBuilder with TestCompiler and SelectsBenchmark
1 parent 981f444 commit b7a29cf

7 files changed

Lines changed: 288 additions & 0 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using SqlKata.Compilers;
2+
3+
namespace QueryBuilder.Benchmarks.Infrastructure;
4+
5+
public class TestCompiler
6+
: Compiler
7+
{
8+
public override string EngineCode { get; } = "generic";
9+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using SqlKata;
2+
using SqlKata.Compilers;
3+
4+
namespace QueryBuilder.Benchmarks.Infrastructure;
5+
6+
public class TestSupport
7+
{
8+
9+
public static SqlResult CompileFor(string engine, Query query, Func<Compiler, Compiler> configuration = null)
10+
{
11+
var compiler = CreateCompiler(engine);
12+
if (configuration != null)
13+
{
14+
compiler = configuration(compiler);
15+
}
16+
17+
return compiler.Compile(query);
18+
}
19+
20+
public static SqlResult CompileFor(string engine, Query query, Action<Compiler> configuration)
21+
{
22+
return CompileFor(engine, query, compiler =>
23+
{
24+
configuration(compiler);
25+
return compiler;
26+
});
27+
}
28+
29+
public static Compiler CreateCompiler(string engine)
30+
{
31+
return engine switch
32+
{
33+
EngineCodes.Firebird => new FirebirdCompiler(),
34+
EngineCodes.MySql => new MySqlCompiler(),
35+
EngineCodes.Oracle => new OracleCompiler
36+
{
37+
UseLegacyPagination = false
38+
},
39+
EngineCodes.PostgreSql => new PostgresCompiler(),
40+
EngineCodes.Sqlite => new SqliteCompiler(),
41+
EngineCodes.SqlServer => new SqlServerCompiler
42+
{
43+
UseLegacyPagination = false
44+
},
45+
EngineCodes.Generic => new TestCompiler(),
46+
_ => throw new ArgumentException($"Unsupported engine type: {engine}", nameof(engine)),
47+
};
48+
}
49+
}

QueryBuilder.Benchmarks/Program.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// See https://aka.ms/new-console-template for more information
2+
3+
using BenchmarkDotNet.Running;
4+
using QueryBuilder.Benchmarks;
5+
6+
SelectsBenchmarkTests.TestAll();
7+
8+
BenchmarkRunner.Run<SelectsBenchmark>();
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<IsPackable>false</IsPackable>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<ProjectReference Include="..\QueryBuilder\QueryBuilder.csproj" />
17+
</ItemGroup>
18+
19+
</Project>
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using System.ComponentModel.DataAnnotations;
2+
using System.Text.RegularExpressions;
3+
using BenchmarkDotNet.Attributes;
4+
using QueryBuilder.Benchmarks.Infrastructure;
5+
using SqlKata;
6+
using SqlKata.Compilers;
7+
8+
namespace QueryBuilder.Benchmarks;
9+
10+
[MemoryDiagnoser]
11+
public class SelectsBenchmark
12+
{
13+
private Query selectSimple;
14+
private Query selectGroupBy;
15+
private Query selectWith;
16+
17+
public Compiler compiler;
18+
19+
[Params(
20+
EngineCodes.SqlServer)]
21+
public string EngineCode { get; set; }
22+
23+
[GlobalSetup]
24+
public void Setup()
25+
{
26+
selectSimple = new Query("Products")
27+
.Select("ProductID", "ProductName", "SupplierID", "CategoryID", "UnitPrice", "UnitsInStock", "UnitsOnOrder",
28+
"ReorderLevel", "Discontinued")
29+
.WhereIn("CategoryID", [1, 2, 3])
30+
.Where("SupplierID", 5)
31+
.Where("UnitPrice", ">=", 10)
32+
.Where("UnitPrice", "<=", 100)
33+
.Take(10)
34+
.Skip(20)
35+
.OrderBy("UnitPrice", "ProductName");
36+
37+
38+
selectGroupBy = new Query("Products")
39+
.Select("SupplierID", "CategoryID")
40+
.SelectAvg("UnitPrice")
41+
.SelectMin("UnitPrice")
42+
.SelectMax("UnitPrice")
43+
.Where("CategoryID", 123)
44+
.GroupBy("SupplierID", "CategoryID")
45+
.HavingRaw("MIN(UnitPrice) >= ?", 10)
46+
.Take(10)
47+
.Skip(20)
48+
.OrderBy("SupplierID", "CategoryID");
49+
50+
var activePosts = new Query("Comments")
51+
.Select("PostId")
52+
.SelectRaw("count(1) as Count")
53+
.GroupBy("PostId")
54+
.HavingRaw("count(1) > 100");
55+
56+
selectWith = new Query("Posts")
57+
.With("ActivePosts", activePosts)
58+
.Join("ActivePosts", "ActivePosts.PostId", "Posts.Id")
59+
.Select("Posts.*", "ActivePosts.Count");
60+
61+
compiler = TestSupport.CreateCompiler(EngineCode);
62+
}
63+
64+
[Benchmark]
65+
public SqlResult SelectSimple()
66+
{
67+
return compiler.Compile(selectSimple);
68+
}
69+
70+
[Benchmark]
71+
public SqlResult SelectGroupBy()
72+
{
73+
return compiler.Compile(selectGroupBy);
74+
}
75+
76+
[Benchmark]
77+
public SqlResult SelectWith()
78+
{
79+
return compiler.Compile(selectWith);
80+
}
81+
82+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
using System.ComponentModel.DataAnnotations;
2+
using System.Text.RegularExpressions;
3+
using SqlKata;
4+
using SqlKata.Compilers;
5+
6+
namespace QueryBuilder.Benchmarks;
7+
8+
public static partial class SelectsBenchmarkTests
9+
{
10+
public static void TestAll()
11+
{
12+
TestSelectSimple();
13+
TestSelectGroupBy();
14+
TestSelectWith();
15+
}
16+
17+
public static void TestSelectSimple()
18+
{
19+
var benchmark = CreateBenchmark();
20+
21+
var result = benchmark.SelectSimple();
22+
23+
// language=SQL
24+
ValidateResult(
25+
"""
26+
SELECT [ProductID], [ProductName], [SupplierID], [CategoryID], [UnitPrice],
27+
[UnitsInStock], [UnitsOnOrder], [ReorderLevel], [Discontinued]
28+
FROM [Products]
29+
WHERE [CategoryID] IN (1, 2, 3)
30+
AND [SupplierID] = 5
31+
AND [UnitPrice] >= 10
32+
AND [UnitPrice] <= 100
33+
ORDER BY [UnitPrice], [ProductName]
34+
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY
35+
""", result);
36+
}
37+
38+
public static void TestSelectGroupBy()
39+
{
40+
var benchmark = CreateBenchmark();
41+
42+
var result = benchmark.SelectGroupBy();
43+
44+
// language=SQL
45+
ValidateResult(
46+
"""
47+
SELECT [SupplierID], [CategoryID],
48+
AVG([UnitPrice]), MIN([UnitPrice]), MAX([UnitPrice])
49+
FROM [Products]
50+
WHERE [CategoryID] = 123
51+
GROUP BY [SupplierID], [CategoryID]
52+
HAVING MIN(UnitPrice) >= 10
53+
ORDER BY [SupplierID], [CategoryID]
54+
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY
55+
""", result);
56+
}
57+
58+
public static void TestSelectWith()
59+
{
60+
var benchmark = CreateBenchmark();
61+
62+
var result = benchmark.SelectWith();
63+
64+
// language=SQL
65+
ValidateResult(
66+
"""
67+
WITH [ActivePosts] AS (SELECT [PostId], count(1) as Count FROM [Comments] GROUP BY [PostId] HAVING count(1) > 100)
68+
SELECT [Posts].*, [ActivePosts].[Count]
69+
FROM [Posts]
70+
INNER JOIN [ActivePosts] ON [ActivePosts].[PostId] = [Posts].[Id]
71+
""", result);
72+
}
73+
74+
private static SelectsBenchmark CreateBenchmark()
75+
{
76+
var benchmark = new SelectsBenchmark
77+
{
78+
EngineCode = EngineCodes.SqlServer
79+
};
80+
benchmark.Setup();
81+
return benchmark;
82+
}
83+
84+
private static void ValidateResult(string expected, SqlResult result)
85+
{
86+
var actual = result.ToString();
87+
if (WhiteSpaces().Replace(actual, " ") != WhiteSpaces().Replace(expected, " "))
88+
{
89+
throw new ValidationException($"Invalid result: {actual}");
90+
}
91+
}
92+
93+
[GeneratedRegex(@"\s+")]
94+
private static partial Regex WhiteSpaces();
95+
}

sqlkata.sln

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
1414
.editorconfig = .editorconfig
1515
EndProjectSection
1616
EndProject
17+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QueryBuilder.Benchmarks", "QueryBuilder.Benchmarks\QueryBuilder.Benchmarks.csproj", "{4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}"
18+
EndProject
1719
Global
1820
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1921
Debug|Any CPU = Debug|Any CPU
@@ -60,6 +62,30 @@ Global
6062
{B6DF0569-6040-4EAF-A38B-E4DEB8DC76E0}.Release|x64.Build.0 = Release|Any CPU
6163
{B6DF0569-6040-4EAF-A38B-E4DEB8DC76E0}.Release|x86.ActiveCfg = Release|Any CPU
6264
{B6DF0569-6040-4EAF-A38B-E4DEB8DC76E0}.Release|x86.Build.0 = Release|Any CPU
65+
{5DEA7DBC-5B8A-44A9-A070-55E95881A4CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
66+
{5DEA7DBC-5B8A-44A9-A070-55E95881A4CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
67+
{5DEA7DBC-5B8A-44A9-A070-55E95881A4CF}.Debug|x64.ActiveCfg = Debug|Any CPU
68+
{5DEA7DBC-5B8A-44A9-A070-55E95881A4CF}.Debug|x64.Build.0 = Debug|Any CPU
69+
{5DEA7DBC-5B8A-44A9-A070-55E95881A4CF}.Debug|x86.ActiveCfg = Debug|Any CPU
70+
{5DEA7DBC-5B8A-44A9-A070-55E95881A4CF}.Debug|x86.Build.0 = Debug|Any CPU
71+
{5DEA7DBC-5B8A-44A9-A070-55E95881A4CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
72+
{5DEA7DBC-5B8A-44A9-A070-55E95881A4CF}.Release|Any CPU.Build.0 = Release|Any CPU
73+
{5DEA7DBC-5B8A-44A9-A070-55E95881A4CF}.Release|x64.ActiveCfg = Release|Any CPU
74+
{5DEA7DBC-5B8A-44A9-A070-55E95881A4CF}.Release|x64.Build.0 = Release|Any CPU
75+
{5DEA7DBC-5B8A-44A9-A070-55E95881A4CF}.Release|x86.ActiveCfg = Release|Any CPU
76+
{5DEA7DBC-5B8A-44A9-A070-55E95881A4CF}.Release|x86.Build.0 = Release|Any CPU
77+
{4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
78+
{4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}.Debug|Any CPU.Build.0 = Debug|Any CPU
79+
{4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}.Debug|x64.ActiveCfg = Debug|Any CPU
80+
{4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}.Debug|x64.Build.0 = Debug|Any CPU
81+
{4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}.Debug|x86.ActiveCfg = Debug|Any CPU
82+
{4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}.Debug|x86.Build.0 = Debug|Any CPU
83+
{4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}.Release|Any CPU.ActiveCfg = Release|Any CPU
84+
{4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}.Release|Any CPU.Build.0 = Release|Any CPU
85+
{4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}.Release|x64.ActiveCfg = Release|Any CPU
86+
{4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}.Release|x64.Build.0 = Release|Any CPU
87+
{4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}.Release|x86.ActiveCfg = Release|Any CPU
88+
{4C4BB0A9-0FD3-455D-B4C4-2BB43C5C7388}.Release|x86.Build.0 = Release|Any CPU
6389
EndGlobalSection
6490
GlobalSection(SolutionProperties) = preSolution
6591
HideSolutionNode = FALSE

0 commit comments

Comments
 (0)