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