Skip to content

Commit

Permalink
Merge pull request #5908 from mjfwebb/7x-revert-create-always-on-connect
Browse files Browse the repository at this point in the history
Revert "Merge pull request #5892 from mjfwebb/7x-connect-always-creat…
  • Loading branch information
mjfwebb authored Dec 20, 2024
2 parents ed611f5 + 30651f0 commit a650bb0
Show file tree
Hide file tree
Showing 36 changed files with 305 additions and 322 deletions.
5 changes: 0 additions & 5 deletions .changeset/thick-dogs-provide.md

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ describe("createConnectAndParams", () => {
WITH connectedNodes, parentNodes
UNWIND parentNodes as this
UNWIND connectedNodes as this0_node
CREATE (this)-[:\`SIMILAR\`]->(this0_node)
MERGE (this)-[:\`SIMILAR\`]->(this0_node)
}
}
WITH this, this0_node
Expand All @@ -153,7 +153,7 @@ describe("createConnectAndParams", () => {
WITH connectedNodes, parentNodes
UNWIND parentNodes as this0_node
UNWIND connectedNodes as this0_node_similarMovies0_node
CREATE (this0_node)-[:\`SIMILAR\`]->(this0_node_similarMovies0_node)
MERGE (this0_node)-[:\`SIMILAR\`]->(this0_node_similarMovies0_node)
}
}
WITH this, this0_node, this0_node_similarMovies0_node
Expand Down
33 changes: 32 additions & 1 deletion packages/graphql/src/translate/create-connect-and-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { asArray } from "../utils/utils";
import { checkAuthentication } from "./authorization/check-authentication";
import { createAuthorizationAfterAndParams } from "./authorization/compatibility/create-authorization-after-and-params";
import { createAuthorizationBeforeAndParams } from "./authorization/compatibility/create-authorization-before-and-params";
import { createRelationshipValidationString } from "./create-relationship-validation-string";
import { createSetRelationshipProperties } from "./create-set-relationship-properties";
import { filterMetaVariable } from "./subscriptions/filter-meta-variable";
import { createWhereNodePredicate } from "./where/create-where-predicate";
Expand All @@ -58,6 +59,7 @@ function createConnectAndParams({
callbackBucket,
labelOverride,
parentNode,
includeRelationshipValidation,
isFirstLevel = true,
source,
indexPrefix,
Expand All @@ -72,6 +74,7 @@ function createConnectAndParams({
refNodes: Node[];
labelOverride?: string;
parentNode: Node;
includeRelationshipValidation?: boolean;
isFirstLevel?: boolean;
source: "CREATE" | "UPDATE" | "CONNECT";
indexPrefix?: string;
Expand All @@ -92,6 +95,7 @@ function createConnectAndParams({
const inStr = relationField.direction === "IN" ? "<-" : "-";
const outStr = relationField.direction === "OUT" ? "->" : "-";
const relTypeStr = `[${relationField.properties ? relationshipName : ""}:${relationField.type}]`;
const isOverwriteNotAllowed = connect.overwrite === false;

const subquery: string[] = [];
const labels = relatedNode.getLabelString(context);
Expand Down Expand Up @@ -170,7 +174,8 @@ function createConnectAndParams({
subquery.push("\t\t\tWITH connectedNodes, parentNodes"); //
subquery.push(`\t\t\tUNWIND parentNodes as ${parentVar}`);
subquery.push(`\t\t\tUNWIND connectedNodes as ${nodeName}`);
subquery.push(`\t\t\tCREATE (${parentVar})${inStr}${relTypeStr}${outStr}(${nodeName})`);
const connectOperator = isOverwriteNotAllowed ? "CREATE" : "MERGE";
subquery.push(`\t\t\t${connectOperator} (${parentVar})${inStr}${relTypeStr}${outStr}(${nodeName})`);

if (relationField.properties) {
const relationship = context.relationships.find(
Expand Down Expand Up @@ -210,6 +215,31 @@ function createConnectAndParams({

const innerMetaStr = "";

if (includeRelationshipValidation || isOverwriteNotAllowed) {
const relValidationStrs: string[] = [];
const matrixItems = [
[parentNode, parentVar],
[relatedNode, nodeName],
] as [Node, string][];

matrixItems.forEach((mi) => {
const relValidationStr = createRelationshipValidationString({
node: mi[0],
context,
varName: mi[1],
...(isOverwriteNotAllowed && { relationshipFieldNotOverwritable: relationField.fieldName }),
});
if (relValidationStr) {
relValidationStrs.push(relValidationStr);
}
});

if (relValidationStrs.length) {
subquery.push(`\tWITH ${[...filterMetaVariable(withVars), nodeName].join(", ")}${innerMetaStr}`);
subquery.push(relValidationStrs.join("\n"));
}
}

subquery.push(`WITH ${[...filterMetaVariable(withVars), nodeName].join(", ")}${innerMetaStr}`);

if (connect.connect) {
Expand Down Expand Up @@ -245,6 +275,7 @@ function createConnectAndParams({
refNodes: [newRefNode],
parentNode: relatedNode,
labelOverride: relField.union ? newRefNode.name : "",
includeRelationshipValidation: true,
isFirstLevel: false,
source: "CONNECT",
});
Expand Down
23 changes: 23 additions & 0 deletions packages/graphql/src/translate/create-create-and-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
createAuthorizationAfterAndParamsField,
} from "./authorization/compatibility/create-authorization-after-and-params";
import createConnectAndParams from "./create-connect-and-params";
import { createRelationshipValidationString } from "./create-relationship-validation-string";
import { createSetRelationshipProperties } from "./create-set-relationship-properties";
import { assertNonAmbiguousUpdate } from "./utils/assert-non-ambiguous-update";
import { addCallbackAndSetParam } from "./utils/callback-utils";
Expand Down Expand Up @@ -56,6 +57,7 @@ function createCreateAndParams({
context,
callbackBucket,
withVars,
includeRelationshipValidation,
topLevelNodeVariable,
authorizationPrefix = [0],
}: {
Expand All @@ -65,6 +67,7 @@ function createCreateAndParams({
context: Neo4jGraphQLTranslationContext;
callbackBucket: CallbackBucket;
withVars: string[];
includeRelationshipValidation?: boolean;
topLevelNodeVariable?: string;
//used to build authorization variable in auth subqueries
authorizationPrefix?: number[];
Expand Down Expand Up @@ -141,6 +144,7 @@ function createCreateAndParams({
node: refNode,
varName: nodeName,
withVars: [...withVars, nodeName],
includeRelationshipValidation: false,
topLevelNodeVariable,
authorizationPrefix: [...authorizationPrefix, reducerIndex, createIndex, refNodeIndex],
});
Expand Down Expand Up @@ -180,6 +184,16 @@ function createCreateAndParams({
}
res.meta.authorizationPredicates.push(...authorizationPredicates);
}

const relationshipValidationStr = createRelationshipValidationString({
node: refNode,
context,
varName: nodeName,
});
if (relationshipValidationStr) {
res.creates.push(`WITH *`);
res.creates.push(relationshipValidationStr);
}
});
}

Expand Down Expand Up @@ -317,6 +331,15 @@ function createCreateAndParams({
params = { ...params, ...authParams };
}

if (includeRelationshipValidation) {
const str = createRelationshipValidationString({ node, context, varName });

if (str) {
creates.push(`WITH *`);
creates.push(str);
}
}

return { create: creates.join("\n"), params, authorizationPredicates, authorizationSubqueries };
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* 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 { Node } from "../classes";
import { RELATIONSHIP_REQUIREMENT_PREFIX } from "../constants";
import type { Neo4jGraphQLTranslationContext } from "../types/neo4j-graphql-translation-context";

export function createRelationshipValidationString({
node,
context,
varName,
relationshipFieldNotOverwritable,
}: {
node: Node;
context: Neo4jGraphQLTranslationContext;
varName: string;
relationshipFieldNotOverwritable?: string;
}): string {
const strs: string[] = [];

node.relationFields.forEach((field) => {
const isArray = field.typeMeta.array;
const isUnionOrInterface = Boolean(field.union) || Boolean(field.interface);
if (isUnionOrInterface) {
return;
}

const toNode = context.nodes.find((n) => n.name === field.typeMeta.name) as Node;
const inStr = field.direction === "IN" ? "<-" : "-";
const outStr = field.direction === "OUT" ? "->" : "-";
const relVarname = `${varName}_${field.fieldName}_${toNode.name}_unique`;

let predicate: string;
let errorMsg: string;
let subQuery: string | undefined;
if (isArray) {
if (relationshipFieldNotOverwritable === field.fieldName) {
predicate = `c = 1`;
errorMsg = `${RELATIONSHIP_REQUIREMENT_PREFIX}${node.name}.${field.fieldName} required exactly once for a specific ${toNode.name}`;
subQuery = [
`CALL {`,
`\tWITH ${varName}`,
`\tMATCH (${varName})${inStr}[${relVarname}:${field.type}]${outStr}(other${toNode.getLabelString(
context
)})`,
`\tWITH count(${relVarname}) as c, other`,
`\tWHERE apoc.util.validatePredicate(NOT (${predicate}), '${errorMsg}', [0])`,
`\tRETURN collect(c) AS ${relVarname}_ignored`,
`}`,
].join("\n");
}
} else {
predicate = `c = 1`;
errorMsg = `${RELATIONSHIP_REQUIREMENT_PREFIX}${node.name}.${field.fieldName} required exactly once`;
if (!field.typeMeta.required) {
predicate = `c <= 1`;
errorMsg = `${RELATIONSHIP_REQUIREMENT_PREFIX}${node.name}.${field.fieldName} must be less than or equal to one`;
}

subQuery = [
`CALL {`,
`\tWITH ${varName}`,
`\tMATCH (${varName})${inStr}[${relVarname}:${field.type}]${outStr}(${toNode.getLabelString(context)})`,
`\tWITH count(${relVarname}) as c`,
`\tWHERE apoc.util.validatePredicate(NOT (${predicate}), '${errorMsg}', [0])`,
`\tRETURN c AS ${relVarname}_ignored`,
`}`,
].join("\n");
}

if (subQuery) {
strs.push(subQuery);
}
});

return strs.join("\n");
}
31 changes: 30 additions & 1 deletion packages/graphql/src/translate/create-update-and-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import createConnectAndParams from "./create-connect-and-params";
import createCreateAndParams from "./create-create-and-params";
import createDeleteAndParams from "./create-delete-and-params";
import createDisconnectAndParams from "./create-disconnect-and-params";
import { createRelationshipValidationString } from "./create-relationship-validation-string";
import { createSetRelationshipProperties } from "./create-set-relationship-properties";
import { assertNonAmbiguousUpdate } from "./utils/assert-non-ambiguous-update";
import { addCallbackAndSetParam } from "./utils/callback-utils";
Expand Down Expand Up @@ -72,6 +73,7 @@ export default function createUpdateAndParams({
context,
callbackBucket,
parameterPrefix,
includeRelationshipValidation,
}: {
parentVar: string;
updateInput: any;
Expand All @@ -82,6 +84,7 @@ export default function createUpdateAndParams({
context: Neo4jGraphQLTranslationContext;
callbackBucket: CallbackBucket;
parameterPrefix: string;
includeRelationshipValidation?: boolean;
}): [string, any] {
let hasAppliedTimeStamps = false;

Expand Down Expand Up @@ -302,6 +305,7 @@ export default function createUpdateAndParams({
parameterPrefix: `${parameterPrefix}.${key}${
relationField.union ? `.${refNode.name}` : ""
}${relationField.typeMeta.array ? `[${index}]` : ``}.update.node`,
includeRelationshipValidation: true,
});
res.params = { ...res.params, ...updateAndParams[1] };
innerUpdate.push(updateAndParams[0]);
Expand Down Expand Up @@ -424,6 +428,7 @@ export default function createUpdateAndParams({
callbackBucket,
varName: nodeName,
withVars: [...withVars, nodeName],
includeRelationshipValidation: false,
...createNodeInput,
});
subquery.push(nestedCreate);
Expand Down Expand Up @@ -459,6 +464,16 @@ export default function createUpdateAndParams({
subquery.push(
...getAuthorizationStatements(authorizationPredicates, authorizationSubqueries)
);

const relationshipValidationStr = createRelationshipValidationString({
node: refNode,
context,
varName: nodeName,
});
if (relationshipValidationStr) {
subquery.push(`WITH ${[...withVars, nodeName].join(", ")}`);
subquery.push(relationshipValidationStr);
}
});
}

Expand Down Expand Up @@ -609,6 +624,11 @@ export default function createUpdateAndParams({

const preUpdatePredicates = authorizationBeforeStrs;

const preArrayMethodValidationStr = "";
const relationshipValidationStr = includeRelationshipValidation
? createRelationshipValidationString({ node, context, varName })
: "";

if (meta.preArrayMethodValidationStrs.length) {
const nullChecks = meta.preArrayMethodValidationStrs.map((validationStr) => `${validationStr[0]} IS NULL`);
const propertyNames = meta.preArrayMethodValidationStrs.map((validationStr) => validationStr[1]);
Expand Down Expand Up @@ -648,7 +668,16 @@ export default function createUpdateAndParams({

const statements = strs;

return [[preUpdatePredicatesStr, ...statements, authorizationAfterStr].join("\n"), params];
return [
[
preUpdatePredicatesStr,
preArrayMethodValidationStr,
...statements,
authorizationAfterStr,
...(relationshipValidationStr ? [withStr, relationshipValidationStr] : []),
].join("\n"),
params,
];
}

function validateNonNullProperty(res: Res, varName: string, field: BaseField) {
Expand Down
1 change: 1 addition & 0 deletions packages/graphql/src/translate/translate-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export default async function translateCreate({
context,
varName,
withVars,
includeRelationshipValidation: true,
topLevelNodeVariable: varName,
callbackBucket,
});
Expand Down
Loading

0 comments on commit a650bb0

Please sign in to comment.