UNPKG

@neo4j/graphql

Version:

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

202 lines 9.12 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 }); exports.UpdateOperation = 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 operations_1 = require("./operations"); const wrap_subquery_in_calls_1 = require("../../utils/wrap-subquery-in-calls"); const ParamInputField_1 = require("../input-fields/ParamInputField"); class UpdateOperation extends operations_1.Operation { constructor({ target, relationship, selectionPattern, }) { super(); this.authFilters = []; this.filters = []; this.inputFields = []; // The response fields in the mutation, currently only READ operations are supported in the MutationResponse this.projectionOperations = []; this.target = target; this.relationship = relationship; this.selectionPattern = selectionPattern; } /** Prints the name of the Node */ print() { return `${super.print()} <${this.target.name}>`; } getChildren() { return (0, utils_1.filterTruthy)([ this.selectionPattern, ...this.inputFields, ...this.filters, ...this.authFilters, ...this.projectionOperations, ]); } addProjectionOperations(operations) { this.projectionOperations.push(...operations); } addAuthFilters(...filter) { this.authFilters.push(...filter); } addField(field) { this.inputFields.push(field); } addFilters(...filters) { this.filters.push(...filters); } transpile(context) { if (!context.target) throw new Error("No parent node found!"); context.env.topLevelOperationName = "UPDATE"; const { nestedContext, pattern } = this.selectionPattern.apply(context); this.nestedContext = nestedContext; (0, check_authentication_1.checkEntityAuthentication)({ context: context.neo4jGraphQLContext, entity: this.target.entity, targetOperations: ["UPDATE"], }); this.inputFields.forEach((field) => { if (field.attachedTo === "node" && field instanceof ParamInputField_1.ParamInputField) { (0, check_authentication_1.checkEntityAuthentication)({ context: context.neo4jGraphQLContext, entity: this.target.entity, targetOperations: ["UPDATE"], field: field.name, }); } }); const setParams = Array.from(this.inputFields.values()).flatMap((input) => { return input.getSetParams(nestedContext); }); const mutationSubqueries = Array.from(this.inputFields.values()) .flatMap((input) => { const subqueries = input.getSubqueries(nestedContext); const authSubqueries = input.getAuthorizationSubqueries(nestedContext); if (!authSubqueries.length && !subqueries.length) { return undefined; } if (authSubqueries.length && subqueries.length) { return cypher_builder_1.default.utils.concat(...subqueries, new cypher_builder_1.default.With("*"), ...authSubqueries); } return cypher_builder_1.default.utils.concat(...subqueries, ...authSubqueries); }) .filter((s) => s !== undefined); // This is a small optimization, to avoid subqueries with no changes // Top level should still be generated for projection if (this.relationship) { if (setParams.length === 0 && mutationSubqueries.length === 0) { return { projectionExpr: nestedContext.target, clauses: [] }; } } // We need to call the filter subqueries before predicate to handle aggregate filters const filterSubqueries = (0, wrap_subquery_in_calls_1.wrapSubqueriesInCypherCalls)(nestedContext, this.filters, [nestedContext.target]); const authBeforeClauses = this.getAuthorizationClauses(nestedContext); const afterFilterSubqueries = this.authFilters .flatMap((af) => af.getSubqueriesAfter(nestedContext)) .map((sq) => { return new cypher_builder_1.default.Call(sq, [nestedContext.target]); }); const predicate = this.getPredicate(nestedContext); const matchClause = new cypher_builder_1.default.Match(pattern); const filtersWith = new cypher_builder_1.default.With("*").where(predicate); if (authBeforeClauses.length > 0) { filtersWith.with("*"); } let withAndSet; if (authBeforeClauses.length === 0) { filtersWith.set(...setParams); } else { withAndSet = new cypher_builder_1.default.With("*").set(...setParams); } const clauses = cypher_builder_1.default.utils.concat(matchClause, ...filterSubqueries, filtersWith, ...authBeforeClauses, withAndSet, afterFilterSubqueries.length > 0 ? new cypher_builder_1.default.With("*") : undefined, ...mutationSubqueries.map((sq) => cypher_builder_1.default.utils.concat(new cypher_builder_1.default.With("*"), new cypher_builder_1.default.Call(sq, "*"))), ...afterFilterSubqueries, ...this.getAuthorizationClausesAfter(nestedContext) // THESE ARE "AFTER" AUTH ); return { projectionExpr: nestedContext.target, clauses: [clauses] }; } /** Post subqueries */ 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 []; } getAuthorizationClauses(context) { const { subqueries, validations } = this.transpileAuthClauses(context); const authSubqueries = subqueries.map((sq) => { return new cypher_builder_1.default.Call(sq, "*"); }); if (!validations.length) { return []; } return [...authSubqueries, ...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 []; } transpileAuthClauses(context) { const selections = []; const subqueries = []; const predicates = []; const validations = []; for (const authFilter of this.authFilters) { const extraSelections = authFilter.getSelection(context); const authSubqueries = authFilter.getSubqueriesBefore(context); const authPredicate = authFilter.getPredicate(context); const validationBefore = authFilter.getValidation(context, "BEFORE"); if (extraSelections) { selections.push(...extraSelections); } if (authSubqueries) { subqueries.push(...authSubqueries); } if (authPredicate) { predicates.push(authPredicate); } if (validationBefore) { validations.push(validationBefore); } } return { selections, subqueries, predicates, validations }; } getPredicate(queryASTContext) { const authBeforePredicates = this.getAuthFilterPredicate(queryASTContext); return cypher_builder_1.default.and(...this.filters.map((f) => f.getPredicate(queryASTContext)), ...this.inputFields.map((f) => f.getPredicate(queryASTContext)), ...authBeforePredicates); } getAuthFilterPredicate(context) { return (0, utils_1.filterTruthy)(this.authFilters.map((f) => f.getPredicate(context))); } } exports.UpdateOperation = UpdateOperation; //# sourceMappingURL=UpdateOperation.js.map