Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update context-matching logic to use earlier path edges if possible #3114

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 121 additions & 62 deletions query-graphs-js/src/graphPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1938,6 +1938,8 @@
return undefined;
}

type PreContextMapEntry = Pick<ContextMapEntry, 'levelsInDataPath' | 'levelsInQueryPath' | 'inboundEdge'>;

function canSatisfyConditions<TTrigger, V extends Vertex, TNullEdge extends null | never = never>(
path: GraphPath<TTrigger, V, TNullEdge>,
edge: Edge,
Expand All @@ -1956,81 +1958,138 @@
const contextMap = new Map<string, ContextMapEntry>();

if (requiredContexts.length > 0) {
// if one of the conditions fails to satisfy, it's ok to bail
let someSelectionUnsatisfied = false;
for (const cxt of requiredContexts) {
let levelsInQueryPath = 0;
let levelsInDataPath = 0;
for (const [e, trigger] of [...path].reverse()) {
const parentType = getFieldParentType(trigger);
levelsInQueryPath += 1;
if (parentType) {
levelsInDataPath += 1;
assert(edge.transition.kind === 'FieldCollection', () => `Expected edge to be a FieldCollection edge, got ${edge.transition.kind}`);

Check warning on line 1961 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L1961

Added line #L1961 was not covered by tests

const unmatchedContexts = new Map<string, Set<string>>();
for (const ctx of requiredContexts) {
unmatchedContexts.set(ctx.context, ctx.typesWithContextSet);

Check warning on line 1965 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L1963-L1965

Added lines #L1963 - L1965 were not covered by tests
}
const matchInProgressContexts = new Map<string, CompositeType>();
const matchedContexts = new Map<string, [CompositeType, PreContextMapEntry]>();

Check warning on line 1968 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L1967-L1968

Added lines #L1967 - L1968 were not covered by tests

let levelsInQueryPath = 0;
let levelsInDataPath = 0;
for (const [e, trigger] of [...path].reverse()) {

Check warning on line 1972 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L1970-L1972

Added lines #L1970 - L1972 were not covered by tests
// If all contexts have been matched, then there's no point in continuing
// to iterate.
if (unmatchedContexts.size === 0 && matchInProgressContexts.size === 0) {
break;

Check warning on line 1976 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L1976

Added line #L1976 was not covered by tests
}

levelsInQueryPath += 1;
const parentType = getFieldParentType(trigger);

Check warning on line 1980 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L1979-L1980

Added lines #L1979 - L1980 were not covered by tests
if (parentType) {
levelsInDataPath += 1;

Check warning on line 1982 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L1982

Added line #L1982 was not covered by tests
}

// For any in-progress matches, if the current edge is a key or root type
// resolution edge, then the match is still considered in-progress, and
// the recorded match should be updated. If the edge is some other kind,
// then any in-progress matches are considered to have ended.
if (e !== null && (e.transition.kind === 'KeyResolution' || e.transition.kind === 'RootTypeResolution')) {
for (const [ctxName, matchType] of matchInProgressContexts) {
matchedContexts.set(ctxName, [matchType, {

Check warning on line 1991 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L1990-L1991

Added lines #L1990 - L1991 were not covered by tests
levelsInDataPath,
levelsInQueryPath,
inboundEdge: e,
}]);
}
if (e !== null && !contextMap.has(cxt.namedParameter) && !someSelectionUnsatisfied) {
const matches = Array.from(cxt.typesWithContextSet).some(t => {
if (parentType) {
const parentInSupergraph = path.graph.schema.type(parentType.name)!;
if (parentInSupergraph.name === t) {
} else {
matchInProgressContexts.clear();

Check warning on line 1998 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L1997-L1998

Added lines #L1997 - L1998 were not covered by tests
}

// For any unmatched contexts, determine whether a match has occurred. If
// so, consider the match as in-progress, and record that a match has
// occurred.
if (parentType && e !== null) {
const parentInSupergraph = path.graph.schema.type(parentType.name)!;
assert(isCompositeType(parentInSupergraph), "Parent type should be composite type");
for (const [ctxName, typesWithContextSet] of unmatchedContexts) {
const isMatch = Array.from(typesWithContextSet).some(t => {

Check warning on line 2008 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L2005-L2008

Added lines #L2005 - L2008 were not covered by tests
if (parentInSupergraph.name === t) {
return true;

Check warning on line 2010 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L2010

Added line #L2010 was not covered by tests
}
if (isObjectType(parentInSupergraph) || isInterfaceType(parentInSupergraph)) {
if (parentInSupergraph.interfaces().some(i => i.name === t)) {
return true;
}
if (isObjectType(parentInSupergraph) || isInterfaceType(parentInSupergraph)) {
if (parentInSupergraph.interfaces().some(i => i.name === t)) {
return true;
}
}
const tInSupergraph = parentInSupergraph.schema().type(t);
if (tInSupergraph && isUnionType(tInSupergraph)) {
return tInSupergraph.types().some(t => t.name === parentType.name);
}
}
const tInSupergraph = parentInSupergraph.schema().type(t);

Check warning on line 2017 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L2017

Added line #L2017 was not covered by tests
if (tInSupergraph && isUnionType(tInSupergraph)) {
return tInSupergraph.types().some(m => m.name === parentType.name);

Check warning on line 2019 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L2019

Added line #L2019 was not covered by tests
}
return false;
});
if (parentType && matches) {
const parentInSupergraph = path.graph.schema.type(parentType.name)!;
assert(isCompositeType(parentInSupergraph), "Parent type should be composite type");
let selectionSet = parseSelectionSet({ parentType: parentInSupergraph, source: cxt.selection });

// We want to ignore type conditions that are impossible/don't intersect with the parent type
selectionSet = selectionSet.lazyMap((selection): Selection | undefined => {
if (selection.kind === 'FragmentSelection') {
if (selection.element.typeCondition && isObjectType(selection.element.typeCondition)) {
if (!possibleRuntimeTypes(parentInSupergraph).includes(selection.element.typeCondition)) {
return undefined;
}
}
}
return selection;
})
const resolution = conditionResolver(e, context, excludedEdges, excludedConditions, selectionSet);
assert(edge.transition.kind === 'FieldCollection', () => `Expected edge to be a FieldCollection edge, got ${edge.transition.kind}`);

const argIndices = path.graph.subgraphToArgIndices.get(cxt.subgraphName);
assert(argIndices, () => `Expected to find arg indices for subgraph ${cxt.subgraphName}`);

const id = argIndices.get(cxt.coordinate);
assert(id !== undefined, () => `Expected to find arg index for ${cxt.coordinate}`);
contextMap.set(cxt.namedParameter, { selectionSet, levelsInDataPath, levelsInQueryPath, inboundEdge: e, pathTree: resolution.pathTree, paramName: cxt.namedParameter, id, argType: cxt.argType });
someSelectionUnsatisfied = someSelectionUnsatisfied || !resolution.satisfied;
if (resolution.cost === -1 || totalCost === -1) {
totalCost = -1;
} else {
totalCost += resolution.cost;
}
if (isMatch) {
matchInProgressContexts.set(ctxName, parentInSupergraph);
matchedContexts.set(ctxName, [parentInSupergraph, {

Check warning on line 2025 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L2024-L2025

Added lines #L2024 - L2025 were not covered by tests
levelsInDataPath,
levelsInQueryPath,
inboundEdge: e,
}]);
unmatchedContexts.delete(ctxName);

Check warning on line 2030 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L2030

Added line #L2030 was not covered by tests
}
}
}
}
if (requiredContexts.some(c => !contextMap.has(c.namedParameter))) {
// in this case there is a context that is unsatisfied. Return no path.

if (unmatchedContexts.size > 0) {
// In this case, there is a context that is unsatisfied. Return no path.
debug.groupEnd('@fromContext requires a context that is not set in graph path');
return { ...unsatisfiedConditionsResolution, unsatisfiedConditionReason: UnsatisfiedConditionReason.NO_CONTEXT_SET };
}

if (someSelectionUnsatisfied) {
debug.groupEnd('@fromContext selection set is unsatisfied');
return { ...unsatisfiedConditionsResolution };

for (const ctx of requiredContexts) {

Check warning on line 2042 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L2042

Added line #L2042 was not covered by tests
const [matchType, {
levelsInDataPath,
levelsInQueryPath,
inboundEdge,
}] = matchedContexts.get(ctx.context)!;

Check warning on line 2047 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L2047

Added line #L2047 was not covered by tests

// Parse the selection against the matched type.
let selectionSet = parseSelectionSet({ parentType: matchType, source: ctx.selection });

Check warning on line 2050 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L2050

Added line #L2050 was not covered by tests

// We want to ignore type conditions that are impossible/don't intersect with the matched type.
selectionSet = selectionSet.lazyMap((selection): Selection | undefined => {

Check warning on line 2053 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L2053

Added line #L2053 was not covered by tests
if (selection.kind === 'FragmentSelection') {
if (selection.element.typeCondition && isObjectType(selection.element.typeCondition)) {
if (!possibleRuntimeTypes(matchType).includes(selection.element.typeCondition)) {
return undefined;

Check warning on line 2057 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L2057

Added line #L2057 was not covered by tests
}
}
}
return selection;

Check warning on line 2061 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L2061

Added line #L2061 was not covered by tests
});

// Resolve the selection set against the matched edge's head.
const resolution = conditionResolver(inboundEdge, context, excludedEdges, excludedConditions, selectionSet);

Check warning on line 2065 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L2065

Added line #L2065 was not covered by tests
if (!resolution.satisfied) {
// If one of the conditions fails to satisfy, it's ok to bail.
debug.groupEnd('@fromContext selection set is unsatisfied');
return { ...unsatisfiedConditionsResolution };

Check warning on line 2069 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L2068-L2069

Added lines #L2068 - L2069 were not covered by tests
}

// Record the resolution for the parameter/argument.
const argIndices = path.graph.subgraphToArgIndices.get(ctx.subgraphName);
assert(argIndices, () => `Expected to find arg indices for subgraph ${ctx.subgraphName}`);
const id = argIndices.get(ctx.coordinate);
assert(id !== undefined, () => `Expected to find arg index for ${ctx.coordinate}`);
contextMap.set(ctx.namedParameter, {

Check warning on line 2077 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L2073-L2077

Added lines #L2073 - L2077 were not covered by tests
selectionSet,
levelsInDataPath,
levelsInQueryPath,
inboundEdge,
pathTree: resolution.pathTree,
paramName: ctx.namedParameter,
id,
argType: ctx.argType
});

if (resolution.cost === -1 || totalCost === -1) {
totalCost = -1;
} else {
totalCost += resolution.cost;

Check warning on line 2091 in query-graphs-js/src/graphPath.ts

View check run for this annotation

Codecov / codecov/patch

query-graphs-js/src/graphPath.ts#L2089-L2091

Added lines #L2089 - L2091 were not covered by tests
}
}

// it's possible that we will need to create a new fetch group at this point, in which case we'll need to collect the keys
Expand Down