UNPKG

@neo4j/graphql

Version:

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

221 lines 10.6 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.UnwindCreateOperation = void 0; const cypher_builder_1 = __importDefault(require("@neo4j/cypher-builder")); const RelationshipAdapter_1 = require("../../../../schema-model/relationship/model-adapters/RelationshipAdapter"); const check_authentication_1 = require("../../../authorization/check-authentication"); const create_relationship_validation_clauses_1 = require("../../../create-relationship-validation-clauses"); const create_node_from_entity_1 = require("../../utils/create-node-from-entity"); const is_concrete_entity_1 = require("../../utils/is-concrete-entity"); const QueryASTContext_1 = require("../QueryASTContext"); const PropertyInputField_1 = require("../input-fields/PropertyInputField"); const operations_1 = require("./operations"); class UnwindCreateOperation extends operations_1.MutationOperation { constructor({ target, argumentToUnwind, }) { super(); this.projectionOperations = []; // This array is always length 1 or 0 this.authFilters = []; this.target = target; this.inputFields = new Map(); this.argumentToUnwind = argumentToUnwind; this.unwindVariable = new cypher_builder_1.default.Variable(); this.isNested = target instanceof RelationshipAdapter_1.RelationshipAdapter; } getChildren() { return [...this.inputFields.values(), ...this.authFilters, ...this.projectionOperations]; } addAuthFilters(...filter) { this.authFilters.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); } } getCypherVariable() { return this.unwindVariable; } addProjectionOperations(operations) { this.projectionOperations.push(...operations); } getAuthorizationSubqueries(_context) { return []; } transpile(context) { const nestedContext = this.getNestedContext(context); nestedContext.env.topLevelOperationName = "CREATE"; if (!nestedContext.hasTarget()) { throw new Error("No parent node found!"); } const target = this.getTarget(); (0, check_authentication_1.checkEntityAuthentication)({ context: nestedContext.neo4jGraphQLContext, entity: target.entity, targetOperations: ["CREATE"], }); this.inputFields.forEach((field) => { if (field.attachedTo === "node" && field instanceof PropertyInputField_1.PropertyInputField) { (0, check_authentication_1.checkEntityAuthentication)({ context: nestedContext.neo4jGraphQLContext, entity: target.entity, targetOperations: ["CREATE"], field: field.name, }); } }); const unwindClause = new cypher_builder_1.default.Unwind([this.argumentToUnwind, this.unwindVariable]); const createClause = new cypher_builder_1.default.Create(new cypher_builder_1.default.Pattern(nestedContext.target, { labels: (0, create_node_from_entity_1.getEntityLabels)(target, context.neo4jGraphQLContext) })); const setSubqueries = []; const mergeClause = this.getMergeClause(nestedContext); for (const field of this.inputFields.values()) { if (field.attachedTo === "relationship" && mergeClause) { mergeClause.set(...field.getSetParams(nestedContext, this.unwindVariable)); } else if (field.attachedTo === "node") { createClause.set(...field.getSetParams(nestedContext, this.unwindVariable)); setSubqueries.push(...field.getSubqueries(nestedContext)); } } const nestedSubqueries = setSubqueries.flatMap((clause) => [ new cypher_builder_1.default.With(nestedContext.target, this.unwindVariable), new cypher_builder_1.default.Call(clause, [nestedContext.target, this.unwindVariable]), ]); const authorizationClauses = this.getAuthorizationClauses(nestedContext); const cardinalityClauses = (0, create_relationship_validation_clauses_1.createRelationshipValidationClauses)({ entity: target, context: nestedContext.neo4jGraphQLContext, varName: nestedContext.target, }); const unwindCreateClauses = cypher_builder_1.default.utils.concat(createClause, mergeClause, ...nestedSubqueries, ...authorizationClauses, ...(cardinalityClauses.length ? [new cypher_builder_1.default.With(nestedContext.target), ...cardinalityClauses] : [])); let subQueryClause; if (this.isNested) { subQueryClause = cypher_builder_1.default.utils.concat(unwindCreateClauses, new cypher_builder_1.default.Return([cypher_builder_1.default.collect(cypher_builder_1.default.Null), new cypher_builder_1.default.Variable()])); } else { subQueryClause = new cypher_builder_1.default.Call(cypher_builder_1.default.utils.concat(unwindCreateClauses, new cypher_builder_1.default.Return(nestedContext.target)), [this.unwindVariable]); } const projectionContext = new QueryASTContext_1.QueryASTContext({ ...nestedContext, target: nestedContext.target, returnVariable: new cypher_builder_1.default.NamedVariable("data"), shouldCollect: true, }); const clauses = cypher_builder_1.default.utils.concat(unwindClause, subQueryClause, ...this.getProjectionClause(projectionContext)); return { projectionExpr: nestedContext.returnVariable, clauses: [clauses] }; } getMergeClause(context) { if (this.isNested) { if (!context.source || !context.relationship) { throw new Error("Transpile error: No source or relationship found!"); } if (!(this.target instanceof RelationshipAdapter_1.RelationshipAdapter)) { throw new Error("Transpile error: Invalid target"); } return new cypher_builder_1.default.Merge(new cypher_builder_1.default.Pattern(context.source) .related(context.relationship, { type: this.target.type, direction: this.target.cypherDirectionFromRelDirection(), }) .to(context.target)); } } getTarget() { if (this.target instanceof RelationshipAdapter_1.RelationshipAdapter) { const targetAdapter = this.target.target; (0, is_concrete_entity_1.assertIsConcreteEntity)(targetAdapter); return targetAdapter; } return this.target; } getNestedContext(context) { if (this.target instanceof RelationshipAdapter_1.RelationshipAdapter) { const target = new cypher_builder_1.default.Node(); const relationship = new cypher_builder_1.default.Relationship(); const nestedContext = context.push({ target, relationship, }); return nestedContext; } return context; } getAuthorizationClauses(context) { const { selections, subqueries, predicates, validations } = this.transpileAuthClauses(context); const predicate = cypher_builder_1.default.and(...predicates); const lastSelection = selections[selections.length - 1]; if (!predicates.length && !validations.length) { return []; } else { if (lastSelection) { lastSelection.where(predicate); return [...subqueries, new cypher_builder_1.default.With("*"), ...selections, ...validations]; } return [...subqueries, new cypher_builder_1.default.With("*").where(predicate), ...selections, ...validations]; } } 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 authPredicate = authFilter.getPredicate(context); const validation = authFilter.getValidation(context, "AFTER"); if (extraSelections) { selections.push(...extraSelections); } if (authSubqueries) { subqueries.push(...authSubqueries); } if (authPredicate) { predicates.push(authPredicate); } if (validation) { validations.push(validation); } } return { selections, subqueries, predicates, validations }; } getProjectionClause(context) { if (this.projectionOperations.length === 0 && !this.isNested) { const emptyProjection = new cypher_builder_1.default.Literal("Query cannot conclude with CALL"); return [new cypher_builder_1.default.Return(emptyProjection)]; } return this.projectionOperations.map((operationField) => { return cypher_builder_1.default.utils.concat(...operationField.transpile(context).clauses); }); } } exports.UnwindCreateOperation = UnwindCreateOperation; //# sourceMappingURL=UnwindCreateOperation.js.map