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

Fix #462 #463

Merged
merged 4 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
44 changes: 44 additions & 0 deletions .changeset/gentle-pens-smoke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
"@neo4j/cypher-builder": patch
---

Fix order of set remove subclauses. The generated cypher will now maintain the order of multiple `SET` and `REMOVE` statements.

For example:

```js
const matchQuery = new Cypher.Match(new Cypher.Pattern(personNode, { labels: ["Person"] }))
.where(personNode, { name: nameParam })
.set([personNode.property("name"), evilKeanu])
.remove(personNode.property("anotherName"))
.set([personNode.property("anotherName"), new Cypher.Param(nameParam)])
.set([personNode.property("oldName"), new Cypher.Param(nameParam)])
.return(personNode);
```

Before

```cypher
MATCH (this0:Person)
WHERE this0.name = $param0
SET
this0.name = $param1
this0.anotherName = $param2,
this0.oldName = $param3
REMOVE this0.anotherName
RETURN this0
```

After

```cypher
MATCH (this0:Person)
WHERE this0.name = $param0
SET
this0.name = $param1
REMOVE this0.anotherName
SET
this0.anotherName = $param2,
this0.oldName = $param3
RETURN this0
```
28 changes: 14 additions & 14 deletions DEVELOPING.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
# Development

- `npm test` to run cypher builder tests
- `npm run build` to compile cypher builder library
- `npm run docs` to generate the API reference docs
- `npm test` to run cypher builder tests
- `npm run build` to compile cypher builder library
- `npm run docs` to generate the API reference docs

## Link Cypher Builder locally with yarn

In the Cypher Builder folder run:

- `yarn link`
- `yarn build`
- `yarn link`
- `yarn build`

In the root of the package run:

- `yarn link -p [path-to-local-cypher-builder]`
- `yarn link -p [path-to-local-cypher-builder]`

To unlink, in the project using cypher-builder:

- `yarn unlink @neo4j/cypher-builder`
- `yarn unlink @neo4j/cypher-builder`

# TSDoc references

Each public element of the library should have a TSDoc comment compatible with [TypeDoc](https://typedoc.org/guides/overview).
The comments should follow these conventions:

- Brief description
- @group - This should be the Cypher concept related to this interface. Cypher Functions, Clauses, Operators, Procedures, Other Expressions.
- @see [Cypher Documentation](https://neo4j.com/docs/cypher-manual) - A link to the element in the Cypher documentation
- @internal - If used by the library and not exposed
- @example - example of usage and resulting Cypher
- Brief description
- @group - This should be the Cypher concept related to this interface. Cypher Functions, Clauses, Operators, Procedures, Other Expressions.
- @see [Cypher Documentation](https://neo4j.com/docs/cypher-manual) - A link to the element in the Cypher documentation
- @internal - If used by the library and not exposed
- @example - example of usage and resulting Cypher

````
* @example
Expand All @@ -43,5 +43,5 @@ The comments should follow these conventions:

## Files

- `tsdoc.json` Defines the tsdoc shcema
- `typedoc.json` Configures the tool typedoc
- `tsdoc.json` Defines the tsdoc shcema
- `typedoc.json` Configures the tool typedoc
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

Cypher Builder is a JavaScript programmatic API to create [Cypher](https://neo4j.com/docs/cypher-manual/current/) queries for [Neo4j](https://neo4j.com/).

- [Documentation](https://neo4j.github.io/cypher-builder/cypher-builder/current/)
- [Documentation](https://neo4j.github.io/cypher-builder/cypher-builder/current/)

```typescript
import Cypher from "@neo4j/cypher-builder";
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,21 @@
"homepage": "https://neo4j.github.io/cypher-builder/",
"devDependencies": {
"@changesets/changelog-github": "^0.5.0",
"@changesets/cli": "^2.27.9",
"@changesets/cli": "^2.27.10",
"@eslint/js": "^9.15.0",
"@tsconfig/node16": "^16.1.3",
"@types/jest": "^29.5.14",
"@types/node": "^22.9.0",
"@typescript-eslint/eslint-plugin": "^8.15.0",
"@typescript-eslint/parser": "^8.15.0",
"@types/node": "^22.10.0",
"@typescript-eslint/eslint-plugin": "^8.16.0",
"@typescript-eslint/parser": "^8.16.0",
"eslint": "^9.15.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-tsdoc": "^0.4.0",
"globals": "^15.12.0",
"jest": "^29.7.0",
"jest-extended": "^4.0.2",
"neo4j-driver": "^5.26.0",
"prettier": "^3.3.3",
"prettier": "^3.4.1",
"ts-jest": "^29.2.5",
"typedoc": "^0.26.11",
"typescript": "^5.6.3"
Expand Down
13 changes: 5 additions & 8 deletions src/clauses/Call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ import { WithUnwind } from "./mixins/clauses/WithUnwind";
import { WithWith } from "./mixins/clauses/WithWith";
import { WithDelete } from "./mixins/sub-clauses/WithDelete";
import { WithOrder } from "./mixins/sub-clauses/WithOrder";
import { WithRemove } from "./mixins/sub-clauses/WithRemove";
import { WithSet } from "./mixins/sub-clauses/WithSet";
import { WithSetRemove } from "./mixins/sub-clauses/WithSetRemove";
import { ImportWith } from "./sub-clauses/ImportWith";
import { CompositeClause } from "./utils/concat";
import { mixin } from "./utils/mixin";
Expand All @@ -42,8 +41,7 @@ export interface Call
extends WithReturn,
WithWith,
WithUnwind,
WithSet,
WithRemove,
WithSetRemove,
WithDelete,
WithMatch,
WithCreate,
Expand All @@ -60,7 +58,7 @@ type InTransactionConfig = {
* @see [Cypher Documentation](https://neo4j.com/docs/cypher-manual/current/clauses/call-subquery/)
* @category Clauses
*/
@mixin(WithReturn, WithWith, WithUnwind, WithRemove, WithDelete, WithSet, WithMatch, WithCreate, WithMerge, WithOrder)
@mixin(WithReturn, WithWith, WithUnwind, WithDelete, WithSetRemove, WithMatch, WithCreate, WithMerge, WithOrder)
export class Call extends Clause {
private readonly subquery: CypherASTNode;
private _importWith?: ImportWith;
Expand Down Expand Up @@ -113,9 +111,8 @@ export class Call extends Clause {

const subQueryStr = this.getSubqueryCypher(env, importWithCypher);

const removeCypher = compileCypherIfExists(this.removeClause, env, { prefix: "\n" });
const setCypher = this.compileSetCypher(env);
const deleteCypher = compileCypherIfExists(this.deleteClause, env, { prefix: "\n" });
const setCypher = compileCypherIfExists(this.setSubClause, env, { prefix: "\n" });
const orderByCypher = compileCypherIfExists(this.orderByStatement, env, { prefix: "\n" });
const inTransactionCypher = this.generateInTransactionStr();

Expand All @@ -125,7 +122,7 @@ export class Call extends Clause {

const optionalStr = this._optional ? "OPTIONAL " : "";

return `${optionalStr}CALL${variableScopeStr} {\n${padBlock(inCallBlock)}\n}${inTransactionCypher}${setCypher}${removeCypher}${deleteCypher}${orderByCypher}${nextClause}`;
return `${optionalStr}CALL${variableScopeStr} {\n${padBlock(inCallBlock)}\n}${inTransactionCypher}${setCypher}${deleteCypher}${orderByCypher}${nextClause}`;
}

private getSubqueryCypher(env: CypherEnvironment, importWithCypher: string | undefined): string {
Expand Down
23 changes: 5 additions & 18 deletions src/clauses/Create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,16 @@ import { WithReturn } from "./mixins/clauses/WithReturn";
import { WithWith } from "./mixins/clauses/WithWith";
import { WithDelete } from "./mixins/sub-clauses/WithDelete";
import { WithOrder } from "./mixins/sub-clauses/WithOrder";
import { WithRemove } from "./mixins/sub-clauses/WithRemove";
import { WithSet } from "./mixins/sub-clauses/WithSet";
import { SetClause } from "./sub-clauses/Set";
import { WithSetRemove } from "./mixins/sub-clauses/WithSetRemove";
import { mixin } from "./utils/mixin";

export interface Create
extends WithReturn,
WithSet,
WithWith,
WithDelete,
WithRemove,
WithMerge,
WithFinish,
WithOrder {}
export interface Create extends WithReturn, WithSetRemove, WithWith, WithDelete, WithMerge, WithFinish, WithOrder {}

/**
* @see [Cypher Documentation](https://neo4j.com/docs/cypher-manual/current/clauses/create/)
* @category Clauses
*/
@mixin(WithReturn, WithSet, WithWith, WithDelete, WithRemove, WithMerge, WithFinish, WithOrder)
@mixin(WithReturn, WithSetRemove, WithWith, WithDelete, WithMerge, WithFinish, WithOrder)
export class Create extends Clause {
private readonly pattern: Pattern | PathAssign<Pattern>;

Expand All @@ -58,8 +48,6 @@ export class Create extends Clause {
} else {
this.pattern = new Pattern(pattern);
}

this.setSubClause = new SetClause(this);
}

/** Add a {@link Create} clause
Expand All @@ -81,12 +69,11 @@ export class Create extends Clause {
public getCypher(env: CypherEnvironment): string {
const patternCypher = this.pattern.getCypher(env);

const setCypher = compileCypherIfExists(this.setSubClause, env, { prefix: "\n" });
const setCypher = this.compileSetCypher(env);
const deleteStr = compileCypherIfExists(this.deleteClause, env, { prefix: "\n" });
const removeCypher = compileCypherIfExists(this.removeClause, env, { prefix: "\n" });
const orderByCypher = compileCypherIfExists(this.orderByStatement, env, { prefix: "\n" });

const nextClause = this.compileNextClause(env);
return `CREATE ${patternCypher}${setCypher}${removeCypher}${deleteStr}${orderByCypher}${nextClause}`;
return `CREATE ${patternCypher}${setCypher}${deleteStr}${orderByCypher}${nextClause}`;
}
}
2 changes: 1 addition & 1 deletion src/clauses/Foreach.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ describe("Foreach", () => {
"FOREACH (var0 IN [1, 2, 3] |
CREATE (this1:Movie)
)
REMOVE this1.title
SET
this1.id = var0
REMOVE this1.title
DELETE this1
WITH *"
`);
Expand Down
12 changes: 5 additions & 7 deletions src/clauses/Foreach.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,21 @@ import { WithMerge } from "./mixins/clauses/WithMerge";
import { WithReturn } from "./mixins/clauses/WithReturn";
import { WithWith } from "./mixins/clauses/WithWith";
import { WithDelete } from "./mixins/sub-clauses/WithDelete";
import { WithRemove } from "./mixins/sub-clauses/WithRemove";
import { WithSet } from "./mixins/sub-clauses/WithSet";
import { WithSetRemove } from "./mixins/sub-clauses/WithSetRemove";
import type { DeleteClause } from "./sub-clauses/Delete";
import type { RemoveClause } from "./sub-clauses/Remove";
import type { SetClause } from "./sub-clauses/Set";
import { mixin } from "./utils/mixin";

export interface Foreach extends WithWith, WithReturn, WithRemove, WithSet, WithDelete, WithCreate, WithMerge {}
export interface Foreach extends WithWith, WithReturn, WithSetRemove, WithDelete, WithCreate, WithMerge {}

type ForeachClauses = Foreach | SetClause | RemoveClause | Create | Merge | DeleteClause;

/**
* @see [Cypher Documentation](https://neo4j.com/docs/cypher-manual/current/clauses/foreach/)
* @category Clauses
*/
@mixin(WithWith, WithReturn, WithRemove, WithSet, WithDelete, WithCreate, WithMerge)
@mixin(WithWith, WithReturn, WithSetRemove, WithDelete, WithCreate, WithMerge)
export class Foreach extends Clause {
private readonly variable: Variable;
private readonly listExpr: Expr;
Expand All @@ -67,10 +66,9 @@ export class Foreach extends Clause {

const foreachStr = [`FOREACH (${variableStr} IN ${listExpr} |`, padBlock(mapClauseStr), `)`].join("\n");

const setCypher = compileCypherIfExists(this.setSubClause, env, { prefix: "\n" });
const setCypher = this.compileSetCypher(env);
const deleteCypher = compileCypherIfExists(this.deleteClause, env, { prefix: "\n" });
const removeCypher = compileCypherIfExists(this.removeClause, env, { prefix: "\n" });

return `${foreachStr}${setCypher}${removeCypher}${deleteCypher}${nextClause}`;
return `${foreachStr}${setCypher}${deleteCypher}${nextClause}`;
}
}
10 changes: 5 additions & 5 deletions src/clauses/Match.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,11 @@ describe("CypherBuilder Match", () => {

const queryResult = matchQuery.build();
expect(queryResult.cypher).toMatchInlineSnapshot(`
"MATCH (this0:Movie)
WHERE (this0.id = $param0 AND this0.name = $param1)
REMOVE this0.name,this0.released
RETURN this0.id"
`);
"MATCH (this0:Movie)
WHERE (this0.id = $param0 AND this0.name = $param1)
REMOVE this0.name, this0.released
RETURN this0.id"
`);

expect(queryResult.params).toMatchInlineSnapshot(`
{
Expand Down
14 changes: 5 additions & 9 deletions src/clauses/Match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,16 @@ import { WithUnwind } from "./mixins/clauses/WithUnwind";
import { WithWith } from "./mixins/clauses/WithWith";
import { WithDelete } from "./mixins/sub-clauses/WithDelete";
import { WithOrder } from "./mixins/sub-clauses/WithOrder";
import { WithRemove } from "./mixins/sub-clauses/WithRemove";
import { WithSet } from "./mixins/sub-clauses/WithSet";
import { WithSetRemove } from "./mixins/sub-clauses/WithSetRemove";
import { WithWhere } from "./mixins/sub-clauses/WithWhere";
import { mixin } from "./utils/mixin";

export interface Match
extends WithReturn,
WithWhere,
WithSet,
WithSetRemove,
WithWith,
WithDelete,
WithRemove,
WithUnwind,
WithCreate,
WithMerge,
Expand All @@ -65,10 +63,9 @@ type ShortestStatement = {
@mixin(
WithReturn,
WithWhere,
WithSet,
WithSetRemove,
WithWith,
WithDelete,
WithRemove,
WithUnwind,
WithCreate,
WithMerge,
Expand Down Expand Up @@ -162,14 +159,13 @@ export class Match extends Clause {
const whereCypher = compileCypherIfExists(this.whereSubClause, env, { prefix: "\n" });

const nextClause = this.compileNextClause(env);
const setCypher = compileCypherIfExists(this.setSubClause, env, { prefix: "\n" });
const setCypher = this.compileSetCypher(env);
const deleteCypher = compileCypherIfExists(this.deleteClause, env, { prefix: "\n" });
const removeCypher = compileCypherIfExists(this.removeClause, env, { prefix: "\n" });
const orderByCypher = compileCypherIfExists(this.orderByStatement, env, { prefix: "\n" });
const optionalMatch = this._optional ? "OPTIONAL " : "";
const shortestStatement = this.getShortestStatement();

return `${optionalMatch}MATCH ${shortestStatement}${patternCypher}${whereCypher}${setCypher}${removeCypher}${deleteCypher}${orderByCypher}${nextClause}`;
return `${optionalMatch}MATCH ${shortestStatement}${patternCypher}${whereCypher}${setCypher}${deleteCypher}${orderByCypher}${nextClause}`;
}

private getShortestStatement(): string {
Expand Down
Loading
Loading