From b7ded77e601fd170d5fea9bd84bc44113360b65e Mon Sep 17 00:00:00 2001
From: Fati Iseni <fati.iseni@pozitrongroup.com>
Date: Sat, 16 Mar 2024 01:07:16 +0100
Subject: [PATCH 1/2] Fixed bug for in-memory LIKE impementation.

---
 .../Evaluators/SearchExtension.cs             | 24 ++++++++++++++++---
 .../EvaluatorTests/SearchExtension_Like.cs    |  6 +++++
 2 files changed, 27 insertions(+), 3 deletions(-)

diff --git a/Specification/src/Ardalis.Specification/Evaluators/SearchExtension.cs b/Specification/src/Ardalis.Specification/Evaluators/SearchExtension.cs
index e6899e65..fc694bfd 100644
--- a/Specification/src/Ardalis.Specification/Evaluators/SearchExtension.cs
+++ b/Specification/src/Ardalis.Specification/Evaluators/SearchExtension.cs
@@ -1,5 +1,6 @@
 ﻿using System;
 using System.Collections.Generic;
+using System.Text.RegularExpressions;
 
 namespace Ardalis.Specification;
 
@@ -11,16 +12,33 @@ public static bool Like(this string input, string pattern)
         {
             return SqlLike(input, pattern);
         }
-        catch (Exception)
+        catch (Exception ex)
         {
-            throw new InvalidSearchPatternException(pattern);
+            throw new InvalidSearchPatternException(pattern, ex);
         }
     }
 
+    private static bool SqlLike(this string input, string pattern)
+    {
+        // Escape special regex characters, excluding those handled separately
+        var regexPattern = Regex.Escape(pattern)
+            .Replace("%", ".*")     // Translate SQL LIKE wildcard '%' to regex '.*'
+            .Replace("_", ".")      // Translate SQL LIKE wildcard '_' to regex '.'
+            .Replace(@"\[", "[")    // Unescape '[' as it's used for character classes/ranges
+            .Replace(@"\^", "^");   // Unescape '^' as it can be used for negation in character classes
+
+        // Step 4: Ensure the pattern matches the entire string
+        regexPattern = "^" + regexPattern + "$";
+        var regex = new Regex(regexPattern, RegexOptions.IgnoreCase);
+        return regex.IsMatch(input);
+    }
+
     // This C# implementation of SQL Like operator is based on the following SO post https://stackoverflow.com/a/8583383/10577116
     // It covers almost all of the scenarios, and it's faster than regex based implementations.
     // It may fail/throw in some very specific and edge cases, hence, wrap it in try/catch.
-    private static bool SqlLike(string str, string pattern)
+    // UPDATE: it returns incorrect results for some obvious cases.
+    // More details in this issue https://github.com/ardalis/Specification/issues/390
+    private static bool SqlLikeOption2(string str, string pattern)
     {
         var isMatch = true;
         var isWildCardOn = false;
diff --git a/Specification/tests/Ardalis.Specification.UnitTests/EvaluatorTests/SearchExtension_Like.cs b/Specification/tests/Ardalis.Specification.UnitTests/EvaluatorTests/SearchExtension_Like.cs
index 6c79d78e..b03d5091 100644
--- a/Specification/tests/Ardalis.Specification.UnitTests/EvaluatorTests/SearchExtension_Like.cs
+++ b/Specification/tests/Ardalis.Specification.UnitTests/EvaluatorTests/SearchExtension_Like.cs
@@ -72,6 +72,12 @@ public class SearchExtension_Like
     [InlineData(false, "_Stuff_.txt_", "1Stuff.txt4")]
     [InlineData(false, "_Stuff_.txt_", "1Stuff3.txt")]
     [InlineData(false, "_Stuff_.txt_", "Stuff3.txt4")]
+    [InlineData(true, "%ab%", "ab")]
+    [InlineData(true, "%ab%", "abb")]
+    [InlineData(true, "%ab%", "aaab")]
+    [InlineData(true, "%ab%", "aaaab")]
+    [InlineData(true, "%ab%", "aaaaab")]
+    [InlineData(true, "%ab%", "aab")]
     public void ReturnsExpectedResult_GivenPatternAndInput(bool expectedResult, string pattern, string input)
     {
         var result = input.Like(pattern);

From f0472ec3f145c3a3cf1b3cafd4982cfe5f59c0fa Mon Sep 17 00:00:00 2001
From: Fati Iseni <fati.iseni@pozitrongroup.com>
Date: Sat, 16 Mar 2024 01:12:12 +0100
Subject: [PATCH 2/2] Cleanup.

---
 .../src/Ardalis.Specification/Evaluators/SearchExtension.cs    | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/Specification/src/Ardalis.Specification/Evaluators/SearchExtension.cs b/Specification/src/Ardalis.Specification/Evaluators/SearchExtension.cs
index fc694bfd..8e778f5b 100644
--- a/Specification/src/Ardalis.Specification/Evaluators/SearchExtension.cs
+++ b/Specification/src/Ardalis.Specification/Evaluators/SearchExtension.cs
@@ -27,9 +27,10 @@ private static bool SqlLike(this string input, string pattern)
             .Replace(@"\[", "[")    // Unescape '[' as it's used for character classes/ranges
             .Replace(@"\^", "^");   // Unescape '^' as it can be used for negation in character classes
 
-        // Step 4: Ensure the pattern matches the entire string
+        // Ensure the pattern matches the entire string
         regexPattern = "^" + regexPattern + "$";
         var regex = new Regex(regexPattern, RegexOptions.IgnoreCase);
+
         return regex.IsMatch(input);
     }
 
