Skip to content

Commit 2a2ed41

Browse files
VectorTetraViktor Tochonov
and
Viktor Tochonov
authored
Fixed Contains and In operators implementation for collections (#525)
* Fixed `Contains` and `In` operators implementation for collections * Skipped `Contains` operator test that fails --------- Co-authored-by: Viktor Tochonov <[email protected]>
1 parent 1e372c7 commit 2a2ed41

File tree

3 files changed

+82
-39
lines changed

3 files changed

+82
-39
lines changed

src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs

+41-39
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ type ObjectListFilter =
1818
| In of FieldFilter<System.IComparable list>
1919
| StartsWith of FieldFilter<string>
2020
| EndsWith of FieldFilter<string>
21-
| Contains of FieldFilter<string>
21+
| Contains of FieldFilter<System.IComparable>
2222
| OfTypes of Type list
2323
| FilterField of FieldFilter<ObjectListFilter>
2424

@@ -142,25 +142,27 @@ module ObjectListFilter =
142142
let private StringStartsWithMethod = stringType.GetMethod ("StartsWith", [| stringType |])
143143
let private StringEndsWithMethod = stringType.GetMethod ("EndsWith", [| stringType |])
144144
let private StringContainsMethod = stringType.GetMethod ("Contains", [| stringType |])
145-
let private getEnumerableContainsMethod (memberType : Type) =
145+
let private getCollectionInstanceContainsMethod (memberType : Type) =
146+
memberType
147+
.GetMethods(BindingFlags.Instance ||| BindingFlags.Public)
148+
.FirstOrDefault (fun m -> m.Name = "Contains" && m.GetParameters().Length = 1)
149+
|> ValueOption.ofObj
150+
let private getEnumerableContainsMethod (itemType : Type) =
146151
match
147152
typeof<Enumerable>
148153
.GetMethods(BindingFlags.Static ||| BindingFlags.Public)
149154
.FirstOrDefault (fun m -> m.Name = "Contains" && m.GetParameters().Length = 2)
150155
with
151156
| null -> raise (MissingMemberException "Static 'Contains' method with 2 parameters not found on 'Enumerable' class")
152-
| containsGenericStaticMethod ->
153-
if
154-
memberType.IsGenericType
155-
&& memberType.GenericTypeArguments.Length = 1
156-
then
157-
containsGenericStaticMethod.MakeGenericMethod (memberType.GenericTypeArguments)
158-
else
159-
let ienumerable =
160-
memberType
161-
.GetInterfaces()
162-
.First (fun i -> i.FullName.StartsWith "System.Collections.Generic.IEnumerable`1")
163-
containsGenericStaticMethod.MakeGenericMethod ([| ienumerable.GenericTypeArguments[0] |])
157+
| containsGenericStaticMethod -> containsGenericStaticMethod.MakeGenericMethod ([| itemType |])
158+
let private getEnumerableCastMethod (itemType : Type) =
159+
match
160+
typeof<Enumerable>
161+
.GetMethods(BindingFlags.Static ||| BindingFlags.Public)
162+
.FirstOrDefault (fun m -> m.Name = "Cast" && m.GetParameters().Length = 1)
163+
with
164+
| null -> raise (MissingMemberException "Static 'Cast' method with 1 parameter not found on 'Enumerable' class")
165+
| castGenericStaticMethod -> castGenericStaticMethod.MakeGenericMethod ([| itemType |])
164166

165167
let getField (param : ParameterExpression) fieldName = Expression.PropertyOrField (param, fieldName)
166168

@@ -204,38 +206,38 @@ module ObjectListFilter =
204206
&& memberType
205207
.GetInterfaces()
206208
.Any (fun i -> i.FullName.StartsWith "System.Collections.Generic.IEnumerable`1")
209+
let callContains memberType =
210+
let itemType =
211+
if ``member``.Type.IsArray then ``member``.Type.GetElementType()
212+
else ``member``.Type.GetGenericArguments()[0]
213+
let valueType =
214+
match f.Value with
215+
| null -> itemType
216+
| value -> value.GetType()
217+
let castedMember =
218+
if itemType = valueType then ``member`` :> Expression
219+
else
220+
let castMethod = getEnumerableCastMethod valueType
221+
Expression.Call (castMethod, ``member``)
222+
match getCollectionInstanceContainsMethod memberType with
223+
| ValueNone ->
224+
let enumerableContains = getEnumerableContainsMethod valueType
225+
Expression.Call (enumerableContains, castedMember, Expression.Constant (f.Value))
226+
| ValueSome instanceContainsMethod ->
227+
Expression.Call (castedMember, instanceContainsMethod, Expression.Constant (f.Value))
207228
match ``member``.Member with
208-
| :? PropertyInfo as prop when prop.PropertyType |> isEnumerable ->
209-
match
210-
prop.PropertyType
211-
.GetMethods(BindingFlags.Instance ||| BindingFlags.Public)
212-
.FirstOrDefault (fun m -> m.Name = "Contains" && m.GetParameters().Length = 1)
213-
with
214-
| null ->
215-
Expression.Call (
216-
getEnumerableContainsMethod prop.PropertyType,
217-
Expression.PropertyOrField (param, f.FieldName),
218-
Expression.Constant (f.Value)
219-
)
220-
| instanceContainsMethod ->
221-
Expression.Call (Expression.PropertyOrField (param, f.FieldName), instanceContainsMethod, Expression.Constant (f.Value))
222-
| :? FieldInfo as field when field.FieldType |> isEnumerable ->
223-
Expression.Call (
224-
getEnumerableContainsMethod field.FieldType,
225-
Expression.PropertyOrField (param, f.FieldName),
226-
Expression.Constant (f.Value)
227-
)
229+
| :? PropertyInfo as prop when prop.PropertyType |> isEnumerable -> callContains prop.PropertyType
230+
| :? FieldInfo as field when field.FieldType |> isEnumerable -> callContains field.FieldType
228231
| _ ->
229232
if ``member``.Type = stringType then
230233
Expression.Call (``member``, StringContainsMethod, Expression.Constant (f.Value))
231234
else
232235
Expression.Call (Expression.Convert (``member``, stringType), StringContainsMethod, Expression.Constant (f.Value))
233-
| In f ->
236+
| In f when not (f.Value.IsEmpty) ->
234237
let ``member`` = Expression.PropertyOrField (param, f.FieldName)
235-
f.Value
236-
|> Seq.map (fun v -> Expression.Equal (``member``, Expression.Constant (v)))
237-
|> Seq.reduce (fun acc expr -> Expression.OrElse (acc, expr))
238-
:> Expression
238+
let enumerableContains = getEnumerableContainsMethod typeof<IComparable>
239+
Expression.Call (enumerableContains, Expression.Constant (f.Value), Expression.Convert (``member``, typeof<IComparable>))
240+
| In f -> Expression.Constant (true)
239241
| OfTypes types ->
240242
types
241243
|> Seq.map (fun t -> buildTypeDiscriminatorCheck param t)

tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqGenerateTests.fs

+20
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ type ValidIntObject =
7979
type FakeEntity = {
8080
ValidStringStruct : ValidStringStruct
8181
ValidStringObject : ValidStringObject
82+
ValidStringStructList : ValidStringStruct list
83+
ValidStringObjectList : ValidStringObject list
8284
string : string
8385
ValidIntStruct : ValidIntStruct
8486
ValidIntObject : ValidIntObject
@@ -125,6 +127,23 @@ let ``ObjectListFilter works with Contains operator for ValidStringStruct`` () =
125127
let queryDefinition = CosmosLinqExtensions.ToQueryDefinition filterQuery
126128
equals queryDefinition.QueryText, """SELECT VALUE root FROM root WHERE CONTAINS(root["validStringStruct"], "athan")"""
127129

130+
[<Fact (Skip = "Cast not supported in Cosmos LINQ")>]
131+
let ``ObjectListFilter works with Contains operator for ValidStringStruct list`` () =
132+
let queryable = container.GetItemLinqQueryable<FakeEntity> ()
133+
let filter = Contains { FieldName = "validStringStructList"; Value = "athan" }
134+
let filterQuery = queryable.Apply (filter, filterOptions)
135+
let queryDefinition = CosmosLinqExtensions.ToQueryDefinition filterQuery
136+
equals queryDefinition.QueryText, """SELECT VALUE root FROM root WHERE ARRAY_CONTAINS(root["validStringStructList"], "athan")"""
137+
138+
[<Fact>]
139+
let ``ObjectListFilter works with In operator for ValidStringStruct list`` () =
140+
let queryable = container.GetItemLinqQueryable<FakeEntity> ()
141+
let filter = In { FieldName = "validStringStruct"; Value = [ "athan"; "gaja" ] }
142+
let filterQuery = queryable.Apply (filter, filterOptions)
143+
let queryDefinition = CosmosLinqExtensions.ToQueryDefinition filterQuery
144+
equals queryDefinition.QueryText, """SELECT VALUE root FROM root WHERE ARRAY_CONTAINS([ "athan", "gaja" ], root["validStringStruct"])"""
145+
146+
128147
[<Fact>]
129148
let ``ObjectListFilter works with Equals operator for ValidStringObject`` () =
130149
let filter = Equals { FieldName = "validStringObject"; Value = ValidStringObject "Jonathan" }
@@ -133,6 +152,7 @@ let ``ObjectListFilter works with Equals operator for ValidStringObject`` () =
133152
let queryDefinition = CosmosLinqExtensions.ToQueryDefinition filterQuery
134153
equals queryDefinition.QueryText, """SELECT VALUE root FROM root WHERE (root["validStringObject"] = "Jonathan")"""
135154

155+
136156
[<Fact>]
137157
let ``ObjectListFilter works with GreaterThan operator for ValidIntStruct`` () =
138158
let queryable = container.GetItemLinqQueryable<FakeEntity> ()

tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs

+21
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,27 @@ let ``ObjectListFilter works with IN operator for int type field`` () =
189189
result.Contact |> equals { Email = "[email protected]" }
190190
result.Friends |> equals [ { Email = "[email protected]" }; { Email = "[email protected]" } ]
191191

192+
[<Fact>]
193+
let ``ObjectListFilter works with Contains operator for array type field`` () =
194+
let filter = Contains { FieldName = "friends"; Value = { Email = "[email protected]" } }
195+
let queryable = data.AsQueryable ()
196+
let filteredData = queryable.Apply (filter) |> Seq.toList
197+
List.length filteredData |> equals 2
198+
do
199+
let result = List.head filteredData
200+
result.ID |> equals 4
201+
result.FirstName |> equals "Ben"
202+
result.LastName |> equals "Adams"
203+
result.Contact |> equals { Email = "[email protected]" }
204+
result.Friends |> equals [ { Email = "[email protected]" }; { Email = "[email protected]" } ]
205+
do
206+
let result = List.last filteredData
207+
result.ID |> equals 7
208+
result.FirstName |> equals "Jeneffer"
209+
result.LastName |> equals "Trif"
210+
result.Contact |> equals { Email = "[email protected]" }
211+
result.Friends |> equals [ { Email = "[email protected]" } ]
212+
192213
[<Fact>]
193214
let ``ObjectListFilter works with FilterField operator`` () =
194215
let filter =

0 commit comments

Comments
 (0)