diff --git a/.changeset/easy-horses-repeat.md b/.changeset/easy-horses-repeat.md new file mode 100644 index 00000000..267ae183 --- /dev/null +++ b/.changeset/easy-horses-repeat.md @@ -0,0 +1,25 @@ +--- +"@neo4j/cypher-builder": major +--- + +Remove labelOperator, all labels will now use operator `&` + +No longer supported: + +```js +const { cypher, params } = matchQuery.build({ + labelOperator: "&", +}); +``` + +_Before_ + +```js +MATCH (this1:Movie:Film) +``` + +_After_ + +```cypher +MATCH (this1:Movie&Film) +``` diff --git a/examples/patterns/label-expressions.ts b/examples/patterns/label-expressions.ts index 75dcc67f..275f77bf 100644 --- a/examples/patterns/label-expressions.ts +++ b/examples/patterns/label-expressions.ts @@ -30,9 +30,7 @@ const matchQuery = new Cypher.Match( }) ).return(movieNode); -const { cypher, params } = matchQuery.build({ - labelOperator: "&", -}); +const { cypher, params } = matchQuery.build(); console.log("Cypher"); console.log(cypher); diff --git a/examples/patterns/length.ts b/examples/patterns/length.ts index f39bce04..7332254b 100644 --- a/examples/patterns/length.ts +++ b/examples/patterns/length.ts @@ -29,9 +29,7 @@ const pattern = new Cypher.Pattern(movie).related(actedIn, { type: "ACTED_IN", l const matchQuery = new Cypher.Match(pattern).return(movie); -const { cypher, params } = matchQuery.build({ - labelOperator: "&", -}); +const { cypher, params } = matchQuery.build(); console.log("Cypher"); console.log(cypher); diff --git a/examples/patterns/quantified-path-patterns.ts b/examples/patterns/quantified-path-patterns.ts index 31bd9f5b..4431b23e 100644 --- a/examples/patterns/quantified-path-patterns.ts +++ b/examples/patterns/quantified-path-patterns.ts @@ -41,9 +41,7 @@ const quantifiedPath = new Cypher.QuantifiedPath( const query = new Cypher.Match(quantifiedPath).return(m2); -const { cypher, params } = query.build({ - labelOperator: "&", -}); +const { cypher, params } = query.build(); console.log("Cypher"); console.log(cypher); diff --git a/src/Environment.ts b/src/Environment.ts index f1b1d9c4..4607175f 100644 --- a/src/Environment.ts +++ b/src/Environment.ts @@ -27,13 +27,11 @@ export type EnvPrefix = { }; export type EnvConfig = { - labelOperator: NonNullable; unsafeEscapeOptions: NonNullable; cypherVersion: BuildConfig["cypherVersion"]; }; const defaultConfig: EnvConfig = { - labelOperator: ":", unsafeEscapeOptions: {}, cypherVersion: undefined, }; diff --git a/src/clauses/Clause.ts b/src/clauses/Clause.ts index 63d41138..bfcfe8bf 100644 --- a/src/clauses/Clause.ts +++ b/src/clauses/Clause.ts @@ -30,18 +30,6 @@ const customInspectSymbol = Symbol.for("nodejs.util.inspect.custom"); * @group Clauses */ export type BuildConfig = Partial<{ - /** Defines the default operator for adding multiple labels in a Pattern. Defaults to `":"` - * - * If the target Cypher is version 5 or above, `"&"` is recommended - * - * @example - * `MATCH (this:Movie:Film)` - * `MATCH (this:Movie&Film)` - * - * @deprecated This will be removed in the following major release and the value `"&"` will be used in the generated Cypher - * - */ - labelOperator: ":" | "&"; /** Will prefix generated queries with the Cypher version * @example `CYPHER 5` */ @@ -85,11 +73,10 @@ export abstract class Clause extends CypherASTNode { /** Compiles a clause into Cypher and params */ public build(config?: BuildConfig): CypherResult { - const { prefix, extraParams = {}, labelOperator = ":", cypherVersion, unsafeEscapeOptions = {} } = config ?? {}; + const { prefix, extraParams = {}, cypherVersion, unsafeEscapeOptions = {} } = config ?? {}; if (this.isRoot) { const env = this.getEnv(prefix, { - labelOperator, cypherVersion, unsafeEscapeOptions, }); @@ -104,7 +91,7 @@ export abstract class Clause extends CypherASTNode { } const root = this.getRoot(); if (root instanceof Clause) { - return root.build({ prefix, extraParams, labelOperator, cypherVersion, unsafeEscapeOptions }); + return root.build({ prefix, extraParams, cypherVersion, unsafeEscapeOptions }); } throw new Error(`Cannot build root: ${root.constructor.name}`); } diff --git a/src/expressions/HasLabel.test.ts b/src/expressions/HasLabel.test.ts index 7b7fd1c8..4a03980a 100644 --- a/src/expressions/HasLabel.test.ts +++ b/src/expressions/HasLabel.test.ts @@ -55,9 +55,9 @@ describe("HasLabel", () => { const queryResult = new TestClause(query).build(); expect(queryResult.cypher).toMatchInlineSnapshot(` - "MATCH (this0:Movie) - WHERE this0:Movie:Film" - `); +"MATCH (this0:Movie) +WHERE this0:Movie&Film" +`); expect(queryResult.params).toMatchInlineSnapshot(`{}`); }); diff --git a/src/expressions/HasLabel.ts b/src/expressions/HasLabel.ts index 7958e265..3f29556a 100644 --- a/src/expressions/HasLabel.ts +++ b/src/expressions/HasLabel.ts @@ -58,9 +58,9 @@ export class HasLabel extends CypherASTNode { private generateLabelExpressionStr(env: CypherEnvironment): string { if (Array.isArray(this.expectedLabels)) { const escapedLabels = this.expectedLabels.map((label) => escapeLabel(label)); - return addLabelToken(env.config.labelOperator, ...escapedLabels); + return addLabelToken(...escapedLabels); } else { - return addLabelToken(env.config.labelOperator, this.expectedLabels.getCypher(env)); + return addLabelToken(this.expectedLabels.getCypher(env)); } } diff --git a/src/pattern/Pattern.test.ts b/src/pattern/Pattern.test.ts index 00d9e4fc..888ec48c 100644 --- a/src/pattern/Pattern.test.ts +++ b/src/pattern/Pattern.test.ts @@ -191,9 +191,7 @@ describe("Patterns", () => { .to(b) ); const queryResult = query.build(); - expect(queryResult.cypher).toMatchInlineSnapshot( - `"(this0:Person:Actor { name: $param0, surname: $param1 })-[this1:ACTED_IN { roles: $param2 }]->(this2)"` - ); + expect(queryResult.cypher).toMatchInlineSnapshot(`"(this0:Person&Actor { name: $param0, surname: $param1 })-[this1:ACTED_IN { roles: $param2 }]->(this2)"`); expect(queryResult.params).toMatchInlineSnapshot(` { @@ -225,9 +223,7 @@ describe("Patterns", () => { .to(b) ); const queryResult = query.build(); - expect(queryResult.cypher).toMatchInlineSnapshot( - `"(this0:Person:Actor)-[this1:ACTED_IN { roles: (\\"The \\" + \\"Matrix\\") }]->(this2)"` - ); + expect(queryResult.cypher).toMatchInlineSnapshot(`"(this0:Person&Actor)-[this1:ACTED_IN { roles: (\\"The \\" + \\"Matrix\\") }]->(this2)"`); expect(queryResult.params).toMatchInlineSnapshot(`{}`); }); diff --git a/src/pattern/labels-to-string.ts b/src/pattern/labels-to-string.ts index c490f1c7..6a8dabfa 100644 --- a/src/pattern/labels-to-string.ts +++ b/src/pattern/labels-to-string.ts @@ -44,7 +44,7 @@ function labelOrTypeToString( escapeFunc: (s: string) => string ): string { if (elements instanceof LabelExpr) { - return addLabelToken(env.config.labelOperator, elements.getCypher(env)); + return addLabelToken(elements.getCypher(env)); } else { const escapedLabels = asArray(elements).map((label: string | Expr) => { if (typeof label === "string") { @@ -54,6 +54,6 @@ function labelOrTypeToString( } }); - return addLabelToken(env.config.labelOperator, ...escapedLabels); + return addLabelToken(...escapedLabels); } } diff --git a/src/references/Label.ts b/src/references/Label.ts index 3510a158..1abdbca0 100644 --- a/src/references/Label.ts +++ b/src/references/Label.ts @@ -58,7 +58,7 @@ export class Label extends CypherASTNode { } private generateLabelExpressionStr(env: CypherEnvironment): string { - return addLabelToken(env.config.labelOperator, escapeLabel(this.label)); + return addLabelToken(escapeLabel(this.label)); } } @@ -77,7 +77,7 @@ export class DynamicLabel extends Label { public getCypher(env: CypherEnvironment): string { const nodeId = this.node.getCypher(env); const exprStr = `$(${this.expr.getCypher(env)})`; - const labelStr = addLabelToken(env.config.labelOperator, exprStr); + const labelStr = addLabelToken(exprStr); return `${nodeId}${labelStr}`; } } diff --git a/src/utils/add-label-token.test.ts b/src/utils/add-label-token.test.ts index 17c00b61..8814f1c0 100644 --- a/src/utils/add-label-token.test.ts +++ b/src/utils/add-label-token.test.ts @@ -19,24 +19,24 @@ import { addLabelToken } from "./add-label-token"; -describe.each([":", "&"] as const)("addLabelToken", (operator) => { - test("addLabelToken without labels using operator %s", () => { - const result = addLabelToken(operator); +describe("addLabelToken", () => { + test("addLabelToken without labels", () => { + const result = addLabelToken(); expect(result).toBe(""); }); - test("addLabelToken with a single label using operator %s", () => { - const result = addLabelToken(operator, "Movie"); + test("addLabelToken with a single label", () => { + const result = addLabelToken("Movie"); expect(result).toBe(":Movie"); }); - test("addLabelToken with two labels using operator %s", () => { - const result = addLabelToken(operator, "Movie", "Film"); - expect(result).toBe(`:Movie${operator}Film`); + test("addLabelToken with two labels", () => { + const result = addLabelToken("Movie", "Film"); + expect(result).toBe(`:Movie&Film`); }); - test("addLabelToken with multiple labels using operator %s", () => { - const result = addLabelToken(operator, "Movie", "Film", "Video"); - expect(result).toBe(`:Movie${operator}Film${operator}Video`); + test("addLabelToken with multiple labels", () => { + const result = addLabelToken("Movie", "Film", "Video"); + expect(result).toBe(`:Movie&Film&Video`); }); }); diff --git a/src/utils/add-label-token.ts b/src/utils/add-label-token.ts index 2720c975..626f90fb 100644 --- a/src/utils/add-label-token.ts +++ b/src/utils/add-label-token.ts @@ -18,11 +18,11 @@ */ /** Generates a string with all the labels. For example `:Movie&Film` */ -export function addLabelToken(andToken: ":" | "&", ...labels: string[]): string { +export function addLabelToken(...labels: string[]): string { const firstLabel = labels.shift(); if (!firstLabel) return ""; - const extraLabels = labels.map((label) => `${andToken}${label}`).join(""); + const extraLabels = labels.map((label) => `&${label}`).join(""); return `:${firstLabel}${extraLabels}`; } diff --git a/tests/build-config/label-operator.test.ts b/tests/build-config/label-operator.test.ts deleted file mode 100644 index f807b5e6..00000000 --- a/tests/build-config/label-operator.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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 Cypher from "../../src"; -import { TestClause } from "../../src/utils/TestClause"; - -describe.each([":", "&"] as const)("Config.labelOperator", (labelOperator) => { - test("Pattern", () => { - const node = new Cypher.Node(); - const query = new Cypher.Match(new Cypher.Pattern(node, { labels: ["Movie", "Film"] })); - - const queryResult = new TestClause(query).build({ - labelOperator, - }); - - expect(queryResult.cypher).toBe(`MATCH (this0:Movie${labelOperator}Film)`); - }); - - test("hasLabel", () => { - const node = new Cypher.Node(); - const query = new Cypher.Match(new Cypher.Pattern(node, { labels: ["Movie"] })).where( - node.hasLabels("Movie", "Film") - ); - - const queryResult = new TestClause(query).build({ - labelOperator, - }); - - expect(queryResult.cypher).toBe(`MATCH (this0:Movie) -WHERE this0:Movie${labelOperator}Film`); - }); -}); diff --git a/tests/issues/479.test.ts b/tests/issues/479.test.ts index b9ca5ad8..bd9ffd2e 100644 --- a/tests/issues/479.test.ts +++ b/tests/issues/479.test.ts @@ -116,7 +116,7 @@ RETURN count(this1)" expect(queryResult.cypher).toMatchInlineSnapshot(` "WITH $param0 AS var0 -MATCH (this1:$(var0):\`normal$Label\`:$($param1)) +MATCH (this1:$(var0)&\`normal$Label\`&$($param1)) RETURN count(this1)" `);