diff --git a/.changeset/stupid-geese-camp.md b/.changeset/stupid-geese-camp.md new file mode 100644 index 000000000..99967382f --- /dev/null +++ b/.changeset/stupid-geese-camp.md @@ -0,0 +1,9 @@ +--- +"@apollo/query-planner": patch +"@apollo/query-graphs": patch +"@apollo/federation-internals": patch +--- + +Fixed missing referenced variables in the `variableUsages` field of fetch operations + +Query variables used in fetch operation should be listed in the `variableUsages` field. However, there was a bug where variables referenced by query-level directives could be missing in the field. diff --git a/internals-js/src/operations.ts b/internals-js/src/operations.ts index ea2bf3eb4..bad09b9e0 100644 --- a/internals-js/src/operations.ts +++ b/internals-js/src/operations.ts @@ -1193,6 +1193,11 @@ export class NamedFragmentDefinition extends DirectiveTargetElement { query testQuery__Subgraph1__0($some_var: String!) @withArgs(arg1: $some_var) { test } - `); // end of test - }); + `); + // Make sure the `variableUsage` also captures the variable as well. + expect(fetch_nodes[0].variableUsages?.includes('some_var')).toBe(true); + }); // end of test }); // end of `describe` describe('@fromContext impacts on query planning', () => { diff --git a/query-planner-js/src/buildPlan.ts b/query-planner-js/src/buildPlan.ts index dce90edad..daab6249d 100644 --- a/query-planner-js/src/buildPlan.ts +++ b/query-planner-js/src/buildPlan.ts @@ -1600,22 +1600,26 @@ class FetchGroup { ); } - // collect all used variables in the selection and in used Fragments - const usedVariables = new Set(selection.usedVariables().map(v => v.name)); + // collect all used variables in the selection and the used directives and fragments. + const collector = new VariableCollector(); + // Note: The operation's selectionSet includes `representations` variable, + // thus we use `selection` here instead. + selection.collectVariables(collector); + operation.collectVariablesInAppliedDirectives(collector); if (operation.fragments) { for (const namedFragment of operation.fragments.definitions()) { - namedFragment.selectionSet.usedVariables().forEach(v => { - usedVariables.add(v.name); - }); + namedFragment.collectVariables(collector); } } + const usedVariables = collector.variables(); + const operationDocument = operationToDocument(operation); const fetchNode: FetchNode = { kind: 'Fetch', id: this.id, serviceName: this.subgraphName, requires: inputNodes ? trimSelectionNodes(inputNodes.selections) : undefined, - variableUsages: Array.from(usedVariables), + variableUsages: usedVariables.map((v) => v.name), operation: stripIgnoredCharacters(print(operationDocument)), operationKind: schemaRootKindToOperationKind(operation.rootKind), operationName: operation.name,