Skip to content

Commit ff4ab0e

Browse files
committed
Add configurable case-insensitive mode for string filters
Introduces the CaseInsensitiveMode enum to control whether case-insensitive string comparisons use ToLower/LOWER or ToUpper/UPPER. Adds global and per-property configuration for this mode in QueryKitConfiguration, QueryKitSettings, and QueryKitPropertyMapping. Refactors all relevant string comparison logic and operators to honor the selected case transformation, enabling consistent and customizable case-insensitive behavior for various data normalization scenarios.
1 parent f194af1 commit ff4ab0e

6 files changed

Lines changed: 117 additions & 70 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace QueryKit.Configuration;
2+
3+
/// <summary>
4+
/// Controls which SQL case transformation is generated for case-insensitive string operators (e.g. @=*, _=*).
5+
/// </summary>
6+
public enum CaseInsensitiveMode
7+
{
8+
/// <summary>Default. Generates ToLower() / LOWER() comparisons.</summary>
9+
Lower = 0,
10+
11+
/// <summary>Generates ToUpper() / UPPER() comparisons. Useful when data is normalized to uppercase.</summary>
12+
Upper = 1
13+
}

QueryKit/Configuration/QueryKitConfiguration.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public interface IQueryKitConfiguration
3333
public string HasOperator { get; set; }
3434
public string DoesNotHaveOperator { get; set; }
3535
public int? MaxPropertyDepth { get; set; }
36+
public CaseInsensitiveMode CaseInsensitiveComparison { get; set; }
3637
}
3738

3839
public class QueryKitConfiguration : IQueryKitConfiguration
@@ -68,6 +69,7 @@ public class QueryKitConfiguration : IQueryKitConfiguration
6869
public bool AllowUnknownProperties { get; set; } = false;
6970
public Type? DbContextType { get; set; }
7071
public int? MaxPropertyDepth { get; set; }
72+
public CaseInsensitiveMode CaseInsensitiveComparison { get; set; }
7173

7274
public QueryKitConfiguration(Action<QueryKitSettings> configureSettings)
7375
{
@@ -106,5 +108,6 @@ public QueryKitConfiguration(Action<QueryKitSettings> configureSettings)
106108
HasOperator = settings.HasOperator;
107109
DoesNotHaveOperator = settings.DoesNotHaveOperator;
108110
MaxPropertyDepth = settings.MaxPropertyDepth;
111+
CaseInsensitiveComparison = settings.CaseInsensitiveComparison;
109112
}
110113
}

QueryKit/Configuration/QueryKitSettings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public class QueryKitSettings
3636
public bool AllowUnknownProperties { get; set; }
3737
public Type? DbContextType { get; set; }
3838
public int? MaxPropertyDepth { get; set; }
39+
public CaseInsensitiveMode CaseInsensitiveComparison { get; set; } = CaseInsensitiveMode.Lower;
3940

4041
public QueryKitPropertyMapping<TModel> Property<TModel>(Expression<Func<TModel, object>>? propertySelector)
4142
{

QueryKit/FilterParser.cs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,18 @@ private static bool IsValidPropertyName(string value)
607607
value.All(c => char.IsLetterOrDigit(c) || c == '_' || c == '.');
608608
}
609609

610+
private static CaseInsensitiveMode ResolveCaseMode(string? propertyPath, IQueryKitConfiguration? config)
611+
{
612+
if (!string.IsNullOrEmpty(propertyPath) && config?.PropertyMappings != null)
613+
{
614+
var propertyInfo = config.PropertyMappings.GetPropertyInfo(propertyPath)
615+
?? config.PropertyMappings.GetPropertyInfoByQueryName(propertyPath);
616+
if (propertyInfo?.CaseInsensitiveComparison.HasValue == true)
617+
return propertyInfo.CaseInsensitiveComparison.Value;
618+
}
619+
return config?.CaseInsensitiveComparison ?? CaseInsensitiveMode.Lower;
620+
}
621+
610622
private static Parser<Expression> ComparisonExprParser<T>(ParameterExpression parameter, IQueryKitConfiguration? config)
611623
{
612624
var comparisonOperatorParser = ComparisonOperatorParser.Token();
@@ -661,7 +673,7 @@ private static Parser<Expression> ComparisonExprParser<T>(ParameterExpression pa
661673
{
662674
var guidStringExpr = HandleGuidConversion(temp.leftExpr, temp.leftExpr.Type);
663675
return temp.op.GetExpression<T>(guidStringExpr, CreateRightExpr(temp.leftExpr, temp.right, temp.op, config, guidPropertyPath),
664-
config?.DbContextType);
676+
config?.DbContextType, ResolveCaseMode(guidPropertyPath, config));
665677
}
666678

667679
// For non-string operators, use direct GUID comparison
@@ -692,7 +704,8 @@ private static Parser<Expression> ComparisonExprParser<T>(ParameterExpression pa
692704

693705
// Ensure compatible types for property-to-property comparison
694706
var (leftCompatible, rightCompatible) = EnsureCompatibleTypes(leftExpr, rightPropertyExpr);
695-
return temp.op.GetExpression<T>(leftCompatible, rightCompatible, config?.DbContextType);
707+
var propToProptPath = temp.leftExpr is MemberExpression ptpMemberExpr ? GetPropertyPath(ptpMemberExpr, parameter) : null;
708+
return temp.op.GetExpression<T>(leftCompatible, rightCompatible, config?.DbContextType, ResolveCaseMode(propToProptPath, config));
696709
}
697710
}
698711

@@ -782,7 +795,7 @@ private static Parser<Expression> ComparisonExprParser<T>(ParameterExpression pa
782795
}
783796

784797

785-
return temp.op.GetExpression<T>(leftExprForComparison, rightExpr, config?.DbContextType);
798+
return temp.op.GetExpression<T>(leftExprForComparison, rightExpr, config?.DbContextType, ResolveCaseMode(propertyPath, config));
786799
});
787800

788801
return propertyListComparison.Or(arithmeticComparison).Or(regularComparison);
@@ -1134,7 +1147,7 @@ private static Parser<Expression> PropertyListComparisonExprParser<T>(
11341147
}
11351148

11361149
var rightExpr = CreateRightExpr(leftExpr, temp.right, temp.op, config, fullPropPath);
1137-
var comparison = temp.op.GetExpression<T>(leftExpr, rightExpr, config?.DbContextType);
1150+
var comparison = temp.op.GetExpression<T>(leftExpr, rightExpr, config?.DbContextType, ResolveCaseMode(fullPropPath, config));
11381151

11391152
// Combine with AND for negative operators, OR for positive operators
11401153
result = result == null

0 commit comments

Comments
 (0)