@@ -11,27 +11,24 @@ type ObjectListFilter =
1111 | And of ObjectListFilter * ObjectListFilter
1212 | Or of ObjectListFilter * ObjectListFilter
1313 | Not of ObjectListFilter
14- | Equals of FieldFilter < System.IComparable >
14+ | Equals of Filter : FieldFilter<System.IComparable> * Comparer : System.Collections.IComparer
1515 | GreaterThan of FieldFilter < System.IComparable >
1616 | GreaterThanOrEqual of FieldFilter < System.IComparable >
1717 | LessThan of FieldFilter < System.IComparable >
1818 | LessThanOrEqual of FieldFilter < System.IComparable >
1919 | In of FieldFilter < obj list >
20- | StartsWith of FieldFilter < string >
21- | EndsWith of FieldFilter < string >
22- | Contains of FieldFilter < System.IComparable >
20+ | StartsWith of Filter : FieldFilter<string> * Comparer : System.Collections.IComparer
21+ | EndsWith of Filter : FieldFilter<string> * Comparer : System.Collections.IComparer
22+ | Contains of Filter : FieldFilter<System.IComparable> * Comparer : System.Collections.IComparer
2323 | OfTypes of Type list
2424 | FilterField of FieldFilter < ObjectListFilter >
25- | EqualsCI of FieldFilter < string >
26- | StartsWithCI of FieldFilter < string >
27- | EndsWithCI of FieldFilter < string >
28- | ContainsCI of FieldFilter < string >
2925
3026open System.Linq
3127open System.Linq .Expressions
3228open System.Runtime .InteropServices
3329open System.Reflection
3430open System.Collections .Generic
31+ open System.Collections
3532
3633type private CompareDiscriminatorExpression < 'T , 'D > = Expression< Func< 'T, 'D, bool>>
3734
@@ -99,7 +96,7 @@ module ObjectListFilter =
9996 let ( ||| ) x y = Or ( x, y)
10097
10198 /// Creates a new ObjectListFilter representing an EQUALS operation between two comparable values.
102- let ( = == ) fname value = Equals { FieldName = fname; Value = value }
99+ let ( = == ) fname value = Equals ( { FieldName = fname; Value = value }, null )
103100
104101 /// Creates a new ObjectListFilter representing a GREATER THAN operation of a comparable value.
105102 let ( >>> ) fname value = GreaterThan { FieldName = fname; Value = value }
@@ -114,13 +111,13 @@ module ObjectListFilter =
114111 let ( <== ) fname value = LessThanOrEqual { FieldName = fname ; Value = value }
115112
116113 /// Creates a new ObjectListFilter representing a STARTS WITH operation of a string value.
117- let ( =@@ ) fname value = StartsWith { FieldName = fname ; Value = value }
114+ let ( =@@ ) fname value = StartsWith ( { FieldName = fname ; Value = value }, null )
118115
119116 /// Creates a new ObjectListFilter representing an ENDS WITH operation of a string value.
120- let ( @@= ) fname value = EndsWith { FieldName = fname ; Value = value }
117+ let ( @@= ) fname value = EndsWith ( { FieldName = fname ; Value = value }, null )
121118
122119 /// Creates a new ObjectListFilter representing a CONTAINS operation.
123- let ( @=@ ) fname value = Contains { FieldName = fname ; Value = value }
120+ let ( @=@ ) fname value = Contains ( { FieldName = fname ; Value = value }, null )
124121
125122 /// Creates a new ObjectListFilter representing a IN operation.
126123 let ( = ~= ) fname value = In { FieldName = fname ; Value = value }
@@ -132,16 +129,16 @@ module ObjectListFilter =
132129 let ( !!! ) filter = Not filter
133130
134131 /// Creates a new ObjectListFilter representing a case - insensitive EQUALS operation on a string value.
135- let ( === ~ ) fname value = EqualsCI { FieldName = fname ; Value = value }
132+ let ( === ~ ) fname ( value : string ) = Equals ( { FieldName = fname ; Value = value }, StringComparer.OrdinalIgnoreCase )
136133
137134 /// Creates a new ObjectListFilter representing a case - insensitive STARTS WITH operation on a string value.
138- let ( =@@ ~ ) fname value = StartsWithCI { FieldName = fname ; Value = value }
135+ let ( =@@ ~ ) fname value = StartsWith ( { FieldName = fname ; Value = value }, StringComparer.OrdinalIgnoreCase )
139136
140137 /// Creates a new ObjectListFilter representing a case - insensitive ENDS WITH operation on a string value.
141- let ( @@= ~ ) fname value = EndsWithCI { FieldName = fname ; Value = value }
138+ let ( @@= ~ ) fname value = EndsWith ( { FieldName = fname ; Value = value }, StringComparer.OrdinalIgnoreCase )
142139
143140 /// Creates a new ObjectListFilter representing a case - insensitive CONTAINS operation on a string value.
144- let ( @=@ ~ ) fname value = ContainsCI { FieldName = fname ; Value = value }
141+ let ( @=@ ~ ) fname ( value : string ) = Contains ( { FieldName = fname ; Value = value }, StringComparer.OrdinalIgnoreCase )
145142
146143 let private genericWhereMethod =
147144 typeof < Queryable > .GetMethods ()
@@ -164,11 +161,10 @@ module ObjectListFilter =
164161 let private StringEndsWithMethod = stringType.GetMethod ( " EndsWith" , [| stringType |])
165162 let private StringContainsMethod = stringType.GetMethod ( " Contains" , [| stringType |])
166163 let private stringComparisonType = typeof< StringComparison>
167- let private StringEqualsCIMethod = stringType.GetMethod ( " Equals" , [| stringType; stringComparisonType |])
168- let private StringStartsWithCIMethod = stringType.GetMethod ( " StartsWith" , [| stringType; stringComparisonType |])
169- let private StringEndsWithCIMethod = stringType.GetMethod ( " EndsWith" , [| stringType; stringComparisonType |])
170- let private StringContainsCIMethod = stringType.GetMethod ( " Contains" , [| stringType; stringComparisonType |])
171- let private OrdinalIgnoreCase = Expression.Constant ( StringComparison.OrdinalIgnoreCase)
164+ let private StringStartsWithWithComparisonMethod = stringType.GetMethod ( " StartsWith" , [| stringType; stringComparisonType |])
165+ let private StringEndsWithWithComparisonMethod = stringType.GetMethod ( " EndsWith" , [| stringType; stringComparisonType |])
166+ let private StringContainsWithComparisonMethod = stringType.GetMethod ( " Contains" , [| stringType; stringComparisonType |])
167+ let private StringEqualsWithComparisonMethod = stringType.GetMethod ( " Equals" , [| stringType; stringComparisonType |])
172168 let private unwrapOptionMethod =
173169 FSharp.Data.GraphQL.Helpers.moduleType.GetMethod ( nameof Helpers.unwrap)
174170
@@ -227,6 +223,20 @@ module ObjectListFilter =
227223 |> Seq.where ( fun m -> m.Name = " Equals" )
228224 |> Seq.head
229225
226+ /// Maps an IComparer to a StringComparison value.
227+ /// Returns ValueNone for null or Ordinal comparers (use default Expression.Equal path).
228+ let private comparerToStringComparison ( comparer : IComparer ) =
229+ match comparer with
230+ | null -> ValueNone
231+ | :? StringComparer as sc ->
232+ if obj.ReferenceEquals ( sc, StringComparer.OrdinalIgnoreCase) then ValueSome StringComparison.OrdinalIgnoreCase
233+ elif obj.ReferenceEquals ( sc, StringComparer.InvariantCultureIgnoreCase) then ValueSome StringComparison.InvariantCultureIgnoreCase
234+ elif obj.ReferenceEquals ( sc, StringComparer.CurrentCultureIgnoreCase) then ValueSome StringComparison.CurrentCultureIgnoreCase
235+ elif obj.ReferenceEquals ( sc, StringComparer.InvariantCulture) then ValueSome StringComparison.InvariantCulture
236+ elif obj.ReferenceEquals ( sc, StringComparer.CurrentCulture) then ValueSome StringComparison.CurrentCulture
237+ else ValueNone
238+ | _ -> ValueNone
239+
230240 let rec buildFilterExpr isEnumerableQuery ( param : SourceExpression ) buildTypeDiscriminatorCheck filter : Expression =
231241
232242 let build = buildFilterExpr isEnumerableQuery param buildTypeDiscriminatorCheck
@@ -246,31 +256,41 @@ module ObjectListFilter =
246256 | _ -> Expression.Convert ( `` member `` , stringType)
247257
248258 match filter with
249- | Not ( Equals f ) ->
259+ | Not ( Equals ( f , comparer ) ) ->
250260 let ``member`` = Expression.PropertyOrField ( param, f.FieldName)
251- let hasEqualityOperator = hasEqualityOperator `` member `` .Type
252- match f.Value with
253- | NoCast when hasEqualityOperator -> Expression.NotEqual ( `` member `` , Expression.Constant f.Value)
254- | NoCast
255- | NonEnumerableCast _ ->
256- Expression.NotEqual ( Expression.Convert ( `` member `` , objectType), Expression.Convert (( Expression.Constant f.Value), objectType))
257- | Enumerable ->
258- let ``const`` = Expression.Constant ( Values.normalizeOptional `` member `` .Type f.Value)
259- Expression.Not ( Expression.Call ( `` const `` , equalsMethod, `` member `` ))
261+ match comparerToStringComparison comparer with
262+ | ValueSome comparison ->
263+ let value = Helpers.unwrap ( box f.Value) :?> string
264+ Expression.Not ( Expression.Call ( normalizeStringMemberExpr `` member `` , StringEqualsWithComparisonMethod, Expression.Constant ( value, typeof< string>), Expression.Constant comparison)) :> Expression
265+ | ValueNone ->
266+ let hasEqualityOperator = hasEqualityOperator `` member `` .Type
267+ match f.Value with
268+ | NoCast when hasEqualityOperator -> Expression.NotEqual ( `` member `` , Expression.Constant f.Value)
269+ | NoCast
270+ | NonEnumerableCast _ ->
271+ Expression.NotEqual ( Expression.Convert ( `` member `` , objectType), Expression.Convert (( Expression.Constant f.Value), objectType))
272+ | Enumerable ->
273+ let ``const`` = Expression.Constant ( Values.normalizeOptional `` member `` .Type f.Value)
274+ Expression.Not ( Expression.Call ( `` const `` , equalsMethod, `` member `` ))
260275 | Not f -> f |> build |> Expression.Not :> Expression
261276 | And ( f1, f2) -> Expression.AndAlso ( build f1, build f2)
262277 | Or ( f1, f2) -> Expression.OrElse ( build f1, build f2)
263- | Equals f ->
278+ | Equals ( f , comparer ) ->
264279 let ``member`` = Expression.PropertyOrField ( param, f.FieldName)
265- let hasEqualityOperator = hasEqualityOperator `` member `` .Type
266- match f.Value with
267- | NoCast when hasEqualityOperator -> Expression.Equal ( `` member `` , Expression.Constant f.Value)
268- | NoCast
269- | NonEnumerableCast _ ->
270- Expression.Equal ( Expression.Convert ( `` member `` , objectType), Expression.Convert (( Expression.Constant f.Value), objectType))
271- | Enumerable ->
272- let ``const`` = Expression.Constant ( Values.normalizeOptional `` member `` .Type f.Value)
273- Expression.Call ( `` const `` , equalsMethod, `` member `` )
280+ match comparerToStringComparison comparer with
281+ | ValueSome comparison ->
282+ let value = Helpers.unwrap ( box f.Value) :?> string
283+ Expression.Call ( normalizeStringMemberExpr `` member `` , StringEqualsWithComparisonMethod, Expression.Constant ( value, typeof< string>), Expression.Constant comparison) :> Expression
284+ | ValueNone ->
285+ let hasEqualityOperator = hasEqualityOperator `` member `` .Type
286+ match f.Value with
287+ | NoCast when hasEqualityOperator -> Expression.Equal ( `` member `` , Expression.Constant f.Value)
288+ | NoCast
289+ | NonEnumerableCast _ ->
290+ Expression.Equal ( Expression.Convert ( `` member `` , objectType), Expression.Convert (( Expression.Constant f.Value), objectType))
291+ | Enumerable ->
292+ let ``const`` = Expression.Constant ( Values.normalizeOptional `` member `` .Type f.Value)
293+ Expression.Call ( `` const `` , equalsMethod, `` member `` )
274294 | GreaterThan f ->
275295 let ``member`` = Expression.PropertyOrField ( param, f.FieldName)
276296 match f.Value with
@@ -295,14 +315,22 @@ module ObjectListFilter =
295315 | NoCast -> Expression.LessThanOrEqual ( `` member `` , Expression.Constant f.Value)
296316 | Enumerable -> Expression.LessThanOrEqual ( `` member `` , Expression.Constant ( Values.normalizeOptional `` member `` .Type f.Value))
297317 | NonEnumerableCast `` type `` -> Expression.LessThanOrEqual (( unsafeConvertTo `` type `` `` member `` ), Expression.Constant f.Value)
298- | StartsWith f ->
318+ | StartsWith ( f , comparer ) ->
299319 let ``member`` = Expression.PropertyOrField ( param, f.FieldName)
300- Expression.Call ( normalizeStringMemberExpr `` member `` , StringStartsWithMethod, Expression.Constant f.Value)
301- | EndsWith f ->
320+ match comparerToStringComparison comparer with
321+ | ValueSome comparison ->
322+ Expression.Call ( normalizeStringMemberExpr `` member `` , StringStartsWithWithComparisonMethod, Expression.Constant f.Value, Expression.Constant comparison)
323+ | ValueNone ->
324+ Expression.Call ( normalizeStringMemberExpr `` member `` , StringStartsWithMethod, Expression.Constant f.Value)
325+ | EndsWith ( f, comparer) ->
302326 let ``member`` = Expression.PropertyOrField ( param, f.FieldName)
303- Expression.Call ( normalizeStringMemberExpr `` member `` , StringEndsWithMethod, Expression.Constant f.Value)
327+ match comparerToStringComparison comparer with
328+ | ValueSome comparison ->
329+ Expression.Call ( normalizeStringMemberExpr `` member `` , StringEndsWithWithComparisonMethod, Expression.Constant f.Value, Expression.Constant comparison)
330+ | ValueNone ->
331+ Expression.Call ( normalizeStringMemberExpr `` member `` , StringEndsWithMethod, Expression.Constant f.Value)
304332
305- | Contains f ->
333+ | Contains ( f , comparer ) ->
306334 let ``member`` = Expression.PropertyOrField ( param, f.FieldName)
307335 let isEnumerable ( memberType : Type ) =
308336 not ( Type.(=) ( memberType, stringType))
@@ -338,7 +366,11 @@ module ObjectListFilter =
338366 | :? FieldInfo as field when field.FieldType |> isEnumerable -> callContains field.FieldType
339367 | _ ->
340368 let unwrappedValue = Helpers.unwrap f.Value
341- Expression.Call ( normalizeStringMemberExpr `` member `` , StringContainsMethod, Expression.Constant unwrappedValue)
369+ match comparerToStringComparison comparer with
370+ | ValueSome comparison ->
371+ Expression.Call ( normalizeStringMemberExpr `` member `` , StringContainsWithComparisonMethod, Expression.Constant ( unwrappedValue :?> string, typeof< string>), Expression.Constant comparison)
372+ | ValueNone ->
373+ Expression.Call ( normalizeStringMemberExpr `` member `` , StringContainsMethod, Expression.Constant unwrappedValue)
342374 | In f when not ( f.Value.IsEmpty) ->
343375 let ``member`` = Expression.PropertyOrField ( param, f.FieldName)
344376 let enumerableContains = getEnumerableContainsMethod objectType
@@ -351,18 +383,6 @@ module ObjectListFilter =
351383 | FilterField f ->
352384 let paramExpr = Expression.PropertyOrField ( param, f.FieldName)
353385 buildFilterExpr isEnumerableQuery ( SourceExpression paramExpr) buildTypeDiscriminatorCheck f.Value
354- | EqualsCI f ->
355- let ``member`` = Expression.PropertyOrField ( param, f.FieldName)
356- Expression.Call ( normalizeStringMemberExpr `` member `` , StringEqualsCIMethod, Expression.Constant f.Value, OrdinalIgnoreCase)
357- | StartsWithCI f ->
358- let ``member`` = Expression.PropertyOrField ( param, f.FieldName)
359- Expression.Call ( normalizeStringMemberExpr `` member `` , StringStartsWithCIMethod, Expression.Constant f.Value, OrdinalIgnoreCase)
360- | EndsWithCI f ->
361- let ``member`` = Expression.PropertyOrField ( param, f.FieldName)
362- Expression.Call ( normalizeStringMemberExpr `` member `` , StringEndsWithCIMethod, Expression.Constant f.Value, OrdinalIgnoreCase)
363- | ContainsCI f ->
364- let ``member`` = Expression.PropertyOrField ( param, f.FieldName)
365- Expression.Call ( normalizeStringMemberExpr `` member `` , StringContainsCIMethod, Expression.Constant f.Value, OrdinalIgnoreCase)
366386
367387 type private CompareDiscriminatorExpressionVisitor < 'T , 'D >
368388 ( compareDiscriminator : CompareDiscriminatorExpression< 'T, 'D>, param : SourceExpression, value : obj) =
0 commit comments