@@ -14,11 +14,12 @@ https://ibm.github.io/graphql-specs/cost-spec.html
1414
1515It builds on top of IBM spec for @cost and @listSize directive with a few changes.
1616
17- * We use Int! for weights instead of floats packed in String! .
17+ * We use the Int! type for weights.
1818* When weight is specified for the type and a field returns the list of that type,
1919this weight (along with children's costs) is multiplied too.
2020
21- TODO: Weights on arguments of directives
21+ Weights on arguments of directives are supported. If an argument is of InputObject's type,
22+ then the weight from its fields is not counted.
2223
2324*/
2425
@@ -40,8 +41,8 @@ import (
4041const DefaultEnumScalarWeight = 0
4142const DefaultObjectWeight = 1
4243
43- // FieldWeight defines cost configuration for a specific field of an object or input object.
44- type FieldWeight struct {
44+ // FieldCost defines cost configuration for a specific field of an object or input object.
45+ type FieldCost struct {
4546
4647 // Weight is the cost of this field definition. It could be negative or zero.
4748 // Should be used only if HasWeight is true.
@@ -53,6 +54,10 @@ type FieldWeight struct {
5354 // ArgumentWeights maps an argument name to its weight.
5455 // Location: ARGUMENT_DEFINITION
5556 ArgumentWeights map [string ]int
57+
58+ // DirectiveArgumentWeights maps a directive.argument coords to its weight.
59+ // Populated by composition from @cost on directive argument definitions.
60+ DirectiveArgumentWeights map [string ]int
5661}
5762
5863// FieldListSize contains parsed data from the @listSize directive for an object field.
@@ -118,7 +123,7 @@ func (ls *FieldListSize) multiplier(arguments map[string]ArgumentInfo, vars *ast
118123type DataSourceCostConfig struct {
119124 // Weights maps field coordinate to its weights. Cannot be on fields of interfaces.
120125 // Location: FIELD_DEFINITION, INPUT_FIELD_DEFINITION
121- Weights map [FieldCoordinate ]* FieldWeight
126+ Weights map [FieldCoordinate ]* FieldCost
122127
123128 // ListSizes maps field coordinates to their respective list size configurations.
124129 // Location: FIELD_DEFINITION
@@ -129,19 +134,12 @@ type DataSourceCostConfig struct {
129134 // Weight assigned to the field or argument definitions overrides the weight of type definition.
130135 // Location: ENUM, OBJECT, SCALAR
131136 Types map [string ]int
132-
133- // Arguments on directives is a special case. They use a special kind of coordinate:
134- // directive name + argument name. That should be the key mapped to the weight.
135- //
136- // Directives can be used on [input] object fields and arguments of fields. This creates
137- // mutual recursion between them; it complicates cost calculation.
138- // We avoid them intentionally in the first iteration.
139137}
140138
141139// NewDataSourceCostConfig creates a new cost config with defaults
142140func NewDataSourceCostConfig () * DataSourceCostConfig {
143141 return & DataSourceCostConfig {
144- Weights : make (map [FieldCoordinate ]* FieldWeight ),
142+ Weights : make (map [FieldCoordinate ]* FieldCost ),
145143 ListSizes : make (map [FieldCoordinate ]* FieldListSize ),
146144 Types : make (map [string ]int ),
147145 }
@@ -238,7 +236,7 @@ type inputObjectField struct {
238236
239237// inputFieldsCost computes the cost of input object fields from the variable value.
240238// It handles both single objects and arrays of objects.
241- func (arg * ArgumentInfo ) inputFieldsCost (variables * astjson.Value , weights map [FieldCoordinate ]* FieldWeight ) int {
239+ func (arg * ArgumentInfo ) inputFieldsCost (variables * astjson.Value , weights map [FieldCoordinate ]* FieldCost ) int {
242240 if ! arg .hasVariable {
243241 return 0
244242 }
@@ -262,8 +260,8 @@ func (arg *ArgumentInfo) inputFieldsCost(variables *astjson.Value, weights map[F
262260 return 0
263261}
264262
265- func (node * CostTreeNode ) maxWeightImplementingField (config * DataSourceCostConfig , fieldName string ) * FieldWeight {
266- var maxWeight * FieldWeight
263+ func (node * CostTreeNode ) maxWeightImplementingField (config * DataSourceCostConfig , fieldName string ) * FieldCost {
264+ var maxWeight * FieldCost
267265 for _ , implTypeName := range node .implementingTypeNames {
268266 // Get the cost config for the field of an implementing type.
269267 coord := FieldCoordinate {implTypeName , fieldName }
@@ -330,6 +328,29 @@ func (node *CostTreeNode) sizedFieldImplementingFields(config *DataSourceCostCon
330328 return result
331329}
332330
331+ // maxDirectiveArgumentWeightsImplementingFields returns the union of DirectiveArgumentWeights
332+ // from implementing types' field definitions. For each directive.argument pair, it takes the
333+ // maximum weight across all implementing types.
334+ func (node * CostTreeNode ) maxDirectiveArgumentWeightsImplementingFields (config * DataSourceCostConfig , fieldName string ) map [string ]int {
335+ var result map [string ]int
336+ for _ , implTypeName := range node .implementingTypeNames {
337+ coords := FieldCoordinate {implTypeName , fieldName }
338+ fw := config .Weights [coords ]
339+ if fw == nil || len (fw .DirectiveArgumentWeights ) == 0 {
340+ continue
341+ }
342+ if result == nil {
343+ result = make (map [string ]int )
344+ }
345+ for dirArg , weight := range fw .DirectiveArgumentWeights {
346+ if existing , ok := result [dirArg ]; ! ok || weight > existing {
347+ result [dirArg ] = weight
348+ }
349+ }
350+ }
351+ return result
352+ }
353+
333354// cost calculates the estimated/actual cost of this node and all descendants.
334355//
335356// defaultListSize designates the mode of operation.
@@ -382,7 +403,7 @@ func (node *CostTreeNode) cost(configs map[DSHash]*DataSourceCostConfig, variabl
382403//
383404// fieldCost is the weight of this field or its returned type
384405// argsCost is the sum of argument weights and input fields used on this field.
385- // Weights on directives ignored for now .
406+ // directiveCost is the sum of directive argument weights .
386407//
387408// defaultListSize designates the mode of operation.
388409// When it is positive, then its value is used as a fallback value of list sizes for the estimated cost.
@@ -391,7 +412,7 @@ func (node *CostTreeNode) cost(configs map[DSHash]*DataSourceCostConfig, variabl
391412// When estimating cost, it picks the highest multiplier among different data sources.
392413// Also, it picks the maximum field weight of implementing types and then
393414// the maximum among slicing arguments.
394- func (node * CostTreeNode ) costsAndMultiplier (configs map [DSHash ]* DataSourceCostConfig , variables * astjson.Value , defaultListSize int , actualListSizes map [string ]int ) (fieldCost , argsCost , directiveCost int , multiplier float64 ) {
415+ func (node * CostTreeNode ) costsAndMultiplier (configs map [DSHash ]* DataSourceCostConfig , variables * astjson.Value , defaultListSize int , actualListSizes map [string ]int ) (fieldCost , argsCost , directivesCost int , multiplier float64 ) {
395416 if len (node .dataSourceHashes ) <= 0 {
396417 // no data source is responsible for this field
397418 return
@@ -400,7 +421,7 @@ func (node *CostTreeNode) costsAndMultiplier(configs map[DSHash]*DataSourceCostC
400421 parent := node .parent
401422 fieldCost = 0
402423 argsCost = 0
403- directiveCost = 0
424+ directivesCost = 0
404425 multiplier = 0
405426
406427 isEstimation := defaultListSize > 0
@@ -479,6 +500,18 @@ func (node *CostTreeNode) costsAndMultiplier(configs map[DSHash]*DataSourceCostC
479500 }
480501 }
481502
503+ // Directive weights: sum from the field's own DirectiveArgumentWeights,
504+ // or from implementing types when the enclosing type is abstract.
505+ if node .isEnclosingTypeAbstract && parent .returnsAbstractType {
506+ for _ , weight := range parent .maxDirectiveArgumentWeightsImplementingFields (dsCostConfig , node .fieldCoords .FieldName ) {
507+ directivesCost += weight
508+ }
509+ } else if fieldWeight != nil {
510+ for _ , weight := range fieldWeight .DirectiveArgumentWeights {
511+ directivesCost += weight
512+ }
513+ }
514+
482515 if ! node .returnsListType || ! isEstimation {
483516 continue
484517 }
@@ -571,7 +604,7 @@ func (node *CostTreeNode) costsAndMultiplier(configs map[DSHash]*DataSourceCostC
571604func inputObjectCost (
572605 typeName string ,
573606 value * astjson.Object ,
574- weights map [FieldCoordinate ]* FieldWeight ,
607+ weights map [FieldCoordinate ]* FieldCost ,
575608 types map [FieldCoordinate ]inputObjectField ) int {
576609 if value == nil {
577610 return 0
0 commit comments