Skip to content

Commit d7f7e8e

Browse files
Merge pull request #327 from mathijs-dumon/master
Adding Escape clause to LIKE operator conditions - related to #237
2 parents 0acb06e + 6b56a71 commit d7f7e8e

File tree

5 files changed

+186
-66
lines changed

5 files changed

+186
-66
lines changed

QueryBuilder.Tests/SelectTests.cs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using SqlKata.Compilers;
22
using SqlKata.Extensions;
33
using SqlKata.Tests.Infrastructure;
4+
using System;
45
using Xunit;
56

67
namespace SqlKata.Tests
@@ -698,5 +699,95 @@ public void MultipleOrHaving()
698699

699700
Assert.Equal("SELECT * FROM [Table1] HAVING [Column1] > 1 OR [Column2] = 1", c[EngineCodes.SqlServer]);
700701
}
702+
703+
[Fact]
704+
public void EscapedWhereLike()
705+
{
706+
var q = new Query("Table1")
707+
.WhereLike("Column1", @"TestString\%", false, @"\");
708+
var c = Compile(q);
709+
710+
Assert.Equal(@"SELECT * FROM [Table1] WHERE LOWER([Column1]) like 'teststring\%' ESCAPE '\'", c[EngineCodes.SqlServer]);
711+
}
712+
713+
[Fact]
714+
public void EscapedWhereStarts()
715+
{
716+
var q = new Query("Table1")
717+
.WhereStarts("Column1", @"TestString\%", false, @"\");
718+
var c = Compile(q);
719+
720+
Assert.Equal(@"SELECT * FROM [Table1] WHERE LOWER([Column1]) like 'teststring\%%' ESCAPE '\'", c[EngineCodes.SqlServer]);
721+
}
722+
723+
[Fact]
724+
public void EscapedWhereEnds()
725+
{
726+
var q = new Query("Table1")
727+
.WhereEnds("Column1", @"TestString\%", false, @"\");
728+
var c = Compile(q);
729+
730+
Assert.Equal(@"SELECT * FROM [Table1] WHERE LOWER([Column1]) like '%teststring\%' ESCAPE '\'", c[EngineCodes.SqlServer]);
731+
}
732+
733+
[Fact]
734+
public void EscapedWhereContains()
735+
{
736+
var q = new Query("Table1")
737+
.WhereContains("Column1", @"TestString\%", false, @"\");
738+
var c = Compile(q);
739+
740+
Assert.Equal(@"SELECT * FROM [Table1] WHERE LOWER([Column1]) like '%teststring\%%' ESCAPE '\'", c[EngineCodes.SqlServer]);
741+
}
742+
743+
[Fact]
744+
public void EscapedHavingLike()
745+
{
746+
var q = new Query("Table1")
747+
.HavingLike("Column1", @"TestString\%", false, @"\");
748+
var c = Compile(q);
749+
750+
Assert.Equal(@"SELECT * FROM [Table1] HAVING LOWER([Column1]) like 'teststring\%' ESCAPE '\'", c[EngineCodes.SqlServer]);
751+
}
752+
753+
[Fact]
754+
public void EscapedHavingStarts()
755+
{
756+
var q = new Query("Table1")
757+
.HavingStarts("Column1", @"TestString\%", false, @"\");
758+
var c = Compile(q);
759+
760+
Assert.Equal(@"SELECT * FROM [Table1] HAVING LOWER([Column1]) like 'teststring\%%' ESCAPE '\'", c[EngineCodes.SqlServer]);
761+
}
762+
763+
[Fact]
764+
public void EscapedHavingEnds()
765+
{
766+
var q = new Query("Table1")
767+
.HavingEnds("Column1", @"TestString\%", false, @"\");
768+
var c = Compile(q);
769+
770+
Assert.Equal(@"SELECT * FROM [Table1] HAVING LOWER([Column1]) like '%teststring\%' ESCAPE '\'", c[EngineCodes.SqlServer]);
771+
}
772+
773+
[Fact]
774+
public void EscapedHavingContains()
775+
{
776+
var q = new Query("Table1")
777+
.HavingContains("Column1", @"TestString\%", false, @"\");
778+
var c = Compile(q);
779+
780+
Assert.Equal(@"SELECT * FROM [Table1] HAVING LOWER([Column1]) like '%teststring\%%' ESCAPE '\'", c[EngineCodes.SqlServer]);
781+
}
782+
783+
[Fact]
784+
public void EscapeClauseThrowsForMultipleCharacters()
785+
{
786+
Assert.ThrowsAny<ArgumentException>(() =>
787+
{
788+
var q = new Query("Table1")
789+
.HavingContains("Column1", @"TestString\%", false, @"\aa");
790+
});
791+
}
701792
}
702793
}

QueryBuilder/Base.Where.cs

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -228,115 +228,119 @@ public Q OrWhereFalse(string column)
228228
return Or().WhereFalse(column);
229229
}
230230

231-
public Q WhereLike(string column, object value, bool caseSensitive = false)
231+
public Q WhereLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null)
232232
{
233233
return AddComponent("where", new BasicStringCondition
234234
{
235235
Operator = "like",
236236
Column = column,
237237
Value = value,
238238
CaseSensitive = caseSensitive,
239+
EscapeCharacter = escapeCharacter,
239240
IsOr = GetOr(),
240241
IsNot = GetNot(),
241242
});
242243
}
243244

244-
public Q WhereNotLike(string column, object value, bool caseSensitive = false)
245+
public Q WhereNotLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null)
245246
{
246-
return Not().WhereLike(column, value, caseSensitive);
247+
return Not().WhereLike(column, value, caseSensitive, escapeCharacter);
247248
}
248249

249-
public Q OrWhereLike(string column, object value, bool caseSensitive = false)
250+
public Q OrWhereLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null)
250251
{
251-
return Or().WhereLike(column, value, caseSensitive);
252+
return Or().WhereLike(column, value, caseSensitive, escapeCharacter);
252253
}
253254

254-
public Q OrWhereNotLike(string column, object value, bool caseSensitive = false)
255+
public Q OrWhereNotLike(string column, object value, bool caseSensitive = false, string escapeCharacter = null)
255256
{
256-
return Or().Not().WhereLike(column, value, caseSensitive);
257+
return Or().Not().WhereLike(column, value, caseSensitive, escapeCharacter);
257258
}
258-
public Q WhereStarts(string column, object value, bool caseSensitive = false)
259+
public Q WhereStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null)
259260
{
260261
return AddComponent("where", new BasicStringCondition
261262
{
262263
Operator = "starts",
263264
Column = column,
264265
Value = value,
265266
CaseSensitive = caseSensitive,
267+
EscapeCharacter = escapeCharacter,
266268
IsOr = GetOr(),
267269
IsNot = GetNot(),
268270
});
269271
}
270272

271-
public Q WhereNotStarts(string column, object value, bool caseSensitive = false)
273+
public Q WhereNotStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null)
272274
{
273-
return Not().WhereStarts(column, value, caseSensitive);
275+
return Not().WhereStarts(column, value, caseSensitive, escapeCharacter);
274276
}
275277

276-
public Q OrWhereStarts(string column, object value, bool caseSensitive = false)
278+
public Q OrWhereStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null)
277279
{
278-
return Or().WhereStarts(column, value, caseSensitive);
280+
return Or().WhereStarts(column, value, caseSensitive, escapeCharacter);
279281
}
280282

281-
public Q OrWhereNotStarts(string column, object value, bool caseSensitive = false)
283+
public Q OrWhereNotStarts(string column, object value, bool caseSensitive = false, string escapeCharacter = null)
282284
{
283-
return Or().Not().WhereStarts(column, value, caseSensitive);
285+
return Or().Not().WhereStarts(column, value, caseSensitive, escapeCharacter);
284286
}
285287

286-
public Q WhereEnds(string column, object value, bool caseSensitive = false)
288+
public Q WhereEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null)
287289
{
288290
return AddComponent("where", new BasicStringCondition
289291
{
290292
Operator = "ends",
291293
Column = column,
292294
Value = value,
293295
CaseSensitive = caseSensitive,
296+
EscapeCharacter = escapeCharacter,
294297
IsOr = GetOr(),
295298
IsNot = GetNot(),
296299
});
297300
}
298301

299-
public Q WhereNotEnds(string column, object value, bool caseSensitive = false)
302+
public Q WhereNotEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null)
300303
{
301-
return Not().WhereEnds(column, value, caseSensitive);
304+
return Not().WhereEnds(column, value, caseSensitive, escapeCharacter);
302305
}
303306

304-
public Q OrWhereEnds(string column, object value, bool caseSensitive = false)
307+
public Q OrWhereEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null)
305308
{
306-
return Or().WhereEnds(column, value, caseSensitive);
309+
return Or().WhereEnds(column, value, caseSensitive, escapeCharacter);
307310
}
308311

309-
public Q OrWhereNotEnds(string column, object value, bool caseSensitive = false)
312+
public Q OrWhereNotEnds(string column, object value, bool caseSensitive = false, string escapeCharacter = null)
310313
{
311-
return Or().Not().WhereEnds(column, value, caseSensitive);
314+
return Or().Not().WhereEnds(column, value, caseSensitive, escapeCharacter);
312315
}
313316

314-
public Q WhereContains(string column, object value, bool caseSensitive = false)
317+
public Q WhereContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null)
315318
{
316319
return AddComponent("where", new BasicStringCondition
317320
{
318321
Operator = "contains",
319322
Column = column,
320323
Value = value,
321324
CaseSensitive = caseSensitive,
325+
EscapeCharacter = escapeCharacter,
322326
IsOr = GetOr(),
323327
IsNot = GetNot(),
324328
});
325329
}
326330

327-
public Q WhereNotContains(string column, object value, bool caseSensitive = false)
331+
public Q WhereNotContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null)
328332
{
329-
return Not().WhereContains(column, value, caseSensitive);
333+
return Not().WhereContains(column, value, caseSensitive, escapeCharacter);
330334
}
331335

332-
public Q OrWhereContains(string column, object value, bool caseSensitive = false)
336+
public Q OrWhereContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null)
333337
{
334-
return Or().WhereContains(column, value, caseSensitive);
338+
return Or().WhereContains(column, value, caseSensitive, escapeCharacter);
335339
}
336340

337-
public Q OrWhereNotContains(string column, object value, bool caseSensitive = false)
341+
public Q OrWhereNotContains(string column, object value, bool caseSensitive = false, string escapeCharacter = null)
338342
{
339-
return Or().Not().WhereContains(column, value, caseSensitive);
343+
return Or().Not().WhereContains(column, value, caseSensitive, escapeCharacter);
340344
}
341345

342346
public Q WhereBetween<T>(string column, T lower, T higher)

QueryBuilder/Clauses/ConditionClause.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Collections.Generic;
23

34
namespace SqlKata
@@ -35,8 +36,22 @@ public override AbstractClause Clone()
3536

3637
public class BasicStringCondition : BasicCondition
3738
{
39+
3840
public bool CaseSensitive { get; set; } = false;
3941

42+
private string escapeCharacter = null;
43+
public string EscapeCharacter
44+
{
45+
get => escapeCharacter;
46+
set
47+
{
48+
if (string.IsNullOrWhiteSpace(value))
49+
value = null;
50+
else if (value.Length > 1)
51+
throw new ArgumentOutOfRangeException("The EscapeCharacter can only contain a single character!");
52+
escapeCharacter = value;
53+
}
54+
}
4055
/// <inheritdoc />
4156
public override AbstractClause Clone()
4257
{
@@ -49,6 +64,7 @@ public override AbstractClause Clone()
4964
IsOr = IsOr,
5065
IsNot = IsNot,
5166
CaseSensitive = CaseSensitive,
67+
EscapeCharacter = EscapeCharacter,
5268
Component = Component,
5369
};
5470
}

QueryBuilder/Compilers/Compiler.Conditions.cs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -117,17 +117,17 @@ protected virtual string CompileBasicStringCondition(SqlResult ctx, BasicStringC
117117

118118
method = "LIKE";
119119

120-
if (x.Operator == "starts")
120+
switch (x.Operator)
121121
{
122-
value = $"{value}%";
123-
}
124-
else if (x.Operator == "ends")
125-
{
126-
value = $"%{value}";
127-
}
128-
else if (x.Operator == "contains")
129-
{
130-
value = $"%{value}%";
122+
case "starts":
123+
value = $"{value}%";
124+
break;
125+
case "ends":
126+
value = $"%{value}";
127+
break;
128+
case "contains":
129+
value = $"%{value}%";
130+
break;
131131
}
132132
}
133133

@@ -149,6 +149,11 @@ protected virtual string CompileBasicStringCondition(SqlResult ctx, BasicStringC
149149
sql = $"{column} {checkOperator(method)} {Parameter(ctx, value)}";
150150
}
151151

152+
if (!string.IsNullOrEmpty(x.EscapeCharacter))
153+
{
154+
sql = $"{sql} ESCAPE '{x.EscapeCharacter}'";
155+
}
156+
152157
return x.IsNot ? $"NOT ({sql})" : sql;
153158

154159
}

0 commit comments

Comments
 (0)