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

apoc.util.validate to apoc.util.validatePredicate #3672

Merged
merged 12 commits into from
Jul 18, 2023
Merged
Show file tree
Hide file tree
Changes from 7 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
48 changes: 1 addition & 47 deletions packages/graphql/src/classes/utils/verify-database.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

import type { Driver, Session } from "neo4j-driver";
import checkNeo4jCompat from "./verify-database";
import { REQUIRED_APOC_FUNCTIONS, REQUIRED_APOC_PROCEDURES, MIN_NEO4J_VERSION } from "../../constants";
import { REQUIRED_APOC_FUNCTIONS, MIN_NEO4J_VERSION } from "../../constants";
import { Neo4jDatabaseInfo } from "../Neo4jDatabaseInfo";
import type { Neo4jGraphQLSessionConfig } from "../Executor";

Expand All @@ -37,7 +37,6 @@ describe("checkNeo4jCompat", () => {
toObject: () => ({
version: minVersion,
functions: REQUIRED_APOC_FUNCTIONS,
procedures: REQUIRED_APOC_PROCEDURES,
}),
},
],
Expand Down Expand Up @@ -82,7 +81,6 @@ describe("checkNeo4jCompat", () => {
toObject: () => ({
version: invalidVersion,
functions: REQUIRED_APOC_FUNCTIONS,
procedures: REQUIRED_APOC_PROCEDURES,
}),
},
],
Expand Down Expand Up @@ -118,7 +116,6 @@ describe("checkNeo4jCompat", () => {
toObject: () => ({
version: invalidVersion,
functions: REQUIRED_APOC_FUNCTIONS,
procedures: REQUIRED_APOC_PROCEDURES,
}),
},
],
Expand Down Expand Up @@ -152,7 +149,6 @@ describe("checkNeo4jCompat", () => {
toObject: () => ({
version: "4.0-aura",
functions: REQUIRED_APOC_FUNCTIONS,
procedures: REQUIRED_APOC_PROCEDURES,
}),
},
],
Expand Down Expand Up @@ -185,7 +181,6 @@ describe("checkNeo4jCompat", () => {
toObject: () => ({
version: minVersion,
functions: [],
procedures: REQUIRED_APOC_PROCEDURES,
}),
},
],
Expand All @@ -211,44 +206,6 @@ describe("checkNeo4jCompat", () => {
);
});

test("should throw missing APOC procedures", async () => {
const minVersion = MIN_NEO4J_VERSION;

// @ts-ignore
const fakeSession: Session = {
// @ts-ignore
run: () => ({
records: [
{
toObject: () => ({
version: minVersion,
functions: REQUIRED_APOC_FUNCTIONS,
procedures: [],
}),
},
],
}),
// @ts-ignore
close: () => undefined,
};

// @ts-ignore
const fakeDriver: Driver = {
// @ts-ignore
session: () => fakeSession,
// @ts-ignore
verifyConnectivity: () => undefined,
};

await expect(
checkNeo4jCompat({ driver: fakeDriver, dbInfo: new Neo4jDatabaseInfo(minVersion) })
).rejects.toThrow(
`Encountered the following DBMS compatiblility issues:\nMissing APOC procedures: [ ${REQUIRED_APOC_PROCEDURES.join(
", "
)} ]`
);
});

test("should throw no errors with valid DB", async () => {
const minVersion = MIN_NEO4J_VERSION;

Expand All @@ -261,7 +218,6 @@ describe("checkNeo4jCompat", () => {
toObject: () => ({
version: minVersion,
functions: REQUIRED_APOC_FUNCTIONS,
procedures: REQUIRED_APOC_PROCEDURES,
}),
},
],
Expand Down Expand Up @@ -293,7 +249,6 @@ describe("checkNeo4jCompat", () => {
toObject: () => ({
version: "20.1.1",
functions: REQUIRED_APOC_FUNCTIONS,
procedures: REQUIRED_APOC_PROCEDURES,
}),
},
],
Expand Down Expand Up @@ -325,7 +280,6 @@ describe("checkNeo4jCompat", () => {
toObject: () => ({
version: "4.1.10",
functions: REQUIRED_APOC_FUNCTIONS,
procedures: REQUIRED_APOC_PROCEDURES,
}),
},
],
Expand Down
16 changes: 5 additions & 11 deletions packages/graphql/src/classes/utils/verify-database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import type { Driver } from "neo4j-driver";
import type { Neo4jDatabaseInfo } from "../Neo4jDatabaseInfo";
import { verifyFunctions } from "./verify-functions";
import { verifyProcedures } from "./verify-procedures";
import { verifyVersion } from "./verify-version";
import type { Neo4jGraphQLSessionConfig } from "../Executor";

Expand All @@ -45,16 +44,11 @@ async function checkNeo4jCompat({
errors.push((e as Error).message);
}

const verificationResults = await Promise.allSettled([
verifyFunctions(sessionFactory),
verifyProcedures(sessionFactory),
]);

verificationResults.forEach((v) => {
if (v.status === "rejected") {
errors.push((v.reason as Error).message);
}
});
try {
await verifyFunctions(sessionFactory);
} catch (e) {
errors.push((e as Error).message);
}

if (errors.length) {
throw new Error(`Encountered the following DBMS compatiblility issues:\n${errors.join("\n")}`);
Expand Down
45 changes: 0 additions & 45 deletions packages/graphql/src/classes/utils/verify-procedures.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/graphql/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ export const AUTH_FORBIDDEN_ERROR = "@neo4j/graphql/FORBIDDEN";
export const AUTH_UNAUTHENTICATED_ERROR = "@neo4j/graphql/UNAUTHENTICATED";
export const MIN_NEO4J_VERSION = "4.4";
export const REQUIRED_APOC_FUNCTIONS = ["apoc.util.validatePredicate", "apoc.date.convertFormat"];
export const REQUIRED_APOC_PROCEDURES = ["apoc.util.validate"];
export const AUTHORIZATION_UNAUTHENTICATED = "Unauthenticated";
export const DEBUG_ALL = `${DEBUG_PREFIX}:*`;
export const DEBUG_AUTH = `${DEBUG_PREFIX}:auth`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function createRelationshipValidationString({
context
)})`,
`\tWITH count(${relVarname}) as c, other`,
`\tCALL apoc.util.validate(NOT (${predicate}), '${errorMsg}', [0])`,
`\tWHERE apoc.util.validatePredicate(NOT (${predicate}), '${errorMsg}', [0])`,
`\tRETURN collect(c) AS ${relVarname}_ignored`,
`}`,
].join("\n");
Expand All @@ -78,7 +78,7 @@ function createRelationshipValidationString({
`\tWITH ${varName}`,
`\tMATCH (${varName})${inStr}[${relVarname}:${field.type}]${outStr}(${toNode.getLabelString(context)})`,
`\tWITH count(${relVarname}) as c`,
`\tCALL apoc.util.validate(NOT (${predicate}), '${errorMsg}', [0])`,
`\tWHERE apoc.util.validatePredicate(NOT (${predicate}), '${errorMsg}', [0])`,
`\tRETURN c AS ${relVarname}_ignored`,
`}`,
].join("\n");
Expand Down
58 changes: 17 additions & 41 deletions packages/graphql/src/translate/create-update-and-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import type { BaseField, Context } from "../types";
import createConnectAndParams from "./create-connect-and-params";
import createDisconnectAndParams from "./create-disconnect-and-params";
import createCreateAndParams from "./create-create-and-params";
import { AUTH_FORBIDDEN_ERROR, META_CYPHER_VARIABLE, META_OLD_PROPS_CYPHER_VARIABLE } from "../constants";
import { META_CYPHER_VARIABLE, META_OLD_PROPS_CYPHER_VARIABLE } from "../constants";
import createDeleteAndParams from "./create-delete-and-params";
import createSetRelationshipProperties from "./create-set-relationship-properties";
import createConnectionWhereAndParams from "./where/create-connection-where-and-params";
Expand Down Expand Up @@ -54,8 +54,6 @@ interface Res {

interface UpdateMeta {
preArrayMethodValidationStrs: [string, string][];
preAuthStrs: string[];
postAuthStrs: string[];
authorizationBeforeSubqueries: string[];
authorizationBeforePredicates: string[];
authorizationAfterSubqueries: string[];
Expand Down Expand Up @@ -664,8 +662,6 @@ export default function createUpdateAndParams({
strs: [],
meta: {
preArrayMethodValidationStrs: [],
preAuthStrs: [],
postAuthStrs: [],
authorizationBeforeSubqueries: [],
authorizationBeforePredicates: [],
authorizationAfterSubqueries: [],
Expand All @@ -676,9 +672,6 @@ export default function createUpdateAndParams({
const { strs, meta } = reducedUpdate;
let params = reducedUpdate.params;

let preAuthStrs: string[] = [];
let postAuthStrs: string[] = [];

const authorizationBeforeStrs = meta.authorizationBeforePredicates;
const authorizationBeforeSubqueries = meta.authorizationBeforeSubqueries;
const authorizationAfterStrs = meta.authorizationAfterPredicates;
Expand All @@ -705,52 +698,37 @@ export default function createUpdateAndParams({
}
}

if (meta) {
preAuthStrs = [...preAuthStrs, ...meta.preAuthStrs];
postAuthStrs = [...postAuthStrs, ...meta.postAuthStrs];
}
const preUpdatePredicates = authorizationBeforeStrs;

let preArrayMethodValidationStr = "";
let preAuthStr = "";
let postAuthStr = "";
const preArrayMethodValidationStr = "";
const relationshipValidationStr = includeRelationshipValidation
? createRelationshipValidationStr({ node, context, varName })
: "";

const forbiddenString = `"${AUTH_FORBIDDEN_ERROR}"`;

if (meta.preArrayMethodValidationStrs.length) {
const nullChecks = meta.preArrayMethodValidationStrs.map((validationStr) => `${validationStr[0]} IS NULL`);
const propertyNames = meta.preArrayMethodValidationStrs.map((validationStr) => validationStr[1]);

preArrayMethodValidationStr = `CALL apoc.util.validate(${nullChecks.join(" OR ")}, "${pluralize(
"Property",
propertyNames.length
)} ${propertyNames.map(() => "%s").join(", ")} cannot be NULL", [${wrapStringInApostrophes(propertyNames).join(
", "
)}])`;
}

if (preAuthStrs.length) {
const apocStr = `CALL apoc.util.validate(NOT (${preAuthStrs.join(" AND ")}), ${forbiddenString}, [0])`;
preAuthStr = `${withStr}\n${apocStr}`;
}

if (postAuthStrs.length) {
const apocStr = `CALL apoc.util.validate(NOT (${postAuthStrs.join(" AND ")}), ${forbiddenString}, [0])`;
postAuthStr = `${withStr}\n${apocStr}`;
preUpdatePredicates.push(
`apoc.util.validatePredicate(${nullChecks.join(" OR ")}, "${pluralize(
"Property",
propertyNames.length
)} ${propertyNames.map(() => "%s").join(", ")} cannot be NULL", [${wrapStringInApostrophes(
propertyNames
).join(", ")}])`
);
}

let authorizationBeforeStr = "";
let preUpdatePredicatesStr = "";
let authorizationAfterStr = "";

if (authorizationBeforeStrs.length) {
if (preUpdatePredicates.length) {
if (authorizationBeforeSubqueries.length) {
authorizationBeforeStr = `${withStr}\n${authorizationBeforeSubqueries.join(
preUpdatePredicatesStr = `${withStr}\n${authorizationBeforeSubqueries.join(
"\n"
)}\nWITH *\nWHERE ${authorizationBeforeStrs.join(" AND ")}`;
)}\nWITH *\nWHERE ${preUpdatePredicates.join(" AND ")}`;
} else {
authorizationBeforeStr = `${withStr}\nWHERE ${authorizationBeforeStrs.join(" AND ")}`;
preUpdatePredicatesStr = `${withStr}\nWHERE ${preUpdatePredicates.join(" AND ")}`;
}
}

Expand All @@ -775,12 +753,10 @@ export default function createUpdateAndParams({
}
return [
[
authorizationBeforeStr,
preAuthStr,
preUpdatePredicatesStr,
preArrayMethodValidationStr,
...statements,
authorizationAfterStr,
postAuthStr,
...(relationshipValidationStr ? [withStr, relationshipValidationStr] : []),
].join("\n"),
params,
Expand Down
33 changes: 25 additions & 8 deletions packages/graphql/src/translate/translate-update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,10 +208,23 @@ export default async function translateUpdate({
if (!relationField.typeMeta.array) {
const inStr = relationField.direction === "IN" ? "<-" : "-";
const outStr = relationField.direction === "OUT" ? "->" : "-";

const validatePredicates: string[] = [];
refNodes.forEach((refNode) => {
const validateRelationshipExistence = `CALL apoc.util.validate(EXISTS((${varName})${inStr}[:${relationField.type}]${outStr}(:${refNode.name})),'Relationship field "%s.%s" cannot have more than one node linked',["${relationField.connectionPrefix}","${relationField.fieldName}"])`;
connectStrs.push(validateRelationshipExistence);
const validateRelationshipExistence = `EXISTS((${varName})${inStr}[:${relationField.type}]${outStr}(:${refNode.name}))`;
validatePredicates.push(validateRelationshipExistence);
});

if (validatePredicates.length) {
connectStrs.push("WITH *");
connectStrs.push(
`WHERE apoc.util.validatePredicate(${validatePredicates.join(
" OR "
)},'Relationship field "%s.%s" cannot have more than one node linked',["${
relationField.connectionPrefix
}","${relationField.fieldName}"])`
);
}
}

const connectAndParams = createConnectAndParams({
Expand Down Expand Up @@ -339,23 +352,27 @@ export default async function translateUpdate({
const relTypeStr = `[${relationVarName}:${relationField.type}]`;

if (!relationField.typeMeta.array) {
// const validateRelationshipExistence = `CALL apoc.util.validate(EXISTS((${varName})${inStr}[:${relationField.type}]${outStr}(:${refNode.name})),'Relationship field "%s.%s" cannot have more than one node linked',["${relationField.connectionPrefix}","${relationField.fieldName}"])`;
// createStrs.push(validateRelationshipExistence);
createStrs.push("WITH *");

const validatePredicateTemplate = (condition: string) =>
`WHERE apoc.util.validatePredicate(${condition},'Relationship field "%s.%s" cannot have more than one node linked',["${relationField.connectionPrefix}","${relationField.fieldName}"])`;

const singleCardinalityValidationTemplate = (nodeName) =>
`CALL apoc.util.validate(EXISTS((${varName})${inStr}[:${relationField.type}]${outStr}(:${nodeName})),'Relationship field "%s.%s" cannot have more than one node linked',["${relationField.connectionPrefix}","${relationField.fieldName}"])`;
`EXISTS((${varName})${inStr}[:${relationField.type}]${outStr}(:${nodeName}))`;

if (relationField.union && relationField.union.nodes) {
const validateRelationshipExistence = relationField.union.nodes.map(
singleCardinalityValidationTemplate
);
createStrs.push(...validateRelationshipExistence);
createStrs.push(validatePredicateTemplate(validateRelationshipExistence.join(" OR ")));
} else if (relationField.interface && relationField.interface.implementations) {
const validateRelationshipExistence = relationField.interface.implementations.map(
singleCardinalityValidationTemplate
);
createStrs.push(...validateRelationshipExistence);
createStrs.push(validatePredicateTemplate(validateRelationshipExistence.join(" OR ")));
} else {
const validateRelationshipExistence = singleCardinalityValidationTemplate(refNode.name);
createStrs.push(validateRelationshipExistence);
createStrs.push(validatePredicateTemplate(validateRelationshipExistence));
}
}

Expand Down
Loading