UNPKG

@neo4j/graphql

Version:

A GraphQL to Cypher query execution layer for Neo4j and JavaScript GraphQL implementations

220 lines 11.2 kB
"use strict"; /* * 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. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const cypher_builder_1 = __importDefault(require("@neo4j/cypher-builder")); const case_where_1 = require("../utils/case-where"); const get_relationship_type_1 = require("../utils/get-relationship-type"); const check_authentication_1 = require("./authorization/check-authentication"); const create_authorization_after_and_params_1 = require("./authorization/compatibility/create-authorization-after-and-params"); const create_authorization_before_and_params_1 = require("./authorization/compatibility/create-authorization-before-and-params"); const build_clause_1 = require("./utils/build-clause"); const get_relationship_direction_1 = require("./utils/get-relationship-direction"); const create_connection_where_and_params_1 = __importDefault(require("./where/create-connection-where-and-params")); function createDisconnectAndParams({ withVars, value, varName, relationField, parentVar, refNodes, context, labelOverride, parentNode, parameterPrefix, isFirstLevel = true, }) { (0, check_authentication_1.checkAuthentication)({ context, node: parentNode, targetOperations: ["DELETE_RELATIONSHIP"] }); function createSubqueryContents(relatedNode, disconnect, index) { (0, check_authentication_1.checkAuthentication)({ context, node: relatedNode, targetOperations: ["DELETE_RELATIONSHIP"] }); const variableName = `${varName}${index}`; const { inStr, outStr } = (0, get_relationship_direction_1.getRelationshipDirection)(relationField); const relVarName = `${variableName}_rel`; const fieldType = (0, get_relationship_type_1.getRelationshipType)(relationField, context.features); const relTypeStr = `[${relVarName}:${fieldType}]`; const subquery = []; let params; const labels = relatedNode.getLabelString(context); const label = labelOverride ? `:${labelOverride}` : labels; subquery.push(`WITH ${withVars.join(", ")}`); subquery.push(`OPTIONAL MATCH (${parentVar})${inStr}${relTypeStr}${outStr}(${variableName}${label})`); const relationship = context.relationships.find((x) => x.properties === relationField.properties); const whereStrs = []; let aggregationWhere = false; if (disconnect.where) { try { const { cypher: whereCypher, subquery: preComputedSubqueries, params: whereParams, } = (0, create_connection_where_and_params_1.default)({ nodeVariable: variableName, whereInput: disconnect.where, node: relatedNode, context, relationshipVariable: relVarName, relationship, parameterPrefix: `${parameterPrefix}${relationField.typeMeta.array ? `[${index}]` : ""}.where.${relatedNode.name}`, }); if (whereCypher) { whereStrs.push(whereCypher); params = { ...params, ...whereParams }; if (preComputedSubqueries) { subquery.push(preComputedSubqueries); aggregationWhere = true; } } } catch { return { subquery: "", params: {} }; } } const authorizationBeforeAndParams = (0, create_authorization_before_and_params_1.createAuthorizationBeforeAndParams)({ context, nodes: [ { node: parentNode, variable: parentVar }, { node: relatedNode, variable: variableName }, ], operations: ["DELETE_RELATIONSHIP"], }); if (authorizationBeforeAndParams) { const { cypher, params: authWhereParams, subqueries } = authorizationBeforeAndParams; whereStrs.push(cypher); params = { ...params, ...authWhereParams }; if (subqueries) { subquery.push(subqueries); if (whereStrs.length) { subquery.push("WITH *"); } } } if (whereStrs.length) { const predicate = `${whereStrs.join(" AND ")}`; if (aggregationWhere) { const columns = [new cypher_builder_1.default.NamedVariable(relVarName), new cypher_builder_1.default.NamedVariable(variableName)]; const caseWhereClause = (0, case_where_1.caseWhere)(new cypher_builder_1.default.Raw(predicate), columns); const { cypher } = (0, build_clause_1.buildClause)(caseWhereClause, { context, prefix: "aggregateWhereFilter" }); subquery.push(cypher); } else { subquery.push(`WHERE ${predicate}`); } } subquery.push(`CALL (${variableName}, ${relVarName}, ${parentVar}) {`); // Trick to avoid execution on null values subquery.push(`\tWITH collect(${variableName}) as ${variableName}_x, ${relVarName}, ${parentVar}`); subquery.push(`\tUNWIND ${variableName}_x as x`); subquery.push(`\tDELETE ${relVarName}`); subquery.push(`}`); // TODO - relationship validation - Blocking, if this were to be enforced it would stop someone from 'reconnecting' if (disconnect.disconnect) { const disconnects = Array.isArray(disconnect.disconnect) ? disconnect.disconnect : [disconnect.disconnect]; disconnects.forEach((c) => { const reduced = Object.entries(c).reduce((r, [k, v]) => { const relField = relatedNode.relationFields.find((x) => k.startsWith(x.fieldName)); const newRefNodes = []; if (relField.union) { Object.keys(v).forEach((modelName) => { newRefNodes.push(context.nodes.find((x) => x.name === modelName)); }); } else if (relField.interface) { relField.interface.implementations.forEach((modelName) => { newRefNodes.push(context.nodes.find((x) => x.name === modelName)); }); } else { newRefNodes.push(context.nodes.find((x) => x.name === relField.typeMeta.name)); } newRefNodes.forEach((newRefNode, i) => { const recurse = createDisconnectAndParams({ withVars: [...withVars, variableName], value: relField.union ? v[newRefNode.name] : v, varName: `${variableName}_${k}${relField.union ? `_${newRefNode.name}` : ""}`, relationField: relField, parentVar: variableName, context, refNodes: [newRefNode], parentNode: relatedNode, parameterPrefix: `${parameterPrefix}${relField.typeMeta.array ? `[${i}]` : ""}.disconnect.${k}${relField.union ? `.${newRefNode.name}` : ""}`, labelOverride: relField.union ? newRefNode.name : "", isFirstLevel: false, }); r.disconnects.push(recurse[0]); r.params = { ...r.params, ...recurse[1] }; }); return r; }, { disconnects: [], params: {} }); subquery.push(reduced.disconnects.join("\n")); params = { ...params, ...reduced.params }; }); } const authorizationAfterAndParams = (0, create_authorization_after_and_params_1.createAuthorizationAfterAndParams)({ context, nodes: [ { node: parentNode, variable: parentVar }, { node: relatedNode, variable: variableName }, ], operations: ["DELETE_RELATIONSHIP"], }); if (authorizationAfterAndParams) { const { cypher, params: authWhereParams, subqueries } = authorizationAfterAndParams; if (cypher) { if (subqueries) { subquery.push(`WITH *`); subquery.push(`${subqueries}`); subquery.push(`WITH *`); } else { subquery.push(`WITH ${[...withVars, variableName].join(", ")}`); } subquery.push(`WHERE ${cypher}`); params = { ...params, ...authWhereParams }; } } subquery.push(`RETURN count(*) AS disconnect_${varName}_${relatedNode.name}`); return { subquery: subquery.join("\n"), params }; } function reducer(res, disconnect, index) { if (isFirstLevel) { res.disconnects.push(`WITH ${withVars.join(", ")}`); } const inner = []; if (relationField.interface) { const subqueries = []; refNodes.forEach((refNode) => { const subquery = createSubqueryContents(refNode, disconnect, index); if (subquery.subquery) { subqueries.push(subquery.subquery); res.params = { ...res.params, ...subquery.params }; } }); if (subqueries.length > 0) { inner.push(subqueries.join("\n}\nCALL(*) {\n\t")); } } else { const subquery = createSubqueryContents(refNodes[0], disconnect, index); inner.push(subquery.subquery); res.params = { ...res.params, ...subquery.params }; } if (inner.length > 0) { res.disconnects.push("CALL(*) {"); res.disconnects.push(...inner); res.disconnects.push("}"); } return res; } const { disconnects, params } = (relationField.typeMeta.array ? value : [value]).reduce(reducer, { disconnects: [], params: {}, }); return [disconnects.join("\n"), params]; } exports.default = createDisconnectAndParams; //# sourceMappingURL=create-disconnect-and-params.js.map