@neo4j/graphql
Version:
A GraphQL to Cypher query execution layer for Neo4j and JavaScript GraphQL implementations
202 lines • 9.12 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.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