Skip to content

Commit 2f9cfef

Browse files
committed
Merge remote-tracking branch 'origin/dev' into update-copyright
2 parents 5863b82 + abfc26b commit 2f9cfef

File tree

4 files changed

+293
-130
lines changed

4 files changed

+293
-130
lines changed

.changeset/all-deer-talk.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@neo4j/graphql": patch
3+
---
4+
5+
Change algorithm for query complexity calculation

packages/graphql/src/classes/ComplexityEstimatorHelper.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ import {
1010
GraphQLObjectType,
1111
Kind,
1212
} from "graphql";
13-
import type { ComplexityEstimator } from "graphql-query-complexity";
14-
import { fieldExtensionsEstimator, simpleEstimator } from "graphql-query-complexity";
1513

1614
export class ComplexityEstimatorHelper {
1715
private objectTypeNameToFieldNamesMapForComplexityExtensions: Map<string, string[]>;
@@ -54,17 +52,17 @@ export class ComplexityEstimatorHelper {
5452
...f,
5553
extensions: {
5654
// COMPLEXITY FORMULA
57-
// c = c_child + lvl_limit * c_field, where
55+
// c = c_child * lvl_limit + c_field, where
5856
// c_field = 1
5957
// lvl_limit defaults to 1
6058
// c_child comes from simpleEstimator
6159
complexity: ({ childComplexity, args }) => {
6260
const fieldDefaultComplexity = 1;
6361
const defaultLimitIfNotProvided = 1;
6462
if (args.limit ?? args.first) {
65-
return childComplexity + (args.limit ?? args.first) * fieldDefaultComplexity;
63+
return childComplexity * (args.limit ?? args.first) + fieldDefaultComplexity;
6664
}
67-
return childComplexity + defaultLimitIfNotProvided;
65+
return childComplexity * defaultLimitIfNotProvided + fieldDefaultComplexity;
6866
},
6967
},
7068
};
@@ -89,13 +87,6 @@ export class ComplexityEstimatorHelper {
8987
});
9088
}
9189

92-
public getComplexityEstimators(): ComplexityEstimator[] {
93-
if (!this.useComplexityEstimators) {
94-
return [];
95-
}
96-
return [fieldExtensionsEstimator(), simpleEstimator({ defaultComplexity: 1 })];
97-
}
98-
9990
private getFieldsForParentTypeName(parentObjectTypeNameOrInterfaceTypeName: string): string[] {
10091
return (
10192
this.objectTypeNameToFieldNamesMapForComplexityExtensions.get(parentObjectTypeNameOrInterfaceTypeName) || []

packages/graphql/tests/e2e/complexity-estimators.e2e.test.ts

Lines changed: 105 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,24 @@ describe("limitRequired enabled", () => {
2121
}
2222
type Movie implements Production @node {
2323
title: String
24-
actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN)
24+
actors: [Actor!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN)
2525
directors: [Actor!]! @relationship(type: "DIRECTED", direction: IN)
2626
}
27+
type Series implements Production @node {
28+
title: String
29+
actors: [Actor!]! @relationship(type: "ACTED_IN", properties: "ActedInSeries", direction: IN)
30+
}
2731
type Actor @node {
2832
name: String
33+
movies: [Movie!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: OUT)
34+
}
35+
type ActedIn @relationshipProperties {
36+
roles: [String!]!
37+
}
38+
type ActedInSeries @relationshipProperties {
39+
roles: [String!]!
40+
episodes: [Int!]!
41+
year: Int
2942
}
3043
`;
3144

@@ -53,12 +66,12 @@ describe("limitRequired enabled", () => {
5366
await server.close();
5467
});
5568

56-
test("movies result", async () => {
69+
test("movies result", async () => {
5770
const complexity = await server.computeQueryComplexity(
5871
parse(`
5972
query {
60-
movies(limit: 9) {
61-
title
73+
movies(limit: 9) { # 9 * 1 + 1
74+
title # 1
6275
}
6376
}
6477
`)
@@ -70,51 +83,102 @@ describe("limitRequired enabled", () => {
7083
const complexity = await server.computeQueryComplexity(
7184
parse(`
7285
query {
73-
movies(limit: 5) {
74-
title
75-
actors(limit: 6) {
76-
name
86+
movies(limit: 5) { # (7 + 1) * 5 + 1 = 41
87+
title # 1
88+
actors(limit: 6) { # 6 * 1 + 1 = 7
89+
name # 1
7790
}
7891
}
7992
}
8093
`)
8194
);
82-
expect(complexity).toBe(13);
95+
expect(complexity).toBe(41);
8396
});
8497

8598
test("movies with actors and directors result", async () => {
8699
const complexity = await server.computeQueryComplexity(
87100
parse(`
88101
query {
89-
movies(limit: 5) {
90-
title
91-
actors(limit: 10) {
92-
name
102+
movies(limit: 5) { # (5 + 11 + 1) * 5 + 1 = 86
103+
title # 1
104+
actors(limit: 10) { # (10 * 1) + 1 = 11
105+
name # 1
93106
}
94-
directors(limit: 4) {
95-
name
107+
directors(limit: 4) { # (4 * 1) + 1 = 5
108+
name # 1
96109
}
97110
}
98111
}
99112
`)
100113
);
101-
expect(complexity).toBe(22);
114+
expect(complexity).toBe(86);
115+
});
116+
117+
test("movies with actors with nested movies result", async () => {
118+
const complexity = await server.computeQueryComplexity(
119+
parse(`
120+
query {
121+
movies(limit: 5) { # (31 + 1) * 5 + 1 = 161
122+
title # 1
123+
actors(limit: 6) { # (4 + 1) * 6 + 1 = 31
124+
name # 1
125+
movies(limit: 3) { # (3 * 1) + 1 = 4
126+
title # 1
127+
}
128+
}
129+
}
130+
}
131+
`)
132+
);
133+
expect(complexity).toBe(161);
102134
});
103135

104136
test("productions with actors and directors result", async () => {
105137
const complexity = await server.computeQueryComplexity(
106138
parse(`
107139
query {
108-
productions(limit: 5) {
109-
title
110-
actors(limit: 10) {
111-
name
140+
productions(limit: 5) { # (11 + 1) * 5) + 1 = 61
141+
title # 1
142+
actors(limit: 10) { # (10 * 1) + 1 = 11
143+
name # 1
112144
}
113145
}
114146
}
115147
`)
116148
);
117-
expect(complexity).toBe(17);
149+
expect(complexity).toBe(61);
150+
});
151+
test("connection query with fragments", async () => {
152+
const document = parse(`
153+
query {
154+
productionsConnection(first: 10, sort: [{ title: ASC }]) { # (12 + 2x) * 10 + 1 = 121 + 20x
155+
edges { # 12 + 2x
156+
node { # 9 + 2x + 1 + 1 = 11 + 2x
157+
title # 1
158+
actorsConnection(first: 2, sort: { node: { name: ASC } }) { # (4+x)*2+1 = 9 + 2x
159+
edges { # 3 + x + 1 = 4 + x
160+
node { # 2
161+
name # 1
162+
}
163+
properties { # 1 + x; x = max nr of properties in below fragments
164+
... on ActedIn {
165+
roles
166+
}
167+
... on ActedInSeries {
168+
roles
169+
episodes
170+
year
171+
}
172+
}
173+
}
174+
}
175+
}
176+
}
177+
}
178+
}
179+
`);
180+
const complexity = await server.computeQueryComplexity(document);
181+
expect(complexity).toBe(181);
118182
});
119183
});
120184

@@ -136,6 +200,7 @@ describe("limitRequired not enabled", () => {
136200
}
137201
type Actor @node {
138202
name: String
203+
movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT)
139204
}
140205
`;
141206

@@ -179,6 +244,25 @@ describe("limitRequired not enabled", () => {
179244
expect(complexity).toBe(6);
180245
});
181246

247+
test("movies with actors with nested movies result", async () => {
248+
const complexity = await server.computeQueryComplexity(
249+
parse(`
250+
query {
251+
movies {
252+
title
253+
actors {
254+
name
255+
movies {
256+
title
257+
}
258+
}
259+
}
260+
}
261+
`)
262+
);
263+
expect(complexity).toBe(6);
264+
});
265+
182266
test("productions with actors and directors result - no limit args", async () => {
183267
const complexity = await server.computeQueryComplexity(
184268
parse(`

0 commit comments

Comments
 (0)