@apollo/query-planner
Advanced tools
Comparing version 2.0.0-alpha.6 to 2.0.0-preview.0
@@ -270,3 +270,3 @@ "use strict"; | ||
(0, federation_internals_1.assert)(root, () => `Shouldn't have a ${operation.rootKind} operation if the subgraphs don't have a ${operation.rootKind} root`); | ||
const processor = fetchGroupToPlanProcessor(operation.variableDefinitions, operation.selectionSet.fragments); | ||
const processor = fetchGroupToPlanProcessor(operation.variableDefinitions, operation.selectionSet.fragments, operation.name); | ||
if (operation.rootKind === 'mutation') { | ||
@@ -336,5 +336,12 @@ const dependencyGraphs = computeRootSerialDependencyGraph(supergraphSchema, operation, federatedQueryGraph, root); | ||
} | ||
function fetchGroupToPlanProcessor(variableDefinitions, fragments) { | ||
function toValidGraphQLName(subgraphName) { | ||
const sanitized = subgraphName | ||
.replace(/-/ig, '_') | ||
.replace(/[^_0-9A-Za-z]/ig, ''); | ||
return sanitized.match(/^[0-9].*/i) ? '_' + sanitized : sanitized; | ||
} | ||
function fetchGroupToPlanProcessor(variableDefinitions, fragments, operationName) { | ||
let counter = 0; | ||
return { | ||
onFetchGroup: (group) => group.toPlanNode(variableDefinitions, fragments), | ||
onFetchGroup: (group) => group.toPlanNode(variableDefinitions, fragments, operationName ? `${operationName}__${toValidGraphQLName(group.subgraphName)}__${counter++}` : undefined), | ||
reduceParallel: (values) => flatWrap('Parallel', values), | ||
@@ -455,3 +462,3 @@ reduceSequence: (values) => flatWrap('Sequence', values), | ||
} | ||
toPlanNode(variableDefinitions, fragments) { | ||
toPlanNode(variableDefinitions, fragments, operationName) { | ||
var _a; | ||
@@ -466,4 +473,4 @@ addTypenameFieldForAbstractTypes(this.selection); | ||
const operation = this.isEntityFetch | ||
? operationForEntitiesFetch(this.dependencyGraph.subgraphSchemas.get(this.subgraphName), this.selection, variableDefinitions, fragments) | ||
: operationForQueryFetch(this.rootKind, this.selection, variableDefinitions, fragments); | ||
? operationForEntitiesFetch(this.dependencyGraph.subgraphSchemas.get(this.subgraphName), this.selection, variableDefinitions, fragments, operationName) | ||
: operationForQueryFetch(this.rootKind, this.selection, variableDefinitions, fragments, operationName); | ||
const fetchNode = { | ||
@@ -476,2 +483,3 @@ kind: 'Fetch', | ||
operationKind: schemaRootKindToOperationKind(operation.rootKind), | ||
operationName: operation.name, | ||
}; | ||
@@ -528,2 +536,9 @@ return this.isTopLevel | ||
} | ||
federationMetadata(subgraphName) { | ||
const schema = this.subgraphSchemas.get(subgraphName); | ||
(0, federation_internals_1.assert)(schema, () => `Unknown schema ${subgraphName}`); | ||
const metadata = (0, federation_internals_1.federationMetadata)(schema); | ||
(0, federation_internals_1.assert)(metadata, () => `Schema ${subgraphName} should be a federation subgraph`); | ||
return metadata; | ||
} | ||
clone() { | ||
@@ -579,3 +594,4 @@ const cloned = new FetchDependencyGraph(this.subgraphSchemas, this.federatedQueryGraph, new federation_internals_1.MapWithCachedArrays(), new Array(this.groups.length), this.adjacencies.map(a => a.concat()), this.inEdges.map(a => a.concat()), this.pathsInParents.concat()); | ||
} | ||
const entityType = this.subgraphSchemas.get(subgraphName).type(federation_internals_1.entityTypeName); | ||
const entityType = this.federationMetadata(subgraphName).entityType(); | ||
(0, federation_internals_1.assert)(entityType, () => `Subgraph ${subgraphName} has not entities defined`); | ||
return this.newFetchGroup(subgraphName, entityType, true, 'query', mergeAt, directParent, pathInParent); | ||
@@ -598,3 +614,4 @@ } | ||
newKeyFetchGroup(subgraphName, mergeAt) { | ||
const entityType = this.subgraphSchemas.get(subgraphName).type(federation_internals_1.entityTypeName); | ||
const entityType = this.federationMetadata(subgraphName).entityType(); | ||
(0, federation_internals_1.assert)(entityType, () => `Subgraph ${subgraphName} has not entities defined`); | ||
return this.newFetchGroup(subgraphName, entityType, true, 'query', mergeAt); | ||
@@ -1081,8 +1098,8 @@ } | ||
function representationsVariableDefinition(schema) { | ||
const anyType = schema.type('_Any'); | ||
(0, federation_internals_1.assert)(anyType, `Cannot find _Any type in schema`); | ||
const representationsType = new federation_internals_1.NonNullType(new federation_internals_1.ListType(new federation_internals_1.NonNullType(anyType))); | ||
const metadata = (0, federation_internals_1.federationMetadata)(schema); | ||
(0, federation_internals_1.assert)(metadata, 'Expected schema to be a federation subgraph'); | ||
const representationsType = new federation_internals_1.NonNullType(new federation_internals_1.ListType(new federation_internals_1.NonNullType(metadata.anyType()))); | ||
return new federation_internals_1.VariableDefinition(schema, representationsVariable, representationsType); | ||
} | ||
function operationForEntitiesFetch(subgraphSchema, selectionSet, allVariableDefinitions, fragments) { | ||
function operationForEntitiesFetch(subgraphSchema, selectionSet, allVariableDefinitions, fragments, operationName) { | ||
const variableDefinitions = new federation_internals_1.VariableDefinitions(); | ||
@@ -1093,7 +1110,7 @@ variableDefinitions.add(representationsVariableDefinition(subgraphSchema)); | ||
(0, federation_internals_1.assert)(queryType, `Subgraphs should always have a query root (they should at least provides _entities)`); | ||
const entities = queryType.field('_entities'); | ||
const entities = queryType.field(federation_internals_1.entitiesFieldName); | ||
(0, federation_internals_1.assert)(entities, `Subgraphs should always have the _entities field`); | ||
const entitiesCall = new federation_internals_1.SelectionSet(queryType); | ||
entitiesCall.add(new federation_internals_1.FieldSelection(new federation_internals_1.Field(entities, { 'representations': representationsVariable }, variableDefinitions), selectionSet)); | ||
return new federation_internals_1.Operation('query', entitiesCall, variableDefinitions).optimize(fragments); | ||
entitiesCall.add(new federation_internals_1.FieldSelection(new federation_internals_1.Field(entities, { representations: representationsVariable }, variableDefinitions), selectionSet)); | ||
return new federation_internals_1.Operation('query', entitiesCall, variableDefinitions, operationName).optimize(fragments); | ||
} | ||
@@ -1110,5 +1127,5 @@ function flatWrap(kind, nodes) { | ||
} | ||
function operationForQueryFetch(rootKind, selectionSet, allVariableDefinitions, fragments) { | ||
return new federation_internals_1.Operation(rootKind, selectionSet, allVariableDefinitions.filter(selectionSet.usedVariables())).optimize(fragments); | ||
function operationForQueryFetch(rootKind, selectionSet, allVariableDefinitions, fragments, operationName) { | ||
return new federation_internals_1.Operation(rootKind, selectionSet, allVariableDefinitions.filter(selectionSet.usedVariables()), operationName).optimize(fragments); | ||
} | ||
//# sourceMappingURL=buildPlan.js.map |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
@@ -6,0 +10,0 @@ if (k2 === undefined) k2 = k; |
@@ -22,2 +22,3 @@ import { SelectionNode as GraphQLJSSelectionNode, OperationTypeNode } from 'graphql'; | ||
operation: string; | ||
operationName: string | undefined; | ||
operationKind: OperationTypeNode; | ||
@@ -24,0 +25,0 @@ } |
{ | ||
"name": "@apollo/query-planner", | ||
"version": "2.0.0-alpha.6", | ||
"version": "2.0.0-preview.0", | ||
"description": "Apollo Query Planner", | ||
@@ -28,4 +28,4 @@ "author": "Apollo <packages@apollographql.com>", | ||
"dependencies": { | ||
"@apollo/federation-internals": "^2.0.0-alpha.6", | ||
"@apollo/query-graphs": "^2.0.0-alpha.6", | ||
"@apollo/federation-internals": "^2.0.0-preview.0", | ||
"@apollo/query-graphs": "^2.0.0-preview.0", | ||
"chalk": "^4.1.0", | ||
@@ -38,3 +38,3 @@ "deep-equal": "^2.0.5", | ||
}, | ||
"gitHead": "ee84b3fd9df161c3a94ae3d70e82a14dba5794ba" | ||
"gitHead": "e76fd3e16bf140ba9b30fe26d48657bccca180dc" | ||
} |
import { astSerializer, queryPlanSerializer, QueryPlanner } from '@apollo/query-planner'; | ||
import { composeServices } from '@apollo/composition'; | ||
import { assert, buildSchema, operationFromDocument, Schema, ServiceDefinition } from '@apollo/federation-internals'; | ||
import { asFed2SubgraphDocument, assert, buildSchema, operationFromDocument, Schema, ServiceDefinition } from '@apollo/federation-internals'; | ||
import gql from 'graphql-tag'; | ||
import { MAX_COMPUTED_PLANS } from '../buildPlan'; | ||
import { FetchNode } from '../QueryPlan'; | ||
import { FetchNode, FlattenNode, SequenceNode } from '../QueryPlan'; | ||
import { FieldNode, OperationDefinitionNode, parse } from 'graphql'; | ||
@@ -13,3 +13,5 @@ | ||
function composeAndCreatePlanner(...services: ServiceDefinition[]): [Schema, QueryPlanner] { | ||
const compositionResults = composeServices(services); | ||
const compositionResults = composeServices( | ||
services.map((s) => ({ ...s, typeDefs: asFed2SubgraphDocument(s.typeDefs) })) | ||
); | ||
expect(compositionResults.errors).toBeUndefined(); | ||
@@ -27,3 +29,3 @@ return [ | ||
type Query { | ||
me: User! | ||
me: User! @shareable | ||
} | ||
@@ -42,3 +44,3 @@ | ||
type Query { | ||
me: User! | ||
me: User! @shareable | ||
} | ||
@@ -230,3 +232,3 @@ | ||
id: ID! | ||
subSubResponseValue: Int | ||
subSubResponseValue: Int @shareable | ||
} | ||
@@ -331,3 +333,3 @@ ` | ||
type Value { | ||
a: Int | ||
a: Int @shareable | ||
} | ||
@@ -351,3 +353,3 @@ | ||
type Value { | ||
a: Int | ||
a: Int @shareable | ||
b: Int | ||
@@ -358,3 +360,3 @@ } | ||
id: ID! | ||
v: Value | ||
v: Value @shareable | ||
} | ||
@@ -364,3 +366,3 @@ | ||
id: ID! | ||
v: Value | ||
v: Value @shareable | ||
} | ||
@@ -554,3 +556,3 @@ ` | ||
id: ID! | ||
a: Int | ||
a: Int @shareable | ||
} | ||
@@ -560,3 +562,3 @@ | ||
id: ID! | ||
b: Int | ||
b: Int @shareable | ||
} | ||
@@ -790,4 +792,4 @@ ` | ||
id: ID! | ||
a: Int | ||
b: Int | ||
a: Int @shareable | ||
b: Int @shareable | ||
} | ||
@@ -1076,2 +1078,206 @@ ` | ||
describe('fetch operation names', () => { | ||
test('handle subgraph with - in the name', () => { | ||
const subgraph1 = { | ||
name: 'S1', | ||
typeDefs: gql` | ||
type Query { | ||
t: T | ||
} | ||
type T @key(fields: "id") { | ||
id: ID! | ||
} | ||
` | ||
} | ||
const subgraph2 = { | ||
name: 'non-graphql-name', | ||
typeDefs: gql` | ||
type T @key(fields: "id") { | ||
id: ID! | ||
x: Int | ||
} | ||
` | ||
} | ||
const [api, queryPlanner] = composeAndCreatePlanner(subgraph1, subgraph2); | ||
const operation = operationFromDocument(api, gql` | ||
query myOp { | ||
t { | ||
x | ||
} | ||
} | ||
`); | ||
const plan = queryPlanner.buildQueryPlan(operation); | ||
expect(plan).toMatchInlineSnapshot(` | ||
QueryPlan { | ||
Sequence { | ||
Fetch(service: "S1") { | ||
{ | ||
t { | ||
__typename | ||
id | ||
} | ||
} | ||
}, | ||
Flatten(path: "t") { | ||
Fetch(service: "non-graphql-name") { | ||
{ | ||
... on T { | ||
__typename | ||
id | ||
} | ||
} => | ||
{ | ||
... on T { | ||
x | ||
} | ||
} | ||
}, | ||
}, | ||
}, | ||
} | ||
`); | ||
const fetch = ((plan.node as SequenceNode).nodes[1] as FlattenNode).node as FetchNode; | ||
expect(fetch.operation).toMatch(/^query myOp__non_graphql_name__1.*/i); | ||
}); | ||
test('ensures sanitization applies repeatedly', () => { | ||
const subgraph1 = { | ||
name: 'S1', | ||
typeDefs: gql` | ||
type Query { | ||
t: T | ||
} | ||
type T @key(fields: "id") { | ||
id: ID! | ||
} | ||
` | ||
} | ||
const subgraph2 = { | ||
name: 'a-na&me-with-plen&ty-replace*ments', | ||
typeDefs: gql` | ||
type T @key(fields: "id") { | ||
id: ID! | ||
x: Int | ||
} | ||
` | ||
} | ||
const [api, queryPlanner] = composeAndCreatePlanner(subgraph1, subgraph2); | ||
const operation = operationFromDocument(api, gql` | ||
query myOp { | ||
t { | ||
x | ||
} | ||
} | ||
`); | ||
const plan = queryPlanner.buildQueryPlan(operation); | ||
expect(plan).toMatchInlineSnapshot(` | ||
QueryPlan { | ||
Sequence { | ||
Fetch(service: "S1") { | ||
{ | ||
t { | ||
__typename | ||
id | ||
} | ||
} | ||
}, | ||
Flatten(path: "t") { | ||
Fetch(service: "a-na&me-with-plen&ty-replace*ments") { | ||
{ | ||
... on T { | ||
__typename | ||
id | ||
} | ||
} => | ||
{ | ||
... on T { | ||
x | ||
} | ||
} | ||
}, | ||
}, | ||
}, | ||
} | ||
`); | ||
const fetch = ((plan.node as SequenceNode).nodes[1] as FlattenNode).node as FetchNode; | ||
expect(fetch.operation).toMatch(/^query myOp__a_name_with_plenty_replacements__1.*/i); | ||
}); | ||
test('handle very non-graph subgraph name', () => { | ||
const subgraph1 = { | ||
name: 'S1', | ||
typeDefs: gql` | ||
type Query { | ||
t: T | ||
} | ||
type T @key(fields: "id") { | ||
id: ID! | ||
} | ||
` | ||
} | ||
const subgraph2 = { | ||
name: '42!', | ||
typeDefs: gql` | ||
type T @key(fields: "id") { | ||
id: ID! | ||
x: Int | ||
} | ||
` | ||
} | ||
const [api, queryPlanner] = composeAndCreatePlanner(subgraph1, subgraph2); | ||
const operation = operationFromDocument(api, gql` | ||
query myOp { | ||
t { | ||
x | ||
} | ||
} | ||
`); | ||
const plan = queryPlanner.buildQueryPlan(operation); | ||
expect(plan).toMatchInlineSnapshot(` | ||
QueryPlan { | ||
Sequence { | ||
Fetch(service: "S1") { | ||
{ | ||
t { | ||
__typename | ||
id | ||
} | ||
} | ||
}, | ||
Flatten(path: "t") { | ||
Fetch(service: "42!") { | ||
{ | ||
... on T { | ||
__typename | ||
id | ||
} | ||
} => | ||
{ | ||
... on T { | ||
x | ||
} | ||
} | ||
}, | ||
}, | ||
}, | ||
} | ||
`); | ||
const fetch = ((plan.node as SequenceNode).nodes[1] as FlattenNode).node as FetchNode; | ||
expect(fetch.operation).toMatch(/^query myOp___42__1.*/i); | ||
}); | ||
}); | ||
test('Correctly handle case where there is too many plans to consider', () => { | ||
@@ -1090,7 +1296,7 @@ // Creating realistic examples where there is too many plan to consider is not trivial, but creating unrealistic examples | ||
type Query { | ||
t: T | ||
t: T @shareable | ||
} | ||
type T { | ||
${fields.map((f) => `${f}: Int\n`)} | ||
${fields.map((f) => `${f}: Int @shareable\n`)} | ||
} | ||
@@ -1097,0 +1303,0 @@ `; |
@@ -34,2 +34,3 @@ import { | ||
operation: string; | ||
operationName: string | undefined; | ||
operationKind: OperationTypeNode; | ||
@@ -36,0 +37,0 @@ } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
431161
5349