1
+ import Dispatch
2
+
1
3
/**
2
4
* Terminology
3
5
*
26
28
*/
27
29
public final class ExecutionContext {
28
30
29
- let queryStrategy : FieldExecutionStrategy
30
- let mutationStrategy : FieldExecutionStrategy
31
- let subscriptionStrategy : FieldExecutionStrategy
32
- let schema : GraphQLSchema
33
- let fragments : [ String : FragmentDefinition ]
34
- let rootValue : Any
35
- let contextValue : Any
36
- let operation : OperationDefinition
37
- let variableValues : [ String : Map ]
38
- var errors : [ GraphQLError ]
31
+ let queryStrategy : QueryFieldExecutionStrategy
32
+ let mutationStrategy : MutationFieldExecutionStrategy
33
+ let subscriptionStrategy : SubscriptionFieldExecutionStrategy
34
+ public let schema : GraphQLSchema
35
+ public let fragments : [ String : FragmentDefinition ]
36
+ public let rootValue : Any
37
+ public let contextValue : Any
38
+ public let operation : OperationDefinition
39
+ public let variableValues : [ String : Map ]
40
+
41
+ private var errorsSemaphore = DispatchSemaphore ( value: 1 )
42
+ private var _errors : [ GraphQLError ]
43
+
44
+ var errors : [ GraphQLError ] {
45
+ get {
46
+ errorsSemaphore. wait ( )
47
+ defer {
48
+ errorsSemaphore. signal ( )
49
+ }
50
+ return _errors
51
+ }
52
+ }
39
53
40
54
init (
41
- queryStrategy: FieldExecutionStrategy ,
42
- mutationStrategy: FieldExecutionStrategy ,
43
- subscriptionStrategy: FieldExecutionStrategy ,
55
+ queryStrategy: QueryFieldExecutionStrategy ,
56
+ mutationStrategy: MutationFieldExecutionStrategy ,
57
+ subscriptionStrategy: SubscriptionFieldExecutionStrategy ,
44
58
schema: GraphQLSchema ,
45
59
fragments: [ String : FragmentDefinition ] ,
46
60
rootValue: Any ,
@@ -58,9 +72,17 @@ public final class ExecutionContext {
58
72
self . contextValue = contextValue
59
73
self . operation = operation
60
74
self . variableValues = variableValues
61
- self . errors = errors
75
+ self . _errors = errors
76
+ }
62
77
78
+ public func append( error: GraphQLError ) {
79
+ errorsSemaphore. wait ( )
80
+ defer {
81
+ errorsSemaphore. signal ( )
82
+ }
83
+ _errors. append ( error)
63
84
}
85
+
64
86
}
65
87
66
88
public protocol FieldExecutionStrategy {
@@ -73,10 +95,17 @@ public protocol FieldExecutionStrategy {
73
95
) throws -> [ String : Any ]
74
96
}
75
97
98
+ public protocol MutationFieldExecutionStrategy : FieldExecutionStrategy { }
99
+ public protocol QueryFieldExecutionStrategy : FieldExecutionStrategy { }
100
+ public protocol SubscriptionFieldExecutionStrategy : FieldExecutionStrategy { }
101
+
76
102
/**
77
103
* Serial field execution strategy that's suitable for the "Evaluating selection sets" section of the spec for "write" mode.
78
104
*/
79
- public struct SerialFieldExecutionStrategy : FieldExecutionStrategy {
105
+ public struct SerialFieldExecutionStrategy : QueryFieldExecutionStrategy , MutationFieldExecutionStrategy , SubscriptionFieldExecutionStrategy {
106
+
107
+ public init ( ) { }
108
+
80
109
public func executeFields(
81
110
exeContext: ExecutionContext ,
82
111
parentType: GraphQLObjectType ,
@@ -103,16 +132,88 @@ public struct SerialFieldExecutionStrategy: FieldExecutionStrategy {
103
132
}
104
133
}
105
134
135
+ /**
136
+ * Serial field execution strategy that's suitable for the "Evaluating selection sets" section of the spec for "read" mode.
137
+ *
138
+ * Each field is resolved as an individual task on a concurrent dispatch queue.
139
+ */
140
+ public struct ConcurrentDispatchFieldExecutionStrategy : QueryFieldExecutionStrategy , SubscriptionFieldExecutionStrategy {
141
+
142
+ let dispatchQueue : DispatchQueue
143
+
144
+ public init ( dispatchQueue: DispatchQueue ) {
145
+ self . dispatchQueue = dispatchQueue
146
+ }
147
+
148
+ public init ( queueLabel: String = " GraphQL field execution " , queueQoS: DispatchQoS = . userInitiated) {
149
+ self . dispatchQueue = DispatchQueue (
150
+ label: queueLabel,
151
+ qos: queueQoS,
152
+ attributes: . concurrent
153
+ )
154
+ }
155
+
156
+ public func executeFields(
157
+ exeContext: ExecutionContext ,
158
+ parentType: GraphQLObjectType ,
159
+ sourceValue: Any ,
160
+ path: [ IndexPathElement ] ,
161
+ fields: [ String : [ Field ] ]
162
+ ) throws -> [ String : Any ] {
163
+
164
+ let resultsQueue = DispatchQueue (
165
+ label: " \( dispatchQueue. label) results " ,
166
+ qos: dispatchQueue. qos
167
+ )
168
+ let group = DispatchGroup ( )
169
+ var results : [ String : Any ] = [ : ]
170
+ var err : Error ? = nil
171
+
172
+ fields. forEach { field in
173
+ let fieldASTs = field. value
174
+ let fieldKey = field. key
175
+ let fieldPath = path + [ fieldKey] as [ IndexPathElement ]
176
+ dispatchQueue. async ( group: group) {
177
+ guard err == nil else {
178
+ return
179
+ }
180
+ do {
181
+ let result = try resolveField (
182
+ exeContext: exeContext,
183
+ parentType: parentType,
184
+ source: sourceValue,
185
+ fieldASTs: fieldASTs,
186
+ path: fieldPath
187
+ )
188
+ resultsQueue. async ( group: group) {
189
+ results [ fieldKey] = result ?? Map . null
190
+ }
191
+ } catch {
192
+ resultsQueue. async ( group: group) {
193
+ err = error
194
+ }
195
+ }
196
+ }
197
+ }
198
+ group. wait ( )
199
+ if let error = err {
200
+ throw error
201
+ }
202
+ return results
203
+ }
204
+
205
+ }
206
+
106
207
/**
107
208
* Implements the "Evaluating requests" section of the GraphQL specification.
108
209
*
109
210
* If the arguments to this func do not result in a legal execution context,
110
211
* a GraphQLError will be thrown immediately explaining the invalid input.
111
212
*/
112
213
func execute(
113
- queryStrategy: FieldExecutionStrategy ,
114
- mutationStrategy: FieldExecutionStrategy ,
115
- subscriptionStrategy: FieldExecutionStrategy ,
214
+ queryStrategy: QueryFieldExecutionStrategy ,
215
+ mutationStrategy: MutationFieldExecutionStrategy ,
216
+ subscriptionStrategy: SubscriptionFieldExecutionStrategy ,
116
217
schema: GraphQLSchema ,
117
218
documentAST: Document ,
118
219
rootValue: Any ,
@@ -166,9 +267,9 @@ func execute(
166
267
* Throws a GraphQLError if a valid execution context cannot be created.
167
268
*/
168
269
func buildExecutionContext(
169
- queryStrategy: FieldExecutionStrategy ,
170
- mutationStrategy: FieldExecutionStrategy ,
171
- subscriptionStrategy: FieldExecutionStrategy ,
270
+ queryStrategy: QueryFieldExecutionStrategy ,
271
+ mutationStrategy: MutationFieldExecutionStrategy ,
272
+ subscriptionStrategy: SubscriptionFieldExecutionStrategy ,
172
273
schema: GraphQLSchema ,
173
274
documentAST: Document ,
174
275
rootValue: Any ,
@@ -608,7 +709,7 @@ func completeValueCatchingError(
608
709
} catch let error as GraphQLError {
609
710
// If `completeValueWithLocatedError` returned abruptly (threw an error),
610
711
// log the error and return .null.
611
- exeContext. errors . append ( error)
712
+ exeContext. append ( error : error)
612
713
return nil
613
714
} catch {
614
715
fatalError ( )
0 commit comments