Browse files

Renamed RELATE to CREATE UNIQUE

  • Loading branch information...
1 parent 1728938 commit c7dbbb929abfef600266a20f065d760e7a1fff2e @systay committed Jul 19, 2012
Showing with 273 additions and 225 deletions.
  1. +1 −0 cypher/CHANGES.txt
  2. +2 −2 cypher/src/docs/dev/index.txt
  3. +21 −0 cypher/src/docs/dev/ql/create-unique/index.txt
  4. +2 −1 cypher/src/docs/dev/ql/foreach/index.txt
  5. +0 −20 cypher/src/docs/dev/ql/relate/index.txt
  6. +1 −1 cypher/src/main/scala/org/neo4j/cypher/CypherException.scala
  7. +2 −3 cypher/src/main/scala/org/neo4j/cypher/internal/commands/Query.scala
  8. +11 −13 cypher/src/main/scala/org/neo4j/cypher/internal/commands/StartItem.scala
  9. +2 −0 cypher/src/main/scala/org/neo4j/cypher/internal/executionplan/builders/QueryToken.scala
  10. +27 −4 cypher/src/main/scala/org/neo4j/cypher/internal/executionplan/builders/UpdateActionBuilder.scala
  11. +17 −17 ...src/main/scala/org/neo4j/cypher/internal/mutation/{RelateAction.scala → CreateUniqueAction.scala}
  12. +12 −12 cypher/src/main/scala/org/neo4j/cypher/internal/mutation/{RelateLink.scala → UniqueLink.scala}
  13. +1 −1 cypher/src/main/scala/org/neo4j/cypher/internal/mutation/UpdateAction.scala
  14. +2 −0 cypher/src/main/scala/org/neo4j/cypher/internal/parser/v1_8/Base.scala
  15. +69 −0 cypher/src/main/scala/org/neo4j/cypher/internal/parser/v1_8/CreateUnique.scala
  16. +6 −4 cypher/src/main/scala/org/neo4j/cypher/internal/parser/v1_8/StartClause.scala
  17. +1 −42 cypher/src/main/scala/org/neo4j/cypher/internal/parser/v1_8/Updates.scala
  18. +24 −32 ...src/test/scala/org/neo4j/cypher/{RelateAcceptanceTests.scala → CreateUniqueAcceptanceTests.scala}
  19. +24 −24 cypher/src/test/scala/org/neo4j/cypher/CypherParserTest.scala
  20. +2 −2 cypher/src/test/scala/org/neo4j/cypher/ErrorMessagesTest.scala
  21. +9 −9 cypher/src/test/scala/org/neo4j/cypher/MutatingIntegrationTests.scala
  22. +1 −1 cypher/src/test/scala/org/neo4j/cypher/docgen/CreateTest.scala
  23. +9 −9 cypher/src/test/scala/org/neo4j/cypher/docgen/{RelateTest.scala → CreateUniqueTest.scala}
  24. +19 −20 cypher/src/test/scala/org/neo4j/cypher/docgen/PatternTest.scala
  25. +2 −2 cypher/src/test/scala/org/neo4j/cypher/docgen/cookbook/CoFavoritedPlacesTest.scala
  26. +6 −6 ...a/org/neo4j/cypher/internal/mutation/{RelateUniqueTest.scala → DoubleCheckCreateUniqueTest.scala}
View
1 cypher/CHANGES.txt
@@ -1,6 +1,7 @@
1.8
--------------------
o Added escape characters to string literals
+o Renamed `RELATE` to `CREATE UNIQUE`
1.8.M06 (2012-07-06)
--------------------
View
4 cypher/src/docs/dev/index.txt
@@ -66,15 +66,15 @@ include::ql/create/index.txt[]
:leveloffset: 2
-include::ql/delete/index.txt[]
+include::ql/create-unique/index.txt[]
:leveloffset: 2
include::ql/set/index.txt[]
:leveloffset: 2
-include::ql/relate/index.txt[]
+include::ql/delete/index.txt[]
:leveloffset: 2
View
21 cypher/src/docs/dev/ql/create-unique/index.txt
@@ -0,0 +1,21 @@
+[[query-create-unique]]
+Create Unique
+=============
++CREATE UNIQUE+ is in the middle of +MATCH+ and +CREATE+ -- it will match what it can, and create what is missing.
++CREATE UNIQUE+ will always make the least change possible to the graph -- if it can use parts of the existing graph,
+it will.
+
+Another difference to +MATCH+ is that +CREATE UNIQUE+ assumes the pattern to be unique. If multiple matching subgraphs
+are found an exception will be thrown.
+
+[TIP]
+In the +CREATE UNIQUE+ clause, patterns are used a lot.
+Read <<introduction-pattern>> for an introduction.
+
+:leveloffset: 2
+
+include::create-relationship-if-it-is-missing.txt[]
+include::create-node-if-missing.txt[]
+include::create-nodes-with-values.txt[]
+include::create-relationship-with-values.txt[]
+include::describe-complex-pattern.txt[]
View
3 cypher/src/docs/dev/ql/foreach/index.txt
@@ -8,7 +8,8 @@ The identifier context inside of the foreach parenthesis is separate from the on
node identifier inside of a `FOREACH`, you will not be able to use it outside of the foreach statement, unless you
match to find it.
-Inside of the `FOREACH` parenthesis, you can do any updating commands -- `CREATE`, `DELETE`, `RELATE`, and `FOREACH`.
+Inside of the `FOREACH` parenthesis, you can do any updating commands -- `CREATE`, `CREATE UNIQUE`, `DELETE`,
+and `FOREACH`.
:leveloffset: 2
View
20 cypher/src/docs/dev/ql/relate/index.txt
@@ -1,20 +0,0 @@
-[[query-relate]]
-Relate
-======
-+RELATE+ is in the middle of +MATCH+ and +CREATE+ -- it will match what it can, and create what is missing. +RELATE+
-will always make the least change possible to the graph -- if it can use parts of the existing graph, it will.
-
-Another difference to +MATCH+ is that +RELATE+ assumes the pattern to be unique. If multiple matching subgraphs are
-found an exception will be thrown.
-
-[TIP]
-In the `RELATE` clause, patterns are used a lot.
-Read <<introduction-pattern>> for an introduction.
-
-:leveloffset: 2
-
-include::create-relationship-if-it-is-missing.txt[]
-include::create-node-if-missing.txt[]
-include::create-nodes-with-values.txt[]
-include::create-relationship-with-values.txt[]
-include::describe-complex-pattern.txt[]
View
2 cypher/src/main/scala/org/neo4j/cypher/CypherException.scala
@@ -25,7 +25,7 @@ abstract class CypherException(message: String, cause: Throwable) extends Runtim
def this(message:String) = this(message, null)
}
-class RelatePathNotUnique(message:String) extends CypherException(message)
+class UniquePathNotUniqueException(message:String) extends CypherException(message)
class EntityNotFoundException(message:String, cause:Throwable=null) extends CypherException(message, cause)
View
5 cypher/src/main/scala/org/neo4j/cypher/internal/commands/Query.scala
@@ -19,13 +19,12 @@
*/
package org.neo4j.cypher.internal.commands
-import org.neo4j.cypher.internal.mutation.{RelateAction, RelateLink, UpdateAction}
-
+import org.neo4j.cypher.internal.mutation.{CreateUniqueAction, UniqueLink, UpdateAction}
object Query {
def start(startItems: StartItem*) = new QueryBuilder(startItems)
def updates(cmds:UpdateAction*) = new QueryBuilder(Seq()).updates(cmds:_*)
- def relate(cmds:RelateLink*) = new QueryBuilder(Seq()).updates(RelateAction(cmds:_*))
+ def unique(cmds:UniqueLink*) = new QueryBuilder(Seq(CreateUniqueAction(cmds:_*)))
}
case class Query(returns: Return,
View
24 cypher/src/main/scala/org/neo4j/cypher/internal/commands/StartItem.scala
@@ -27,32 +27,30 @@ import org.neo4j.graphdb.{DynamicRelationshipType, Node}
import org.neo4j.cypher.internal.symbols._
-abstract sealed class StartItem(val identifierName: String) {
+abstract class StartItem(val identifierName: String) {
def mutating = false
}
-abstract class RelationshipStartItem(id: String) extends StartItem(id)
+case class RelationshipById(varName: String, expression: Expression) extends StartItem(varName)
-abstract class NodeStartItem(id: String) extends StartItem(id)
+case class RelationshipByIndex(varName: String, idxName: String, key: Expression, expression: Expression) extends StartItem(varName)
-case class RelationshipById(varName: String, expression: Expression) extends RelationshipStartItem(varName)
+case class RelationshipByIndexQuery(varName: String, idxName: String, query: Expression) extends StartItem(varName)
-case class RelationshipByIndex(varName: String, idxName: String, key: Expression, expression: Expression) extends RelationshipStartItem(varName)
+case class NodeByIndex(varName: String, idxName: String, key: Expression, expression: Expression) extends StartItem(varName)
-case class RelationshipByIndexQuery(varName: String, idxName: String, query: Expression) extends RelationshipStartItem(varName)
+case class NodeByIndexQuery(varName: String, idxName: String, query: Expression) extends StartItem(varName)
-case class NodeByIndex(varName: String, idxName: String, key: Expression, expression: Expression) extends NodeStartItem(varName)
+case class NodeById(varName: String, expression: Expression) extends StartItem(varName)
-case class NodeByIndexQuery(varName: String, idxName: String, query: Expression) extends NodeStartItem(varName)
+case class AllNodes(columnName: String) extends StartItem(columnName)
-case class NodeById(varName: String, expression: Expression) extends NodeStartItem(varName)
+case class AllRelationships(columnName: String) extends StartItem(columnName)
-case class AllNodes(columnName: String) extends NodeStartItem(columnName)
-case class AllRelationships(columnName: String) extends RelationshipStartItem(columnName)
case class CreateNodeStartItem(key: String, props: Map[String, Expression])
- extends NodeStartItem(key)
+ extends StartItem(key)
with Mutator
with UpdateAction
with GraphElementPropertyFunctions
@@ -88,7 +86,7 @@ case class CreateNodeStartItem(key: String, props: Map[String, Expression])
}
case class CreateRelationshipStartItem(key: String, from: (Expression, Map[String, Expression]), to: (Expression, Map[String, Expression]), typ: String, props: Map[String, Expression])
- extends NodeStartItem(key)
+ extends StartItem(key)
with Mutator
with UpdateAction
with GraphElementPropertyFunctions {
View
2 cypher/src/main/scala/org/neo4j/cypher/internal/executionplan/builders/QueryToken.scala
@@ -25,6 +25,8 @@ abstract sealed class QueryToken[T](val token: T) {
def unsolved = !solved
def solve: QueryToken[T] = Solved(token)
+
+ def map[B](f : T => B):QueryToken[B] = if (solved) Solved(f(token)) else Unsolved(f(token))
}
case class Solved[T](t: T) extends QueryToken[T](t) {
View
31 ...src/main/scala/org/neo4j/cypher/internal/executionplan/builders/UpdateActionBuilder.scala
@@ -21,7 +21,9 @@ package org.neo4j.cypher.internal.executionplan.builders
import org.neo4j.cypher.internal.executionplan.{ExecutionPlanInProgress, PlanBuilder}
import org.neo4j.graphdb.GraphDatabaseService
-import org.neo4j.cypher.internal.pipes.{ExecuteUpdateCommandsPipe, TransactionStartPipe}
+import org.neo4j.cypher.internal.pipes.{Pipe, ExecuteUpdateCommandsPipe, TransactionStartPipe}
+import org.neo4j.cypher.internal.mutation.{CreateUniqueAction, UpdateAction}
+import org.neo4j.cypher.internal.commands.StartItem
class UpdateActionBuilder(db: GraphDatabaseService) extends PlanBuilder {
def apply(plan: ExecutionPlanInProgress) = {
@@ -32,22 +34,43 @@ class UpdateActionBuilder(db: GraphDatabaseService) extends PlanBuilder {
new TransactionStartPipe(plan.pipe, db)
}
- val commands = plan.query.updates.filter(cmd => cmd.unsolved && p.symbols.satisfies(cmd.token.dependencies))
+ val updateCmds: Seq[QueryToken[UpdateAction]] = extractValidUpdateActions(plan, p)
+ val startItems: Seq[QueryToken[StartItem]] = extractValidStartItems(plan, p)
+ val startCmds = startItems.map(_.map(_.asInstanceOf[UpdateAction]))
+ val commands = updateCmds ++ startCmds
+
+
val resultPipe = new ExecuteUpdateCommandsPipe(p, db, commands.map(_.token))
plan.copy(
containsTransaction = true,
- query = plan.query.copy(updates = plan.query.updates.filterNot(commands.contains) ++ commands.map(_.solve)),
+ query = plan.query.copy(
+ updates = plan.query.updates.filterNot(updateCmds.contains) ++ updateCmds.map(_.solve),
+ start = plan.query.start.filterNot(startItems.contains) ++ startItems.map(_.solve)),
pipe = resultPipe
)
}
- def canWorkWith(plan: ExecutionPlanInProgress) = plan.query.updates.exists(cmd => cmd.unsolved && plan.pipe.symbols.satisfies(cmd.token.dependencies))
+
+ private def extractValidStartItems(plan: ExecutionPlanInProgress, p: Pipe): Seq[QueryToken[StartItem]] = {
+ plan.query.start.filter(cmd => cmd.unsolved && cmd.token.isInstanceOf[UpdateAction] && p.symbols.satisfies(cmd.token.asInstanceOf[UpdateAction].dependencies))
+ }
+
+ private def extractValidUpdateActions(plan: ExecutionPlanInProgress, p: Pipe): Seq[QueryToken[UpdateAction]] = {
+ plan.query.updates.filter(cmd => cmd.unsolved && p.symbols.satisfies(cmd.token.dependencies))
+ }
+
+ def canWorkWith(plan: ExecutionPlanInProgress) =
+ extractValidUpdateActions(plan, plan.pipe).nonEmpty ||
+ extractValidStartItems(plan, plan.pipe).nonEmpty
def priority = PlanBuilder.Mutation
override def missingDependencies(plan: ExecutionPlanInProgress): Seq[String] = plan.query.updates.flatMap {
case Unsolved(cmd) => plan.pipe.symbols.missingDependencies(cmd.dependencies).map(_.name)
case _ => None
+ } ++ plan.query.start.flatMap {
+ case Unsolved(cmd) if cmd.isInstanceOf[UpdateAction] => plan.pipe.symbols.missingDependencies(cmd.asInstanceOf[UpdateAction].dependencies).map(_.name)
+ case _ => None
}
}
View
34 ...pher/internal/mutation/RelateAction.scala → ...nternal/mutation/CreateUniqueAction.scala
@@ -23,17 +23,17 @@ import org.neo4j.cypher.internal.symbols.Identifier
import org.neo4j.cypher.internal.pipes.{QueryState, ExecutionContext}
import org.neo4j.helpers.ThisShouldNotHappenError
import org.neo4j.cypher.internal.commands.{StartItem, Expression}
-import org.neo4j.cypher.RelatePathNotUnique
+import org.neo4j.cypher.UniquePathNotUniqueException
import org.neo4j.graphdb.{Lock, PropertyContainer}
-case class RelateAction(links: RelateLink*) extends UpdateAction {
+case class CreateUniqueAction(links: UniqueLink*) extends StartItem("noooes") with UpdateAction {
def dependencies: Seq[Identifier] = links.flatMap(_.dependencies)
def exec(context: ExecutionContext, state: QueryState): Traversable[ExecutionContext] = {
- var linksToDo: Seq[RelateLink] = links
+ var linksToDo: Seq[UniqueLink] = links
var ctx = context
while (linksToDo.nonEmpty) {
- val results: Seq[(RelateLink, RelateResult)] = executeAllRemainingPatterns(linksToDo, ctx, state)
+ val results: Seq[(UniqueLink, CreateUniqueResult)] = executeAllRemainingPatterns(linksToDo, ctx, state)
linksToDo = results.map(_._1)
val updateCommands = extractUpdateCommands(results)
val traversals = extractTraversals(results)
@@ -59,8 +59,8 @@ case class RelateAction(links: RelateLink*) extends UpdateAction {
Stream(ctx)
}
- private def tryAgain(linksToDo: Seq[RelateLink], context: ExecutionContext, state: QueryState): ExecutionContext = {
- val results: Seq[(RelateLink, RelateResult)] = executeAllRemainingPatterns(linksToDo, context, state)
+ private def tryAgain(linksToDo: Seq[UniqueLink], context: ExecutionContext, state: QueryState): ExecutionContext = {
+ val results: Seq[(UniqueLink, CreateUniqueResult)] = executeAllRemainingPatterns(linksToDo, context, state)
val updateCommands = extractUpdateCommands(results)
val traversals = extractTraversals(results)
@@ -84,7 +84,7 @@ case class RelateAction(links: RelateLink*) extends UpdateAction {
if (uniqueKeys.size != uniqueKVPs.size) {
//We can only go forward following a unique path. Fail.
- throw new RelatePathNotUnique("The pattern " + this + " produced multiple possible paths, and that is not allowed")
+ throw new UniquePathNotUniqueException("The pattern " + this + " produced multiple possible paths, and that is not allowed")
} else {
oldContext.newWith(uniqueKeys)
}
@@ -106,7 +106,7 @@ case class RelateAction(links: RelateLink*) extends UpdateAction {
case (currentContext, updateCommand) => {
val result = updateCommand.cmd.exec(currentContext, state)
if (result.size != 1) {
- throw new RelatePathNotUnique("The pattern " + this + " produced multiple possible paths, and that is not allowed")
+ throw new UniquePathNotUniqueException("The pattern " + this + " produced multiple possible paths, and that is not allowed")
} else {
result.head
}
@@ -118,36 +118,36 @@ case class RelateAction(links: RelateLink*) extends UpdateAction {
context
}
- private def extractUpdateCommands(results: scala.Seq[(RelateLink, RelateResult)]): Seq[Update] =
+ private def extractUpdateCommands(results: scala.Seq[(UniqueLink, CreateUniqueResult)]): Seq[Update] =
results.flatMap {
case (_, u: Update) => Some(u)
case _ => None
}
- private def extractTraversals(results: scala.Seq[(RelateLink, RelateResult)]): Seq[(String, PropertyContainer)] =
+ private def extractTraversals(results: scala.Seq[(UniqueLink, CreateUniqueResult)]): Seq[(String, PropertyContainer)] =
results.flatMap {
case (_, Traverse(ctx@_*)) => ctx
case _ => None
}
- private def executeAllRemainingPatterns(linksToDo: Seq[RelateLink], ctx: ExecutionContext, state: QueryState): Seq[(RelateLink, RelateResult)] = linksToDo.flatMap(link => link.exec(ctx, state))
+ private def executeAllRemainingPatterns(linksToDo: Seq[UniqueLink], ctx: ExecutionContext, state: QueryState): Seq[(UniqueLink, CreateUniqueResult)] = linksToDo.flatMap(link => link.exec(ctx, state))
- private def canNotAdvanced(results: scala.Seq[(RelateLink, RelateResult)]) = results.forall(_._2 == CanNotAdvance())
+ private def canNotAdvanced(results: scala.Seq[(UniqueLink, CreateUniqueResult)]) = results.forall(_._2 == CanNotAdvance())
def filter(f: (Expression) => Boolean): Seq[Expression] = links.flatMap(_.filter(f)).distinct
def identifier: Seq[Identifier] = links.flatMap(_.identifier).distinct
- def rewrite(f: (Expression) => Expression): UpdateAction = RelateAction(links.map(_.rewrite(f)): _*)
+ def rewrite(f: (Expression) => Expression): CreateUniqueAction = CreateUniqueAction(links.map(_.rewrite(f)): _*)
}
-sealed abstract class RelateResult
+sealed abstract class CreateUniqueResult
-case class CanNotAdvance() extends RelateResult
+case class CanNotAdvance() extends CreateUniqueResult
-case class Traverse(result: (String, PropertyContainer)*) extends RelateResult
+case class Traverse(result: (String, PropertyContainer)*) extends CreateUniqueResult
-case class Update(cmds: Seq[UpdateWrapper], locker: () => Seq[Lock]) extends RelateResult {
+case class Update(cmds: Seq[UpdateWrapper], locker: () => Seq[Lock]) extends CreateUniqueResult {
def lock(): Seq[Lock] = locker()
}
View
24 ...cypher/internal/mutation/RelateLink.scala → ...cypher/internal/mutation/UniqueLink.scala
@@ -25,7 +25,7 @@ import org.neo4j.cypher.internal.symbols.{RelationshipType, NodeType, Identifier
import collection.Map
import org.neo4j.graphdb._
import org.neo4j.cypher.internal.commands._
-import org.neo4j.cypher.{RelatePathNotUnique, CypherTypeException}
+import org.neo4j.cypher.{UniquePathNotUniqueException, CypherTypeException}
case class NamedExpectation(name: String, properties: Map[String, Expression])
extends GraphElementPropertyFunctions
@@ -48,16 +48,16 @@ case class NamedExpectation(name: String, properties: Map[String, Expression])
}
}
-object RelateLink {
- def apply(start: String, end: String, relName: String, relType: String, dir: Direction): RelateLink =
- new RelateLink(NamedExpectation(start, Map.empty), NamedExpectation(end, Map.empty), NamedExpectation(relName, Map.empty), relType, dir)
+object UniqueLink {
+ def apply(start: String, end: String, relName: String, relType: String, dir: Direction): UniqueLink =
+ new UniqueLink(NamedExpectation(start, Map.empty), NamedExpectation(end, Map.empty), NamedExpectation(relName, Map.empty), relType, dir)
}
-case class RelateLink(start: NamedExpectation, end: NamedExpectation, rel: NamedExpectation, relType: String, dir: Direction)
+case class UniqueLink(start: NamedExpectation, end: NamedExpectation, rel: NamedExpectation, relType: String, dir: Direction)
extends GraphElementPropertyFunctions {
lazy val relationshipType = DynamicRelationshipType.withName(relType)
- def exec(context: ExecutionContext, state: QueryState): Option[(RelateLink, RelateResult)] = {
+ def exec(context: ExecutionContext, state: QueryState): Option[(UniqueLink, CreateUniqueResult)] = {
// We haven't yet figured out if we already have both elements in the context
// so let's start by finding that first
@@ -82,7 +82,7 @@ case class RelateLink(start: NamedExpectation, end: NamedExpectation, rel: Named
// This method sees if a matching relationship already exists between two nodes
// If any matching rels are found, they are returned. Otherwise, a new one is
// created and returned.
- private def twoNodes(startNode: Node, endNode: Node, ctx: ExecutionContext, state: QueryState): Option[(RelateLink, RelateResult)] = {
+ private def twoNodes(startNode: Node, endNode: Node, ctx: ExecutionContext, state: QueryState): Option[(UniqueLink, CreateUniqueResult)] = {
val rels = startNode.getRelationships(relationshipType, dir).asScala.
filter(r => {
r.getOtherNode(startNode) == endNode && rel.compareWithExpectations(r, ctx)
@@ -96,14 +96,14 @@ case class RelateLink(start: NamedExpectation, end: NamedExpectation, rel: Named
Seq(tx.acquireWriteLock(startNode), tx.acquireWriteLock(endNode))
}))
case List(r) => Some(this->Traverse(rel.name -> r))
- case _ => throw new RelatePathNotUnique("The pattern " + this + " produced multiple possible paths, and that is not allowed")
+ case _ => throw new UniquePathNotUniqueException("The pattern " + this + " produced multiple possible paths, and that is not allowed")
}
}
// When only one node exists in the context, we'll traverse all the relationships of that node
// and try to find a matching node/rel. If matches are found, they are returned. If nothing is
// found, we'll create it and return it
- private def oneNode(startNode: Node, ctx: ExecutionContext, dir: Direction, state: QueryState, end: NamedExpectation): Option[(RelateLink, RelateResult)] = {
+ private def oneNode(startNode: Node, ctx: ExecutionContext, dir: Direction, state: QueryState, end: NamedExpectation): Option[(UniqueLink, CreateUniqueResult)] = {
val rels = startNode.getRelationships(relationshipType, dir).asScala.filter(r => {
rel.compareWithExpectations(r, ctx) && end.compareWithExpectations(r.getOtherNode(startNode), ctx)
}).toList
@@ -115,7 +115,7 @@ case class RelateLink(start: NamedExpectation, end: NamedExpectation, rel: Named
Seq(tx.acquireWriteLock(startNode))
}))
case List(r) => Some(this->Traverse(rel.name -> r, end.name -> r.getOtherNode(startNode)))
- case _ => throw new RelatePathNotUnique("The pattern " + this + " produced multiple possible paths, and that is not allowed")
+ case _ => throw new UniquePathNotUniqueException("The pattern " + this + " produced multiple possible paths, and that is not allowed")
}
}
@@ -144,11 +144,11 @@ case class RelateLink(start: NamedExpectation, end: NamedExpectation, rel: Named
def dependencies = (propDependencies(start.properties) ++ propDependencies(end.properties) ++ propDependencies(rel.properties)).distinct
- def rewrite(f: (Expression) => Expression): RelateLink = {
+ def rewrite(f: (Expression) => Expression): UniqueLink = {
val s = NamedExpectation(start.name, rewrite(start.properties, f))
val e = NamedExpectation(end.name, rewrite(end.properties, f))
val r = NamedExpectation(rel.name, rewrite(rel.properties, f))
- RelateLink(s, e, r, relType, dir)
+ UniqueLink(s, e, r, relType, dir)
}
def filter(f: (Expression) => Boolean) = Seq.empty
View
2 cypher/src/main/scala/org/neo4j/cypher/internal/mutation/UpdateAction.scala
@@ -81,7 +81,7 @@ trait GraphElementPropertyFunctions extends IterableSupport {
val seq = a.asInstanceOf[Traversable[_]].toSeq
if (seq.size == 0) {
- Array[String]();
+ Array[String]()
} else try {
seq.head match {
case c: String => seq.map(_.asInstanceOf[String]).toArray[String]
View
2 cypher/src/main/scala/org/neo4j/cypher/internal/parser/v1_8/Base.scala
@@ -40,6 +40,8 @@ abstract class Base extends JavaTokenParsers {
}
}
+ def reduce[A,B](in:Seq[(Seq[A], Seq[B])]):(Seq[A], Seq[B]) = if (in.isEmpty) (Seq(),Seq()) else in.reduce((a, b) => (a._1 ++ b._1, a._2 ++ b._2))
+
def ignoreCases(strings: String*): Parser[String] = ignoreCases(strings.toList)
def ignoreCases(strings: List[String]): Parser[String] = strings match {
View
69 cypher/src/main/scala/org/neo4j/cypher/internal/parser/v1_8/CreateUnique.scala
@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2002-2012 "Neo Technology,"
+ * Network Engine for Objects in Lund AB [http://neotechnology.com]
+ *
+ * This file is part of Neo4j.
+ *
+ * Neo4j is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.neo4j.cypher.internal.parser.v1_8
+
+import org.neo4j.cypher.internal.commands._
+import org.neo4j.cypher.internal.mutation.UniqueLink
+import org.neo4j.cypher.internal.commands.NamedPath
+import org.neo4j.cypher.internal.mutation.CreateUniqueAction
+import org.neo4j.cypher.internal.commands.Entity
+import org.neo4j.cypher.internal.mutation.NamedExpectation
+import org.neo4j.cypher.internal.commands.True
+
+
+trait CreateUnique extends Base with ParserPattern {
+ case class PathAndRelateLink(path:Option[NamedPath], links:Seq[UniqueLink])
+
+ def relate: Parser[(Seq[StartItem], Seq[NamedPath])] = ignoreCase("create unique") ~> usePattern(translate) ^^ (patterns => {
+ val (links, path)= reduce(patterns.map {
+ case PathAndRelateLink(p, l) => (l, p.toSeq)
+ })
+
+ (Seq(CreateUniqueAction(links:_*)), path)
+ })
+
+ private def translate(abstractPattern: AbstractPattern): Maybe[PathAndRelateLink] = abstractPattern match {
+ case ParsedNamedPath(name, patterns) =>
+ val namedPathPatterns = patterns.map(matchTranslator).reduce(_ ++ _)
+ val startItems = patterns.map(translate).reduce(_ ++ _)
+
+ startItems match {
+ case No(msg) => No(msg)
+ case Yes(links) => namedPathPatterns.seqMap(p => {
+ val namedPath = NamedPath(name, p.map(_.asInstanceOf[Pattern]): _*)
+ Seq(PathAndRelateLink(Some(namedPath), links.flatMap(_.links)))
+ })
+ }
+
+ case ParsedRelation(name, props, ParsedEntity(Entity(startName), startProps, True()), ParsedEntity(Entity(endName), endProps, True()), typ, dir, map, True()) if typ.size == 1 =>
+ val link = UniqueLink(
+ start = NamedExpectation(startName, startProps),
+ end = NamedExpectation(endName, endProps),
+ rel = NamedExpectation(name, props),
+ relType = typ.head,
+ dir = dir
+ )
+
+ Yes(Seq(PathAndRelateLink(None,Seq(link))))
+ case _ => No(Seq())
+ }
+
+ def matchTranslator(abstractPattern: AbstractPattern): Maybe[Any]
+}
View
10 cypher/src/main/scala/org/neo4j/cypher/internal/parser/v1_8/StartClause.scala
@@ -23,12 +23,14 @@ import org.neo4j.cypher.internal.commands._
import org.neo4j.graphdb.Direction
-trait StartClause extends Base with Expressions {
- def start: Parser[(Seq[StartItem], Seq[NamedPath])] = createStart | readStart
+trait StartClause extends Base with Expressions with CreateUnique {
+ def start: Parser[(Seq[StartItem], Seq[NamedPath])] = createStart | readStart | failure("expected START or CREATE")
- def readStart: Parser[(Seq[StartItem], Seq[NamedPath])] = ignoreCase("start") ~> commaList(startBit) ^^ (x => (x, Seq())) | failure("expected START or CREATE")
+ def readStart: Parser[(Seq[StartItem], Seq[NamedPath])] = ignoreCase("start") ~> commaList(startBit) ^^ (x => (x, Seq()))
- def createStart = ignoreCase("create") ~> commaList(usePattern(translate)) ^^ {
+ def createStart: Parser[(Seq[StartItem], Seq[NamedPath])] = relate|create
+
+ def create = ignoreCase("create") ~> commaList(usePattern(translate)) ^^ {
case matching =>
val pathsAndItems = matching.flatten.filter(_.isInstanceOf[NamedPathWStartItems]).map(_.asInstanceOf[NamedPathWStartItems])
val startItems = matching.flatten.filter(_.isInstanceOf[StartItem]).map(_.asInstanceOf[StartItem])
View
43 cypher/src/main/scala/org/neo4j/cypher/internal/parser/v1_8/Updates.scala
@@ -24,7 +24,7 @@ import org.neo4j.cypher.internal.commands._
import org.neo4j.cypher.SyntaxException
trait Updates extends Base with Expressions with StartClause {
- def updates: Parser[(Seq[UpdateAction], Seq[NamedPath])] = rep(delete | set | foreach | relate) ^^ (cmds => reduce(cmds))
+ def updates: Parser[(Seq[UpdateAction], Seq[NamedPath])] = rep(delete | set | foreach) ^^ (cmds => reduce(cmds))
def foreach: Parser[(Seq[UpdateAction], Seq[NamedPath])] = ignoreCase("foreach") ~> "(" ~> identity ~ ignoreCase("in") ~ expression ~ ":" ~ opt(createStart) ~ opt(updates) <~ ")" ^^ {
case id ~ in ~ iterable ~ ":" ~ creates ~ innerUpdates => {
@@ -37,7 +37,6 @@ trait Updates extends Base with Expressions with StartClause {
}
}
- private def reduce[A,B](in:Seq[(Seq[A], Seq[B])]):(Seq[A], Seq[B]) = if (in.isEmpty) (Seq(),Seq()) else in.reduce((a, b) => (a._1 ++ b._1, a._2 ++ b._2))
def delete: Parser[(Seq[UpdateAction], Seq[NamedPath])] = ignoreCase("delete") ~> commaList(expression) ^^ {
case expressions => val updateActions: List[UpdateAction with Product] = expressions.map {
@@ -47,50 +46,10 @@ trait Updates extends Base with Expressions with StartClause {
(updateActions, Seq())
}
- case class PathAndRelateLink(path:Option[NamedPath], links:Seq[RelateLink])
-
- private def translate(abstractPattern: AbstractPattern): Maybe[PathAndRelateLink] = abstractPattern match {
- case ParsedNamedPath(name, patterns) =>
- val namedPathPatterns = patterns.map(matchTranslator).reduce(_ ++ _)
- val startItems = patterns.map(translate).reduce(_ ++ _)
-
- startItems match {
- case No(msg) => No(msg)
- case Yes(stuff) => namedPathPatterns.seqMap(p => {
- val namedPath = NamedPath(name, p.map(_.asInstanceOf[Pattern]): _*)
- val links = stuff.map(_.asInstanceOf[PathAndRelateLink])
-
- Seq(PathAndRelateLink(Some(namedPath), links.flatMap(_.links)))
- })
- }
-
- case ParsedRelation(name, props, ParsedEntity(Entity(startName), startProps, True()), ParsedEntity(Entity(endName), endProps, True()), typ, dir, map, True()) if typ.size == 1 =>
- val link = RelateLink(
- start = NamedExpectation(startName, startProps),
- end = NamedExpectation(endName, endProps),
- rel = NamedExpectation(name, props),
- relType = typ.head,
- dir = dir
- )
-
- Yes(Seq(PathAndRelateLink(None,Seq(link))))
- case _ => No(Seq())
- }
-
def set: Parser[(Seq[UpdateAction], Seq[NamedPath])] = ignoreCase("set") ~> commaList(propertySet) ^^ ((_,Seq()))
- def relate: Parser[(Seq[UpdateAction], Seq[NamedPath])] = ignoreCase("relate") ~> usePattern(translate) ^^ (patterns => {
- val (links, path)= reduce(patterns.map {
- case PathAndRelateLink(p, l) => (l, p.toSeq)
- })
-
- (Seq(RelateAction(links:_*)), path)
- })
-
def propertySet = property ~ "=" ~ expression ^^ {
case p ~ "=" ~ e => PropertySetAction(p.asInstanceOf[Property], e)
}
-
- def matchTranslator(abstractPattern: AbstractPattern): Maybe[Any]
}
View
56 .../neo4j/cypher/RelateAcceptanceTests.scala → .../cypher/CreateUniqueAcceptanceTests.scala
@@ -25,7 +25,7 @@ import collection.JavaConverters._
import org.scalatest.Assertions
import org.neo4j.graphdb.{Node, Relationship}
-class RelateAcceptanceTests extends ExecutionEngineHelper with Assertions with StatisticsChecker {
+class CreateUniqueAcceptanceTests extends ExecutionEngineHelper with Assertions with StatisticsChecker {
val stats = QueryStatistics.empty
@@ -34,7 +34,7 @@ class RelateAcceptanceTests extends ExecutionEngineHelper with Assertions with S
val a = createNode()
val b = createNode()
- val result = parseAndExecute("start a = node(1), b=node(2) relate a-[r:X]->b return r")
+ val result = parseAndExecute("start a = node(1), b=node(2) create unique a-[r:X]->b return r")
val createdRel = result.columnAs[Relationship]("r").toList.head
assertStats(result, relationshipsCreated = 1)
@@ -51,7 +51,7 @@ class RelateAcceptanceTests extends ExecutionEngineHelper with Assertions with S
val a = createNode()
val b = createNode()
val r = relate(a, b, "X")
- val result = parseAndExecute("start a = node(1), b=node(2) relate a-[r:X]->b return r")
+ val result = parseAndExecute("start a = node(1), b=node(2) create unique a-[r:X]->b return r")
val createdRel = result.columnAs[Relationship]("r").toList.head
assert(a.getRelationships.asScala.size === 1)
@@ -63,7 +63,7 @@ class RelateAcceptanceTests extends ExecutionEngineHelper with Assertions with S
val a = createNode()
val b = createNode()
val r = relate(a, b, "BAR")
- val result = parseAndExecute("start a = node(1), b=node(2) relate a-[r:FOO]->b return r")
+ val result = parseAndExecute("start a = node(1), b=node(2) create unique a-[r:FOO]->b return r")
val createdRel = result.columnAs[Relationship]("r").toList.head
assertStats(result, relationshipsCreated = 1)
@@ -79,7 +79,7 @@ class RelateAcceptanceTests extends ExecutionEngineHelper with Assertions with S
val c = createNode()
val d = createNode()
- val result = parseAndExecute("start a = node(1,2), b=node(3), c=node(4) relate a-[:X]->b-[:X]->c")
+ val result = parseAndExecute("start a = node(1,2), b=node(3), c=node(4) create unique a-[:X]->b-[:X]->c")
assertStats(result, relationshipsCreated = 3)
@@ -93,7 +93,7 @@ class RelateAcceptanceTests extends ExecutionEngineHelper with Assertions with S
def creates_minimal_amount_of_nodes_reverse() {
val a = createNode()
- val result = parseAndExecute("start a = node(1) relate c-[:X]->b-[:X]->a")
+ val result = parseAndExecute("start a = node(1) create unique c-[:X]->b-[:X]->a")
assertStats(result, nodesCreated = 2, relationshipsCreated = 2)
@@ -107,7 +107,7 @@ class RelateAcceptanceTests extends ExecutionEngineHelper with Assertions with S
def creates_node_if_it_is_missing() {
val a = createNode()
- val result = parseAndExecute("start a = node(1) relate a-[:X]->root return root")
+ val result = parseAndExecute("start a = node(1) create unique a-[:X]->root return root")
assertStats(result, nodesCreated = 1, relationshipsCreated = 1)
@@ -119,7 +119,7 @@ class RelateAcceptanceTests extends ExecutionEngineHelper with Assertions with S
def creates_node_if_it_is_missing_pattern_reversed() {
val a = createNode()
- val result = parseAndExecute("start a = node(1) relate root-[:X]->a return root")
+ val result = parseAndExecute("start a = node(1) create unique root-[:X]->a return root")
assertStats(result, nodesCreated = 1, relationshipsCreated = 1)
@@ -133,7 +133,7 @@ class RelateAcceptanceTests extends ExecutionEngineHelper with Assertions with S
val b = createNode("name" -> "Michael")
relate(a, b, "X")
- val createdNode = executeScalar[Node]("start a = node(1) relate a-[:X]->(b {name:'Andres'}) return b")
+ val createdNode = executeScalar[Node]("start a = node(1) create unique a-[:X]->(b {name:'Andres'}) return b")
assert(b != createdNode, "We should have created a new node - this one doesn't match")
assert(createdNode.getProperty("name") === "Andres")
@@ -145,7 +145,7 @@ class RelateAcceptanceTests extends ExecutionEngineHelper with Assertions with S
val b = createNode()
relate(a, b, "X", Map("foo" -> "bar"))
- val createdNode = executeScalar[Node]("start a = node(1) relate a-[:X {foo:'not bar'}]->b return b")
+ val createdNode = executeScalar[Node]("start a = node(1) create unique a-[:X {foo:'not bar'}]->b return b")
assert(b != createdNode, "We should have created a new node - this one doesn't match")
val createdRelationship = createdNode.getRelationships.asScala.toList.head
@@ -158,7 +158,7 @@ class RelateAcceptanceTests extends ExecutionEngineHelper with Assertions with S
val b = createNode()
val r = relate(a, b, "X", Map("foo" -> "bar"))
- val createdRel = executeScalar[Relationship]("start a = node(1), b = node(2) relate a-[r:X {foo:'not bar'}]->b return r")
+ val createdRel = executeScalar[Relationship]("start a = node(1), b = node(2) create unique a-[r:X {foo:'not bar'}]->b return r")
assert(r != createdRel, "We should have created a new rel - this one doesn't match")
assert(createdRel.getProperty("foo") === "not bar")
@@ -170,23 +170,15 @@ class RelateAcceptanceTests extends ExecutionEngineHelper with Assertions with S
def handle_optional_nulls() {
createNode()
- intercept[CypherTypeException](parseAndExecute("start a = node(1) match a-[?]->b relate b-[:X]->c"))
+ intercept[CypherTypeException](parseAndExecute("start a = node(1) match a-[?]->b create unique b-[:X]->c"))
}
-
- // discard based on rel-props and node-props
- // two_nodes_with_one_more_rel_created() {
- // start r = rel(0),a=node(0) relate a-[r:X]-b //BLOW UP
- // start a=node(0) match a-[?]-b relate b-[:X]->a //Blow up if b is null
- // test self-relationships
- // double delete and then a create after
-
@Test
def creates_single_node_if_it_is_missing() {
val a = createNode()
val b = createNode()
- val result = parseAndExecute("start a = node(1), b=node(2) relate a-[:X]->root<-[:X]-b return root")
+ val result = parseAndExecute("start a = node(1), b=node(2) create unique a-[:X]->root<-[:X]-b return root")
assertStats(result, nodesCreated = 1, relationshipsCreated = 2)
val aRels = a.getRelationships.asScala.toList
@@ -231,7 +223,7 @@ class RelateAcceptanceTests extends ExecutionEngineHelper with Assertions with S
START root = node(1)
CREATE book
FOREACH(name in ['a','b','c'] :
- RELATE root-[:tag]->(tag {name:name})<-[:tagged]-book
+ CREATE UNIQUE root-[:tag]->(tag {name:name})<-[:tagged]-book
)
return book
""")
@@ -264,7 +256,7 @@ return book
val result = parseAndExecute("""
START root = node(1)
FOREACH(name in ['a','b','c'] :
- RELATE root-[:tag]->(tag {name:name})
+ CREATE UNIQUE root-[:tag]->(tag {name:name})
)
""")
@@ -283,17 +275,17 @@ FOREACH(name in ['a','b','c'] :
relate(a,b1,"X")
relate(a,b2,"X")
- intercept[RelatePathNotUnique](parseAndExecute("""START a = node(1) RELATE a-[:X]->b-[:X]->d"""))
+ intercept[UniquePathNotUniqueException](parseAndExecute("""START a = node(1) CREATE UNIQUE a-[:X]->b-[:X]->d"""))
}
@Test
def tree_structure() {
val a = createNode()
val result = parseAndExecute("""START root=node(1)
- CREATE (value {year:2012, month:5, day:11})
- WITH root,value
- RELATE root-[:X]->(year {value:value.year})-[:X]->(month {value:value.month})-[:X]->(day {value:value.day})-[:X]->value
- return value;""")
+CREATE (value {year:2012, month:5, day:11})
+WITH root,value
+CREATE UNIQUE root-[:X]->(year {value:value.year})-[:X]->(month {value:value.month})-[:X]->(day {value:value.day})-[:X]->value
+RETURN value;""")
/*
root
@@ -310,10 +302,10 @@ FOREACH(name in ['a','b','c'] :
assertStats(result, nodesCreated = 4, relationshipsCreated = 4, propertiesSet = 6)
val result2 = parseAndExecute("""START root=node(1)
- CREATE (value { year:2012, month:5, day:12 })
- WITH root,value
- RELATE root-[:X]->(year {value:value.year})-[:X]->(month {value:value.month})-[:X]->(day {value:value.day})-[:X]->value
- return value;""")
+CREATE (value { year:2012, month:5, day:12 })
+WITH root,value
+CREATE UNIQUE root-[:X]->(year {value:value.year})-[:X]->(month {value:value.month})-[:X]->(day {value:value.day})-[:X]->value
+RETURN value;""")
assertStats(result2, nodesCreated = 2, relationshipsCreated = 2, propertiesSet = 4)
View
48 cypher/src/test/scala/org/neo4j/cypher/CypherParserTest.scala
@@ -1733,80 +1733,80 @@ create a-[r:REL]->b
returns(AllIdentifiers()))
}
- @Test def single_relate() {
+ @Test def single_create_unique() {
val secondQ = Query.
- relate(RelateLink("a", "b", " UNNAMED1", "reltype", Direction.OUTGOING)).
+ unique(UniqueLink("a", "b", " UNNAMED1", "reltype", Direction.OUTGOING)).
returns()
val q = Query.
start(NodeById("a", 1), NodeById("b", 2)).
tail(secondQ).
returns(AllIdentifiers())
- testFrom_1_8("start a = node(1), b=node(2) relate a-[:reltype]->b", q)
+ testFrom_1_8("start a = node(1), b=node(2) create unique a-[:reltype]->b", q)
}
- @Test def single_relate_with_rel() {
+ @Test def single_create_unique_with_rel() {
val secondQ = Query.
- relate(RelateLink("a", "b", "r", "reltype", Direction.OUTGOING)).
+ unique(UniqueLink("a", "b", "r", "reltype", Direction.OUTGOING)).
returns()
val q = Query.
start(NodeById("a", 1), NodeById("b", 2)).
tail(secondQ).
returns(AllIdentifiers())
- testFrom_1_8("start a = node(1), b=node(2) relate a-[r:reltype]->b", q)
+ testFrom_1_8("start a = node(1), b=node(2) create unique a-[r:reltype]->b", q)
}
@Test def single_relate_with_empty_parenthesis() {
val secondQ = Query.
- relate(RelateLink("a", " UNNAMED1", " UNNAMED2", "reltype", Direction.OUTGOING)).
+ unique(UniqueLink("a", " UNNAMED1", " UNNAMED2", "reltype", Direction.OUTGOING)).
returns()
val q = Query.
start(NodeById("a", 1), NodeById("b", 2)).
tail(secondQ).
returns(AllIdentifiers())
- testFrom_1_8("start a = node(1), b=node(2) relate a-[:reltype]->()", q)
+ testFrom_1_8("start a = node(1), b=node(2) create unique a-[:reltype]->()", q)
}
@Test def two_relates() {
val secondQ = Query.
- relate(
- RelateLink("a", "b", " UNNAMED1", "X", Direction.OUTGOING),
- RelateLink("c", "b", " UNNAMED2", "X", Direction.OUTGOING)).
+ unique(
+ UniqueLink("a", "b", " UNNAMED1", "X", Direction.OUTGOING),
+ UniqueLink("c", "b", " UNNAMED2", "X", Direction.OUTGOING)).
returns()
val q = Query.
start(NodeById("a", 1)).
tail(secondQ).
returns(AllIdentifiers())
- testFrom_1_8("start a = node(1) relate a-[:X]->b<-[:X]-c", q)
+ testFrom_1_8("start a = node(1) create unique a-[:X]->b<-[:X]-c", q)
}
@Test def relate_with_initial_values_for_node() {
val secondQ = Query.
- relate(
- RelateLink(new NamedExpectation("a"), NamedExpectation("b", Map[String, Expression]("name" -> Literal("Andres"))), new NamedExpectation(" UNNAMED1"), "X", Direction.OUTGOING)).
+ unique(
+ UniqueLink(new NamedExpectation("a"), NamedExpectation("b", Map[String, Expression]("name" -> Literal("Andres"))), new NamedExpectation(" UNNAMED1"), "X", Direction.OUTGOING)).
returns()
val q = Query.
start(NodeById("a", 1)).
tail(secondQ).
returns(AllIdentifiers())
- testFrom_1_8("start a = node(1) relate a-[:X]->(b {name:'Andres'})", q)
+ testFrom_1_8("start a = node(1) create unique a-[:X]->(b {name:'Andres'})", q)
}
@Test def relate_with_initial_values_for_rel() {
val secondQ = Query.
- relate(
- RelateLink(new NamedExpectation("a"), new NamedExpectation("b"), NamedExpectation(" UNNAMED1", Map[String, Expression]("name" -> Literal("Andres"))), "X", Direction.OUTGOING)).
+ unique(
+ UniqueLink(new NamedExpectation("a"), new NamedExpectation("b"), NamedExpectation(" UNNAMED1", Map[String, Expression]("name" -> Literal("Andres"))), "X", Direction.OUTGOING)).
returns()
val q = Query.
start(NodeById("a", 1)).
tail(secondQ).
returns(AllIdentifiers())
- testFrom_1_8("start a = node(1) relate a-[:X {name:'Andres'}]->b", q)
+ testFrom_1_8("start a = node(1) create unique a-[:X {name:'Andres'}]->b", q)
}
@Test def foreach_with_literal_collection() {
@@ -1837,15 +1837,15 @@ create a-[r:REL]->b
@Test def relate_with_two_rels_to_same_node() {
val returns = Query.
- updates(RelateAction(
- RelateLink("root", "x", "r1", "X", Direction.OUTGOING),
- RelateLink("root", "x", "r2", "Y", Direction.OUTGOING)))
+ start(CreateUniqueAction(
+ UniqueLink("root", "x", "r1", "X", Direction.OUTGOING),
+ UniqueLink("root", "x", "r2", "Y", Direction.OUTGOING)))
.returns(ReturnItem(Entity("x"), "x"))
val q = Query.start(NodeById("root", 0)).tail(returns).returns(AllIdentifiers())
testFrom_1_8(
- "start root=node(0) relate x<-[r1:X]-root-[r2:Y]->x return x",
+ "start root=node(0) create unique x<-[r1:X]-root-[r2:Y]->x return x",
q
)
}
@@ -1906,7 +1906,7 @@ create a-[r:REL]->b
@Test def relate_and_assign_to_path_identifier() {
val q2 = Query.
- updates(RelateAction(RelateLink("a", " UNNAMED1", "r", "KNOWS", Direction.OUTGOING))).
+ start(CreateUniqueAction(UniqueLink("a", " UNNAMED1", "r", "KNOWS", Direction.OUTGOING))).
namedPaths(NamedPath("p", RelatedTo("a", " UNNAMED1", "r", "KNOWS", Direction.OUTGOING, optional = false, predicate = True()))).
returns(ReturnItem(Entity("p"), "p"))
@@ -1915,7 +1915,7 @@ create a-[r:REL]->b
tail(q2).
returns(AllIdentifiers())
- testFrom_1_8("start a=node(0) relate p = a-[r:KNOWS]->() return p", q)
+ testFrom_1_8("start a=node(0) create unique p = a-[r:KNOWS]->() return p", q)
}
@Test(expected = classOf[SyntaxException]) def assign_to_path_inside_foreach_should_work() {
View
4 cypher/src/test/scala/org/neo4j/cypher/ErrorMessagesTest.scala
@@ -178,9 +178,9 @@ class ErrorMessagesTest extends ExecutionEngineHelper with Assertions with Strin
"These columns can't be listen in the WITH statement without renaming: count(*)")
}
- @Test def missing_relate_dependency_correctly_reported() {
+ @Test def missing_dependency_correctly_reported() {
expectError(
- "START a=node(0) RELATE a-[:KNOWS]->(b {name:missing}) RETURN b",
+ "START a=node(0) CREATE UNIQUE a-[:KNOWS]->(b {name:missing}) RETURN b",
"Unknown identifier `missing`")
}
View
18 cypher/src/test/scala/org/neo4j/cypher/MutatingIntegrationTests.scala
@@ -401,33 +401,33 @@ return distinct center""")
@Test
def failed_query_should_not_leave_dangling_transactions() {
- intercept[NotFoundException](parseAndExecute("START left=node(1), right=node(3,4) RELATE left-[r:KNOWS]->right RETURN r"))
+ intercept[NotFoundException](parseAndExecute("START left=node(1), right=node(3,4) CREATE UNIQUE left-[r:KNOWS]->right RETURN r"))
assertNull("Did not expect to be in a transaction now", graph.getTxManager.getTransaction)
}
@Test
- def relate_twice_with_param_map() {
+ def create_unique_twice_with_param_map() {
createNode()
createNode()
val map1 = Map("name" -> "Anders")
val map2 = new HashMap[String, Any]()
map2.put("name", "Anders")
- val r1 = executeScalar[Relationship]("start a=node(1), b=node(2) relate a-[r:FOO {param}]->b return r", "param" -> map1)
- val r2 = executeScalar[Relationship]("start a=node(1), b=node(2) relate a-[r:FOO {param}]->b return r", "param" -> map2)
+ val r1 = executeScalar[Relationship]("start a=node(1), b=node(2) create unique a-[r:FOO {param}]->b return r", "param" -> map1)
+ val r2 = executeScalar[Relationship]("start a=node(1), b=node(2) create unique a-[r:FOO {param}]->b return r", "param" -> map2)
assert(r1 === r2)
}
@Test
- def relate_twice_with_array_prop() {
+ def create_unique_twice_with_array_prop() {
createNode()
createNode()
- parseAndExecute("start a=node(1) relate a-[:X]->({foo:[1,2,3]})")
- val result = parseAndExecute("start a=node(1) relate a-[:X]->({foo:[1,2,3]})")
+ parseAndExecute("start a=node(1) create unique a-[:X]->({foo:[1,2,3]})")
+ val result = parseAndExecute("start a=node(1) create unique a-[:X]->({foo:[1,2,3]})")
assertFalse("Should not have created node", result.queryStatistics().containsUpdates)
}
@@ -460,10 +460,10 @@ return distinct center""")
}
@Test
- def related_paths_honor_directions() {
+ def create_unique_paths_honor_directions() {
val a = createNode()
val b = createNode()
- val result = parseAndExecute("start a=node(1), b=node(2) relate p = a<-[:X]-b return p").toList.head("p").asInstanceOf[Path]
+ val result = parseAndExecute("start a=node(1), b=node(2) create unique p = a<-[:X]-b return p").toList.head("p").asInstanceOf[Path]
assert(result.startNode() === a)
assert(result.endNode() === b)
View
2 cypher/src/test/scala/org/neo4j/cypher/docgen/CreateTest.scala
@@ -52,7 +52,7 @@ class CreateTest extends DocumentingTestBase with StatisticsChecker {
title = "Return created node",
text = "Creating a single node is done by issuing the following query.",
queryText = "create (a {name : 'Andres'}) return a",
- returns = "The newly created node is returned. This query uses the alternative syntax, which fits with how +RELATE+ looks.",
+ returns = "The newly created node is returned. This query uses the alternative syntax for single node creation",
assertions = (p) => assert(p.size === 1)
)
}
View
18 .../org/neo4j/cypher/docgen/RelateTest.scala → ...eo4j/cypher/docgen/CreateUniqueTest.scala
@@ -22,21 +22,21 @@ package org.neo4j.cypher.docgen
import org.neo4j.cypher.StatisticsChecker
import org.junit.Test
-class RelateTest extends DocumentingTestBase with StatisticsChecker {
+class CreateUniqueTest extends DocumentingTestBase with StatisticsChecker {
def graphDescription = List(
"root X A",
"root X B",
"root X C",
"A KNOWS C"
)
- def section = "Relate"
+ def section = "Create Unique"
@Test def create_relationship_when_missing() {
testQuery(
title = "Create relationship if it is missing",
- text = "+RELATE+ is used to describe the pattern that should be found or created.",
- queryText = "start left=node(%A%), right=node(%B%,%C%) relate left-[r:KNOWS]->right return r",
+ text = "+CREATE UNIQUE+ is used to describe the pattern that should be found or created.",
+ queryText = "start left=node(%A%), right=node(%B%,%C%) create unique left-[r:KNOWS]->right return r",
returns = "The left node is matched agains the two right nodes. One relationship already exists and can be " +
"matched, and the other relationship is created before it is returned.",
assertions = (p) => assertStats(p, relationshipsCreated = 1))
@@ -46,7 +46,7 @@ class RelateTest extends DocumentingTestBase with StatisticsChecker {
testQuery(
title = "Create node if missing",
text = "If the pattern described needs a node, and it can't be matched, a new node will be created.",
- queryText = "start root=node(%root%) relate root-[:LOVES]-someone return someone",
+ queryText = "start root=node(%root%) create unique root-[:LOVES]-someone return someone",
returns = "The root node doesn't have any LOVES relationships, and so a node is created, and also a relationship " +
"to that node.",
assertions = (p) => assertStats(p, relationshipsCreated = 1, nodesCreated = 1))
@@ -56,7 +56,7 @@ class RelateTest extends DocumentingTestBase with StatisticsChecker {
testQuery(
title = "Create nodes with values",
text = "The pattern described can also contain values on the node. These are given using the JSON-like prop : <expression> syntax.",
- queryText = "start root=node(%root%) relate root-[:X]-(leaf {name:'D'} ) return leaf",
+ queryText = "start root=node(%root%) create unique root-[:X]-(leaf {name:'D'} ) return leaf",
returns = "Node node connected with the root node has the name 'D', and so a new node needs to be created to " +
"match the pattern.",
assertions = (p) => assertStats(p, relationshipsCreated = 1, nodesCreated = 1, propertiesSet = 1))
@@ -66,7 +66,7 @@ class RelateTest extends DocumentingTestBase with StatisticsChecker {
testQuery(
title = "Create relationship with values",
text = "Relationships created can also be matched on values.",
- queryText = "start root=node(%root%) relate root-[r:X {since:'forever'}]-() return r",
+ queryText = "start root=node(%root%) create unique root-[r:X {since:'forever'}]-() return r",
returns = "In this example, we want the relationship to have a value, and since no such relationship can be found," +
" a new node and relationship are created. Note that since we are not interested in the created node, we don't " +
"name it.",
@@ -76,8 +76,8 @@ class RelateTest extends DocumentingTestBase with StatisticsChecker {
@Test def commad_separated_pattern() {
testQuery(
title = "Describe complex pattern",
- text = "The pattern described by +RELATE+ can be separated by commas, just like in +MATCH+ and +CREATE+",
- queryText = "start root=node(%root%) relate root-[:FOO]->x, root-[:BAR]->x return x",
+ text = "The pattern described by +CREATE UNIQUE+ can be separated by commas, just like in +MATCH+ and +CREATE+",
+ queryText = "start root=node(%root%) create unique root-[:FOO]->x, root-[:BAR]->x return x",
returns = "This example pattern uses two paths, separated by a comma.",
assertions = (p) => assertStats(p, relationshipsCreated = 2, nodesCreated = 1))
}
View
39 cypher/src/test/scala/org/neo4j/cypher/docgen/PatternTest.scala
@@ -41,16 +41,15 @@ class PatternTest extends ArticleTest {
val title = "Pattern"
val section = "Introduction"
val text =
-"""
+ """
Patterns
========
Patterns are at the very core of Cypher, and are used in a lot of different places.
Using patterns, you describe the shape of the data that you are looking for.
Patterns are used in the `MATCH` clause. Path patterns are expressions.
-Since these expressions are collections, they can also be used as
-predicates (a non-empty collection signifies true). They are also used to `CREATE` the graph, and by the `RELATE`
-clause.
+Since these expressions are collections, they can also be used as predicates (a non-empty collection signifies true).
+They are also used to `CREATE`/`CREATE UNIQUE` the graph.
So, understanding patterns is important, to be able to be effective with Cypher.
@@ -63,13 +62,13 @@ graph nodes or relationships. All parts of the pattern must be directly or indir
where parts of the pattern are not reachable from any starting point will be rejected.
[options="header", cols=">s,^,^,^,^,^", width="100%"]
-|===================
-|Clause|Optional|Multiple rel. types|Varlength|Paths|Maps
-|Match|Yes|Yes|Yes|Yes|-
-|Create|-|-|-|Yes|Yes
-|Relate|-|-|-|Yes|Yes
-|Expressions|-|Yes|Yes|-|-
-|===================
+ |===================
+ |Clause|Optional|Multiple rel. types|Varlength|Paths|Maps
+ |Match|Yes|Yes|Yes|Yes|-
+ |Create|-|-|-|Yes|Yes
+ |Create Unique|-|-|-|Yes|Yes
+ |Expressions|-|Yes|Yes|-|-
+ |===================
== Patterns for related nodes ==
@@ -110,7 +109,7 @@ If multiple relationship types are acceptable, you can list them, separating the
+`a-[r:TYPE1|TYPE2]->b`+
This pattern matches a relationship of type +TYPE1+ or +TYPE2+, going from `a` to `b`. The relationship is named `r`.
-Multiple relationship types can not be used with `CREATE` or `RELATE`.
+Multiple relationship types can not be used with `CREATE` or `CREATE UNIQUE`.
== Optional relationships ==
@@ -175,7 +174,7 @@ You can set a minimum set of steps that can be taken, and/or the maximum number
This is a variable length relationship containing at least three graph relationships, and at most five.
-Variable length relationships can not be used with `CREATE` and `RELATE`.
+Variable length relationships can not be used with `CREATE` and `CREATE UNIQUE`.
As a simple example, let's take the query below:
@@ -194,14 +193,14 @@ identifier, like so:
+`p = (a)-[*3..5]->(b)`+
-You can do this in `MATCH`, `RELATE` and `CREATE`, but not when using patterns as expressions. Example of the three in a
-single query:
+You can do this in `MATCH`, `CREATE` and `CREATE UNIQUE`, but not when using patterns as expressions. Example of the
+three in a single query:
###no-results
START me=node(%F%)
MATCH p1 = me-[*2]-friendOfFriend
CREATE p2 = me-[:MARRIED_TO]-(wife {name:"Gunhild"})
-RELATE p3 = wife-[:KNOWS]-friendOfFriend
+CREATE UNIQUE p3 = wife-[:KNOWS]-friendOfFriend
RETURN p1,p2,p3###
== Setting properties ==
@@ -212,11 +211,11 @@ Properties are expressed in patterns using the map-construct, which is simply cu
key-expression pairs, separated by commas, e.g. `{ name: "Andres", sport: "BJJ" }`. If the map is supplied through a
parameter, the normal parameter expression is used: `{ paramName }`.
-Maps are only used by `CREATE` and `RELATE`. In `CREATE` they are used to set the properties on the newly created nodes
-and relationships.
+Maps are only used by `CREATE` and `CREATE UNIQUE`. In `CREATE` they are used to set the properties on the newly created
+nodes and relationships.
-When used with `RELATE`, they are used to try to match a pattern element with the corresponding graph element. The
-match is successful if the properties on the pattern element can be matched exactly against properties on the graph
+When used with `CREATE UNIQUE`, they are used to try to match a pattern element with the corresponding graph element.
+The match is successful if the properties on the pattern element can be matched exactly against properties on the graph
elements. The graph element can have additional properties, and they do not affect the match. If Neo4j fails to find
matching graph elements, the maps is used to set the properties on the newly created elements.
"""
View
4 cypher/src/test/scala/org/neo4j/cypher/docgen/cookbook/CoFavoritedPlacesTest.scala
@@ -48,7 +48,7 @@ class CoFavoritedPlacesTest extends DocumentingTestBase {
@Test def coFavoritedPlaces() {
testQuery(
- title = "Co-Favorited Places -- Users Who Like x Also Like y",
+ title = "Co-favorited places -- users who like x also like y",
text = """Find places that people also like who favorite this place:
* Determine who has favorited place x.
@@ -65,7 +65,7 @@ class CoFavoritedPlacesTest extends DocumentingTestBase {
@Test def coTaggedPlaces() {
testQuery(
- title = "Co-Tagged Places -- Places Related through Tags",
+ title = "Co-Tagged places -- places related through tags",
text = """Find places that are tagged with the same tags:
* Determine the tags for place x.
View
12 .../internal/mutation/RelateUniqueTest.scala → ...utation/DoubleCheckCreateUniqueTest.scala
@@ -31,18 +31,18 @@ import collection.mutable.Map
/*
-This test tries to set up a situation where RELATE would fail, unless we guard with locks to prevent creating
+This test tries to set up a situation where CREATE UNIQUE would fail, unless we guard with locks to prevent creating
multiple relationships.
-It does so by using a decorator around ImpermanentGraphDatabase, so directly after RELATE has done getRelationships on
-a node, we'll create a new relationship.
+It does so by using a decorator around ImpermanentGraphDatabase, so directly after CREATE UNIQUE has done
+getRelationships on a node, we'll create a new relationship.
*/
-class RelateUniqueTest extends Assertions {
+class DoubleCheckCreateUniqueTest extends Assertions {
var done = false
val db = new ImpermanentGraphDatabase() with TripIt
- @Test def double_check_relate() {
+ @Test def double_check_unique() {
db.afterGetRelationship = createRel
@@ -62,7 +62,7 @@ class RelateUniqueTest extends Assertions {
assert(a.getRelationships.asScala.size === 1)
}
- val relateAction = RelateAction(RelateLink("a", "b", "r", "X", Direction.OUTGOING))
+ val relateAction = CreateUniqueAction(UniqueLink("a", "b", "r", "X", Direction.OUTGOING))
private def createExecutionContext(a: Node): ExecutionContext = {

0 comments on commit c7dbbb9

Please sign in to comment.