Skip to content

Commit 88ad982

Browse files
author
Viktor Tochonov
committed
Implemented support for valueOption properties in CosmosDB tests
1 parent 2fa3df2 commit 88ad982

File tree

5 files changed

+237
-22
lines changed

5 files changed

+237
-22
lines changed

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

Lines changed: 89 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace FSharp.Data.GraphQL.Server.Middleware
22

33
open System
4+
open FSharp.Data.GraphQL
45

56
/// A filter definition for a field value.
67
type FieldFilter<'Val> = { FieldName : string; Value : 'Val }
@@ -26,6 +27,8 @@ open System.Linq
2627
open System.Linq.Expressions
2728
open System.Runtime.InteropServices
2829
open System.Reflection
30+
open System.Collections
31+
open System.Collections.Generic
2932

3033
type private CompareDiscriminatorExpression<'T, 'D> = Expression<Func<'T, 'D, bool>>
3134

@@ -139,6 +142,7 @@ module ObjectListFilter =
139142
Expression.Call (whereMethod, [| query.Expression; Expression.Lambda<Func<'T, bool>> (predicate, param) |])
140143

141144
let private stringType = typeof<string>
145+
let private genericIEnumerableType = typedefof<IEnumerable<_>>
142146
let private StringStartsWithMethod = stringType.GetMethod ("StartsWith", [| stringType |])
143147
let private StringEndsWithMethod = stringType.GetMethod ("EndsWith", [| stringType |])
144148
let private StringContainsMethod = stringType.GetMethod ("Contains", [| stringType |])
@@ -175,29 +179,89 @@ module ObjectListFilter =
175179
static member op_Implicit (parameter : ParameterExpression) = SourceExpression (parameter :> Expression)
176180
static member op_Implicit (``member`` : MemberExpression) = SourceExpression (``member`` :> Expression)
177181

178-
let rec buildFilterExpr (param : SourceExpression) buildTypeDiscriminatorCheck filter : Expression =
179-
let build = buildFilterExpr param buildTypeDiscriminatorCheck
182+
//let iequtableType = typedefof<IEquatable<_>>
183+
let equalsMethod =
184+
typeof<obj>
185+
|> _.GetMethods(BindingFlags.Instance ||| BindingFlags.Public)
186+
|> Seq.where (fun m -> m.Name = "Equals")
187+
|> Seq.head
188+
189+
let rec buildFilterExpr isEnumerableQuery (param : SourceExpression) buildTypeDiscriminatorCheck filter : Expression =
190+
let build = buildFilterExpr isEnumerableQuery param buildTypeDiscriminatorCheck
191+
let (|NoCast|Enumerable|NonEnumerableCast|) value =
192+
if obj.ReferenceEquals (value, null) then
193+
NoCast
194+
else
195+
if isEnumerableQuery then Enumerable
196+
else NonEnumerableCast (value.GetType ())
197+
let unsafeConvertTo ``type`` ``member`` = Expression.Convert (Expression.Convert(``member``, typeof<obj>), ``type``)
198+
180199
match filter with
200+
| Not (Equals f) ->
201+
let ``member`` = Expression.PropertyOrField (param, f.FieldName)
202+
let ``const`` = Expression.Constant (Values.normalizeOptional ``member``.Type f.Value)
203+
if isEnumerableQuery && f.Value <> null then
204+
Expression.Not (Expression.Call (``const``, equalsMethod, ``member``))
205+
else
206+
Expression.NotEqual (``member``, ``const``)
181207
| Not f -> f |> build |> Expression.Not :> Expression
182208
| And (f1, f2) -> Expression.AndAlso (build f1, build f2)
183209
| Or (f1, f2) -> Expression.OrElse (build f1, build f2)
184-
| Equals f -> Expression.Equal (Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value))
185-
| GreaterThan f -> Expression.GreaterThan (Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value))
186-
| LessThan f -> Expression.LessThan (Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value))
187-
| GreaterThanOrEqual f -> Expression.GreaterThanOrEqual (Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value))
188-
| LessThanOrEqual f -> Expression.LessThanOrEqual (Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value))
210+
| Equals f ->
211+
let ``member`` = Expression.PropertyOrField (param, f.FieldName)
212+
match f.Value with
213+
| NoCast ->
214+
Expression.Equal (``member``, Expression.Constant f.Value)
215+
| Enumerable ->
216+
Expression.Equal (``member``, Expression.Constant (Values.normalizeOptional ``member``.Type f.Value))
217+
| NonEnumerableCast ``type`` ->
218+
Expression.Equal ((unsafeConvertTo ``type`` ``member``), Expression.Constant f.Value)
219+
| GreaterThan f ->
220+
let ``member`` = Expression.PropertyOrField (param, f.FieldName)
221+
let ``const`` = Expression.Constant (Values.normalizeOptional ``member``.Type f.Value)
222+
if isEnumerableQuery then
223+
Expression.GreaterThan (``member``, Expression.Constant (Values.normalizeOptional ``member``.Type f.Value))
224+
else
225+
Expression.GreaterThan (Expression.Convert (Expression.Convert(``member``, typeof<obj>), f.Value.GetType ()), Expression.Constant f.Value)
226+
| LessThan f ->
227+
let ``member`` = Expression.PropertyOrField (param, f.FieldName)
228+
match f.Value with
229+
| NoCast ->
230+
Expression.LessThan (``member``, Expression.Constant f.Value)
231+
| Enumerable ->
232+
Expression.LessThan (``member``, Expression.Constant (Values.normalizeOptional ``member``.Type f.Value))
233+
| NonEnumerableCast ``type`` ->
234+
Expression.LessThan ((unsafeConvertTo ``type`` ``member``), Expression.Constant f.Value)
235+
| GreaterThanOrEqual f ->
236+
let ``member`` = Expression.PropertyOrField (param, f.FieldName)
237+
match f.Value with
238+
| NoCast ->
239+
Expression.GreaterThanOrEqual (``member``, Expression.Constant f.Value)
240+
| Enumerable ->
241+
Expression.GreaterThanOrEqual (``member``, Expression.Constant (Values.normalizeOptional ``member``.Type f.Value))
242+
| NonEnumerableCast ``type`` ->
243+
Expression.GreaterThanOrEqual ((unsafeConvertTo ``type`` ``member``), Expression.Constant f.Value)
244+
| LessThanOrEqual f ->
245+
let ``member`` = Expression.PropertyOrField (param, f.FieldName)
246+
match f.Value with
247+
| NoCast ->
248+
Expression.LessThanOrEqual (``member``, Expression.Constant f.Value)
249+
| Enumerable ->
250+
Expression.LessThanOrEqual (``member``, Expression.Constant (Values.normalizeOptional ``member``.Type f.Value))
251+
| NonEnumerableCast ``type`` ->
252+
Expression.LessThanOrEqual ((unsafeConvertTo ``type`` ``member``), Expression.Constant f.Value)
189253
| StartsWith f ->
190254
let ``member`` = Expression.PropertyOrField (param, f.FieldName)
191255
if ``member``.Type = stringType then
192-
Expression.Call (``member``, StringStartsWithMethod, Expression.Constant (f.Value))
256+
Expression.Call (``member``, StringStartsWithMethod, Expression.Constant (Values.normalizeOptional ``member``.Type f.Value))
193257
else
194-
Expression.Call (Expression.Convert (``member``, stringType), StringStartsWithMethod, Expression.Constant (f.Value))
258+
Expression.Call (Expression.Convert (``member``, stringType), StringStartsWithMethod, Expression.Constant (Values.normalizeOptional ``member``.Type f.Value))
195259
| EndsWith f ->
196260
let ``member`` = Expression.PropertyOrField (param, f.FieldName)
197261
if ``member``.Type = stringType then
198-
Expression.Call (``member``, StringEndsWithMethod, Expression.Constant (f.Value))
262+
Expression.Call (``member``, StringEndsWithMethod, Expression.Constant (Values.normalizeOptional ``member``.Type f.Value))
199263
else
200-
Expression.Call (Expression.Convert (``member``, stringType), StringEndsWithMethod, Expression.Constant (f.Value))
264+
Expression.Call (Expression.Convert (``member``, stringType), StringEndsWithMethod, Expression.Constant (Values.normalizeOptional ``member``.Type f.Value))
201265
| Contains f ->
202266
let ``member`` = Expression.PropertyOrField (param, f.FieldName)
203267
let isEnumerable (memberType : Type) =
@@ -216,35 +280,38 @@ module ObjectListFilter =
216280
| value -> value.GetType()
217281
let castedMember =
218282
if itemType = valueType then ``member`` :> Expression
219-
else
283+
elif isEnumerableQuery then
220284
let castMethod = getEnumerableCastMethod valueType
221285
Expression.Call (castMethod, ``member``)
286+
else
287+
let castedEnumerableType = genericIEnumerableType.MakeGenericType ([| valueType |])
288+
unsafeConvertTo castedEnumerableType ``member``
222289
match getCollectionInstanceContainsMethod memberType with
223290
| ValueNone ->
224291
let enumerableContains = getEnumerableContainsMethod valueType
225-
Expression.Call (enumerableContains, castedMember, Expression.Constant (f.Value))
292+
Expression.Call (enumerableContains, castedMember, Expression.Constant (Values.normalizeOptional ``member``.Type f.Value))
226293
| ValueSome instanceContainsMethod ->
227-
Expression.Call (castedMember, instanceContainsMethod, Expression.Constant (f.Value))
294+
Expression.Call (castedMember, instanceContainsMethod, Expression.Constant (Values.normalizeOptional ``member``.Type f.Value))
228295
match ``member``.Member with
229296
| :? PropertyInfo as prop when prop.PropertyType |> isEnumerable -> callContains prop.PropertyType
230297
| :? FieldInfo as field when field.FieldType |> isEnumerable -> callContains field.FieldType
231298
| _ ->
232299
if ``member``.Type = stringType then
233-
Expression.Call (``member``, StringContainsMethod, Expression.Constant (f.Value))
300+
Expression.Call (``member``, StringContainsMethod, Expression.Constant (Values.normalizeOptional ``member``.Type f.Value))
234301
else
235-
Expression.Call (Expression.Convert (``member``, stringType), StringContainsMethod, Expression.Constant (f.Value))
302+
Expression.Call (Expression.Convert (``member``, stringType), StringContainsMethod, Expression.Constant (Values.normalizeOptional ``member``.Type f.Value))
236303
| In f when not (f.Value.IsEmpty) ->
237304
let ``member`` = Expression.PropertyOrField (param, f.FieldName)
238305
let enumerableContains = getEnumerableContainsMethod typeof<IComparable>
239-
Expression.Call (enumerableContains, Expression.Constant (f.Value), Expression.Convert (``member``, typeof<IComparable>))
306+
Expression.Call (enumerableContains, Expression.Constant (Values.normalizeOptional ``member``.Type f.Value), Expression.Convert (``member``, typeof<IComparable>))
240307
| In f -> Expression.Constant (true)
241308
| OfTypes types ->
242309
types
243310
|> Seq.map (fun t -> buildTypeDiscriminatorCheck param t)
244311
|> Seq.reduce (fun acc expr -> Expression.OrElse (acc, expr))
245312
| FilterField f ->
246313
let paramExpr = Expression.PropertyOrField (param, f.FieldName)
247-
buildFilterExpr (SourceExpression paramExpr) buildTypeDiscriminatorCheck f.Value
314+
buildFilterExpr isEnumerableQuery (SourceExpression paramExpr) buildTypeDiscriminatorCheck f.Value
248315

249316
type private CompareDiscriminatorExpressionVisitor<'T, 'D> (
250317
compareDiscriminator : CompareDiscriminatorExpression<'T, 'D>,
@@ -260,7 +327,10 @@ module ObjectListFilter =
260327
else
261328
node :> Expression
262329

330+
let enumerableQueryType = typedefof<EnumerableQuery<_>>
331+
263332
let apply (options : ObjectListFilterLinqOptions<'T, 'D>) (filter : ObjectListFilter) (query : IQueryable<'T>) =
333+
let isEnumerableQuery = query.GetType().GetGenericTypeDefinition() = enumerableQueryType
264334
// Helper for discriminator comparison
265335
let buildTypeDiscriminatorCheck (param : SourceExpression) (t : Type) =
266336
match options.CompareDiscriminator, options.GetDiscriminatorValue with
@@ -290,7 +360,7 @@ module ObjectListFilter =
290360
replacer.Visit discExpr.Body
291361
let queryExpr =
292362
let param = Expression.Parameter (typeof<'T>, "x")
293-
let body = buildFilterExpr (SourceExpression param) buildTypeDiscriminatorCheck filter
363+
let body = buildFilterExpr isEnumerableQuery (SourceExpression param) buildTypeDiscriminatorCheck filter
294364
whereExpr<'T> query param body
295365
// Create and execute the final expression
296366
query.Provider.CreateQuery<'T> (queryExpr)

src/FSharp.Data.GraphQL.Server/FSharp.Data.GraphQL.Server.fsproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
2727
<_Parameter1>FSharp.Data.GraphQL.Server.AspNetCore</_Parameter1>
2828
</AssemblyAttribute>
29+
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
30+
<_Parameter1>FSharp.Data.GraphQL.Server.Middleware</_Parameter1>
31+
</AssemblyAttribute>
2932
</ItemGroup>
3033

3134
<ItemGroup>

src/FSharp.Data.GraphQL.Server/Values.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ let private wrapOptionalNone (outputType : Type) (inputType : Type) =
3030
else
3131
null
3232

33-
let private normalizeOptional (outputType : Type) value =
33+
let normalizeOptional (outputType : Type) value =
3434
match value with
3535
| null -> wrapOptionalNone outputType typeof<obj>
3636
| value ->

src/FSharp.Data.GraphQL.Shared/Helpers/ObjAndStructConversions.fs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,27 @@ module internal Seq =
3838
|> Seq.map ValueSome
3939
|> _.FirstOrDefault()
4040

41+
let vtryHead (source : 'T seq) =
42+
use enumerator = source.GetEnumerator ()
43+
if not (enumerator.MoveNext ()) then
44+
ValueNone
45+
else
46+
match enumerator.Current with
47+
| null -> ValueNone
48+
| head -> ValueSome head
49+
50+
let vtryLast (source : 'T seq) =
51+
use enumerator = source.GetEnumerator ()
52+
if not (enumerator.MoveNext ()) then
53+
ValueNone
54+
else
55+
let mutable last = enumerator.Current
56+
while enumerator.MoveNext () do
57+
last <- enumerator.Current
58+
match last with
59+
| null -> ValueNone
60+
| last -> ValueSome last
61+
4162
module internal List =
4263

4364
let vchoose mapping list = list |> Seq.ofList |> Seq.vchoose mapping |> List.ofSeq

0 commit comments

Comments
 (0)