Skip to content

Commit 8f0091d

Browse files
authored
Merge pull request #6555 from neo4j/optimize-total-count
Optimize total count
2 parents c220327 + 1b451b9 commit 8f0091d

File tree

70 files changed

+243
-374
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+243
-374
lines changed

.changeset/chatty-cobras-guess.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
"@neo4j/graphql": patch
3+
---
4+
5+
Optimize connection queries without `totalCount` or `pageInfo` such as:
6+
7+
```graphql
8+
query {
9+
moviesConnection(first: 20, sort: [{ title: ASC }]) {
10+
edges {
11+
node {
12+
title
13+
}
14+
}
15+
}
16+
}
17+
```
18+
19+
Will no longer calculate `totalCount` in the generated Cypher

packages/graphql/src/translate/queryAST/ast/operations/ConnectionReadOperation.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ export class ConnectionReadOperation extends Operation {
5454

5555
protected selection: EntitySelection;
5656

57+
private hasTotalCount = false;
58+
5759
constructor({
5860
relationship,
5961
target,
@@ -69,6 +71,10 @@ export class ConnectionReadOperation extends Operation {
6971
this.selection = selection;
7072
}
7173

74+
public setHasTotalCount(value: boolean): void {
75+
this.hasTotalCount = value;
76+
}
77+
7278
public setNodeFields(fields: Field[]) {
7379
this.nodeFields = fields;
7480
}
@@ -131,11 +137,12 @@ export class ConnectionReadOperation extends Operation {
131137

132138
const extraColumnsVariables = extraColumns.map((c) => c[1]);
133139

134-
return new Cypher.With([Cypher.collect(nodeAndRelationshipMap), edgesVar], ...extraColumns).with(
135-
edgesVar,
136-
[Cypher.size(edgesVar), totalCount],
137-
...extraColumnsVariables
138-
);
140+
const withClause = new Cypher.With([Cypher.collect(nodeAndRelationshipMap), edgesVar], ...extraColumns);
141+
142+
if (this.hasTotalCount) {
143+
return withClause.with(edgesVar, [Cypher.size(edgesVar), totalCount], ...extraColumnsVariables);
144+
}
145+
return withClause;
139146
}
140147

141148
public transpile(context: QueryASTContext): OperationTranspileResult {
@@ -228,8 +235,11 @@ export class ConnectionReadOperation extends Operation {
228235
projectionMap.set("edges", edgesProjectionVar);
229236
}
230237

238+
if (this.hasTotalCount) {
239+
projectionMap.set("totalCount", totalCount);
240+
}
241+
231242
projectionMap.set({
232-
totalCount: totalCount,
233243
...aggregationProjection,
234244
});
235245

@@ -246,10 +256,12 @@ export class ConnectionReadOperation extends Operation {
246256
);
247257

248258
if (aggregationSubqueries.length > 0) {
249-
connectionClauses = new Cypher.Call( // NOTE: this call is only needed when aggregate is used
250-
Cypher.utils.concat(connectionClauses, new Cypher.Return(edgesProjectionVar, totalCount)),
251-
"*"
252-
);
259+
const returnClause = new Cypher.Return(edgesProjectionVar);
260+
if (this.hasTotalCount) {
261+
returnClause.addColumns(totalCount);
262+
}
263+
264+
connectionClauses = new Cypher.Call(Cypher.utils.concat(connectionClauses, returnClause), "*"); // NOTE: this call is only needed when aggregate is used
253265
}
254266

255267
return {

packages/graphql/src/translate/queryAST/factory/Operations/ConnectionFactory.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,11 @@ export class ConnectionFactory {
219219
totalCountEdgeField = totalCount;
220220
pageInfoEdgeField = pageInfo;
221221
}
222-
const operation = new ConnectionReadOperation({ relationship, target, selection });
222+
const operation = new ConnectionReadOperation({
223+
relationship,
224+
target,
225+
selection,
226+
});
223227

224228
if (Object.keys(resolveTreeEdgeFields).length === 0 && !totalCountEdgeField && !pageInfoEdgeField) {
225229
operation.skipConnection = true;
@@ -452,6 +456,15 @@ export class ConnectionFactory {
452456

453457
const nodeFieldsRaw = findFieldsByNameInFieldsByTypeNameField(resolveTreeEdgeFields, "node");
454458
const propertiesFieldsRaw = findFieldsByNameInFieldsByTypeNameField(resolveTreeEdgeFields, "properties");
459+
460+
const { totalCount, pageInfo } = this.parseConnectionFields({
461+
entityOrRel,
462+
target,
463+
resolveTree,
464+
});
465+
466+
operation.setHasTotalCount(Boolean(totalCount || pageInfo));
467+
455468
this.hydrateConnectionOperationsASTWithSort({
456469
entityOrRel,
457470
resolveTree,

packages/graphql/tests/tck/aggregations/where/authorization-with-aggregation-filter.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,12 @@ describe("Authorization with aggregation filter rule", () => {
103103
WITH *
104104
WHERE ($isAuthenticated = true AND var3 = true)
105105
WITH collect({ node: this0 }) AS edges
106-
WITH edges, size(edges) AS totalCount
107106
CALL (edges) {
108107
UNWIND edges AS edge
109108
WITH edge.node AS this0
110109
RETURN collect({ node: { content: this0.content, __resolveType: \\"Post\\" } }) AS var4
111110
}
112-
RETURN { edges: var4, totalCount: totalCount } AS this"
111+
RETURN { edges: var4 } AS this"
113112
`);
114113

115114
expect(formatParams(result.params)).toMatchInlineSnapshot(`

packages/graphql/tests/tck/array-methods.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -514,13 +514,12 @@ describe("Arrays Methods", () => {
514514
CALL (this) {
515515
MATCH (this)-[update_this3:ACTED_IN]->(update_this4:Movie)
516516
WITH collect({ node: update_this4, relationship: update_this3 }) AS edges
517-
WITH edges, size(edges) AS totalCount
518517
CALL (edges) {
519518
UNWIND edges AS edge
520519
WITH edge.node AS update_this4, edge.relationship AS update_this3
521520
RETURN collect({ properties: { pay: update_this3.pay, __resolveType: \\"ActedIn\\" }, node: { __id: id(update_this4), __resolveType: \\"Movie\\" } }) AS update_var5
522521
}
523-
RETURN { edges: update_var5, totalCount: totalCount } AS update_var6
522+
RETURN { edges: update_var5 } AS update_var6
524523
}
525524
RETURN collect(DISTINCT this { .name, actedIn: update_var2, actedInConnection: update_var6 }) AS data"
526525
`);
@@ -615,13 +614,12 @@ describe("Arrays Methods", () => {
615614
CALL (this) {
616615
MATCH (this)-[update_this3:ACTED_IN]->(update_this4:Movie)
617616
WITH collect({ node: update_this4, relationship: update_this3 }) AS edges
618-
WITH edges, size(edges) AS totalCount
619617
CALL (edges) {
620618
UNWIND edges AS edge
621619
WITH edge.node AS update_this4, edge.relationship AS update_this3
622620
RETURN collect({ properties: { pay: update_this3.pay, __resolveType: \\"ActedIn\\" }, node: { __id: id(update_this4), __resolveType: \\"Movie\\" } }) AS update_var5
623621
}
624-
RETURN { edges: update_var5, totalCount: totalCount } AS update_var6
622+
RETURN { edges: update_var5 } AS update_var6
625623
}
626624
RETURN collect(DISTINCT this { .name, actedIn: update_var2, actedInConnection: update_var6 }) AS data"
627625
`);

packages/graphql/tests/tck/connections/alias.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,25 +113,23 @@ describe("Connections Alias", () => {
113113
MATCH (this)<-[this0:ACTED_IN]-(this1:Actor)
114114
WHERE this1.name = $param1
115115
WITH collect({ node: this1, relationship: this0 }) AS edges
116-
WITH edges, size(edges) AS totalCount
117116
CALL (edges) {
118117
UNWIND edges AS edge
119118
WITH edge.node AS this1, edge.relationship AS this0
120119
RETURN collect({ properties: { screenTime: this0.screenTime, __resolveType: \\"ActedIn\\" }, node: { name: this1.name, __resolveType: \\"Actor\\" } }) AS var2
121120
}
122-
RETURN { edges: var2, totalCount: totalCount } AS var3
121+
RETURN { edges: var2 } AS var3
123122
}
124123
CALL (this) {
125124
MATCH (this)<-[this4:ACTED_IN]-(this5:Actor)
126125
WHERE this5.name = $param2
127126
WITH collect({ node: this5, relationship: this4 }) AS edges
128-
WITH edges, size(edges) AS totalCount
129127
CALL (edges) {
130128
UNWIND edges AS edge
131129
WITH edge.node AS this5, edge.relationship AS this4
132130
RETURN collect({ properties: { screenTime: this4.screenTime, __resolveType: \\"ActedIn\\" }, node: { name: this5.name, __resolveType: \\"Actor\\" } }) AS var6
133131
}
134-
RETURN { edges: var6, totalCount: totalCount } AS var7
132+
RETURN { edges: var6 } AS var7
135133
}
136134
RETURN this { .title, hanks: var3, jenny: var7 } AS this"
137135
`);

packages/graphql/tests/tck/connections/filtering/composite.test.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,12 @@ describe("Cypher -> Connections -> Filtering -> Composite", () => {
8282
MATCH (this)<-[this0:ACTED_IN]-(this1:Actor)
8383
WHERE ((this1.firstName = $param1 AND this1.lastName = $param2) AND (this0.screenTime > $param3 AND this0.screenTime < $param4))
8484
WITH collect({ node: this1, relationship: this0 }) AS edges
85-
WITH edges, size(edges) AS totalCount
8685
CALL (edges) {
8786
UNWIND edges AS edge
8887
WITH edge.node AS this1, edge.relationship AS this0
8988
RETURN collect({ properties: { screenTime: this0.screenTime, __resolveType: \\"ActedIn\\" }, node: { firstName: this1.firstName, lastName: this1.lastName, __resolveType: \\"Actor\\" } }) AS var2
9089
}
91-
RETURN { edges: var2, totalCount: totalCount } AS var3
90+
RETURN { edges: var2 } AS var3
9291
}
9392
RETURN this { .title, actorsConnection: var3 } AS this"
9493
`);
@@ -145,13 +144,12 @@ describe("Cypher -> Connections -> Filtering -> Composite", () => {
145144
MATCH (this)<-[this0:ACTED_IN]-(this1:Actor)
146145
WHERE (NOT (this1.firstName = $param1 AND this1.lastName = $param2) AND NOT (this0.screenTime > $param3 AND this0.screenTime < $param4))
147146
WITH collect({ node: this1, relationship: this0 }) AS edges
148-
WITH edges, size(edges) AS totalCount
149147
CALL (edges) {
150148
UNWIND edges AS edge
151149
WITH edge.node AS this1, edge.relationship AS this0
152150
RETURN collect({ properties: { screenTime: this0.screenTime, __resolveType: \\"ActedIn\\" }, node: { firstName: this1.firstName, lastName: this1.lastName, __resolveType: \\"Actor\\" } }) AS var2
153151
}
154-
RETURN { edges: var2, totalCount: totalCount } AS var3
152+
RETURN { edges: var2 } AS var3
155153
}
156154
RETURN this { .title, actorsConnection: var3 } AS this"
157155
`);
@@ -210,13 +208,12 @@ describe("Cypher -> Connections -> Filtering -> Composite", () => {
210208
MATCH (this)<-[this0:ACTED_IN]-(this1:Actor)
211209
WHERE ((this1.firstName = $param1 AND this1.lastName = $param2) OR (this0.screenTime > $param3 AND this0.screenTime < $param4))
212210
WITH collect({ node: this1, relationship: this0 }) AS edges
213-
WITH edges, size(edges) AS totalCount
214211
CALL (edges) {
215212
UNWIND edges AS edge
216213
WITH edge.node AS this1, edge.relationship AS this0
217214
RETURN collect({ properties: { screenTime: this0.screenTime, __resolveType: \\"ActedIn\\" }, node: { firstName: this1.firstName, lastName: this1.lastName, __resolveType: \\"Actor\\" } }) AS var2
218215
}
219-
RETURN { edges: var2, totalCount: totalCount } AS var3
216+
RETURN { edges: var2 } AS var3
220217
}
221218
RETURN this { .title, actorsConnection: var3 } AS this"
222219
`);
@@ -277,13 +274,12 @@ describe("Cypher -> Connections -> Filtering -> Composite", () => {
277274
MATCH (this)<-[this0:ACTED_IN]-(this1:Actor)
278275
WHERE NOT ((this1.firstName = $param1 AND this1.lastName = $param2) OR (this0.screenTime > $param3 AND this0.screenTime < $param4))
279276
WITH collect({ node: this1, relationship: this0 }) AS edges
280-
WITH edges, size(edges) AS totalCount
281277
CALL (edges) {
282278
UNWIND edges AS edge
283279
WITH edge.node AS this1, edge.relationship AS this0
284280
RETURN collect({ properties: { screenTime: this0.screenTime, __resolveType: \\"ActedIn\\" }, node: { firstName: this1.firstName, lastName: this1.lastName, __resolveType: \\"Actor\\" } }) AS var2
285281
}
286-
RETURN { edges: var2, totalCount: totalCount } AS var3
282+
RETURN { edges: var2 } AS var3
287283
}
288284
RETURN this { .title, actorsConnection: var3 } AS this"
289285
`);
@@ -353,13 +349,12 @@ describe("Cypher -> Connections -> Filtering -> Composite", () => {
353349
MATCH (this)<-[this0:ACTED_IN]-(this1:Actor)
354350
WHERE NOT (((this1.firstName = $param1 AND this1.lastName = $param2) OR (this0.screenTime > $param3 AND this0.screenTime < $param4)) AND (this1.firstName = $param5 AND this1.lastName = $param6))
355351
WITH collect({ node: this1, relationship: this0 }) AS edges
356-
WITH edges, size(edges) AS totalCount
357352
CALL (edges) {
358353
UNWIND edges AS edge
359354
WITH edge.node AS this1, edge.relationship AS this0
360355
RETURN collect({ properties: { screenTime: this0.screenTime, __resolveType: \\"ActedIn\\" }, node: { firstName: this1.firstName, lastName: this1.lastName, __resolveType: \\"Actor\\" } }) AS var2
361356
}
362-
RETURN { edges: var2, totalCount: totalCount } AS var3
357+
RETURN { edges: var2 } AS var3
363358
}
364359
RETURN this { .title, actorsConnection: var3 } AS this"
365360
`);

packages/graphql/tests/tck/connections/filtering/node/and.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,12 @@ describe("Cypher -> Connections -> Filtering -> Node -> AND", () => {
7878
MATCH (this)<-[this0:ACTED_IN]-(this1:Actor)
7979
WHERE (this1.firstName = $param0 AND this1.lastName = $param1)
8080
WITH collect({ node: this1, relationship: this0 }) AS edges
81-
WITH edges, size(edges) AS totalCount
8281
CALL (edges) {
8382
UNWIND edges AS edge
8483
WITH edge.node AS this1, edge.relationship AS this0
8584
RETURN collect({ properties: { screenTime: this0.screenTime, __resolveType: \\"ActedIn\\" }, node: { firstName: this1.firstName, lastName: this1.lastName, __resolveType: \\"Actor\\" } }) AS var2
8685
}
87-
RETURN { edges: var2, totalCount: totalCount } AS var3
86+
RETURN { edges: var2 } AS var3
8887
}
8988
RETURN this { .title, actorsConnection: var3 } AS this"
9089
`);
@@ -126,13 +125,12 @@ describe("Cypher -> Connections -> Filtering -> Node -> AND", () => {
126125
MATCH (this)<-[this0:ACTED_IN]-(this1:Actor)
127126
WHERE NOT (this1.firstName = $param0)
128127
WITH collect({ node: this1, relationship: this0 }) AS edges
129-
WITH edges, size(edges) AS totalCount
130128
CALL (edges) {
131129
UNWIND edges AS edge
132130
WITH edge.node AS this1, edge.relationship AS this0
133131
RETURN collect({ properties: { screenTime: this0.screenTime, __resolveType: \\"ActedIn\\" }, node: { firstName: this1.firstName, lastName: this1.lastName, __resolveType: \\"Actor\\" } }) AS var2
134132
}
135-
RETURN { edges: var2, totalCount: totalCount } AS var3
133+
RETURN { edges: var2 } AS var3
136134
}
137135
RETURN this { .title, actorsConnection: var3 } AS this"
138136
`);

packages/graphql/tests/tck/connections/filtering/node/arrays.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,12 @@ describe("Cypher -> Connections -> Filtering -> Node -> Arrays", () => {
7575
MATCH (this)<-[this0:ACTED_IN]-(this1:Actor)
7676
WHERE this1.name IN $param0
7777
WITH collect({ node: this1, relationship: this0 }) AS edges
78-
WITH edges, size(edges) AS totalCount
7978
CALL (edges) {
8079
UNWIND edges AS edge
8180
WITH edge.node AS this1, edge.relationship AS this0
8281
RETURN collect({ properties: { screenTime: this0.screenTime, __resolveType: \\"ActedIn\\" }, node: { name: this1.name, __resolveType: \\"Actor\\" } }) AS var2
8382
}
84-
RETURN { edges: var2, totalCount: totalCount } AS var3
83+
RETURN { edges: var2 } AS var3
8584
}
8685
RETURN this { .title, actorsConnection: var3 } AS this"
8786
`);
@@ -125,13 +124,12 @@ describe("Cypher -> Connections -> Filtering -> Node -> Arrays", () => {
125124
MATCH (this)<-[this0:ACTED_IN]-(this1:Actor)
126125
WHERE $param0 IN this1.favouriteColours
127126
WITH collect({ node: this1, relationship: this0 }) AS edges
128-
WITH edges, size(edges) AS totalCount
129127
CALL (edges) {
130128
UNWIND edges AS edge
131129
WITH edge.node AS this1, edge.relationship AS this0
132130
RETURN collect({ properties: { screenTime: this0.screenTime, __resolveType: \\"ActedIn\\" }, node: { name: this1.name, favouriteColours: this1.favouriteColours, __resolveType: \\"Actor\\" } }) AS var2
133131
}
134-
RETURN { edges: var2, totalCount: totalCount } AS var3
132+
RETURN { edges: var2 } AS var3
135133
}
136134
RETURN this { .title, actorsConnection: var3 } AS this"
137135
`);

packages/graphql/tests/tck/connections/filtering/node/equality.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,12 @@ describe("Cypher -> Connections -> Filtering -> Node -> Equality", () => {
7474
MATCH (this)<-[this0:ACTED_IN]-(this1:Actor)
7575
WHERE this1.name = $param0
7676
WITH collect({ node: this1, relationship: this0 }) AS edges
77-
WITH edges, size(edges) AS totalCount
7877
CALL (edges) {
7978
UNWIND edges AS edge
8079
WITH edge.node AS this1, edge.relationship AS this0
8180
RETURN collect({ properties: { screenTime: this0.screenTime, __resolveType: \\"ActedIn\\" }, node: { name: this1.name, __resolveType: \\"Actor\\" } }) AS var2
8281
}
83-
RETURN { edges: var2, totalCount: totalCount } AS var3
82+
RETURN { edges: var2 } AS var3
8483
}
8584
RETURN this { .title, actorsConnection: var3 } AS this"
8685
`);
@@ -120,13 +119,12 @@ describe("Cypher -> Connections -> Filtering -> Node -> Equality", () => {
120119
MATCH (this)<-[this0:ACTED_IN]-(this1:Actor)
121120
WHERE NOT (this1.name = $param0)
122121
WITH collect({ node: this1, relationship: this0 }) AS edges
123-
WITH edges, size(edges) AS totalCount
124122
CALL (edges) {
125123
UNWIND edges AS edge
126124
WITH edge.node AS this1, edge.relationship AS this0
127125
RETURN collect({ properties: { screenTime: this0.screenTime, __resolveType: \\"ActedIn\\" }, node: { name: this1.name, __resolveType: \\"Actor\\" } }) AS var2
128126
}
129-
RETURN { edges: var2, totalCount: totalCount } AS var3
127+
RETURN { edges: var2 } AS var3
130128
}
131129
RETURN this { .title, actorsConnection: var3 } AS this"
132130
`);

0 commit comments

Comments
 (0)