@neo4j/graphql
Version:
A GraphQL to Cypher query execution layer for Neo4j and JavaScript GraphQL implementations
220 lines • 11.2 kB
JavaScript
;
/*
* 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