diff --git a/Packages.props b/Packages.props index 46ef28be..fa30c81f 100644 --- a/Packages.props +++ b/Packages.props @@ -20,6 +20,7 @@ + diff --git a/src/FSharp.Data.GraphQL.Server.Middleware/FSharp.Data.GraphQL.Server.Middleware.fsproj b/src/FSharp.Data.GraphQL.Server.Middleware/FSharp.Data.GraphQL.Server.Middleware.fsproj index 09357bf2..347ffa76 100644 --- a/src/FSharp.Data.GraphQL.Server.Middleware/FSharp.Data.GraphQL.Server.Middleware.fsproj +++ b/src/FSharp.Data.GraphQL.Server.Middleware/FSharp.Data.GraphQL.Server.Middleware.fsproj @@ -16,6 +16,7 @@ + diff --git a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs index da0489d8..2fd5257e 100644 --- a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs +++ b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs @@ -60,12 +60,15 @@ open System.Reflection /// [] type ObjectListFilterLinqOptions<'T, 'D> - ([] compareDiscriminator : Expression> | null, [] getDiscriminatorValue : (Type -> 'D) | null) = + ([] compareDiscriminator : Expression> | null, [] getDiscriminatorValue : (Type -> 'D) | null, [] optimize: bool) = member _.CompareDiscriminator = compareDiscriminator |> ValueOption.ofObj member _.GetDiscriminatorValue = getDiscriminatorValue |> ValueOption.ofObj + /// Whether perform optimization of a LINQ expression + member _.Optimize = optimize - static member None = ObjectListFilterLinqOptions<'T, 'D> (null, null) + /// Empty options with optimization enabled + static member None = ObjectListFilterLinqOptions<'T, 'D> (null, null, true) static member GetCompareDiscriminator (getDiscriminatorValue : Expression>) = let tParam = Expression.Parameter (typeof<'T>, "x") @@ -73,13 +76,13 @@ type ObjectListFilterLinqOptions<'T, 'D> let body = Expression.Equal (Expression.Invoke (getDiscriminatorValue, tParam), dParam) Expression.Lambda> (body, tParam, dParam) - new (getDiscriminator : Expression>) = - ObjectListFilterLinqOptions<'T, 'D> (ObjectListFilterLinqOptions.GetCompareDiscriminator getDiscriminator, null) - new (compareDiscriminator : Expression>) = ObjectListFilterLinqOptions<'T, 'D> (compareDiscriminator, null) - new (getDiscriminatorValue : Type -> 'D) = - ObjectListFilterLinqOptions<'T, 'D> (compareDiscriminator = null, getDiscriminatorValue = getDiscriminatorValue) - new (getDiscriminator : Expression>, getDiscriminatorValue : Type -> 'D) = - ObjectListFilterLinqOptions<'T, 'D> (ObjectListFilterLinqOptions.GetCompareDiscriminator getDiscriminator, getDiscriminatorValue) + new (getDiscriminator : Expression>, [] optimize: bool) = + ObjectListFilterLinqOptions<'T, 'D> (ObjectListFilterLinqOptions.GetCompareDiscriminator getDiscriminator, null, optimize) + new (compareDiscriminator : Expression>, [] optimize: bool) = ObjectListFilterLinqOptions<'T, 'D> (compareDiscriminator, null, optimize) + new (getDiscriminatorValue : Type -> 'D, [] optimize: bool) = + ObjectListFilterLinqOptions<'T, 'D> (compareDiscriminator = null, getDiscriminatorValue = getDiscriminatorValue, optimize = optimize) + new (getDiscriminator : Expression>, getDiscriminatorValue : Type -> 'D, [] optimize: bool) = + ObjectListFilterLinqOptions<'T, 'D> (ObjectListFilterLinqOptions.GetCompareDiscriminator getDiscriminator, getDiscriminatorValue, optimize) /// Contains tooling for working with ObjectListFilter. module ObjectListFilter = @@ -270,8 +273,12 @@ module ObjectListFilter = ) let queryExpr = let param = Expression.Parameter (typeof<'T>, "x") - let body = buildFilterExpr (SourceExpression param) buildTypeDiscriminatorCheck filter - whereExpr<'T> query param body + if options.Optimize then + let body = ExpressionOptimizer.visit(buildFilterExpr (SourceExpression param) buildTypeDiscriminatorCheck filter) + whereExpr<'T> query param body + else + let body = buildFilterExpr (SourceExpression param) buildTypeDiscriminatorCheck filter + whereExpr<'T> query param body // Create and execute the final expression query.Provider.CreateQuery<'T> (queryExpr) @@ -282,9 +289,14 @@ module ObjectListFilterExtensions = type ObjectListFilter with - member inline filter.ApplyTo<'T, 'D> (query : IQueryable<'T>, [] options : ObjectListFilterLinqOptions<'T, 'D>) = + member inline filter.Apply<'T, 'D> (query : IQueryable<'T>) = + apply ObjectListFilterLinqOptions<'T, 'D>.None filter query + + member inline filter.Apply<'T, 'D> (query : IQueryable<'T>, [] options : ObjectListFilterLinqOptions<'T, 'D>) = apply options filter query type IQueryable<'T> with - member inline query.Apply (filter : ObjectListFilter, [] options : ObjectListFilterLinqOptions<'T, 'D>) = apply options filter query + member inline query.Apply (filter : ObjectListFilter) = apply ObjectListFilterLinqOptions.None filter query + + member inline query.Apply (filter : ObjectListFilter, options : ObjectListFilterLinqOptions<'T, 'D>) = apply options filter query diff --git a/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs b/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs index 998aaf93..7a7970e7 100644 --- a/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs @@ -5,11 +5,12 @@ open System open System.Linq open FSharp.Data.GraphQL.Types open FSharp.Data.GraphQL.Server.Middleware +open FSharp.Data.GraphQL.Server.Middleware.ObjectListFilter.Operators open FSharp.Data.GraphQL.Tests.LinqTests [] let ``ObjectListFilter works with Equals operator`` () = - let filter = Equals { FieldName = "firstName"; Value = "Jonathan" } // :> IComparable + let filter = Equals { FieldName = "firstName"; Value = "Jonathan" } let queryable = data.AsQueryable () let filteredData = queryable.Apply (filter) |> Seq.toList List.length filteredData |> equals 1 @@ -22,7 +23,7 @@ let ``ObjectListFilter works with Equals operator`` () = [] let ``ObjectListFilter works with GreaterThan operator`` () = - let filter = GreaterThan { FieldName = "id"; Value = 4 } // :> IComparable + let filter = GreaterThan { FieldName = "id"; Value = 4 } let queryable = data.AsQueryable () let filteredData = queryable.Apply (filter) |> Seq.toList List.length filteredData |> equals 1 @@ -35,7 +36,7 @@ let ``ObjectListFilter works with GreaterThan operator`` () = [] let ``ObjectListFilter works with GreaterThanOrEqual operator`` () = - let filter = GreaterThanOrEqual { FieldName = "id"; Value = 4 } // :> IComparable + let filter = GreaterThanOrEqual { FieldName = "id"; Value = 4 } let queryable = data.AsQueryable () let filteredData = queryable.Apply (filter) |> Seq.toList List.length filteredData |> equals 2 @@ -56,7 +57,7 @@ let ``ObjectListFilter works with GreaterThanOrEqual operator`` () = [] let ``ObjectListFilter works with LessThan operator`` () = - let filter = LessThan { FieldName = "id"; Value = 4 } // :> IComparable + let filter = LessThan { FieldName = "id"; Value = 4 } let queryable = data.AsQueryable () let filteredData = queryable.Apply (filter) |> Seq.toList List.length filteredData |> equals 1 @@ -69,7 +70,7 @@ let ``ObjectListFilter works with LessThan operator`` () = [] let ``ObjectListFilter works with LessThanOrEqual operator`` () = - let filter = LessThanOrEqual { FieldName = "id"; Value = 4 } // :> IComparable + let filter = LessThanOrEqual { FieldName = "id"; Value = 4 } let queryable = data.AsQueryable () let filteredData = queryable.Apply (filter) |> Seq.toList List.length filteredData |> equals 2 @@ -155,6 +156,34 @@ let ``ObjectListFilter works with OR operator`` () = result.Contact |> equals { Email = "b.adams@gmail.com" } result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] +[] +let ``LINQ tree is balanced after multiple usings of OR operator`` () = + let filter = + ("firstName" =@@ "J") + ||| (("id" ==> 2) + ||| (("id" >>> 4) + ||| (("lastName" === "Adams") + ||| (("lastName" @=@ "e") + ||| (("firstName" @=@ "a") + ||| ("lastName" @=@ "a")))))) + let queryable = data.AsQueryable () + let filteredData = queryable.Apply (filter, ObjectListFilterLinqOptions(optimize = true)) |> Seq.toList + List.length filteredData |> equals 3 + do + let result = List.head filteredData + result.ID |> equals 4 + result.FirstName |> equals "Ben" + result.LastName |> equals "Adams" + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + do + let result = List.last filteredData + result.ID |> equals 7 + result.FirstName |> equals "Jeneffer" + result.LastName |> equals "Trif" + result.Contact |> equals { Email = "j.trif@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" } ] + [] let ``ObjectListFilter works with IN operator for string type field`` () = let filter = In { FieldName = "firstName"; Value = [ "Jeneffer"; "Ben" ] } @@ -378,7 +407,8 @@ let ``ObjectListFilter works with getDiscriminatorValue for Horse`` () = (function | t when t = typeof -> t.Name | t when t = typeof -> t.Name - | _ -> raise (NotSupportedException "Type not supported")) + | _ -> raise (NotSupportedException "Type not supported")), + optimize = true ) let filteredData = queryable.Apply (filter, options) |> Seq.toList List.length filteredData |> equals 2