@neo4j/graphql
Version:
A GraphQL to Cypher query execution layer for Neo4j and JavaScript GraphQL implementations
206 lines • 9.15 kB
JavaScript
"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 });
exports.ConnectOperation = void 0;
const cypher_builder_1 = __importDefault(require("@neo4j/cypher-builder"));
const utils_1 = require("../../../../utils/utils");
const check_authentication_1 = require("../../../authorization/check-authentication");
const create_node_from_entity_1 = require("../../utils/create-node-from-entity");
const is_concrete_entity_1 = require("../../utils/is-concrete-entity");
const wrap_subquery_in_calls_1 = require("../../utils/wrap-subquery-in-calls");
const ParamInputField_1 = require("../input-fields/ParamInputField");
const operations_1 = require("./operations");
class ConnectOperation extends operations_1.MutationOperation {
constructor({ target, relationship, selectionPattern, }) {
super();
this.authFilters = [];
this.sourceAuthFilters = [];
this.inputFields = new Map();
this.filters = [];
this.target = target;
this.relationship = relationship;
this.selectionPattern = selectionPattern;
}
getChildren() {
return (0, utils_1.filterTruthy)([
this.selectionPattern,
...this.filters,
...this.authFilters,
...this.inputFields.values(),
]);
}
print() {
return `${super.print()} <${this.target.name}>`;
}
addAuthFilters(...filter) {
this.authFilters.push(...filter);
}
addSourceAuthFilters(...filter) {
this.sourceAuthFilters.push(...filter);
}
/**
* Get and set field methods are utilities to remove duplicate fields between separate inputs
* TODO: This logic should be handled in the factory.
*/
getField(key, attachedTo) {
return this.inputFields.get(`${attachedTo}_${key}`);
}
addField(field, attachedTo) {
if (!this.inputFields.has(field.name)) {
this.inputFields.set(`${attachedTo}_${field.name}`, field);
}
}
addFilters(...filters) {
this.filters.push(...filters);
}
getAuthorizationSubqueries(_context) {
const nestedContext = this.nestedContext;
if (!nestedContext) {
throw new Error("Error parsing query, nested context not available, need to call transpile first. Please contact support");
}
return [...this.inputFields.values()].flatMap((inputField) => {
return inputField.getAuthorizationSubqueries(nestedContext);
});
}
transpile(context) {
if (!context.hasTarget()) {
throw new Error("No parent node found!");
}
const { nestedContext } = this.selectionPattern.apply(context);
this.nestedContext = nestedContext;
(0, check_authentication_1.checkEntityAuthentication)({
context: nestedContext.neo4jGraphQLContext,
entity: this.target.entity,
targetOperations: ["CREATE_RELATIONSHIP"],
});
if ((0, is_concrete_entity_1.isConcreteEntity)(this.relationship.source)) {
(0, check_authentication_1.checkEntityAuthentication)({
context: nestedContext.neo4jGraphQLContext,
entity: this.relationship.source.entity,
targetOperations: ["CREATE_RELATIONSHIP"],
});
}
this.inputFields.forEach((field) => {
if (field.attachedTo === "node" && field instanceof ParamInputField_1.ParamInputField) {
(0, check_authentication_1.checkEntityAuthentication)({
context: nestedContext.neo4jGraphQLContext,
entity: this.target.entity,
targetOperations: ["CREATE_RELATIONSHIP"],
field: field.name,
});
}
});
const matchPattern = new cypher_builder_1.default.Pattern(nestedContext.target, {
labels: (0, create_node_from_entity_1.getEntityLabels)(this.target, context.neo4jGraphQLContext),
});
const allFilters = [...this.authFilters, ...this.filters];
const filterSubqueries = (0, wrap_subquery_in_calls_1.wrapSubqueriesInCypherCalls)(nestedContext, allFilters, [nestedContext.target]);
const predicate = cypher_builder_1.default.and(...allFilters.map((f) => f.getPredicate(nestedContext)));
let matchClause;
if (filterSubqueries.length > 0) {
matchClause = cypher_builder_1.default.utils.concat(new cypher_builder_1.default.Match(matchPattern), ...filterSubqueries, new cypher_builder_1.default.With("*").where(predicate));
}
else {
matchClause = new cypher_builder_1.default.Match(matchPattern).where(predicate);
}
const relVar = new cypher_builder_1.default.Relationship();
const relDirection = this.relationship.cypherDirectionFromRelDirection();
const connectPattern = new cypher_builder_1.default.Pattern(context.target)
.related(relVar, { direction: relDirection, type: this.relationship.type })
.to(nestedContext.target);
const connectContext = context.push({ target: nestedContext.target, relationship: relVar });
const connectClause = new cypher_builder_1.default.Create(connectPattern);
const setParams = Array.from(this.inputFields.values()).flatMap((input) => {
return input.getSetParams(connectContext);
});
connectClause.set(...setParams);
const mutationSubqueries = Array.from(this.inputFields.values()).flatMap((input) => {
return input.getSubqueries(connectContext);
});
const clauses = cypher_builder_1.default.utils.concat(matchClause, ...this.getAuthorizationClauses(nestedContext), // THESE ARE "BEFORE" AUTH
...mutationSubqueries, connectClause, ...this.getAuthorizationClausesAfter(nestedContext), // THESE ARE "AFTER" AUTH
...this.getSourceAuthorizationClausesAfter(context) // ONLY RUN "AFTER" AUTH ON THE SOURCE NODE
);
const callClause = new cypher_builder_1.default.Call(clauses, [context.target]);
return {
projectionExpr: context.returnVariable,
clauses: [callClause],
};
}
getAuthorizationClauses(context) {
const { subqueries, validations } = this.transpileAuthClauses(context);
if (!validations.length) {
return [];
}
return [...subqueries, ...validations];
}
getAuthorizationClausesAfter(context) {
const validationsAfter = [];
for (const authFilter of this.authFilters) {
const validationAfter = authFilter.getValidation(context, "AFTER");
if (validationAfter) {
validationsAfter.push(validationAfter);
}
}
if (validationsAfter.length > 0) {
return [new cypher_builder_1.default.With("*"), ...validationsAfter];
}
return [];
}
getSourceAuthorizationClausesAfter(context) {
const validationsAfter = [];
for (const authFilter of this.sourceAuthFilters) {
const validationAfter = authFilter.getValidation(context, "AFTER");
if (validationAfter) {
validationsAfter.push(validationAfter);
}
}
if (validationsAfter.length > 0) {
return [new cypher_builder_1.default.With("*"), ...validationsAfter];
}
return [];
}
transpileAuthClauses(context) {
const selections = [];
const subqueries = [];
const predicates = [];
const validations = [];
for (const authFilter of this.authFilters) {
const extraSelections = authFilter.getSelection(context);
const authSubqueries = authFilter.getSubqueries(context);
const validation = authFilter.getValidation(context, "BEFORE");
if (extraSelections) {
selections.push(...extraSelections);
}
if (authSubqueries) {
subqueries.push(...authSubqueries);
}
if (validation) {
validations.push(validation);
}
}
return { selections, subqueries, predicates, validations };
}
}
exports.ConnectOperation = ConnectOperation;
//# sourceMappingURL=ConnectOperation.js.map