Skip to content

Commit c1a540d

Browse files
authored
Merge pull request #56 from Horusiath/query-reducers
[WIP] Execution planning phase
2 parents ebfa5c0 + 438ad28 commit c1a540d

File tree

12 files changed

+791
-306
lines changed

12 files changed

+791
-306
lines changed

src/FSharp.Data.GraphQL/Execution.fs

Lines changed: 101 additions & 201 deletions
Large diffs are not rendered by default.

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
<Compile Include="Introspection.fs" />
5353
<Compile Include="Parser.fs" />
5454
<Compile Include="Values.fs" />
55+
<Compile Include="Planning.fs" />
5556
<Compile Include="Execution.fs" />
5657
<Compile Include="Schema.fs" />
5758
<Compile Include="ReflectedSchema.fs" />

src/FSharp.Data.GraphQL/Planning.fs

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
/// The MIT License (MIT)
2+
/// Copyright (c) 2016 Bazinga Technologies Inc
3+
4+
module FSharp.Data.GraphQL.Planning
5+
6+
open System
7+
open System.Reflection
8+
open System.Collections.Generic
9+
open System.Collections.Concurrent
10+
open FSharp.Data.GraphQL.Ast
11+
open FSharp.Data.GraphQL.Types
12+
open FSharp.Data.GraphQL.Types.Introspection
13+
open FSharp.Data.GraphQL.Introspection
14+
15+
let SchemaMetaFieldDef = Define.Field(
16+
name = "__schema",
17+
description = "Access the current type schema of this server.",
18+
typedef = __Schema,
19+
resolve = fun ctx (_: obj) -> ctx.Schema.Introspected)
20+
21+
let TypeMetaFieldDef = Define.Field(
22+
name = "__type",
23+
description = "Request the type information of a single type.",
24+
typedef = __Type,
25+
args = [
26+
{ Name = "name"
27+
Description = None
28+
Type = String
29+
DefaultValue = None
30+
ExecuteInput = variableOrElse(coerceStringInput >> Option.map box >> Option.toObj) }
31+
],
32+
resolve = fun ctx (_:obj) ->
33+
ctx.Schema.Introspected.Types
34+
|> Seq.find (fun t -> t.Name = ctx.Arg("name"))
35+
|> IntrospectionTypeRef.Named)
36+
37+
let TypeNameMetaFieldDef : FieldDef<obj> = Define.Field(
38+
name = "__typename",
39+
description = "The name of the current Object type at runtime.",
40+
typedef = String,
41+
resolve = fun ctx (_:obj) -> ctx.ParentType.Name)
42+
43+
let private tryFindDef (schema: ISchema) (objdef: ObjectDef) (field: Field) : FieldDef option =
44+
match field.Name with
45+
| "__schema" when Object.ReferenceEquals(schema.Query, objdef) -> Some (upcast SchemaMetaFieldDef)
46+
| "__type" when Object.ReferenceEquals(schema.Query, objdef) -> Some (upcast TypeMetaFieldDef)
47+
| "__typename" -> Some (upcast TypeNameMetaFieldDef)
48+
| fieldName -> objdef.Fields |> Map.tryFind fieldName
49+
50+
let private coerceVariables (schema: #ISchema) (variables: VariableDefinition list) (inputs: Map<string, obj> option) =
51+
match inputs with
52+
| None ->
53+
variables
54+
|> List.filter (fun vardef -> Option.isSome vardef.DefaultValue)
55+
|> List.fold (fun acc vardef ->
56+
let variableName = vardef.VariableName
57+
Map.add variableName (coerceVariable schema vardef Map.empty) acc) Map.empty
58+
| Some vars ->
59+
variables
60+
|> List.fold (fun acc vardef ->
61+
let variableName = vardef.VariableName
62+
Map.add variableName (coerceVariable schema vardef vars) acc) Map.empty
63+
64+
let objectData(ctx: PlanningContext, parentDef: ObjectDef, field: Field, includer: Includer) : PlanningData =
65+
match tryFindDef ctx.Schema parentDef field with
66+
| Some fdef ->
67+
{ Identifier = field.AliasOrName
68+
ParentDef = parentDef
69+
Definition = fdef
70+
Ast = field
71+
IsNullable = fdef.Type :? NullableDef
72+
Include = includer }
73+
| None ->
74+
raise (GraphQLException (sprintf "No field '%s' was defined in object definition '%s'" field.Name parentDef.Name))
75+
76+
let rec abstractionData (ctx:PlanningContext) (parentDef: AbstractDef) (field: Field) typeCondition includer : Map<string, PlanningData> =
77+
let objDefs = ctx.Schema.GetPossibleTypes parentDef
78+
match typeCondition with
79+
| None ->
80+
objDefs
81+
|> Array.choose (fun objDef ->
82+
match tryFindDef ctx.Schema objDef field with
83+
| Some fdef ->
84+
let data =
85+
{ Identifier = field.AliasOrName
86+
ParentDef = parentDef
87+
Definition = fdef
88+
Ast = field
89+
IsNullable = fdef.Type :? NullableDef
90+
Include = includer }
91+
Some (objDef.Name, data)
92+
| None -> None)
93+
|> Map.ofArray
94+
| Some typeName ->
95+
match objDefs |> Array.tryFind (fun o -> o.Name = typeName) with
96+
| Some objDef ->
97+
match tryFindDef ctx.Schema objDef field with
98+
| Some fdef ->
99+
let data =
100+
{ Identifier = field.AliasOrName
101+
ParentDef = parentDef
102+
Definition = fdef
103+
Ast = field
104+
IsNullable = fdef.Type :? NullableDef
105+
Include = includer }
106+
Map.ofList [ objDef.Name, data ]
107+
| None -> Map.empty
108+
| None ->
109+
match ctx.Schema.TryFindType typeName with
110+
| Some (Abstract abstractDef) ->
111+
abstractionData ctx abstractDef field None includer
112+
| _ ->
113+
let pname = parentDef :?> NamedDef
114+
raise (GraphQLException (sprintf "There is no object type named '%s' that is a possible type of '%s'" typeName pname.Name))
115+
116+
let private directiveIncluder (directive: Directive) : Includer =
117+
fun variables ->
118+
match directive.If.Value with
119+
| Variable vname -> downcast variables.[vname]
120+
| other ->
121+
match coerceBoolInput other with
122+
| Some s -> s
123+
| None -> raise (
124+
GraphQLException (sprintf "Expected 'if' argument of directive '@%s' to have boolean value but got %A" directive.Name other))
125+
126+
let incl: Includer = fun _ -> true
127+
let excl: Includer = fun _ -> false
128+
let private getIncluder (directives: Directive list) topIncluder : Includer =
129+
directives
130+
|> List.fold (fun acc directive ->
131+
match directive.Name with
132+
| "skip" ->
133+
fun vars -> acc vars && not(directiveIncluder directive vars)
134+
| "include" ->
135+
fun vars -> acc vars && (directiveIncluder directive vars)
136+
| _ -> acc) topIncluder
137+
138+
let private doesFragmentTypeApply (schema: ISchema) fragment (objectType: ObjectDef) =
139+
match fragment.TypeCondition with
140+
| None -> true
141+
| Some typeCondition ->
142+
match schema.TryFindType typeCondition with
143+
| None -> false
144+
| Some conditionalType when conditionalType.Name = objectType.Name -> true
145+
| Some (Abstract conditionalType) -> schema.IsPossibleType conditionalType objectType
146+
| _ -> false
147+
148+
let rec private plan (ctx: PlanningContext) (data: PlanningData) (typedef: TypeDef) : ExecutionPlanInfo =
149+
match typedef with
150+
| Leaf leafDef -> planLeaf ctx data leafDef
151+
| Object objDef -> planSelection ctx { data with ParentDef = objDef } data.Ast.SelectionSet (ref [])
152+
| Nullable innerDef -> plan ctx { data with IsNullable = true } innerDef
153+
| List innerDef -> planList ctx data innerDef
154+
| Abstract abstractDef -> planAbstraction ctx { data with ParentDef = abstractDef } data.Ast.SelectionSet (ref []) None
155+
156+
and private planSelection (ctx: PlanningContext) (data: PlanningData) (selectionSet: Selection list) visitedFragments : ExecutionPlanInfo =
157+
let parentDef = downcast data.ParentDef
158+
let plannedFields =
159+
selectionSet
160+
|> List.fold(fun (fields: ExecutionPlanInfo list) selection ->
161+
//FIXME: includer is not passed along from top level fragments (both inline and spreads)
162+
let includer = getIncluder selection.Directives data.Include
163+
let innerData = { data with Include = includer }
164+
match selection with
165+
| Field field ->
166+
let identifier = field.AliasOrName
167+
if fields |> List.exists (fun f -> f.Data.Identifier = identifier)
168+
then fields
169+
else
170+
let data = objectData(ctx, parentDef, field, includer)
171+
let executionPlan = plan ctx data data.Definition.Type
172+
fields @ [executionPlan] // unfortunatelly, order matters here
173+
| FragmentSpread spread ->
174+
let spreadName = spread.Name
175+
if !visitedFragments |> List.exists (fun name -> name = spreadName)
176+
then fields // fragment already found
177+
else
178+
visitedFragments := spreadName::!visitedFragments
179+
match ctx.Document.Definitions |> List.tryFind (function FragmentDefinition f -> f.Name.Value = spreadName | _ -> false) with
180+
| Some (FragmentDefinition fragment) when doesFragmentTypeApply ctx.Schema fragment parentDef ->
181+
// retrieve fragment data just as it was normal selection set
182+
let (SelectFields(_, fragmentFields)) = planSelection ctx innerData fragment.SelectionSet visitedFragments
183+
// filter out already existing fields
184+
List.mergeBy (fun field -> field.Data.Identifier) fields fragmentFields
185+
| _ -> fields
186+
| InlineFragment fragment when doesFragmentTypeApply ctx.Schema fragment parentDef ->
187+
// retrieve fragment data just as it was normal selection set
188+
let (SelectFields(_, fragmentFields)) = planSelection ctx innerData fragment.SelectionSet visitedFragments
189+
// filter out already existing fields
190+
List.mergeBy (fun field -> field.Data.Identifier) fields fragmentFields
191+
| _ -> fields
192+
) []
193+
SelectFields(data, plannedFields)
194+
195+
and private planList (ctx: PlanningContext) (data: PlanningData) (innerDef: TypeDef) : ExecutionPlanInfo =
196+
ResolveCollection(data, plan ctx data innerDef)
197+
198+
and private planLeaf (ctx: PlanningContext) (data: PlanningData) (leafDef: LeafDef) : ExecutionPlanInfo =
199+
ResolveValue(data)
200+
201+
and private planAbstraction (ctx:PlanningContext) (data: PlanningData) (selectionSet: Selection list) visitedFragments typeCondition : ExecutionPlanInfo =
202+
let parentDef = downcast data.ParentDef
203+
let plannedTypeFields =
204+
selectionSet
205+
|> List.fold(fun (fields: Map<string, ExecutionPlanInfo list>) selection ->
206+
let includer = getIncluder selection.Directives data.Include
207+
let innerData = { data with Include = includer }
208+
match selection with
209+
| Field field ->
210+
abstractionData ctx parentDef field typeCondition includer
211+
|> Map.map (fun typeName data -> [ plan ctx data data.Definition.Type ])
212+
|> Map.merge (fun typeName oldVal newVal -> oldVal @ newVal) fields
213+
| FragmentSpread spread ->
214+
let spreadName = spread.Name
215+
if !visitedFragments |> List.exists (fun name -> name = spreadName)
216+
then fields // fragment already found
217+
else
218+
visitedFragments := spreadName::!visitedFragments
219+
match ctx.Document.Definitions |> List.tryFind (function FragmentDefinition f -> f.Name.Value = spreadName | _ -> false) with
220+
| Some (FragmentDefinition fragment) ->
221+
// retrieve fragment data just as it was normal selection set
222+
let (ResolveAbstraction(_, fragmentFields)) = planAbstraction ctx innerData fragment.SelectionSet visitedFragments fragment.TypeCondition
223+
// filter out already existing fields
224+
Map.merge (fun typeName oldVal newVal -> oldVal @ newVal) fields fragmentFields
225+
| _ -> fields
226+
| InlineFragment fragment ->
227+
// retrieve fragment data just as it was normal selection set
228+
let (ResolveAbstraction(_, fragmentFields)) = planAbstraction ctx innerData fragment.SelectionSet visitedFragments fragment.TypeCondition
229+
// filter out already existing fields
230+
Map.merge (fun typeName oldVal newVal -> oldVal @ newVal) fields fragmentFields
231+
| _ -> fields
232+
) Map.empty
233+
ResolveAbstraction(data, plannedTypeFields)
234+
235+
let planOperation (ctx: PlanningContext) (operation: OperationDefinition) : ExecutionPlan =
236+
let data = {
237+
Identifier = null;
238+
Ast = Unchecked.defaultof<Field>
239+
IsNullable = false
240+
ParentDef = ctx.RootDef
241+
Definition = Unchecked.defaultof<FieldDef>
242+
Include = incl }
243+
let (SelectFields(_, topFields)) = planSelection ctx data operation.SelectionSet (ref [])
244+
match operation.OperationType with
245+
| Query ->
246+
{ Operation = operation
247+
Fields = topFields
248+
RootDef = ctx.Schema.Query
249+
Strategy = Parallel }
250+
| Mutation ->
251+
match ctx.Schema.Mutation with
252+
| Some mutationDef ->
253+
{ Operation = operation
254+
Fields = topFields
255+
RootDef = mutationDef
256+
Strategy = Serial }
257+
| None ->
258+
raise (GraphQLException "Tried to execute a GraphQL mutation on schema with no mutation type defined")

src/FSharp.Data.GraphQL/Prolog.fs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,21 @@ module Array =
2020
i <- i + 1
2121
Array.sub temp 0 i
2222

23+
module List =
24+
let mergeBy f listx listy =
25+
let uniqx =
26+
listx
27+
|> List.filter (fun x -> not <| List.exists(fun y -> f(x) = f(y)) listy)
28+
uniqx @ listy
29+
30+
module Map =
31+
let merge mergeFn mapx mapy =
32+
mapy
33+
|> Map.fold (fun acc ky vy ->
34+
match Map.tryFind ky acc with
35+
| Some vx -> Map.add ky (mergeFn ky vx vy) acc
36+
| None -> Map.add ky vy acc) mapx
37+
2338
module Option =
2439
let toObj value = match value with None -> null | Some x -> x
2540

0 commit comments

Comments
 (0)