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

[Feature Branch] Aggregation 6 #5945

Draft
wants to merge 53 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
c340f32
Add aggregation fields inside connection
angrykoala Jan 17, 2025
484c061
Fix aggregation aliases
angrykoala Jan 20, 2025
6cc7a09
update top level aggregation tests
angrykoala Jan 20, 2025
ef2d979
Add interface aggregations on top level
angrykoala Jan 21, 2025
c3e3416
Update tck tests with aggregate
angrykoala Jan 21, 2025
4e8ab68
Deprecate top level aggregation
angrykoala Jan 21, 2025
233dc74
Fix unit tests
angrykoala Jan 21, 2025
a6e9486
Add deprecation flag
angrykoala Jan 22, 2025
f0cc167
Cleanup of aggregations inside connections code
angrykoala Jan 22, 2025
5d36415
Test multiple aliased aggregations in the same connection
angrykoala Jan 22, 2025
2cb4148
Merge pull request #5944 from neo4j/aggregation-connections
angrykoala Jan 23, 2025
922f788
Merge branch 'dev' into aggregation-6
angrykoala Jan 24, 2025
f32e374
Merge branch 'dev' into aggregation-6
angrykoala Jan 27, 2025
d234105
Merge branch 'dev' into aggregation-6
angrykoala Jan 29, 2025
07ee3d1
Add nested aggregations inside connections in schema
angrykoala Jan 30, 2025
1fb4b68
Update schema tests with nested aggregate types
angrykoala Jan 30, 2025
5e448e1
nested aggregations in connection
angrykoala Jan 31, 2025
5c898e6
Fix aggregation on empty relationships
angrykoala Feb 5, 2025
953ba63
Fix subqueries with interface aggregations
angrykoala Feb 10, 2025
74f727e
Fix projection of aggregations in interfaces
angrykoala Feb 10, 2025
47f915e
Deprecate field aggregate fields in favor of nested connection aggreg…
angrykoala Feb 11, 2025
ee18210
Apply PR suggestions
angrykoala Feb 11, 2025
1540f5a
Merge pull request #5993 from neo4j/nested-aggregations-6
angrykoala Feb 11, 2025
932bfa7
Merge pull request #5999 from neo4j/nested-aggregations-deprecation
angrykoala Feb 11, 2025
31fe533
Merge branch 'dev' into aggregation-6
angrykoala Feb 11, 2025
9047101
Fix changelog messages
angrykoala Feb 11, 2025
d672232
disable-eslint on skipped tests in aggregations
angrykoala Feb 11, 2025
40759a5
WIP count aggregation
angrykoala Feb 12, 2025
613c665
Fix top level count with nested nodes fields
angrykoala Feb 13, 2025
de4c7be
Fix nested node count
angrykoala Feb 13, 2025
2952820
Count in nested aggregation with edges and nodes
angrykoala Feb 14, 2025
7f067a4
Update schema tests
angrykoala Feb 14, 2025
833d911
Fix auth on aggregations
angrykoala Feb 14, 2025
6def30d
Fix performance tests
angrykoala Feb 17, 2025
587ffbb
Add code review changes
angrykoala Feb 17, 2025
7a68d7a
Remove arrow function on numerical resolvers
angrykoala Feb 17, 2025
128051d
Merge pull request #6003 from neo4j/count-aggregation
angrykoala Feb 17, 2025
2243a37
Merge branch 'dev' into aggregation-6
angrykoala Feb 17, 2025
25f3169
Merge branch 'dev' into aggregation-6
angrykoala Feb 17, 2025
9600435
Fix performance issue with new aggregations
angrykoala Feb 17, 2025
da68f2b
Code review changes
angrykoala Feb 18, 2025
3ff5dd9
Add distinct to new count in aggregation
angrykoala Feb 18, 2025
b105601
Update tck tests
angrykoala Feb 18, 2025
d9d8300
Merge branch 'dev' into aggregation-6
angrykoala Feb 18, 2025
d84f56c
Merge pull request #6006 from neo4j/fix-performance
angrykoala Feb 18, 2025
f582b91
Fix unit tests in aggregations 6 branch
angrykoala Feb 18, 2025
b0b1d60
Fix e2e tests
angrykoala Feb 18, 2025
7011898
Update packages/graphql/src/classes/Neo4jGraphQL.ts
angrykoala Feb 18, 2025
27f6492
Merge pull request #6010 from neo4j/fix-tests-aggregation-6
angrykoala Feb 18, 2025
69ff2c9
Update schema tests
angrykoala Feb 18, 2025
b741047
Add distinct to aggregation subqueries
angrykoala Feb 18, 2025
9eb22d3
Merge pull request #6009 from neo4j/fix-count-distinct
angrykoala Feb 18, 2025
5a3afbc
Update tck test
angrykoala Feb 18, 2025
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
32 changes: 32 additions & 0 deletions .changeset/hot-bikes-complain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
"@neo4j/graphql": patch
---

Add count fields in aggregations with support for nodes and edges count:

```graphql
query {
moviesConnection {
aggregate {
count {
nodes
}
}
}
}
```

```graphql
query {
movies {
actorsConnection {
aggregate {
count {
nodes
edges
}
}
}
}
}
```
20 changes: 20 additions & 0 deletions .changeset/hot-windows-whisper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
"@neo4j/graphql": minor
---

Add aggregate field in connection:

```graphql
query {
moviesConnection {
aggregate {
node {
count
int {
longest
}
}
}
}
}
```
5 changes: 5 additions & 0 deletions .changeset/sixty-steaks-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@neo4j/graphql": patch
---

Deprecate aggregation fields (e.g `actedInAggregate`) in favor of the field `aggregate` inside the connection (e.g `actedInConnection -> aggregate`)
25 changes: 25 additions & 0 deletions .changeset/thin-goats-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
"@neo4j/graphql": patch
---

Deprecated old aggregate operations:

```graphql
query {
moviesAggregate {
count
rating {
min
}
}
}
```

These fields can be completely removed from the schema with the new flag `deprecatedAggregateOperations`:

```js
const neoSchema = new Neo4jGraphQL({
typeDefs,
features: { excludeDeprecatedFields: { deprecatedAggregateOperations: true } },
});
```
2 changes: 1 addition & 1 deletion packages/graphql/src/classes/Neo4jDatabaseInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class Neo4jDatabaseInfo {
public edition: Neo4jEdition | undefined;

constructor(version: string, edition?: Neo4jEdition) {
// Quick hack to support CalVar
// Quick hack to support CalVer
version = version.replace(/\.0([0-9]+)/, ".$1");
this.version = this.toSemVer(version);
this.rawVersion = version;
Expand Down
2 changes: 1 addition & 1 deletion packages/graphql/src/graphql/objects/CartesianPoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const CartesianPoint = new GraphQLObjectType({
},
srid: {
type: new GraphQLNonNull(GraphQLInt),
resolve: (source, args, context, info) => numericalResolver(source, args, context, info),
resolve: numericalResolver,
},
},
});
2 changes: 1 addition & 1 deletion packages/graphql/src/graphql/objects/Point.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const Point = new GraphQLObjectType({
},
srid: {
type: new GraphQLNonNull(GraphQLInt),
resolve: (source, args, context, info) => numericalResolver(source, args, context, info),
resolve: numericalResolver,
},
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export type RootTypeFieldNames = {
type AggregateTypeNames = {
selection: string;
input: string;
connection: string;
node: string;
};

type MutationResponseTypeNames = {
Expand Down Expand Up @@ -142,10 +144,15 @@ export class ImplementingEntityOperations<T extends InterfaceEntityAdapter | Con
return `${this.entityAdapter.name}ImplementationsSubscriptionWhere`;
}

/** @deprecated use `getAggregateFieldTypename` instead */
public getAggregationFieldTypename(): string {
return this.aggregateTypeNames.selection;
}

public getAggregateFieldTypename(): string {
return this.aggregateTypeNames.selection;
}

public get rootTypeFieldNames(): RootTypeFieldNames {
return {
connection: `${this.entityAdapter.plural}Connection`,
Expand All @@ -161,6 +168,8 @@ export class ImplementingEntityOperations<T extends InterfaceEntityAdapter | Con
return {
selection: `${this.entityAdapter.name}AggregateSelection`,
input: `${this.entityAdapter.name}AggregateSelectionInput`,
connection: `${this.entityAdapter.name}Aggregate`,
node: `${this.entityAdapter.name}AggregateNode`,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ describe("RelationshipAdapter", () => {

test("should parse selectable", () => {
const relationshipAdapter = userAdapter.relationships.get("accounts");
expect(relationshipAdapter?.isAggregable()).toBeTrue();
expect(relationshipAdapter?.isAggregable()).toBeFalse();
expect(relationshipAdapter?.isReadable()).toBeFalse();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ export class RelationshipAdapter {
}

public isAggregable(): boolean {
if (!this.aggregate) {
return false;
}

return this.annotations.selectable?.onAggregate !== false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
* limitations under the License.
*/

import type { ConcreteEntityAdapter } from "../../entity/model-adapters/ConcreteEntityAdapter";
import { upperFirst } from "../../../utils/upper-first";
import { isUnionEntity } from "../../../translate/queryAST/utils/is-union-entity";
import type { RelationshipDeclarationAdapter } from "./RelationshipDeclarationAdapter";
import { upperFirst } from "../../../utils/upper-first";
import type { ConcreteEntityAdapter } from "../../entity/model-adapters/ConcreteEntityAdapter";
import type { RelationshipAdapter } from "./RelationshipAdapter";
import type { RelationshipDeclarationAdapter } from "./RelationshipDeclarationAdapter";

export abstract class RelationshipBaseOperations<T extends RelationshipAdapter | RelationshipDeclarationAdapter> {
protected constructor(protected readonly relationship: T) {}
Expand All @@ -39,7 +39,10 @@ export abstract class RelationshipBaseOperations<T extends RelationshipAdapter |

protected abstract get edgePrefix(): string;

/**Note: Required for now to infer the types without ResolveTree */
/**Note: Required for now to infer the types without ResolveTree
* @deprecated use getAggregateFieldTypename
*
*/
public getAggregationFieldTypename(nestedField?: "node" | "edge"): string {
const nestedFieldStr = upperFirst(nestedField || "");
const aggregationStr = nestedField ? "Aggregate" : "Aggregation";
Expand All @@ -48,6 +51,13 @@ export abstract class RelationshipBaseOperations<T extends RelationshipAdapter |
)}${nestedFieldStr}${aggregationStr}Selection`;
}

public getAggregateFieldTypename(nestedField?: "node" | "edge"): string {
const nestedFieldStr = upperFirst(nestedField || "");
return `${this.relationship.source.name}${this.relationship.target.name}${upperFirst(
this.relationship.name
)}${nestedFieldStr}AggregateSelection`;
}

public getTargetTypePrettyName(): string {
if (this.relationship.isList) {
return `[${this.relationship.target.name}!]${!this.relationship.isNullable ? "!" : ""}`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* limitations under the License.
*/

import { GraphQLInt, GraphQLNonNull } from "graphql";
import type { ObjectTypeComposer, SchemaComposer } from "graphql-compose";
import type { Subgraph } from "../../classes/Subgraph";
import { idResolver } from "../resolvers/field/id";
Expand All @@ -26,16 +27,56 @@ export class AggregationTypesMapper {
private readonly aggregationSelectionTypes: Record<string, ObjectTypeComposer<unknown, unknown>>;

private readonly subgraph: Subgraph | undefined;
private readonly composer: SchemaComposer;

constructor(composer: SchemaComposer, subgraph?: Subgraph) {
this.subgraph = subgraph;
this.aggregationSelectionTypes = this.getOrCreateAggregationSelectionTypes(composer);
this.composer = composer;
}

public getAggregationType(typeName: string): ObjectTypeComposer<unknown, unknown> | undefined {
return this.aggregationSelectionTypes[typeName];
}

/** Top level count */
public getCountType(): ObjectTypeComposer {
const countFieldName = "Count";
const directives: string[] = this.subgraph ? [this.subgraph.getFullyQualifiedDirectiveName("shareable")] : [];
return this.composer.getOrCreateOTC(countFieldName, (countField) => {
countField.addFields({
nodes: {
type: new GraphQLNonNull(GraphQLInt),
resolve: numericalResolver,
},
});
for (const directiveName of directives) {
countField.setDirectiveByName(directiveName);
}
});
}

/** Nested count */
public getCountConnectionType(): ObjectTypeComposer {
const countFieldName = "CountConnection";
const directives: string[] = this.subgraph ? [this.subgraph.getFullyQualifiedDirectiveName("shareable")] : [];
return this.composer.getOrCreateOTC(countFieldName, (countField) => {
countField.addFields({
nodes: {
type: new GraphQLNonNull(GraphQLInt),
resolve: numericalResolver,
},
edges: {
type: new GraphQLNonNull(GraphQLInt),
resolve: numericalResolver,
},
});
for (const directiveName of directives) {
countField.setDirectiveByName(directiveName);
}
});
}

private getOrCreateAggregationSelectionTypes(
composer: SchemaComposer
): Record<string, ObjectTypeComposer<unknown, unknown>> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ export class FieldAggregationComposer {
);
}

this.composer.createObjectTC({
name: relationshipAdapter.operations.getAggregateFieldTypename(),
fields: {
count: this.aggregationTypesMapper.getCountConnectionType().NonNull,
...(aggregateSelectionNode ? { node: aggregateSelectionNode } : {}),
...(aggregateSelectionEdge ? { edge: aggregateSelectionEdge } : {}),
},
});

return this.composer.createObjectTC({
name: relationshipAdapter.operations.getAggregationFieldTypename(),
fields: {
Expand Down
23 changes: 22 additions & 1 deletion packages/graphql/src/schema/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
*/

import { DEPRECATED } from "../constants";
import type { ConcreteEntityAdapter } from "../schema-model/entity/model-adapters/ConcreteEntityAdapter";
import type { InterfaceEntityAdapter } from "../schema-model/entity/model-adapters/InterfaceEntityAdapter";
import type { RelationshipAdapter } from "../schema-model/relationship/model-adapters/RelationshipAdapter";
import type { RelationshipDeclarationAdapter } from "../schema-model/relationship/model-adapters/RelationshipDeclarationAdapter";

export const DEPRECATE_IMPLICIT_EQUAL_FILTERS = {
name: DEPRECATED,
Expand Down Expand Up @@ -61,7 +65,6 @@ export const DEPRECATE_OVERWRITE = {
},
};


export const DEPRECATE_ID_AGGREGATION = {
name: DEPRECATED,
args: {
Expand All @@ -75,3 +78,21 @@ export const DEPRECATE_TYPENAME_IN = {
reason: "The typename_IN filter is deprecated, please use the typename filter instead",
},
};

export function DEPRECATE_AGGREGATION(entity: ConcreteEntityAdapter | InterfaceEntityAdapter) {
return {
name: DEPRECATED,
args: {
reason: `Please use the explicit field "aggregate" inside "${entity.operations.rootTypeFieldNames.connection}" instead`,
},
};
}

export function DEPRECATE_NESTED_AGGREGATION(relationship: RelationshipAdapter | RelationshipDeclarationAdapter) {
return {
name: DEPRECATED,
args: {
reason: `Please use field "aggregate" inside "${relationship.operations.connectionFieldName}" instead`,
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { RelationshipAdapter } from "../../schema-model/relationship/model-adapt
import { RelationshipDeclarationAdapter } from "../../schema-model/relationship/model-adapters/RelationshipDeclarationAdapter";
import type { Neo4jFeaturesSettings } from "../../types";
import { FieldAggregationComposer } from "../aggregations/field-aggregation-composer";
import { DEPRECATE_NESTED_AGGREGATION } from "../constants";
import { addDirectedArgument } from "../directed-argument";
import {
augmentObjectOrInterfaceTypeWithConnectionField,
Expand All @@ -48,6 +49,7 @@ import { getRelationshipPropertiesTypeDescription, withObjectType } from "../gen
import { withRelationInputType } from "../generation/relation-input";
import { withSortInputType } from "../generation/sort-and-options-input";
import { augmentUpdateInputTypeWithUpdateFieldInput, withUpdateInputType } from "../generation/update-input";
import { shouldAddDeprecatedFields } from "../generation/utils";
import { withSourceWhereInputType, withWhereInputType } from "../generation/where-input";
import { graphqlDirectivesToCompose } from "../to-compose";

Expand Down Expand Up @@ -254,7 +256,6 @@ export function createRelationshipFields({
return;
}

// TODO: new way
if (composeNode instanceof ObjectTypeComposer) {
// make a new fn augmentObjectTypeWithAggregationField
const fieldAggregationComposer = new FieldAggregationComposer(schemaComposer, subgraph);
Expand All @@ -271,13 +272,18 @@ export function createRelationshipFields({
const aggregationFieldsArgs = addDirectedArgument(aggregationFieldsBaseArgs, relationshipAdapter, features);

if (relationshipAdapter.aggregate) {
composeNode.addFields({
[relationshipAdapter.operations.aggregateTypeName]: {
type: aggregationTypeObject,
args: aggregationFieldsArgs,
directives: deprecatedDirectives,
},
});
if (shouldAddDeprecatedFields(features, "deprecatedAggregateOperations")) {
composeNode.addFields({
[relationshipAdapter.operations.aggregateTypeName]: {
type: aggregationTypeObject,
args: aggregationFieldsArgs,
directives:
deprecatedDirectives.length > 0
? deprecatedDirectives
: [DEPRECATE_NESTED_AGGREGATION(relationshipAdapter)],
},
});
}
}
}

Expand Down
Loading
Loading