Skip to content
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
2f8f0ab
Test CypherBuilder against batch creation
MacondoExpress Sep 8, 2022
b3c5270
initial refactore translate-create, and test
MacondoExpress Sep 16, 2022
7e89e40
add autogenerated fiels to unwind-create
MacondoExpress Sep 16, 2022
93b6b16
Merge branch 'neo4j:dev' into feature/unwind-create
MacondoExpress Sep 20, 2022
a932360
Merge pull request #2114 from MacondoExpress/feature/unwind-create
angrykoala Sep 20, 2022
7774637
remove unused imports
MacondoExpress Sep 20, 2022
411e636
remove old translate-create implementation
MacondoExpress Sep 20, 2022
f51f26d
add projection to the unwind-create
MacondoExpress Sep 23, 2022
7dacedf
Refactor unwind-create implementation
MacondoExpress Oct 13, 2022
63ca55c
Refactor implementation with Simple IR
MacondoExpress Oct 18, 2022
8400c47
Merge branch 'dev' into unwind-create
MacondoExpress Oct 18, 2022
2fe26ef
Add integration tests for unwind-create
MacondoExpress Oct 21, 2022
64047ff
Merge branch 'dev' into unwind-create
MacondoExpress Oct 24, 2022
a2a65f8
WIP rollback process on the UNWIND-CREATE optimisation
MacondoExpress Oct 24, 2022
1b91ceb
Merge commit 'refs/pull/2115/head' of github.com:neo4j/graphql into u…
MacondoExpress Oct 24, 2022
7a9260d
Bug fix
MacondoExpress Oct 25, 2022
24036e2
include batch create performance test
MacondoExpress Oct 25, 2022
3376a61
add performance test for interface/union, to test the not yet optimis…
MacondoExpress Oct 25, 2022
b1d880b
add parser test, fix edge case
MacondoExpress Oct 26, 2022
72df35c
Clean up unwind-create, update tck snapshot
MacondoExpress Oct 26, 2022
29fc986
update CypherBuilder MapProjection test suite
MacondoExpress Oct 26, 2022
7453c3d
Apply suggestions from code review
MacondoExpress Oct 26, 2022
d9a748f
Lint fixes
MacondoExpress Oct 26, 2022
0f560f6
Sync with dev
MacondoExpress Oct 27, 2022
1599086
Merge branch 'dev' into unwind-create
MacondoExpress Oct 27, 2022
4e6ef86
Sync with Dev
MacondoExpress Oct 27, 2022
9b5e630
Merge branch 'dev' into unwind-create
MacondoExpress Oct 27, 2022
1ec8ca3
Fixes
MacondoExpress Oct 27, 2022
d9f633e
High level description of the unwind-create
MacondoExpress Oct 27, 2022
c291965
Wording changes
MacondoExpress Oct 27, 2022
3cd83ed
add type Procedure to CypherBuilder
MacondoExpress Oct 27, 2022
e1dcaf1
Typing fix
MacondoExpress Oct 27, 2022
ac34ec6
store results on a single instance of the visitor rather than initial…
MacondoExpress Oct 28, 2022
df1125b
Merge branch 'dev' into unwind-create
MacondoExpress Oct 28, 2022
fa153cf
Merge branch 'dev' into unwind-create
MacondoExpress Oct 28, 2022
3b06caf
Create unwind-create.md
MacondoExpress Oct 28, 2022
d44f903
include changeset, extends tests capabilities
MacondoExpress Oct 28, 2022
7aff0cf
include cypher builder changeset
MacondoExpress Oct 28, 2022
682fc85
Update docs with some troubleshooting around the bulk create operation
MacondoExpress Oct 28, 2022
2cf059c
Appy suggestions from PR
MacondoExpress Oct 28, 2022
ddff747
Update .changeset/unwind-create.md
MacondoExpress Oct 28, 2022
163af39
remove unused code
MacondoExpress Oct 28, 2022
904d10f
Merge commit 'refs/pull/2115/head' of github.com:neo4j/graphql into u…
MacondoExpress Oct 28, 2022
8d4a67c
Apply suggestions from code review
MacondoExpress Oct 28, 2022
601b7e3
space
MacondoExpress Oct 28, 2022
dce853f
Update docs/modules/ROOT/pages/troubleshooting/bulk-create.adoc
MacondoExpress Oct 31, 2022
09302ad
Update docs/modules/ROOT/pages/troubleshooting/bulk-create.adoc
MacondoExpress Oct 31, 2022
274c619
Apply suggestions from code review
MacondoExpress Oct 31, 2022
0761afe
suggestion from PR
MacondoExpress Oct 31, 2022
be5f990
Apply suggestions from code review
MacondoExpress Oct 31, 2022
636d746
remove barrel export from batch-create
MacondoExpress Oct 31, 2022
fdc13e0
update troubleshooting doc
MacondoExpress Oct 31, 2022
1fa6a28
Merge branch 'dev' into unwind-create
MacondoExpress Oct 31, 2022
5448ba2
remove serialize-list from utils and let it be a private member of th…
MacondoExpress Oct 31, 2022
c041cf5
Merge commit 'refs/pull/2115/head' of github.com:neo4j/graphql into u…
MacondoExpress Oct 31, 2022
31a823a
remove root key from UnwindCreateVisitor's Environment
MacondoExpress Oct 31, 2022
f2ced89
Remove GitHub reference from docs
MacondoExpress Oct 31, 2022
59df77f
Do not parse the GraphQLInput to generate the UNWIND statement, do no…
MacondoExpress Oct 31, 2022
bbfc6e8
update tck test to reflect the fact that now are passed as Cypher.Param
MacondoExpress Nov 1, 2022
ddc914c
remove batch/create/utils.ts
MacondoExpress Nov 1, 2022
0a137a2
Merge branch 'dev' into unwind-create
MacondoExpress Nov 1, 2022
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
6 changes: 6 additions & 0 deletions .changeset/cypher-builder-patch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@neo4j/cypher-builder": patch
---

Included List, date, localtime, localdatetime, time, randomUUID.
It's possible now to set edge properties from the Merge clause.
5 changes: 5 additions & 0 deletions .changeset/unwind-create.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@neo4j/graphql": minor
---

Added the unwind-create optimization, when possible, to improve performance when a large numbers of nodes are built in single mutation
1 change: 1 addition & 0 deletions docs/modules/ROOT/content-nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
** xref:troubleshooting/index.adoc[]
*** xref:troubleshooting/faqs.adoc[]
*** xref:troubleshooting/security.adoc[]
*** xref:troubleshooting/bulk-create.adoc[]
** xref:appendix/index.adoc[]
*** xref:appendix/preventing-overfetching.adoc[]
** xref:deprecations.adoc[Deprecations]
27 changes: 27 additions & 0 deletions docs/modules/ROOT/pages/troubleshooting/bulk-create.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[[bulk-create]]
= Bulk create

It's possible using the Neo4jGraphQL library to create several nodes and relationships in a single mutation, however,
it's well known that performance issues are present in performing this task.
Solutions for this issue are already implemented without any input from the user, see https://github.com/neo4j/graphql/blob/dev/docs/rfcs/rfc-024-unwind-create.md.
However, there are still several situations where this kind of optimization is not achievable.

== Subscriptions enabled

No optimizations are available if a Subscription plugin it's being used.

== @populated_by

No optimizations are available if a Node affected by the mutation has a field with the directive @populated_by.

== @auth

No optimizations are available if a Node or Field affected by the mutation is secured with the directive @auth.

== Connect/ConnectOrCreate operation

No optimizations are available if the GraphQL input contains the connect/connectOrCreate operation.

== @auth

No optimizations are available if a Node or Field affected by the mutation is secured with the directive @auth.
9 changes: 9 additions & 0 deletions packages/cypher-builder/src/Cypher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export { Match, OptionalMatch } from "./clauses/Match";
export { Create } from "./clauses/Create";
export { Merge } from "./clauses/Merge";
export { Call } from "./clauses/Call";
export { ProcedureCall } from "./clauses/ProcedureCall";
export { Return } from "./clauses/Return";
export { RawCypher } from "./clauses/RawCypher";
export { With } from "./clauses/With";
Expand Down Expand Up @@ -49,6 +50,7 @@ export * as apoc from "./expressions/procedures/apoc/apoc";
// --Lists
export { ListComprehension } from "./expressions/list/ListComprehension";
export { PatternComprehension } from "./expressions/list/PatternComprehension";
export { ListExpr as List } from "./expressions/list/ListExpr";

// --Map
export { MapExpr as Map } from "./expressions/map/MapExpr";
Expand Down Expand Up @@ -79,12 +81,17 @@ export {
distance,
pointDistance,
cypherDatetime as datetime,
cypherDate as date,
cypherLocalTime as localtime,
cypherLocalDatetime as localdatetime,
cypherTime as time,
labels,
count,
min,
max,
avg,
sum,
randomUUID
} from "./expressions/functions/CypherFunction";
export * from "./expressions/functions/ListFunctions";
export { any, all, exists, single } from "./expressions/functions/PredicateFunctions";
Expand All @@ -107,7 +114,9 @@ export type { CompositeClause } from "./clauses/utils/concat";

// utils
import { escapeLabel } from "./utils/escape-label";
import { compileCypherIfExists } from "./utils/compile-cypher-if-exists";

export const utils = {
escapeLabel,
compileCypherIfExists
};
12 changes: 8 additions & 4 deletions packages/cypher-builder/src/clauses/Merge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ import { Clause } from "./Clause";
import { OnCreate, OnCreateParam } from "./sub-clauses/OnCreate";
import { WithReturn } from "./mixins/WithReturn";
import { mixin } from "./utils/mixin";
import { WithSet } from "./mixins/WithSet";
import { compileCypherIfExists } from "../utils/compile-cypher-if-exists";
import { SetClause } from "./sub-clauses/Set";

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface Merge extends WithReturn {}
export interface Merge extends WithReturn, WithSet {}

@mixin(WithReturn)
@mixin(WithReturn, WithSet)
export class Merge<T extends NodeRef | RelationshipRef = any> extends Clause {
private pattern: Pattern<T>;
private onCreateClause: OnCreate;
Expand All @@ -44,6 +46,7 @@ export class Merge<T extends NodeRef | RelationshipRef = any> extends Clause {
target: addLabelsOption,
}).withParams(params);
this.onCreateClause = new OnCreate(this);
this.setSubClause = new SetClause(this);
}

public onCreate(...onCreateParams: OnCreateParam[]): this {
Expand All @@ -54,6 +57,7 @@ export class Merge<T extends NodeRef | RelationshipRef = any> extends Clause {

public getCypher(env: CypherEnvironment): string {
const mergeStr = `MERGE ${this.pattern.getCypher(env)}`;
const setCypher = compileCypherIfExists(this.setSubClause, env, { prefix: "\n" });
const onCreateStatement = this.onCreateClause.getCypher(env);
const separator = onCreateStatement ? "\n" : "";

Expand All @@ -62,6 +66,6 @@ export class Merge<T extends NodeRef | RelationshipRef = any> extends Clause {
returnCypher = `\n${this.returnStatement.getCypher(env)}`;
}

return `${mergeStr}${separator}${onCreateStatement}${returnCypher}`;
return `${mergeStr}${separator}${setCypher}${onCreateStatement}${returnCypher}`;
}
}
36 changes: 36 additions & 0 deletions packages/cypher-builder/src/clauses/ProcedureCall.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import type { CypherEnvironment } from "../Environment";
import { Clause } from "./Clause";
import type { Procedure } from "../types";

export class ProcedureCall extends Clause {
private procedure: Procedure;

constructor(procedure: Procedure) {
super();
this.procedure = procedure;
}

public getCypher(env: CypherEnvironment): string {
const procedureCypher = this.procedure.getCypher(env);
return `CALL ${procedureCypher}`;
}
}
34 changes: 34 additions & 0 deletions packages/cypher-builder/src/clauses/Unwind.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as Cypher from "../Cypher";

describe("CypherBuilder UNWIND", () => {
test("UNWIND Movies", () => {
const matrix = new Cypher.Map({ title: new Cypher.Literal("Matrix") });
const matrix2 = new Cypher.Map({ title: new Cypher.Literal("Matrix 2") });
const moviesList = new Cypher.List([matrix, matrix2]);
const unwindQuery = new Cypher.Unwind([moviesList, "batch"]);
const queryResult = unwindQuery.build();
expect(queryResult.cypher).toMatchInlineSnapshot(
`"UNWIND [ { title: \\"Matrix\\" }, { title: \\"Matrix 2\\" } ] AS batch"`
);
expect(queryResult.params).toMatchInlineSnapshot(`Object {}`);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,22 @@ export function cypherDatetime(): CypherFunction {
return new CypherFunction("datetime");
}

export function cypherDate(): CypherFunction {
return new CypherFunction("date");
}

export function cypherLocalDatetime(): CypherFunction {
return new CypherFunction("localdatetime");
}

export function cypherLocalTime(): CypherFunction {
return new CypherFunction("localtime");
}

export function cypherTime(): CypherFunction {
return new CypherFunction("time");
}

export function count(expr: Expr): CypherFunction {
return new CypherFunction("count", [expr]);
}
Expand All @@ -87,3 +103,9 @@ export function avg(expr: Expr): CypherFunction {
export function sum(expr: Expr): CypherFunction {
return new CypherFunction("sum", [expr]);
}

export function randomUUID(): CypherFunction {
return new CypherFunction("randomUUID");
}


36 changes: 36 additions & 0 deletions packages/cypher-builder/src/expressions/list/ListExpr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import type { CypherEnvironment } from "../../Environment";
import type { CypherCompilable, Expr } from "../../types";

import { serializeList } from "../../utils/serialize-list";

/** Represents a List */
export class ListExpr implements CypherCompilable {
private value: Expr[];

constructor(value: Expr[]) {
this.value = value;
}

public getCypher(env: CypherEnvironment): string {
return serializeList(env, this.value);
}
}
28 changes: 5 additions & 23 deletions packages/cypher-builder/src/expressions/map/MapProjection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,6 @@ describe("Map Projection", () => {
expect(queryResult.params).toMatchInlineSnapshot(`Object {}`);
});

test("Project map with variables and nodes in projection", () => {
const var1 = new Cypher.Variable();
const var2 = new Cypher.NamedVariable("NamedVar");
const node = new Cypher.Node({});

const mapProjection = new Cypher.MapProjection(new Cypher.Variable(), [var1, var2, node]);

const queryResult = new TestClause(mapProjection).build();

expect(queryResult.cypher).toMatchInlineSnapshot(`"var0 { .var1, .NamedVar, .this2 }"`);

expect(queryResult.params).toMatchInlineSnapshot(`Object {}`);
});

test("Project map with extra values only", () => {
const var1 = new Cypher.Variable();
const var2 = new Cypher.NamedVariable("NamedVar");
Expand All @@ -61,35 +47,31 @@ describe("Map Projection", () => {
expect(queryResult.params).toMatchInlineSnapshot(`Object {}`);
});

test("Project map with variables in projection and extra values", () => {
const var1 = new Cypher.Variable();
const var2 = new Cypher.NamedVariable("NamedVar");
test("Project map with properties in projection and extra values", () => {
const node = new Cypher.Node({});

const mapProjection = new Cypher.MapProjection(new Cypher.Variable(), [var1, var2], {
const mapProjection = new Cypher.MapProjection(new Cypher.Variable(), [".title", ".name"], {
namedValue: Cypher.count(node),
});
const queryResult = new TestClause(mapProjection).build();

expect(queryResult.cypher).toMatchInlineSnapshot(`"var0 { .var2, .NamedVar, namedValue: count(this1) }"`);
expect(queryResult.cypher).toMatchInlineSnapshot(`"var0 { .title, .name, namedValue: count(this1) }"`);

expect(queryResult.params).toMatchInlineSnapshot(`Object {}`);
});

test("Map Projection in return", () => {
const mapVar = new Cypher.Variable();
const var1 = new Cypher.Variable();
const var2 = new Cypher.NamedVariable("NamedVar");
const node = new Cypher.Node({});

const mapProjection = new Cypher.MapProjection(mapVar, [var1, var2], {
const mapProjection = new Cypher.MapProjection(mapVar, [".title", ".name"], {
namedValue: Cypher.count(node),
});

const queryResult = new Cypher.Return([mapProjection, mapVar]).build();

expect(queryResult.cypher).toMatchInlineSnapshot(
`"RETURN var0 { .var2, .NamedVar, namedValue: count(this1) } AS var0"`
`"RETURN var0 { .title, .name, namedValue: count(this1) } AS var0"`
);

expect(queryResult.params).toMatchInlineSnapshot(`Object {}`);
Expand Down
16 changes: 8 additions & 8 deletions packages/cypher-builder/src/expressions/map/MapProjection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,34 +19,34 @@

import type { CypherEnvironment } from "../../Environment";
import type { CypherCompilable, Expr } from "../../types";
import type { Variable } from "../../variables/Variable";
import { serializeMap } from "../../utils/serialize-map";
import { Variable } from "../../variables/Variable";

/** Represents a Map projection https://neo4j.com/docs/cypher-manual/current/syntax/maps/#cypher-map-projection */
export class MapProjection implements CypherCompilable {
private extraValues: Record<string, Expr>;
private variable: Variable;
private projection: Array<Variable>;
private projection: string[];

constructor(variable: Variable, projection: Array<Variable>, extraValues: Record<string, Expr> = {}) {
constructor(variable: Variable, projection: string[], extraValues: Record<string, Expr> = {}) {
this.variable = variable;
this.projection = projection;
this.extraValues = extraValues;
}

public set(values: Record<string, Expr> | Variable): void {
if (values instanceof Variable) {
this.projection.push(values);
public set(values: Record<string, Expr> | string): void {
if (values instanceof String) {
this.projection.push(values as string);
} else {
this.extraValues = { ...this.extraValues, ...values };
this.extraValues = { ...this.extraValues, ...values as Record<string, Expr> };
}
}

public getCypher(env: CypherEnvironment): string {
const variableStr = this.variable.getCypher(env);
const extraValuesStr = serializeMap(env, this.extraValues, true);

const projectionStr = this.projection.map((v) => `.${v.getCypher(env)}`).join(", ");
const projectionStr = this.projection.join(", ");

const commaStr = extraValuesStr && projectionStr ? ", " : "";

Expand Down
Loading