From 4f05a629ccc6119522db165aaa07369ecc1ad5c6 Mon Sep 17 00:00:00 2001 From: angrykoala Date: Wed, 14 Aug 2024 17:41:09 +0100 Subject: [PATCH] Fix #5467 --- .changeset/tough-parents-hear.md | 5 ++ .../utils/replace-arguments-in-statement.ts | 7 +- .../tests/integration/issues/5467.int.test.ts | 82 +++++++++++++++++ .../graphql/tests/tck/issues/5467.test.ts | 87 +++++++++++++++++++ 4 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 .changeset/tough-parents-hear.md create mode 100644 packages/graphql/tests/integration/issues/5467.int.test.ts create mode 100644 packages/graphql/tests/tck/issues/5467.test.ts diff --git a/.changeset/tough-parents-hear.md b/.changeset/tough-parents-hear.md new file mode 100644 index 0000000000..be5fdb1dbb --- /dev/null +++ b/.changeset/tough-parents-hear.md @@ -0,0 +1,5 @@ +--- +"@neo4j/graphql": patch +--- + +Fix problem with parameters colliding in the cypher directive diff --git a/packages/graphql/src/translate/queryAST/utils/replace-arguments-in-statement.ts b/packages/graphql/src/translate/queryAST/utils/replace-arguments-in-statement.ts index 6d040d546c..ffa558c791 100644 --- a/packages/graphql/src/translate/queryAST/utils/replace-arguments-in-statement.ts +++ b/packages/graphql/src/translate/queryAST/utils/replace-arguments-in-statement.ts @@ -35,20 +35,19 @@ export function replaceArgumentsInStatement({ return statement; } const reg = new RegExp(`\\$(${argNames.join("|")})\\b`, "g"); - - const paramsRecord: Record = {}; + const paramsRecord = new Map(); return statement.replaceAll(reg, (_match, arg): string => { const value = rawArguments[arg]; if (value === undefined || value === null) { return "NULL"; } else { - const storedParamName = paramsRecord[value]; + const storedParamName = paramsRecord.get(value); if (storedParamName) { return storedParamName; } const paramName = new Cypher.Param(value).getCypher(env); - paramsRecord[value] = paramName; + paramsRecord.set(value, paramName); return paramName; } }); diff --git a/packages/graphql/tests/integration/issues/5467.int.test.ts b/packages/graphql/tests/integration/issues/5467.int.test.ts new file mode 100644 index 0000000000..64c8bd980c --- /dev/null +++ b/packages/graphql/tests/integration/issues/5467.int.test.ts @@ -0,0 +1,82 @@ +/* + * 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 { UniqueType } from "../../utils/graphql-types"; +import { TestHelper } from "../../utils/tests-helper"; + +describe("https://github.com/neo4j/graphql/issues/5467", () => { + const testHelper = new TestHelper(); + + let Test: UniqueType; + + beforeAll(async () => { + Test = testHelper.createUniqueType("Test"); + + const typeDefs = /* GraphQL */ ` + type ${Test} { + name: String! + groups: [String!] + } + + type Mutation { + mergeTest(name: String!, groups: [String!]): ${Test} + @cypher( + statement: """ + MERGE (t:Test {name: $name}) SET t.groups = $groups + return t + """ + columnName: "t" + ) + } + `; + await testHelper.initNeo4jGraphQL({ + typeDefs, + }); + }); + + afterAll(async () => { + await testHelper.close(); + }); + + test("custom Cypher should correctly interpret array parameters with a single item", async () => { + const query = /* GraphQL */ ` + mutation ($name: String!, $groups: [String!]) { + mergeTest(name: $name, groups: $groups) { + name + groups + } + } + `; + + const response = await testHelper.executeGraphQL(query, { + variableValues: { + name: "test", + groups: ["test"], + }, + }); + + expect(response.errors).toBeFalsy(); + expect(response.data).toEqual({ + mergeTest: { + name: "test", + groups: ["test"], + }, + }); + }); +}); diff --git a/packages/graphql/tests/tck/issues/5467.test.ts b/packages/graphql/tests/tck/issues/5467.test.ts new file mode 100644 index 0000000000..20e0c09a07 --- /dev/null +++ b/packages/graphql/tests/tck/issues/5467.test.ts @@ -0,0 +1,87 @@ +/* + * 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 { Neo4jGraphQL } from "../../../src"; +import { formatCypher, formatParams, translateQuery } from "../utils/tck-test-utils"; + +describe("https://github.com/neo4j/graphql/issues/5467", () => { + let typeDefs: string; + let neoSchema: Neo4jGraphQL; + + beforeAll(() => { + typeDefs = /* GraphQL */ ` + type Test { + name: String! + groups: [String!] + } + + type Mutation { + mergeTest(name: String!, groups: [String!]): Test + @cypher( + statement: """ + MERGE (t:Test {name: $name}) SET t.groups = $groups + return t + """ + columnName: "t" + ) + } + `; + + neoSchema = new Neo4jGraphQL({ + typeDefs, + }); + }); + + test("custom Cypher should correctly interpret array parameters with a single item", async () => { + const query = /* GraphQL */ ` + mutation ($name: String!, $groups: [String!]) { + mergeTest(name: $name, groups: $groups) { + name + groups + } + } + `; + + const result = await translateQuery(neoSchema, query, { + variableValues: { + name: "test", + groups: ["test"], + }, + }); + + expect(formatCypher(result.cypher)).toMatchInlineSnapshot(` + "CALL { + MERGE (t:Test {name: $param0}) SET t.groups = $param1 + return t + } + WITH t AS this0 + WITH this0 { .name, .groups } AS this0 + RETURN this0 AS this" + `); + + expect(formatParams(result.params)).toMatchInlineSnapshot(` + "{ + \\"param0\\": \\"test\\", + \\"param1\\": [ + \\"test\\" + ] + }" + `); + }); +});