Skip to content

Commit

Permalink
use memoization when type merging with lists (#1728)
Browse files Browse the repository at this point in the history
  • Loading branch information
yaacovCR committed Jul 5, 2020
1 parent 6dc5e02 commit e69c95e
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 103 deletions.
80 changes: 80 additions & 0 deletions packages/delegate/src/getFieldsNotInSubschema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { GraphQLSchema, FieldNode, GraphQLObjectType, GraphQLResolveInfo } from 'graphql';

import { collectFields, GraphQLExecutionContext } from '@graphql-tools/utils';
import { isSubschemaConfig } from './Subschema';
import { MergedTypeInfo, SubschemaConfig, StitchingInfo } from './types';
import { memoize3and1 } from './memoize';

function collectSubFields(info: GraphQLResolveInfo, typeName: string): Record<string, Array<FieldNode>> {
let subFieldNodes: Record<string, Array<FieldNode>> = Object.create(null);
const visitedFragmentNames = Object.create(null);

const type = info.schema.getType(typeName) as GraphQLObjectType;
const partialExecutionContext = ({
schema: info.schema,
variableValues: info.variableValues,
fragments: info.fragments,
} as unknown) as GraphQLExecutionContext;

info.fieldNodes.forEach(fieldNode => {
subFieldNodes = collectFields(
partialExecutionContext,
type,
fieldNode.selectionSet,
subFieldNodes,
visitedFragmentNames
);
});

const stitchingInfo = info.schema.extensions.stitchingInfo as StitchingInfo;
const selectionSetsByType = stitchingInfo.selectionSetsByType;
const selectionSetsByField = stitchingInfo.selectionSetsByField;

Object.keys(subFieldNodes).forEach(responseName => {
const fieldName = subFieldNodes[responseName][0].name.value;
const typeSelectionSet = selectionSetsByType[typeName];
if (typeSelectionSet != null) {
subFieldNodes = collectFields(
partialExecutionContext,
type,
typeSelectionSet,
subFieldNodes,
visitedFragmentNames
);
}
const fieldSelectionSet = selectionSetsByField?.[typeName]?.[fieldName];
if (fieldSelectionSet != null) {
subFieldNodes = collectFields(
partialExecutionContext,
type,
fieldSelectionSet,
subFieldNodes,
visitedFragmentNames
);
}
});

return subFieldNodes;
}

export const getFieldsNotInSubschema = memoize3and1(function (
info: GraphQLResolveInfo,
subschema: GraphQLSchema | SubschemaConfig,
mergedTypeInfo: MergedTypeInfo,
typeName: string
): Array<FieldNode> {
const typeMap = isSubschemaConfig(subschema) ? mergedTypeInfo.typeMaps.get(subschema) : subschema.getTypeMap();
const fields = (typeMap[typeName] as GraphQLObjectType).getFields();

const subFieldNodes = collectSubFields(info, typeName);

let fieldsNotInSchema: Array<FieldNode> = [];
Object.keys(subFieldNodes).forEach(responseName => {
const fieldName = subFieldNodes[responseName][0].name.value;
if (!(fieldName in fields)) {
fieldsNotInSchema = fieldsNotInSchema.concat(subFieldNodes[responseName]);
}
});

return fieldsNotInSchema;
});
137 changes: 137 additions & 0 deletions packages/delegate/src/memoize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
export function memoize3and1<
T1 extends Record<string, any>,
T2 extends Record<string, any>,
T3 extends Record<string, any>,
T4 extends string,
R extends any
>(fn: (A1: T1, A2: T2, A3: T3, A4: T4) => R): (A1: T1, A2: T2, A3: T3, A4: T4) => R {
let cache1: WeakMap<T1, WeakMap<T2, WeakMap<T3, Record<string, R>>>>;

function memoized(a1: T1, a2: T2, a3: T3, a4: T4) {
if (!cache1) {
cache1 = new WeakMap();
const cache2: WeakMap<T2, WeakMap<T3, Record<T4, R>>> = new WeakMap();
cache1.set(a1, cache2);
const cache3: WeakMap<T3, Record<T4, R>> = new WeakMap();
cache2.set(a2, cache3);
const cache4 = Object.create(null);
cache3.set(a3, cache4);
const newValue = fn(a1, a2, a3, a4);
cache4[a4] = newValue;
return newValue;
}

let cache2 = cache1.get(a1);
if (!cache2) {
cache2 = new WeakMap();
cache1.set(a1, cache2);
const cache3: WeakMap<T3, Record<T4, R>> = new WeakMap();
cache2.set(a2, cache3);
const cache4 = Object.create(null);
cache3.set(a3, cache4);
const newValue = fn(a1, a2, a3, a4);
cache4[a4] = newValue;
return newValue;
}

let cache3 = cache2.get(a2);
if (!cache3) {
cache3 = new WeakMap();
cache2.set(a2, cache3);
const cache4 = Object.create(null);
cache3.set(a3, cache4);
const newValue = fn(a1, a2, a3, a4);
cache4[a4] = newValue;
return newValue;
}

let cache4 = cache3.get(a3);
if (!cache4) {
cache4 = Object.create(null);
cache3.set(a3, cache4);
const newValue = fn(a1, a2, a3, a4);
cache4[a4] = newValue;
return newValue;
}

const cachedValue = cache4[a4];
if (cachedValue === undefined) {
const newValue = fn(a1, a2, a3, a4);
cache4[a4] = newValue;
return newValue;
}

return cachedValue;
}

return memoized;
}

export function memoize4<
T1 extends Record<string, any>,
T2 extends Record<string, any>,
T3 extends Record<string, any>,
T4 extends Record<string, any>,
R extends any
>(fn: (A1: T1, A2: T2, A3: T3, A4: T4) => R): (A1: T1, A2: T2, A3: T3, A4: T4) => R {
let cache1: WeakMap<T1, WeakMap<T2, WeakMap<T3, WeakMap<T4, R>>>>;

function memoized(a1: T1, a2: T2, a3: T3, a4: T4) {
if (!cache1) {
cache1 = new WeakMap();
const cache2: WeakMap<T2, WeakMap<T3, WeakMap<T4, R>>> = new WeakMap();
cache1.set(a1, cache2);
const cache3: WeakMap<T3, WeakMap<T4, R>> = new WeakMap();
cache2.set(a2, cache3);
const cache4: WeakMap<T4, R> = new WeakMap();
cache3.set(a3, cache4);
const newValue = fn(a1, a2, a3, a4);
cache4.set(a4, newValue);
return newValue;
}

let cache2 = cache1.get(a1);
if (!cache2) {
cache2 = new WeakMap();
cache1.set(a1, cache2);
const cache3: WeakMap<T3, WeakMap<T4, R>> = new WeakMap();
cache2.set(a2, cache3);
const cache4: WeakMap<T4, R> = new WeakMap();
cache3.set(a3, cache4);
const newValue = fn(a1, a2, a3, a4);
cache4.set(a4, newValue);
return newValue;
}

let cache3 = cache2.get(a2);
if (!cache3) {
cache3 = new WeakMap();
cache2.set(a2, cache3);
const cache4: WeakMap<T4, R> = new WeakMap();
cache3.set(a3, cache4);
const newValue = fn(a1, a2, a3, a4);
cache4.set(a4, newValue);
return newValue;
}

let cache4 = cache3.get(a3);
if (!cache4) {
cache4 = new WeakMap();
cache3.set(a3, cache4);
const newValue = fn(a1, a2, a3, a4);
cache4.set(a4, newValue);
return newValue;
}

const cachedValue = cache4.get(a4);
if (cachedValue === undefined) {
const newValue = fn(a1, a2, a3, a4);
cache4.set(a4, newValue);
return newValue;
}

return cachedValue;
}

return memoized;
}
37 changes: 20 additions & 17 deletions packages/delegate/src/mergeFields.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { FieldNode, SelectionNode, Kind, GraphQLResolveInfo } from 'graphql';
import { FieldNode, SelectionNode, Kind, GraphQLResolveInfo, SelectionSetNode } from 'graphql';

import { mergeProxiedResults } from './proxiedResult';
import { MergedTypeInfo, SubschemaConfig } from './types';
import { memoize4 } from './memoize';

function buildDelegationPlan(
const buildDelegationPlan = memoize4(function (
mergedTypeInfo: MergedTypeInfo,
fieldNodes: Array<FieldNode>,
sourceSubschemas: Array<SubschemaConfig>,
targetSubschemas: Array<SubschemaConfig>
): {
delegationMap: Map<SubschemaConfig, Array<SelectionNode>>;
delegationMap: Map<SubschemaConfig, SelectionSetNode>;
unproxiableFieldNodes: Array<FieldNode>;
proxiableSubschemas: Array<SubschemaConfig>;
nonProxiableSubschemas: Array<SubschemaConfig>;
Expand Down Expand Up @@ -87,13 +88,22 @@ function buildDelegationPlan(
}
});

const finalDelegationMap: Map<SubschemaConfig, SelectionSetNode> = new Map();

delegationMap.forEach((selections, subschema) => {
finalDelegationMap.set(subschema, {
kind: Kind.SELECTION_SET,
selections,
});
});

return {
delegationMap,
delegationMap: finalDelegationMap,
unproxiableFieldNodes,
proxiableSubschemas,
nonProxiableSubschemas,
};
}
});

export function mergeFields(
mergedTypeInfo: MergedTypeInfo,
Expand All @@ -120,22 +130,15 @@ export function mergeFields(
return object;
}

let containsPromises = false;
const maybePromises: Promise<any> | any = [];
delegationMap.forEach((selections: Array<SelectionNode>, s: SubschemaConfig) => {
const maybePromise = s.merge[typeName].resolve(object, context, info, s, {
kind: Kind.SELECTION_SET,
selections,
});
delegationMap.forEach((selectionSet: SelectionSetNode, s: SubschemaConfig) => {
const maybePromise = s.merge[typeName].resolve(object, context, info, s, selectionSet);
maybePromises.push(maybePromise);
});

let containsPromises = false;
for (const maybePromise of maybePromises) {
if (maybePromise instanceof Promise) {
if (!containsPromises && maybePromise instanceof Promise) {
containsPromises = true;
break;
}
}
});

return containsPromises
? Promise.all(maybePromises).then(results =>
Expand Down
93 changes: 7 additions & 86 deletions packages/delegate/src/results/handleObject.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import {
GraphQLCompositeType,
GraphQLError,
GraphQLSchema,
isAbstractType,
FieldNode,
GraphQLObjectType,
GraphQLResolveInfo,
} from 'graphql';
import { GraphQLCompositeType, GraphQLError, GraphQLSchema, isAbstractType, GraphQLResolveInfo } from 'graphql';

import { collectFields, GraphQLExecutionContext, setErrors, slicedError } from '@graphql-tools/utils';
import { setObjectSubschema, isSubschemaConfig } from '../Subschema';
import { setErrors, slicedError } from '@graphql-tools/utils';

import { SubschemaConfig } from '../types';

import { setObjectSubschema } from '../Subschema';
import { mergeFields } from '../mergeFields';
import { MergedTypeInfo, SubschemaConfig, StitchingInfo } from '../types';
import { getFieldsNotInSubschema } from '../getFieldsNotInSubschema';

export function handleObject(
type: GraphQLCompositeType,
Expand Down Expand Up @@ -65,77 +60,3 @@ export function handleObject(
info
);
}

function collectSubFields(info: GraphQLResolveInfo, typeName: string): Record<string, Array<FieldNode>> {
let subFieldNodes: Record<string, Array<FieldNode>> = Object.create(null);
const visitedFragmentNames = Object.create(null);

const type = info.schema.getType(typeName) as GraphQLObjectType;
const partialExecutionContext = ({
schema: info.schema,
variableValues: info.variableValues,
fragments: info.fragments,
} as unknown) as GraphQLExecutionContext;

info.fieldNodes.forEach(fieldNode => {
subFieldNodes = collectFields(
partialExecutionContext,
type,
fieldNode.selectionSet,
subFieldNodes,
visitedFragmentNames
);
});

const stitchingInfo = info.schema.extensions.stitchingInfo as StitchingInfo;
const selectionSetsByType = stitchingInfo.selectionSetsByType;
const selectionSetsByField = stitchingInfo.selectionSetsByField;

Object.keys(subFieldNodes).forEach(responseName => {
const fieldName = subFieldNodes[responseName][0].name.value;
const typeSelectionSet = selectionSetsByType[typeName];
if (typeSelectionSet != null) {
subFieldNodes = collectFields(
partialExecutionContext,
type,
typeSelectionSet,
subFieldNodes,
visitedFragmentNames
);
}
const fieldSelectionSet = selectionSetsByField?.[typeName]?.[fieldName];
if (fieldSelectionSet != null) {
subFieldNodes = collectFields(
partialExecutionContext,
type,
fieldSelectionSet,
subFieldNodes,
visitedFragmentNames
);
}
});

return subFieldNodes;
}

function getFieldsNotInSubschema(
info: GraphQLResolveInfo,
subschema: GraphQLSchema | SubschemaConfig,
mergedTypeInfo: MergedTypeInfo,
typeName: string
): Array<FieldNode> {
const typeMap = isSubschemaConfig(subschema) ? mergedTypeInfo.typeMaps.get(subschema) : subschema.getTypeMap();
const fields = (typeMap[typeName] as GraphQLObjectType).getFields();

const subFieldNodes = collectSubFields(info, typeName);

let fieldsNotInSchema: Array<FieldNode> = [];
Object.keys(subFieldNodes).forEach(responseName => {
const fieldName = subFieldNodes[responseName][0].name.value;
if (!(fieldName in fields)) {
fieldsNotInSchema = fieldsNotInSchema.concat(subFieldNodes[responseName]);
}
});

return fieldsNotInSchema;
}

0 comments on commit e69c95e

Please sign in to comment.