-
Notifications
You must be signed in to change notification settings - Fork 151
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5459 from mjfwebb/top-level-delete-6.x
Top-level Delete v6
- Loading branch information
Showing
8 changed files
with
513 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
import Cypher from "@neo4j/cypher-builder"; | ||
import type { ConcreteEntityAdapter } from "../../schema-model/entity/model-adapters/ConcreteEntityAdapter"; | ||
import type { InterfaceEntityAdapter } from "../../schema-model/entity/model-adapters/InterfaceEntityAdapter"; | ||
import type { Filter } from "../../translate/queryAST/ast/filters/Filter"; | ||
import type { OperationTranspileResult } from "../../translate/queryAST/ast/operations/operations"; | ||
import { MutationOperation } from "../../translate/queryAST/ast/operations/operations"; | ||
import type { QueryASTContext } from "../../translate/queryAST/ast/QueryASTContext"; | ||
import type { QueryASTNode } from "../../translate/queryAST/ast/QueryASTNode"; | ||
import type { EntitySelection, SelectionClause } from "../../translate/queryAST/ast/selection/EntitySelection"; | ||
import { wrapSubqueriesInCypherCalls } from "../../translate/queryAST/utils/wrap-subquery-in-calls"; | ||
|
||
export class V6DeleteOperation extends MutationOperation { | ||
public readonly target: ConcreteEntityAdapter | InterfaceEntityAdapter; | ||
private selection: EntitySelection; | ||
private filters: Filter[]; | ||
|
||
constructor({ | ||
target, | ||
selection, | ||
filters = [], | ||
}: { | ||
target: ConcreteEntityAdapter | InterfaceEntityAdapter; | ||
selection: EntitySelection; | ||
filters?: Filter[]; | ||
}) { | ||
super(); | ||
this.target = target; | ||
this.selection = selection; | ||
this.filters = filters; | ||
} | ||
|
||
public getChildren(): QueryASTNode[] { | ||
return [this.selection, ...this.filters]; | ||
} | ||
|
||
public transpile(context: QueryASTContext): OperationTranspileResult { | ||
if (!context.hasTarget()) { | ||
throw new Error("No parent node found!"); | ||
} | ||
const { selection, nestedContext } = this.selection.apply(context); | ||
return this.transpileTopLevel(selection, nestedContext); | ||
} | ||
|
||
private transpileTopLevel( | ||
selection: SelectionClause, | ||
context: QueryASTContext<Cypher.Node> | ||
): OperationTranspileResult { | ||
this.validateSelection(selection); | ||
const filterSubqueries = wrapSubqueriesInCypherCalls(context, this.filters, [context.target]); | ||
const predicate = this.getPredicate(context); | ||
const extraSelections = this.getExtraSelections(context); | ||
|
||
let statements = [selection, ...extraSelections, ...filterSubqueries]; | ||
statements = this.appendFilters(statements, predicate); | ||
statements = this.appendDeleteClause(statements, context); | ||
const ret = Cypher.concat(...statements); | ||
|
||
return { clauses: [ret], projectionExpr: context.target }; | ||
} | ||
|
||
private appendDeleteClause(clauses: Cypher.Clause[], context: QueryASTContext<Cypher.Node>): Cypher.Clause[] { | ||
const lastClause = this.getLastClause(clauses); | ||
if ( | ||
lastClause instanceof Cypher.Match || | ||
lastClause instanceof Cypher.OptionalMatch || | ||
lastClause instanceof Cypher.With | ||
) { | ||
lastClause.detachDelete(context.target); | ||
return clauses; | ||
} | ||
const extraWith = new Cypher.With("*"); | ||
extraWith.detachDelete(context.target); | ||
clauses.push(extraWith); | ||
return clauses; | ||
} | ||
|
||
private getLastClause(clauses: Cypher.Clause[]): Cypher.Clause { | ||
const lastClause = clauses[clauses.length - 1]; | ||
if (!lastClause) { | ||
throw new Error("Transpile error"); | ||
} | ||
return lastClause; | ||
} | ||
|
||
private appendFilters(clauses: Cypher.Clause[], predicate: Cypher.Predicate | undefined): Cypher.Clause[] { | ||
if (!predicate) { | ||
return clauses; | ||
} | ||
const lastClause = this.getLastClause(clauses); | ||
if ( | ||
lastClause instanceof Cypher.Match || | ||
lastClause instanceof Cypher.OptionalMatch || | ||
lastClause instanceof Cypher.With | ||
) { | ||
lastClause.where(predicate); | ||
return clauses; | ||
} | ||
const withClause = new Cypher.With("*"); | ||
withClause.where(predicate); | ||
clauses.push(withClause); | ||
return clauses; | ||
} | ||
|
||
private validateSelection(selection: SelectionClause): asserts selection is Cypher.Match | Cypher.With { | ||
if (!(selection instanceof Cypher.Match || selection instanceof Cypher.With)) { | ||
throw new Error("Cypher Yield statement is not a valid selection for Delete Operation"); | ||
} | ||
} | ||
|
||
private getPredicate(queryASTContext: QueryASTContext): Cypher.Predicate | undefined { | ||
return Cypher.and(...this.filters.map((f) => f.getPredicate(queryASTContext))); | ||
} | ||
|
||
private getExtraSelections(context: QueryASTContext): (Cypher.Match | Cypher.With)[] { | ||
return this.getChildren().flatMap((f) => f.getSelection(context)); | ||
} | ||
} |
78 changes: 78 additions & 0 deletions
78
packages/graphql/src/api-v6/queryIRFactory/DeleteOperationFactory.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
import type { Neo4jGraphQLSchemaModel } from "../../schema-model/Neo4jGraphQLSchemaModel"; | ||
import type { ConcreteEntity } from "../../schema-model/entity/ConcreteEntity"; | ||
import { ConcreteEntityAdapter } from "../../schema-model/entity/model-adapters/ConcreteEntityAdapter"; | ||
import { QueryAST } from "../../translate/queryAST/ast/QueryAST"; | ||
import { NodeSelection } from "../../translate/queryAST/ast/selection/NodeSelection"; | ||
import { filterTruthy } from "../../utils/utils"; | ||
import { V6DeleteOperation } from "../queryIR/DeleteOperation"; | ||
import { FilterFactory } from "./FilterFactory"; | ||
import type { GraphQLTreeDelete } from "./resolve-tree-parser/graphql-tree/graphql-tree"; | ||
|
||
export class DeleteOperationFactory { | ||
public schemaModel: Neo4jGraphQLSchemaModel; | ||
private filterFactory: FilterFactory; | ||
|
||
constructor(schemaModel: Neo4jGraphQLSchemaModel) { | ||
this.schemaModel = schemaModel; | ||
this.filterFactory = new FilterFactory(schemaModel); | ||
} | ||
|
||
public deleteAST({ | ||
graphQLTreeDelete, | ||
entity, | ||
}: { | ||
graphQLTreeDelete: GraphQLTreeDelete; | ||
entity: ConcreteEntity; | ||
}): QueryAST { | ||
const operation = this.generateDeleteOperation({ | ||
graphQLTreeDelete, | ||
entity, | ||
}); | ||
return new QueryAST(operation); | ||
} | ||
|
||
private generateDeleteOperation({ | ||
graphQLTreeDelete, | ||
entity, | ||
}: { | ||
graphQLTreeDelete: GraphQLTreeDelete; | ||
entity: ConcreteEntity; | ||
}): V6DeleteOperation { | ||
const targetAdapter = new ConcreteEntityAdapter(entity); | ||
|
||
const selection = new NodeSelection({ | ||
target: targetAdapter, | ||
}); | ||
|
||
const filters = filterTruthy([ | ||
this.filterFactory.createFilters({ entity, where: graphQLTreeDelete.args.where }), | ||
]); | ||
|
||
const deleteOP = new V6DeleteOperation({ | ||
target: targetAdapter, | ||
selection, | ||
filters, | ||
}); | ||
|
||
return deleteOP; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
44 changes: 44 additions & 0 deletions
44
packages/graphql/src/api-v6/translators/translate-delete-operation.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
import type Cypher from "@neo4j/cypher-builder"; | ||
import Debug from "debug"; | ||
import { DEBUG_TRANSLATE } from "../../constants"; | ||
import type { ConcreteEntity } from "../../schema-model/entity/ConcreteEntity"; | ||
import type { Neo4jGraphQLTranslationContext } from "../../types/neo4j-graphql-translation-context"; | ||
import { DeleteOperationFactory } from "../queryIRFactory/DeleteOperationFactory"; | ||
import type { GraphQLTreeDelete } from "../queryIRFactory/resolve-tree-parser/graphql-tree/graphql-tree"; | ||
|
||
const debug = Debug(DEBUG_TRANSLATE); | ||
|
||
export function translateDeleteResolver({ | ||
context, | ||
entity, | ||
graphQLTreeDelete, | ||
}: { | ||
context: Neo4jGraphQLTranslationContext; | ||
graphQLTreeDelete: GraphQLTreeDelete; | ||
entity: ConcreteEntity; | ||
}): Cypher.CypherResult { | ||
const deleteFactory = new DeleteOperationFactory(context.schemaModel); | ||
const deleteAST = deleteFactory.deleteAST({ graphQLTreeDelete, entity }); | ||
debug(deleteAST.print()); | ||
const results = deleteAST.build(context); | ||
return results.build(); | ||
} |
Oops, something went wrong.