Skip to content

Commit df4a237

Browse files
Merge pull request #787 from sqlkata/refactor_compiler
Refactor compiler to use the Visitor patterns
2 parents 6be5967 + 3531482 commit df4a237

31 files changed

Lines changed: 1727 additions & 1096 deletions

‎QueryBuilder.Tests/DefineTest.cs‎

Lines changed: 10 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -47,77 +47,42 @@ public void Test_Define_SubQuery()
4747
[Fact]
4848
public void Test_Define_WhereEnds()
4949
{
50-
5150
var query1 = new Query("Products")
5251
.Select("ProductId")
5352
.Define("@product", "Coffee")
5453
.WhereEnds("ProductName", Variable("@product"));
5554

56-
57-
var query2 = new Query("Products")
58-
.Select("ProductId", "ProductName")
59-
.Define("@product", "Coffee")
60-
.WhereEnds("ProductName", Variable("@product"), true);
61-
6255
var c1 = Compile(query1);
63-
var c2 = Compile(query2);
64-
65-
Assert.Equal("SELECT [ProductId] FROM [Products] WHERE LOWER([ProductName]) like '%coffee'", c1[EngineCodes.SqlServer]);
66-
67-
Assert.Equal("SELECT [ProductId], [ProductName] FROM [Products] WHERE [ProductName] like '%Coffee'", c2[EngineCodes.SqlServer]);
6856

57+
Assert.Equal("SELECT [ProductId] FROM [Products] WHERE [ProductName] like '%Coffee'", c1[EngineCodes.SqlServer]);
6958
}
7059

71-
72-
7360
[Fact]
7461
public void Test_Define_WhereStarts()
7562
{
76-
77-
7863
var query1 = new Query("Products")
7964
.Select("ProductId", "QuantityPerUnit")
8065
.Define("@perUnit", "12")
8166
.WhereStarts("QuantityPerUnit", Variable("@perUnit"));
8267

83-
84-
var query2 = new Query("Products")
85-
.Select("ProductId", "QuantityPerUnit")
86-
.Define("@perUnit", "12")
87-
.WhereStarts("QuantityPerUnit", Variable("@perUnit"), true);
88-
8968
var c1 = Compile(query1);
90-
var c2 = Compile(query2);
9169

92-
Assert.Equal("SELECT [ProductId], [QuantityPerUnit] FROM [Products] WHERE LOWER([QuantityPerUnit]) like '12%'", c1[EngineCodes.SqlServer]);
93-
Assert.Equal("SELECT [ProductId], [QuantityPerUnit] FROM [Products] WHERE [QuantityPerUnit] like '12%'", c2[EngineCodes.SqlServer]);
70+
Assert.Equal("SELECT [ProductId], [QuantityPerUnit] FROM [Products] WHERE [QuantityPerUnit] like '12%'", c1[EngineCodes.SqlServer]);
9471
}
9572

96-
9773
[Fact]
9874
public void Test_Define_WhereContains()
9975
{
100-
10176
var query1 = new Query("Products")
10277
.Define("@perUnit", "500")
10378
.Select("ProductId", "QuantityPerUnit")
10479
.WhereContains("QuantityPerUnit", Variable("@perUnit"));
10580

106-
107-
var query2 = new Query("Products")
108-
.Define("@perUnit", "500")
109-
.Select("ProductId", "QuantityPerUnit")
110-
.WhereContains("QuantityPerUnit", Variable("@perUnit"), true);
111-
11281
var c1 = Compile(query1);
113-
var c2 = Compile(query2);
114-
115-
Assert.Equal("SELECT [ProductId], [QuantityPerUnit] FROM [Products] WHERE LOWER([QuantityPerUnit]) like '%500%'", c1[EngineCodes.SqlServer]);
116-
Assert.Equal("SELECT [ProductId], [QuantityPerUnit] FROM [Products] WHERE [QuantityPerUnit] like '%500%'", c2[EngineCodes.SqlServer]);
11782

83+
Assert.Equal("SELECT [ProductId], [QuantityPerUnit] FROM [Products] WHERE [QuantityPerUnit] like '%500%'", c1[EngineCodes.SqlServer]);
11884
}
11985

120-
12186
[Fact]
12287
public void Test_Define_WhereLike()
12388
{
@@ -126,18 +91,9 @@ public void Test_Define_WhereLike()
12691
.Define("@id", "20")
12792
.WhereLike("SupplierID", Variable("@id"));
12893

129-
130-
var query2 = new Query("Products")
131-
.Select("ProductId", "ProductName", "SupplierID")
132-
.Define("@id", "20")
133-
.WhereLike("SupplierID", Variable("@id"), true);
134-
13594
var c1 = Compile(query1);
136-
var c2 = Compile(query2);
137-
138-
Assert.Equal("SELECT [ProductId], [ProductName], [SupplierID] FROM [Products] WHERE LOWER([SupplierID]) like '20'", c1[EngineCodes.SqlServer]);
13995

140-
Assert.Equal("SELECT [ProductId], [ProductName], [SupplierID] FROM [Products] WHERE [SupplierID] like '20'", c2[EngineCodes.SqlServer]);
96+
Assert.Equal("SELECT [ProductId], [ProductName], [SupplierID] FROM [Products] WHERE [SupplierID] like '20'", c1[EngineCodes.SqlServer]);
14197
}
14298

14399

@@ -202,7 +158,7 @@ public void Test_Define_HavingStarts()
202158

203159
var c = Compile(query);
204160

205-
Assert.Equal("SELECT COUNT(CustomerID), [Country] FROM [Customers] GROUP BY [Country] HAVING LOWER([Country]) like 'u%'", c[EngineCodes.SqlServer]);
161+
Assert.Equal("SELECT COUNT(CustomerID), [Country] FROM [Customers] GROUP BY [Country] HAVING [Country] like 'U%'", c[EngineCodes.SqlServer]);
206162

207163
}
208164

@@ -220,7 +176,7 @@ public void Test_Define_Having_Ends()
220176

221177
var c = Compile(query);
222178

223-
Assert.Equal("SELECT COUNT(CustomerID), [Country] FROM [Customers] GROUP BY [Country] HAVING LOWER([Country]) like '%d'", c[EngineCodes.SqlServer]);
179+
Assert.Equal("SELECT COUNT(CustomerID), [Country] FROM [Customers] GROUP BY [Country] HAVING [Country] like '%d'", c[EngineCodes.SqlServer]);
224180
}
225181

226182

@@ -238,7 +194,7 @@ public void Test_Define_Having_Contains()
238194

239195
var c = Compile(query);
240196

241-
Assert.Equal("SELECT COUNT(CustomerID), [Country] FROM [Customers] GROUP BY [Country] HAVING LOWER([Country]) like '%d%'", c[EngineCodes.SqlServer]);
197+
Assert.Equal("SELECT COUNT(CustomerID), [Country] FROM [Customers] GROUP BY [Country] HAVING [Country] like '%d%'", c[EngineCodes.SqlServer]);
242198

243199
}
244200

@@ -367,7 +323,7 @@ public void Test_Define_Union()
367323
.WhereNotLike("City", Variable("@city"));
368324

369325
var c = Compile(q2);
370-
Assert.Equal("SELECT [City] FROM [Customers] WHERE NOT (LOWER([City]) like 'z') UNION SELECT [City] FROM [Suppliers] WHERE [City] = 'Beirut'", c[EngineCodes.SqlServer]);
326+
Assert.Equal("SELECT [City] FROM [Customers] WHERE NOT ([City] like 'Z') UNION SELECT [City] FROM [Suppliers] WHERE [City] = 'Beirut'", c[EngineCodes.SqlServer]);
371327
}
372328

373329

@@ -386,7 +342,7 @@ public void Test_Define_Except()
386342
.WhereNotLike("City", Variable("@city"));
387343

388344
var c = Compile(q2);
389-
Assert.Equal("SELECT [City] FROM [Customers] WHERE NOT (LOWER([City]) like 'z') EXCEPT SELECT [City] FROM [Suppliers] WHERE [City] = 'Beirut'", c[EngineCodes.SqlServer]);
345+
Assert.Equal("SELECT [City] FROM [Customers] WHERE NOT ([City] like 'Z') EXCEPT SELECT [City] FROM [Suppliers] WHERE [City] = 'Beirut'", c[EngineCodes.SqlServer]);
390346
}
391347

392348
[Fact]
@@ -404,7 +360,7 @@ public void Test_Define_Intersect()
404360
.WhereNotLike("City", Variable("@city"));
405361

406362
var c = Compile(q2);
407-
Assert.Equal("SELECT [City] FROM [Customers] WHERE NOT (LOWER([City]) like 'z') INTERSECT SELECT [City] FROM [Suppliers] WHERE [City] = 'Beirut'", c[EngineCodes.SqlServer]);
363+
Assert.Equal("SELECT [City] FROM [Customers] WHERE NOT ([City] like 'Z') INTERSECT SELECT [City] FROM [Suppliers] WHERE [City] = 'Beirut'", c[EngineCodes.SqlServer]);
408364
}
409365

410366
/*

‎QueryBuilder.Tests/DeleteTests.cs‎

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,13 @@ public void DeleteWithJoin()
2727
.Where("Authors.Id", 5)
2828
.AsDelete();
2929

30-
var c = Compile(q);
30+
Assert.Equal("DELETE [Posts] FROM [Posts] \nINNER JOIN [Authors] ON [Authors].[Id] = [Posts].[AuthorId] WHERE [Authors].[Id] = 5", Compilers.CompileFor(EngineCodes.SqlServer, q).ToString());
31+
Assert.Equal("DELETE `Posts` FROM `Posts` \nINNER JOIN `Authors` ON `Authors`.`Id` = `Posts`.`AuthorId` WHERE `Authors`.`Id` = 5", Compilers.CompileFor(EngineCodes.MySql, q).ToString());
3132

32-
Assert.Equal("DELETE [Posts] FROM [Posts] \nINNER JOIN [Authors] ON [Authors].[Id] = [Posts].[AuthorId] WHERE [Authors].[Id] = 5", c[EngineCodes.SqlServer]);
33-
Assert.Equal("DELETE `Posts` FROM `Posts` \nINNER JOIN `Authors` ON `Authors`.`Id` = `Posts`.`AuthorId` WHERE `Authors`.`Id` = 5", c[EngineCodes.MySql]);
33+
Assert.Throws<NotSupportedException>(() => Compilers.CompileFor(EngineCodes.PostgreSql, q));
34+
Assert.Throws<NotSupportedException>(() => Compilers.CompileFor(EngineCodes.Sqlite, q));
35+
Assert.Throws<NotSupportedException>(() => Compilers.CompileFor(EngineCodes.Oracle, q));
36+
Assert.Throws<NotSupportedException>(() => Compilers.CompileFor(EngineCodes.Firebird, q));
3437
}
3538

3639
[Fact]
@@ -41,9 +44,19 @@ public void DeleteWithJoinAndAlias()
4144
.Where("Authors.Id", 5)
4245
.AsDelete();
4346

44-
var c = Compile(q);
47+
Assert.Equal("DELETE [P] FROM [Posts] AS [P] \nINNER JOIN [Authors] ON [Authors].[Id] = [P].[AuthorId] WHERE [Authors].[Id] = 5", Compilers.CompileFor(EngineCodes.SqlServer, q).ToString());
48+
Assert.Equal("DELETE `P` FROM `Posts` AS `P` \nINNER JOIN `Authors` ON `Authors`.`Id` = `P`.`AuthorId` WHERE `Authors`.`Id` = 5", Compilers.CompileFor(EngineCodes.MySql, q).ToString());
49+
}
50+
51+
[Fact]
52+
public void DeleteWithJoinAndSchema()
53+
{
54+
var q = new Query("Audit.Posts")
55+
.Join("Authors", "Authors.Id", "Posts.AuthorId")
56+
.AsDelete();
4557

46-
Assert.Equal("DELETE [P] FROM [Posts] AS [P] \nINNER JOIN [Authors] ON [Authors].[Id] = [P].[AuthorId] WHERE [Authors].[Id] = 5", c[EngineCodes.SqlServer]);
58+
Assert.Equal("DELETE [Posts] FROM [Audit].[Posts] \nINNER JOIN [Authors] ON [Authors].[Id] = [Posts].[AuthorId]", Compilers.CompileFor(EngineCodes.SqlServer, q).ToString());
59+
Assert.Equal("DELETE `Posts` FROM `Audit`.`Posts` \nINNER JOIN `Authors` ON `Authors`.`Id` = `Posts`.`AuthorId`", Compilers.CompileFor(EngineCodes.MySql, q).ToString());
4760
}
4861
}
4962
}

‎QueryBuilder.Tests/Firebird/FirebirdLimitTests.cs‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public FirebirdLimitTests()
1717
public void NoLimitNorOffset()
1818
{
1919
var query = new Query("Table");
20-
var ctx = new SqlResult("?", "\\") {Query = query};
20+
var ctx = new CompileContext(query);
2121

2222
Assert.Null(compiler.CompileLimit(ctx));
2323
}
@@ -26,7 +26,7 @@ public void NoLimitNorOffset()
2626
public void LimitOnly()
2727
{
2828
var query = new Query("Table").Limit(10);
29-
var ctx = new SqlResult("?", "\\") {Query = query};
29+
var ctx = new CompileContext(query);
3030

3131
Assert.Null(compiler.CompileLimit(ctx));
3232
}
@@ -35,7 +35,7 @@ public void LimitOnly()
3535
public void OffsetOnly()
3636
{
3737
var query = new Query("Table").Offset(20);
38-
var ctx = new SqlResult("?", "\\") {Query = query};
38+
var ctx = new CompileContext(query);
3939

4040
Assert.Null(compiler.CompileLimit(ctx));
4141
}
@@ -44,7 +44,7 @@ public void OffsetOnly()
4444
public void LimitAndOffset()
4545
{
4646
var query = new Query("Table").Limit(5).Offset(20);
47-
var ctx = new SqlResult("?", "\\") {Query = query};
47+
var ctx = new CompileContext(query);
4848

4949
Assert.Equal("ROWS ? TO ?", compiler.CompileLimit(ctx));
5050
Assert.Equal(21L, ctx.Bindings[0]);

‎QueryBuilder.Tests/GeneralTests.cs‎

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
using System;
2+
using System.Linq;
13
using SqlKata.Compilers;
24
using SqlKata.Extensions;
35
using SqlKata.Tests.Infrastructure;
4-
using System;
5-
using System.Linq;
66
using Xunit;
77

88
namespace SqlKata.Tests
@@ -754,5 +754,96 @@ public void Compile_WhereExists_DoesNotMutateSubquery()
754754

755755
Assert.Equal(2, inner.SelectClauses.Count);
756756
}
757+
758+
[Fact]
759+
public void SqlServer_SelectPrefix_TopWithColumnBindings_PreservesParameterOrder()
760+
{
761+
var query = new Query("users").SelectRaw("? AS [bonus]", 100).Limit(5);
762+
var compiler = new SqlServerCompiler { UseLegacyPagination = true };
763+
764+
var result = compiler.Compile(query);
765+
766+
Assert.Equal("SELECT TOP (@p0) @p1 AS [bonus] FROM [users]", result.Sql);
767+
Assert.Equal(5, result.NamedBindings["@p0"]);
768+
Assert.Equal(100, result.NamedBindings["@p1"]);
769+
}
770+
771+
[Fact]
772+
public void Firebird_SelectPrefix_FirstWithDistinct()
773+
{
774+
var query = new Query("users").Select("name").Distinct().Limit(10);
775+
var compiler = new FirebirdCompiler();
776+
777+
var result = compiler.Compile(query);
778+
779+
Assert.Equal("SELECT FIRST 10 DISTINCT \"NAME\" FROM \"USERS\"", result.ToString());
780+
}
781+
782+
[Fact]
783+
public void OrderByRandom_Compiles_EmitsRandomExpression()
784+
{
785+
var query = new Query("Users").OrderByRandom();
786+
787+
var sqlServer = Compilers.CompileFor(EngineCodes.SqlServer, query).ToString();
788+
Assert.Equal("SELECT * FROM [Users] ORDER BY NEWID()", sqlServer);
789+
790+
var postgres = Compilers.CompileFor(EngineCodes.PostgreSql, query).ToString();
791+
Assert.Equal("SELECT * FROM \"Users\" ORDER BY RANDOM()", postgres);
792+
793+
var sqlite = Compilers.CompileFor(EngineCodes.Sqlite, query).ToString();
794+
Assert.Equal("SELECT * FROM \"Users\" ORDER BY RANDOM()", sqlite);
795+
796+
var mysql = Compilers.CompileFor(EngineCodes.MySql, query).ToString();
797+
Assert.Equal("SELECT * FROM `Users` ORDER BY RAND()", mysql);
798+
799+
var firebird = Compilers.CompileFor(EngineCodes.Firebird, query).ToString();
800+
Assert.Equal("SELECT * FROM \"USERS\" ORDER BY RAND()", firebird);
801+
802+
var oracle = Compilers.CompileFor(EngineCodes.Oracle, query).ToString();
803+
Assert.Equal("SELECT * FROM \"Users\" ORDER BY DBMS_RANDOM.VALUE", oracle);
804+
}
805+
806+
private class SeedCompiler : Compiler
807+
{
808+
public override string EngineCode => "seed_engine";
809+
public override string CompileRandom(int? seed) => $"RANDOM_WITH_SEED({seed})";
810+
}
811+
812+
[Fact]
813+
public void OrderByRandom_RetainsAndForwardsSeed_AndClonesCorrectly()
814+
{
815+
var query = new Query("Users").OrderByRandom(1234);
816+
var cloned = query.Clone();
817+
818+
var clause = Assert.IsType<OrderByRandom>(Assert.Single(cloned.OrderByClauses));
819+
Assert.Equal(1234, clause.Seed);
820+
821+
var compiler = new SeedCompiler();
822+
var result = compiler.Compile(query).ToString();
823+
Assert.Equal("SELECT * FROM \"Users\" ORDER BY RANDOM_WITH_SEED(1234)", result);
824+
}
825+
826+
[Fact]
827+
public void OrderByRandom_RetainsAndForwardsSeed_InMySQL()
828+
{
829+
var query = new Query("Users").OrderByRandom(1234);
830+
var cloned = query.Clone();
831+
832+
var clause = Assert.IsType<OrderByRandom>(Assert.Single(cloned.OrderByClauses));
833+
Assert.Equal(1234, clause.Seed);
834+
835+
var compiler = new MySqlCompiler();
836+
var result = compiler.Compile(query).ToString();
837+
Assert.Equal("SELECT * FROM `Users` ORDER BY RAND(1234)", result);
838+
}
839+
840+
[Fact]
841+
public void OrderByRandom_CombinedWithOtherOrderAndLimit_DoesNotProduceDanglingOrderBy()
842+
{
843+
var query = new Query("Users").OrderBy("Id").OrderByRandom().Limit(10);
844+
845+
var postgres = Compilers.CompileFor(EngineCodes.PostgreSql, query).ToString();
846+
Assert.Equal("SELECT * FROM \"Users\" ORDER BY \"Id\", RANDOM() LIMIT 10", postgres);
847+
}
757848
}
758849
}

‎QueryBuilder.Tests/MySql/MySqlLimitTests.cs‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public MySqlLimitTests()
1717
public void WithNoLimitNorOffset()
1818
{
1919
var query = new Query("Table");
20-
var ctx = new SqlResult("?", "\\") {Query = query};
20+
var ctx = new CompileContext(query);
2121

2222
Assert.Null(compiler.CompileLimit(ctx));
2323
}
@@ -26,7 +26,7 @@ public void WithNoLimitNorOffset()
2626
public void WithNoOffset()
2727
{
2828
var query = new Query("Table").Limit(10);
29-
var ctx = new SqlResult("?", "\\") {Query = query};
29+
var ctx = new CompileContext(query);
3030

3131
Assert.Equal("LIMIT ?", compiler.CompileLimit(ctx));
3232
Assert.Equal(10, ctx.Bindings[0]);
@@ -36,7 +36,7 @@ public void WithNoOffset()
3636
public void WithNoLimit()
3737
{
3838
var query = new Query("Table").Offset(20);
39-
var ctx = new SqlResult("?", "\\") {Query = query};
39+
var ctx = new CompileContext(query);
4040

4141
Assert.Equal("LIMIT 18446744073709551615 OFFSET ?", compiler.CompileLimit(ctx));
4242
Assert.Equal(20L, ctx.Bindings[0]);
@@ -47,7 +47,7 @@ public void WithNoLimit()
4747
public void WithLimitAndOffset()
4848
{
4949
var query = new Query("Table").Limit(5).Offset(20);
50-
var ctx = new SqlResult("?", "\\") {Query = query};
50+
var ctx = new CompileContext(query);
5151

5252
Assert.Equal("LIMIT ? OFFSET ?", compiler.CompileLimit(ctx));
5353
Assert.Equal(5, ctx.Bindings[0]);

0 commit comments

Comments
 (0)