Skip to content

Commit a84c6aa

Browse files
VectorTetraViktor Tochonovxperiandri
authored
Implemented support for optional properties usage in non-Enumerable LINQ queries (#529)
* Implemented support for optional properties usage in non-Enumerable LINQ queries * Implemented tests for queries translation to Azure Cosmos DB --------- Co-authored-by: Viktor Tochonov <[email protected]> Co-authored-by: Andrii Chebukin <[email protected]>
1 parent 2fa3df2 commit a84c6aa

File tree

8 files changed

+454
-91
lines changed

8 files changed

+454
-91
lines changed

src/FSharp.Data.GraphQL.Server.AspNetCore/Helpers.fs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ module ReflectionHelpers =
4343

4444
open Microsoft.FSharp.Quotations.Patterns
4545

46-
let getModuleType = function
46+
let getModuleType quotation =
47+
match quotation with
4748
| PropertyGet (_, propertyInfo, _) -> propertyInfo.DeclaringType
49+
| FieldGet (_, fieldInfo) -> fieldInfo.DeclaringType
4850
| _ -> failwith "Expression is no property."
4951

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

Lines changed: 134 additions & 48 deletions
Large diffs are not rendered by default.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ let rec private coerceObjectListFilterInput x : Result<ObjectListFilter voption,
116116
let! parsedValues =
117117
values
118118
|> Seq.map (function
119-
| EquatableValue v -> Ok v
119+
| EquatableValue v -> Ok (box v)
120120
| NonEquatableValue v ->
121121
Error
122122
{ new IGQLError with

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

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

Lines changed: 71 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,43 +10,28 @@ open System.Reflection
1010
open System.Text.Json.Serialization
1111

1212
/// General helper functions and types.
13-
module Helpers =
13+
module internal ReflectionHelper =
1414

15-
/// Casts a System.Object to a System.Object option.
16-
let optionCast (value: obj) =
17-
if isNull value then None
18-
else
19-
let t = value.GetType()
20-
if t.FullName.StartsWith "Microsoft.FSharp.Core.FSharpOption`1" then
21-
let p = t.GetProperty("Value")
22-
Some (p.GetValue(value, [||]))
23-
elif t.FullName.StartsWith "Microsoft.FSharp.Core.FSharpValueOption`1" then
24-
if value = Activator.CreateInstance t then None
25-
else
26-
let p = t.GetProperty("Value")
27-
Some (p.GetValue(value, [||]))
28-
else None
15+
open Microsoft.FSharp.Quotations.Patterns
2916

30-
/// Matches a System.Object with an option.
31-
/// If the object is an Option, returns it as Some, otherwise, return None.
32-
let (|ObjectOption|_|) = optionCast
17+
let getModuleType quotation =
18+
match quotation with
19+
| PropertyGet (_, propertyInfo, _) -> propertyInfo.DeclaringType
20+
| FieldGet (_, fieldInfo) -> fieldInfo.DeclaringType
21+
| _ -> failwith "Expression is no property."
3322

34-
/// Lifts a System.Object to an option, unless it is already an option.
35-
let toOption x =
36-
match x with
37-
| null -> None
38-
| ObjectOption v
39-
| v -> Some v
40-
41-
module internal ReflectionHelper =
23+
let [<Literal>] OptionTypeName = "Microsoft.FSharp.Core.FSharpOption`1"
24+
let [<Literal>] ValueOptionTypeName = "Microsoft.FSharp.Core.FSharpValueOption`1"
25+
let [<Literal>] SkippableTypeName = "System.Text.Json.Serialization.Skippable`1"
4226

27+
let private listGenericTypeInfo = typedefof<_ list>.GetTypeInfo()
4328
/// <summary>
4429
/// Returns pair of function constructors for `cons(head,tail)` and `nil`
4530
/// used to create list of type <paramref name="t"/> given at runtime.
4631
/// </summary>
4732
/// <param name="t">Type used for result list constructors as type param</param>
4833
let listOfType t =
49-
let listType = typedefof<_ list>.GetTypeInfo().MakeGenericType([|t|]).GetTypeInfo()
34+
let listType = listGenericTypeInfo.MakeGenericType([|t|]).GetTypeInfo()
5035
let nil =
5136
let empty = listType.GetDeclaredProperty "Empty"
5237
empty.GetValue (null)
@@ -67,13 +52,14 @@ module internal ReflectionHelper =
6752
)
6853
array :> obj
6954

55+
let private optionGenericTypeInfo = typedefof<_ option>.GetTypeInfo()
7056
/// <summary>
7157
/// Returns pair of function constructors for `some(value)` and `none`
7258
/// used to create option of type <paramref name="t"/> given at runtime.
7359
/// </summary>
7460
/// <param name="t">Type used for result option constructors as type param</param>
7561
let optionOfType t =
76-
let optionType = typedefof<_ option>.GetTypeInfo().MakeGenericType([|t|]).GetTypeInfo()
62+
let optionType = optionGenericTypeInfo.MakeGenericType([|t|]).GetTypeInfo()
7763
let none =
7864
let x = optionType.GetDeclaredProperty "None"
7965
x.GetValue(null)
@@ -101,13 +87,14 @@ module internal ReflectionHelper =
10187
else input
10288
(some, none, value)
10389

90+
let private valueOptionGenericTypeInfo = typedefof<_ voption>.GetTypeInfo()
10491
/// <summary>
10592
/// Returns pair of function constructors for `some(value)` and `none`
10693
/// used to create option of type <paramref name="t"/> given at runtime.
10794
/// </summary>
10895
/// <param name="t">Type used for result option constructors as type param</param>
10996
let vOptionOfType t =
110-
let optionType = typedefof<_ voption>.GetTypeInfo().MakeGenericType([|t|]).GetTypeInfo()
97+
let optionType = valueOptionGenericTypeInfo.MakeGenericType([|t|]).GetTypeInfo()
11198
let none =
11299
let x = optionType.GetDeclaredProperty "None"
113100
x.GetValue(null)
@@ -157,13 +144,14 @@ module internal ReflectionHelper =
157144
else createInclude.Invoke(null, [| value |])
158145
(``include``, skip)
159146

147+
let skippableGenericTypeInfo = typedefof<_ Skippable>.GetTypeInfo()
160148
/// <summary>
161149
/// Returns pair of function constructors for `include(value)` and `skip`
162150
/// used to create option of type <paramref name="t"/> given at runtime.
163151
/// </summary>
164152
/// <param name="t">Type used for result option constructors as type param</param>
165153
let skippableOfType t =
166-
let skippableType = typedefof<_ Skippable>.GetTypeInfo().MakeGenericType([|t|]).GetTypeInfo()
154+
let skippableType = skippableGenericTypeInfo.MakeGenericType([|t|]).GetTypeInfo()
167155
let skip =
168156
let x = skippableType.GetDeclaredProperty "Skip"
169157
x.GetValue(null)
@@ -178,3 +166,56 @@ module internal ReflectionHelper =
178166
then value
179167
else createInclude.Invoke(null, [| value |])
180168
(``include``, skip)
169+
170+
module Helpers =
171+
172+
let rec internal moduleType = ReflectionHelper.getModuleType <@ moduleType @>
173+
174+
/// <summary>
175+
/// Casts a <see cref="System.Object"/> to a <see cref="option{System.Object}"/>.
176+
/// </summary>
177+
let optionCast (value: obj) =
178+
if isNull value then None
179+
else
180+
let t = value.GetType()
181+
if t.FullName.StartsWith ReflectionHelper.OptionTypeName then
182+
let p = t.GetProperty("Value")
183+
Some (p.GetValue(value, [||]))
184+
elif t.FullName.StartsWith ReflectionHelper.ValueOptionTypeName then
185+
if value = Activator.CreateInstance t then None
186+
else
187+
let p = t.GetProperty("Value")
188+
Some (p.GetValue(value, [||]))
189+
else None
190+
191+
/// <summary>
192+
/// Matches a System.Object with an option.
193+
/// If the object is an <see cref="Option{T}", returns it as Some, otherwise, return <see cref="None"/>.
194+
/// </summary>
195+
let (|ObjectOption|_|) = optionCast
196+
197+
/// <summary>
198+
/// Lifts a <see cref="System.Object"/> to an <see cref="option{System.Object}"/>, unless it is already an <see cref="option{System.Object}"/>.
199+
/// </summary>
200+
let toOption x =
201+
match x with
202+
| null -> None
203+
| ObjectOption v
204+
| v -> Some v
205+
206+
/// <summary>
207+
/// Unwraps a <see cref="System.Object"/> from an <see cref="option{System.Object}"/> or <see cref="voption{System.Object}"/>,
208+
/// unless it is not wrapped.
209+
/// </summary>
210+
let unwrap (value : objnull) =
211+
match value with
212+
| null -> null
213+
| value ->
214+
let t = value.GetType()
215+
if t.FullName.StartsWith ReflectionHelper.OptionTypeName then
216+
t.GetProperty("Value").GetValue (value, [||])
217+
elif t.FullName.StartsWith ReflectionHelper.ValueOptionTypeName then
218+
if value = Activator.CreateInstance t then null
219+
else
220+
t.GetProperty("Value").GetValue (value, [||])
221+
else value

0 commit comments

Comments
 (0)