Permalink
Browse files

Made it possible to add the FORCE clause to DELETE, which removes all…

… relationships from nodes
  • Loading branch information...
1 parent 98adc9f commit 8ba4b9ca88b6cad528e00b7e0e752c66adc7bce9 @systay committed Oct 29, 2012
View
@@ -3,6 +3,8 @@
o The traversal pattern matcher now supports variable length relationship patterns
o Fixes #946 - HAS(...) fails with ThisShouldNotHappenException for some patterns
o Made it possible to delete collections of nodes and relationships
+o Added the FORCE clause to DELETE, to delete all relationships connected to a node
+
1.9.M01 (2012-10-23)
--------------------
@@ -1,12 +1,14 @@
[[query-delete]]
Delete
======
-Removing graph elements -- nodes, relationships and properties, is done with +DELETE+.
+Removing graph elements -- nodes, relationships and properties, is done with +DELETE+. It takes a list of expressions,
+separated by commas. The expressions must either be return either a node, a relationship, a path, or a collection of these;
+or it can be a property expression, which leads to that property being deleted.
+
:leveloffset: 2
include::delete-single-node.asciidoc[]
-
include::remove-a-node-and-connected-relationships.asciidoc[]
-
+include::remove_collections.asciidoc[]
include::remove-a-property.asciidoc[]
@@ -29,7 +29,7 @@ import org.neo4j.cypher.internal.symbols.AnyType
import org.neo4j.graphdb.{PropertyContainer, Path, Relationship, Node}
import org.neo4j.cypher.internal.helpers.{IsCollection, CollectionSupport}
-case class DeleteEntityAction(elementToDelete: Expression)
+case class DeleteEntityAction(elementToDelete: Expression, force:Boolean)
extends UpdateAction with CollectionSupport {
def exec(context: ExecutionContext, state: QueryState) = {
elementToDelete(context) match {
@@ -48,6 +48,9 @@ case class DeleteEntityAction(elementToDelete: Expression)
x match {
case n: Node if (!nodeManager.isDeleted(n)) =>
+ if (force) {
+ n.getRelationships.asScala.foreach(r => delete(r, state))
+ }
state.deletedNodes.increase()
n.delete()
@@ -65,7 +68,7 @@ case class DeleteEntityAction(elementToDelete: Expression)
def identifiers: Seq[(String, CypherType)] = Seq.empty
- def rewrite(f: (Expression) => Expression) = DeleteEntityAction(elementToDelete.rewrite(f))
+ def rewrite(f: (Expression) => Expression) = DeleteEntityAction(elementToDelete.rewrite(f), force)
def filter(f: (Expression) => Boolean) = elementToDelete.filter(f)
@@ -41,7 +41,7 @@ trait Updates extends Base with Expressions with StartClause {
def delete: Parser[(Seq[UpdateAction], Seq[NamedPath])] = ignoreCase("delete") ~> commaList(expression) ^^ {
case expressions => val updateActions: List[UpdateAction with Product] = expressions.map {
case Property(entity, property) => DeletePropertyAction(Identifier(entity), property)
- case x => DeleteEntityAction(x)
+ case x => DeleteEntityAction(x, false)
}
(updateActions, Seq())
}
@@ -38,10 +38,10 @@ trait Updates extends Base with Expressions with StartClause {
}
}
- def delete: Parser[(Seq[UpdateAction], Seq[NamedPath])] = ignoreCase("delete") ~> commaList(expression) ^^ {
- case expressions => val updateActions: List[UpdateAction with Product] = expressions.map {
+ def delete: Parser[(Seq[UpdateAction], Seq[NamedPath])] = opt(ignoreCase("force")) ~ ignoreCase("delete") ~ commaList(expression) ^^ {
+ case force ~ _ ~ expressions => val updateActions: List[UpdateAction with Product] = expressions.map {
case Property(entity, property) => DeletePropertyAction(Identifier(entity), property)
- case x => DeleteEntityAction(x)
+ case x => DeleteEntityAction(x, force.nonEmpty)
}
(updateActions, Seq())
}
@@ -1487,7 +1487,7 @@ create a-[r:REL]->b
@Test def delete_node() {
val secondQ = Query.
- updates(DeleteEntityAction(Identifier("a"))).
+ updates(DeleteEntityAction(Identifier("a"), false)).
returns()
val q = Query.
@@ -1585,7 +1585,7 @@ create a-[r:REL]->b
@Test def simple_delete_node() {
val secondQ = Query.
- updates(DeleteEntityAction(Identifier("a"))).
+ updates(DeleteEntityAction(Identifier("a"), false)).
returns()
val q = Query.
@@ -1596,6 +1596,19 @@ create a-[r:REL]->b
testFrom_1_8("start a=node(0) delete a", q)
}
+ @Test def force_delete_node() {
+ val secondQ = Query.
+ updates(DeleteEntityAction(Identifier("a"), true)).
+ returns()
+
+ val q = Query.
+ start(NodeById("a", 0)).
+ tail(secondQ).
+ returns(AllIdentifiers())
+
+ testFrom_1_9("start a=node(0) force delete a", q)
+ }
+
@Test def simple_set_property_on_node() {
val secondQ = Query.
updates(PropertySetAction(Property("a", "hello"), Literal("world"))).
@@ -512,6 +512,14 @@ return distinct center""")
def should_be_able_to_delete_a_collection() {
parseAndExecute("start n=node(0) with collect(n) as collection delete collection")
}
+
+ @Test
+ def should_be_able_to_delete_a_node_that_has_relationships() {
+ val a = createNode()
+ relate(refNode, a)
+
+ parseAndExecute("start n=node(0) force delete n")
+ }
}
trait StatisticsChecker extends Assertions {
def assertStats(result: ExecutionResult,
@@ -46,20 +46,26 @@ class DeleteTest extends DocumentingTestBase {
text = "To remove a node from the graph, you can delete it with the +DELETE+ clause.",
queryText = "start n = node("+id+") delete n",
returns = "Nothing is returned from this query, except the count of affected nodes.",
- assertions = (p) => {
- intercept[NotFoundException](db.getNodeById(id))
- })
+ assertions = (p) => intercept[NotFoundException](db.getNodeById(id)))
}
@Test def delete_single_node_with_all_relationships() {
testQuery(
title = "Remove a node and connected relationships",
- text = "If you are trying to remove a node with relationships on it, you have to remove these as well.",
- queryText = "start n = node(%Andres%) match n-[r]-() delete n, r",
+ text = "If you are trying to remove a node with relationships on it, you have tell Cypher it is OK to remove " +
+ "these as well. You do that with the FORCE keyword.",
+ queryText = "start n = node(%Andres%) force delete n",
returns = "Nothing is returned from this query, except the count of affected nodes.",
- assertions = (p) => {
- intercept[NotFoundException](node("Andres"))
- })
+ assertions = (p) => intercept[NotFoundException](node("Andres")))
+ }
+
+ @Test def delete_items_from_collection() {
+ testQuery(
+ title = "Remove collections",
+ text = "You can use delete on any expression that returns a node, a relationship, or a collection of these",
+ queryText = "start n = node(*) match n-[r]-() WITH collect(n) as nodes, collect(distinct r) as relationships DELETE nodes, relationships",
+ returns = "Nothing is returned from this query, except the count of affected nodes.",
+ assertions = (p) => intercept[NotFoundException](node("Andres")))
}
@Test def delete_property() {
@@ -69,8 +75,6 @@ class DeleteTest extends DocumentingTestBase {
"just not there. So, to remove a property value on a node or a relationship, is also done with +DELETE+.",
queryText = "start andres = node(%Andres%) delete andres.age return andres",
returns = "The node is returned, and no property `age` exists on it.",
- assertions = (p) => {
- assertFalse("Property was not removed as expected.", node("Andres").hasProperty("age"))
- })
+ assertions = (p) => assertFalse("Property was not removed as expected.", node("Andres").hasProperty("age")))
}
}
@@ -31,15 +31,15 @@ class DeleteAndPropertySetBuilderTest extends BuilderTest {
@Test
def does_not_offer_to_solve_done_queries() {
val q = PartiallySolvedQuery().
- copy(updates = Seq(Solved(DeleteEntityAction(Identifier("x")))))
+ copy(updates = Seq(Solved(DeleteEntityAction(Identifier("x"), false))))
assertFalse("Should not be able to build on this", builder.canWorkWith(plan(q)))
}
@Test
def offers_to_solve_queries() {
val q = PartiallySolvedQuery().
- copy(updates = Seq(Unsolved(DeleteEntityAction(Identifier("x")))))
+ copy(updates = Seq(Unsolved(DeleteEntityAction(Identifier("x"), false))))
val pipe = createPipe(nodes = Seq("x"))
@@ -56,7 +56,7 @@ class DeleteAndPropertySetBuilderTest extends BuilderTest {
@Test
def does_not_offer_to_delete_something_not_yet_there() {
val q = PartiallySolvedQuery().
- copy(updates = Seq(Unsolved(DeleteEntityAction(Identifier("x")))))
+ copy(updates = Seq(Unsolved(DeleteEntityAction(Identifier("x"), false))))
val executionPlan = plan(q)
assertFalse("Should not accept this", builder.canWorkWith(executionPlan))
@@ -110,7 +110,7 @@ class MutationTest extends ExecutionEngineHelper with Assertions {
@Test
def throw_exception_if_wrong_stuff_to_delete() {
- val createRel = DeleteEntityAction(Literal("some text"))
+ val createRel = DeleteEntityAction(Literal("some text"), false)
intercept[CypherTypeException](createRel.exec(ExecutionContext.empty, createQueryState))
}
@@ -119,7 +119,7 @@ class MutationTest extends ExecutionEngineHelper with Assertions {
def delete_node() {
val a: Node = createNode()
val node_id: Long = a.getId
- val deleteCommand = DeleteEntityAction(getNode("a", a))
+ val deleteCommand = DeleteEntityAction(getNode("a", a), false)
val startPipe = new NullPipe
val txBeginPipe = new TransactionStartPipe(startPipe, graph)

0 comments on commit 8ba4b9c

Please sign in to comment.