diff --git a/.changeset/soft-socks-count.md b/.changeset/soft-socks-count.md new file mode 100644 index 0000000000..f1d8652af1 --- /dev/null +++ b/.changeset/soft-socks-count.md @@ -0,0 +1,22 @@ +--- +"@neo4j/graphql": patch +--- + +Add `addVersionPrefix` to `cypherQueryOptions` in context to add a Cypher version with `CYPHER` before each query: + +```js +{ + cypherQueryOptions: { + addVersionPrefix: true, + }, +} +``` + +This prepends all Cypher queries with a `CYPHER [version]` statement: + +```cypher +CYPHER 5 +MATCH (this:Movie) +WHERE this.title = $param0 +RETURN this { .title } AS this +``` diff --git a/packages/graphql/src/classes/Executor.ts b/packages/graphql/src/classes/Executor.ts index 8c03f75417..c8121c5d62 100644 --- a/packages/graphql/src/classes/Executor.ts +++ b/packages/graphql/src/classes/Executor.ts @@ -40,6 +40,7 @@ import { import { debugCypherAndParams } from "../debug/debug-cypher-and-params"; import environment from "../environment"; import type { CypherQueryOptions } from "../types"; +import { isInArray } from "../utils/is-in-array"; import { Neo4jGraphQLAuthenticationError, Neo4jGraphQLConstraintValidationError, @@ -49,6 +50,8 @@ import { const debug = Debug(DEBUG_EXECUTE); +const SUPPORTED_CYPHER_VERSION = "5"; + interface DriverLike { session(config); } @@ -179,16 +182,34 @@ export class Executor { return error; } - private generateQuery(query: string): string { - if (this.cypherQueryOptions && Object.keys(this.cypherQueryOptions).length) { - const cypherQueryOptions = `CYPHER ${Object.entries(this.cypherQueryOptions) - .map(([key, value]) => `${key}=${value}`) - .join(" ")}`; + private addCypherOptionsToQuery(query: string): string { + const cypherVersion = this.getCypherVersionStatement(); + + const cypherQueryOptions = this.getCypherQueryOptionsStatement(); + + return `${cypherVersion}${cypherQueryOptions}${query}`; + } - return `${cypherQueryOptions}\n${query}`; + private getCypherVersionStatement(): string { + if (this.cypherQueryOptions?.addVersionPrefix) { + return `CYPHER ${SUPPORTED_CYPHER_VERSION}\n`; } + return ""; + } - return query; + private getCypherQueryOptionsStatement(): string { + const ignoredCypherQueryOptions: Array = ["addVersionPrefix"]; + const cypherQueryOptions = Object.entries(this.cypherQueryOptions ?? []).filter(([key, _value]) => { + return !isInArray(ignoredCypherQueryOptions, key); + }); + if (cypherQueryOptions.length) { + return `CYPHER ${cypherQueryOptions + .map(([key, value]) => { + return `${key}=${value}`; + }) + .join(" ")}\n`; + } + return ""; } private getTransactionConfig(info?: GraphQLResolveInfo): TransactionConfig { @@ -290,7 +311,7 @@ export class Executor { parameters: Record, transaction: Transaction | ManagedTransaction ): Result { - const queryToRun = this.generateQuery(query); + const queryToRun = this.addCypherOptionsToQuery(query); debugCypherAndParams(debug, queryToRun, parameters); diff --git a/packages/graphql/src/translate/translate-resolve-reference.ts b/packages/graphql/src/translate/translate-resolve-reference.ts index 1fc547d3d9..b3658b5c19 100644 --- a/packages/graphql/src/translate/translate-resolve-reference.ts +++ b/packages/graphql/src/translate/translate-resolve-reference.ts @@ -18,11 +18,11 @@ */ import type Cypher from "@neo4j/cypher-builder"; -import type { Neo4jGraphQLTranslationContext } from "../types/neo4j-graphql-translation-context"; import Debug from "debug"; -import { QueryASTFactory } from "./queryAST/factory/QueryASTFactory"; -import type { EntityAdapter } from "../schema-model/entity/EntityAdapter"; import { DEBUG_TRANSLATE } from "../constants"; +import type { EntityAdapter } from "../schema-model/entity/EntityAdapter"; +import type { Neo4jGraphQLTranslationContext } from "../types/neo4j-graphql-translation-context"; +import { QueryASTFactory } from "./queryAST/factory/QueryASTFactory"; const debug = Debug(DEBUG_TRANSLATE); diff --git a/packages/graphql/src/types/index.ts b/packages/graphql/src/types/index.ts index fdb8639d2b..176cafdefc 100644 --- a/packages/graphql/src/types/index.ts +++ b/packages/graphql/src/types/index.ts @@ -276,6 +276,7 @@ export interface CypherQueryOptions { operatorEngine?: "default" | "interpreted" | "compiled"; interpretedPipesFallback?: "default" | "disabled" | "whitelisted_plans_only" | "all"; replan?: "default" | "force" | "skip"; + addVersionPrefix?: boolean; } /** Input field for graphql-compose */ diff --git a/packages/graphql/tests/integration/config-options/query-options.int.test.ts b/packages/graphql/tests/integration/config-options/query-options.int.test.ts index a8ea939ff9..a78a794d6f 100644 --- a/packages/graphql/tests/integration/config-options/query-options.int.test.ts +++ b/packages/graphql/tests/integration/config-options/query-options.int.test.ts @@ -87,4 +87,5 @@ describe("query options", () => { expect(result?.data?.[Movie.plural]).toEqual([{ id }, { id }, { id }]); }); + });