Skip to content

Commit

Permalink
Made LIKE collation aware
Browse files Browse the repository at this point in the history
  • Loading branch information
MarkMpn committed Mar 24, 2023
1 parent 320b458 commit 8dbb9f9
Showing 1 changed file with 40 additions and 9 deletions.
49 changes: 40 additions & 9 deletions MarkMpn.Sql4Cds.Engine/ExecutionPlan/ExpressionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -973,7 +973,6 @@ private static Expression ToExpression(this LikePredicate like, ExpressionCompil
var pattern = like.SecondExpression.ToExpression(context, contextParam, out var patternType);
var escape = like.EscapeExpression?.ToExpression(context, contextParam, out escapeType);

// TODO: Use the collations of the value/pattern and ensure they are consistent
sqlType = DataTypeHelpers.Bit;
var stringType = DataTypeHelpers.NVarChar(Int32.MaxValue, context.PrimaryDataSource.DefaultCollation, CollationLabel.CoercibleDefault);

Expand Down Expand Up @@ -1032,7 +1031,12 @@ private static Regex LikeToRegex(SqlString pattern, SqlString escape)
var inRange = false;
var escapeChar = escape.IsNull ? '\0' : escape.Value[0];

foreach (var ch in pattern.Value)
var pat = pattern.Value;

if (pattern.SqlCompareOptions.HasFlag(SqlCompareOptions.IgnoreNonSpace))
pat = RemoveDiacritics(pat);

foreach (var ch in pat)
{
if (escapeChar != '\0' && ch == escapeChar)
{
Expand Down Expand Up @@ -1093,7 +1097,7 @@ private static Regex LikeToRegex(SqlString pattern, SqlString escape)

regexBuilder.Append("$");

return new Regex(regexBuilder.ToString(), RegexOptions.IgnoreCase);
return new Regex(regexBuilder.ToString(), pattern.SqlCompareOptions.HasFlag(SqlCompareOptions.IgnoreCase) ? RegexOptions.IgnoreCase : RegexOptions.None);
}

private static SqlBoolean Like(SqlString value, SqlString pattern, SqlString escape, bool not)
Expand All @@ -1103,27 +1107,54 @@ private static SqlBoolean Like(SqlString value, SqlString pattern, SqlString esc

// Convert the LIKE pattern to a regex
var regex = LikeToRegex(pattern, escape);
var result = regex.IsMatch(value.Value);

if (not)
result = !result;

return result;
return Like(value, regex, not);
}

private static SqlBoolean Like(SqlString value, Regex pattern, bool not)
{
if (value.IsNull)
return false;

var result = pattern.IsMatch(value.Value);
var text = value.Value;

if (value.SqlCompareOptions.HasFlag(SqlCompareOptions.IgnoreNonSpace))
text = RemoveDiacritics(text);

var result = pattern.IsMatch(text);

if (not)
result = !result;

return result;
}

/// <summary>
/// Removes accents from a string, used for accent-insensitive collations
/// </summary>
/// <ref>https://stackoverflow.com/a/249126/269629</ref>
/// <param name="text">The text to remove the accents from</param>
/// <returns>A version of the <paramref name="text"/> with accents removed</returns>
static string RemoveDiacritics(string text)
{
var normalizedString = text.Normalize(NormalizationForm.FormD);
var stringBuilder = new StringBuilder(capacity: normalizedString.Length);

for (int i = 0; i < normalizedString.Length; i++)
{
char c = normalizedString[i];
var unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c);
if (unicodeCategory != UnicodeCategory.NonSpacingMark)
{
stringBuilder.Append(c);
}
}

return stringBuilder
.ToString()
.Normalize(NormalizationForm.FormC);
}

private static Expression ToExpression(this SimpleCaseExpression simpleCase, ExpressionCompilationContext context, ParameterExpression contextParam, out DataTypeReference sqlType)
{
// Convert all the different elements to expressions
Expand Down

0 comments on commit 8dbb9f9

Please sign in to comment.