Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Started Pattern-documentation

  • Loading branch information...
commit 92acd1fce35035590612e7fabd616d6583a688ed 1 parent c5be29f
@systay authored
View
2  cypher/src/docs/dev/index.txt
@@ -20,6 +20,8 @@ include::updating.txt[]
include::transactions.txt[]
+include::ql/pattern/patterns.txt[]
+
:leveloffset: 2
include::ql/start/index.txt[]
View
2  cypher/src/docs/dev/ql/where/index.txt
@@ -17,5 +17,5 @@ include::property-exists.txt[]
include::default-true-if-property-is-missing.txt[]
include::default-false-if-property-is-missing.txt[]
include::filter-on-null-values.txt[]
-include::filter-on-relationships.txt[]
+include::filter-on-patterns.txt[]
include::in-operator.txt[]
View
12 cypher/src/main/scala/org/neo4j/cypher/internal/commands/Functions.scala
@@ -159,11 +159,17 @@ case class IdFunction(inner: Expression) extends NullInNullOutExpression(inner)
case class HeadFunction(collection: Expression) extends NullInNullOutExpression(collection) with IterableSupport {
def compute(value: Any, m: Map[String, Any]) = makeTraversable(value).head
- private def myType = collection.identifier.typ match {
- case x: IterableType => x.iteratedType
- case _ => ScalarType()
+ private def myType = {
+ println(collection.identifier.typ)
+
+ collection.identifier.typ match {
+ case x: IterableType => x.iteratedType
+ case _ => ScalarType()
+ }
}
+ override def dependencies(extectedType: AnyType): Seq[Identifier] = declareDependencies(extectedType)
+
val identifier = Identifier("head(" + collection.identifier.name + ")", myType)
def declareDependencies(extectedType: AnyType): Seq[Identifier] = collection.dependencies(AnyIterableType())
View
28 ...src/main/scala/org/neo4j/cypher/internal/executionplan/builders/ColumnFilterBuilder.scala
@@ -30,18 +30,25 @@ class ColumnFilterBuilder extends PlanBuilder {
val p = plan.pipe
val isLastPipe = q.tail.isEmpty
- val returnItems = getReturnItems(q.returns, p.symbols)
- val filterPipe = new ColumnFilterPipe(p, returnItems, isLastPipe)
+ if (!isLastPipe && q.returns == Seq(Unsolved(AllIdentifiers()))) {
+ val resultQ = q.copy(returns = q.returns.map(_.solve))
- val resultPipe = if (filterPipe.symbols != p.symbols || isLastPipe) {
- filterPipe
+ plan.copy(query = resultQ)
} else {
- p
- }
- val resultQ = q.copy(returns = q.returns.map(_.solve))
+ val returnItems = getReturnItems(q.returns, p.symbols)
+ val filterPipe = new ColumnFilterPipe(p, returnItems, isLastPipe)
+
+ val resultPipe = if (filterPipe.symbols != p.symbols || isLastPipe) {
+ filterPipe
+ } else {
+ p
+ }
- plan.copy(pipe = resultPipe, query = resultQ)
+ val resultQ = q.copy(returns = q.returns.map(_.solve))
+
+ plan.copy(pipe = resultPipe, query = resultQ)
+ }
}
def canWorkWith(plan: ExecutionPlanInProgress) = {
@@ -57,6 +64,9 @@ class ColumnFilterBuilder extends PlanBuilder {
private def getReturnItems(q: Seq[QueryToken[ReturnColumn]], symbols: SymbolTable): Seq[ReturnItem] = q.map(_.token).flatMap {
case x: ReturnItem => Seq(x)
- case x: AllIdentifiers => x.expressions(symbols).map(e => ReturnItem(e, e.identifier.name))
+ case x: AllIdentifiers =>
+ val expressions = x.expressions(symbols)
+ val map = expressions.map(e => ReturnItem(e, e.identifier.name))
+ map
}
}
View
25 cypher/src/main/scala/org/neo4j/cypher/internal/parser/v1_8/ParserPattern.scala
@@ -38,23 +38,15 @@ trait ParserPattern extends Base {
}
def usePattern[T](translator: AbstractPattern => Maybe[T]): Parser[Seq[T]] = Parser {
- case in =>
- pattern(in) match {
- case Success(abstractPattern, rest) =>
- val concretePattern = abstractPattern.map(p => translator(p))
-
- concretePattern.find(!_.success) match {
- case Some(No(msg)) => Failure(msg, rest)
- case None => Success(concretePattern.map(_.value), rest)
- }
-
- case Failure(msg, rest) => Failure(msg, rest)
- case Error(msg, rest) => Error(msg, rest)
- }
+ case in => translate(in, translator, pattern(in))
}
def usePath[T](translator: AbstractPattern => Maybe[T]):Parser[Seq[T]] = Parser {
- case in => path(in) match {
+ case in => translate(in, translator, path(in))
+ }
+
+ private def translate[T](in: Input, translator: (AbstractPattern) => Maybe[T], pattern1: ParseResult[Seq[AbstractPattern]]): ParseResult[Seq[T]] with Product with Serializable = {
+ pattern1 match {
case Success(abstractPattern, rest) =>
val concretePattern = abstractPattern.map(p => translator(p))
@@ -85,10 +77,11 @@ trait ParserPattern extends Base {
}
private def node: Parser[ParsedEntity] =
- parens(nodeFromExpression) | // whatever // CREATE (last(p))-[:KNOWS]->me
+ parens(nodeFromExpression) | // whatever expression, but inside parenthesis
singleNodeEqualsMap | // x = {}
nodeIdentifier | // x
- nodeInParenthesis | failure("expected an expression that is a node")
+ nodeInParenthesis | // (x {})
+ failure("expected an expression that is a node")
private def singleNodeEqualsMap = identity ~ "=" ~ properties ^^ {
View
13 cypher/src/test/scala/org/neo4j/cypher/docgen/CreateTest.scala
@@ -78,6 +78,19 @@ class CreateTest extends DocumentingTestBase {
)
}
+ @Test def using_expressions_as_nodes() {
+ val (aId, bId) = createTwoNodes
+
+ testQuery(
+ title = "Using expressions for nodes end points",
+ text = "You can use any expression as a node, as long as it returns a node. Just make sure to encase your " +
+ "expression in parenthesis.",
+ queryText = "start a=node(" + aId + ") with collect(a) as nodes start b=node(" +bId + ") create (head(nodes))-[r:REL]->b return r",
+ returns = "The created relationship is returned.",
+ assertions = (p) => assert(p.size === 1)
+ )
+ }
+
@Test def set_property_to_an_iterable() {
val (aId, bId) = db.inTx(() => {
val a = db.createNode()
View
16 cypher/src/test/scala/org/neo4j/cypher/docgen/DocumentingTestBase.scala
@@ -113,10 +113,9 @@ _Graph_
}
def testQuery(title: String, text: String, queryText: String, returns: String, assertions: (ExecutionResult => Unit)*) {
- var query = queryText
- nodes.keySet.foreach((key) => query = query.replace("%" + key + "%", node(key).getId.toString))
- val result = engine.execute(query)
- assertions.foreach(_.apply(result))
+ val r = testWithoutDocs(queryText, assertions:_*)
+ val result: ExecutionResult = r._1
+ var query: String = r._2
val dir = new File(path + nicefy(section))
if (!dir.exists()) {
@@ -131,6 +130,15 @@ _Graph_
dumpGraphViz(graphViz, graphFileName)
}
+
+ def testWithoutDocs(queryText: String, assertions: (ExecutionResult => Unit)*): (ExecutionResult, String) = {
+ var query = queryText
+ nodes.keySet.foreach((key) => query = query.replace("%" + key + "%", node(key).getId.toString))
+ val result = engine.execute(query)
+ assertions.foreach(_.apply(result))
+ (result, query)
+ }
+
def indexProperties[T <: PropertyContainer](n: T, index: Index[T]) {
indexProps.foreach((property) => {
if (n.hasProperty(property)) {
View
55 cypher/src/test/scala/org/neo4j/cypher/docgen/MatchTest.scala
@@ -297,23 +297,11 @@ RETURN p"""
@Test def intro() {
testQuery(
- title = "introduction",
+ title = "Introduction",
text = """Pattern matching is one of the pillars of Cypher. The pattern is used to describe the shape of the data that we are
looking for. Cypher will then try to find patterns in the graph -- these are called matching subgraphs.
-The description of the pattern is made up of one or more paths, separated by commas. A path is a sequence of nodes and
-relationships that always start and end in nodes. An example path would be:
-+`(a)-->(b)`+
-Paths can be of arbitrary length, and the same node may appear in multiple places in the path. Node identifiers can be
-used with or without surrounding parenthesis. The following two match clauses are semantically identical -- the difference is
-purely aesthetic.
-
-+`MATCH (a)-->(b)`+
-
-and
-
-+`MATCH a-->b`+
Patterns have bound points, or start points. They are the parts of the pattern that are already ``bound'' to a set of
@@ -385,42 +373,11 @@ As a simple example, let's take the following query, executed on the graph pictu
)
}
- @Test def introQ2() {
- testQuery(
- title = "q2",
- text = "",
- queryText = intro_q2,
- returns = "",
- assertions = p => assertTrue(true)
- )
- }
-
- @Test def introQ3() {
- testQuery(
- title = "q3",
- text = "",
- queryText = intro_q3,
- returns = "",
- assertions = p => assertTrue(true)
- )
- }
- @Test def introQ4() {
- testQuery(
- title = "q4",
- text = "",
- queryText = intro_q4,
- returns = "",
- assertions = p => assertTrue(true)
- )
- }
- @Test def introQ5() {
- testQuery(
- title = "q5",
- text = "",
- queryText = intro_q5,
- returns = "",
- assertions = p => assertTrue(true)
- )
+ @Test def runQueries() {
+ testWithoutDocs(intro_q2)
+ testWithoutDocs(intro_q3)
+ testWithoutDocs(intro_q4)
+ testWithoutDocs(intro_q5)
}
}
View
177 cypher/src/test/scala/org/neo4j/cypher/docgen/PatternTest.scala
@@ -0,0 +1,177 @@
+/**
+ * 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.docgen
+
+import org.junit.Assert._
+import org.junit.Test
+
+class PatternTest extends DocumentingTestBase {
+ override def indexProps: List[String] = List("name")
+
+ def graphDescription = List("A KNOWS B", "A BLOCKS C", "D KNOWS A", "B KNOWS E", "C KNOWS E", "B BLOCKS D")
+
+ override val properties = Map(
+ "A" -> Map("name" -> "Anders"),
+ "B" -> Map("name" -> "Bossman"),
+ "C" -> Map("name" -> "Cesar"),
+ "D" -> Map("name" -> "David"),
+ "E" -> Map("name" -> "Emil")
+ )
+
+ def section: String = "PATTERN"
+
+ def intro_q1 = """START me=node(1)
+MATCH me-->friend-[?:parent_of]->children
+RETURN friend, children"""
+
+ def intro_q2 = """START a=node(1)
+MATCH p = a-[?]->b
+RETURN b"""
+
+ def intro_q3 = """START a=node(1)
+MATCH p = a-[?*]->b
+RETURN b"""
+
+ def intro_q4 = """START a=node(1)
+MATCH p = a-[?]->x-->b
+RETURN b"""
+
+ def intro_q5 = """START a=node(1), x=node(2)
+MATCH p = shortestPath( a-[?*]->x )
+RETURN p"""
+
+
+ @Test def intro() {
+ testQuery(
+ title = "Patterns",
+ text = """
+Patterns are at the very core of Cypher, and are used for a lot of different reasons. They all look the same though,
+and the parts of the patterns mean almost, but not quite the same thing. 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.
+
+So, understanding patterns is important, to be able to be effective with Cypher.
+
+The idea is for you to draw your query on a whiteboard, naming the interesting parts of the pattern, so
+you can then use values from these parts to create the result set you are looking for. You describe the pattern, and
+Cypher will figure out how to get that data for you.
+
+The description of the pattern is made up of one or more paths, separated by commas. A path is a sequence of nodes and
+relationships that always start and end in nodes. An example path would be:
+
++`(a)-->(b)`+
+
+This is a path starting from the pattern node `a`, with an outgoing relationship from it to pattern node `b`.
+
+Paths can be of arbitrary length, and the same node may appear in multiple places in the path.
+
+Node identifiers can be used with or without surrounding parenthesis. The following match is semantically identical to
+the one we saw above -- the difference is purely aesthetic.
+
++`a-->b`+
+
+If you don't care about a node, you don't need to name it. Empty parenthesis are used for these nodes, like so:
+
++`a-->()<--b`+
+
+If you need to work with the relationship between two nodes, you can name it.
+
++`a-[r]->b`+
+
+If you don't care about the direction of the relationship, you can omit the arrow at either end of the relationship, like:
+
++`a--b`+
+
+Relationships have types. When you are only interested in a specific relationship type, you can specify this like so:
+
++`a-[:REL_TYPE]->b`+
+
+If multiple relationships are acceptable, you can list them, separating them with the pipe symbol `|`
+
++`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`.
+
+[source,cypher]
+----
+"""
+ + intro_q1 +
+"""
+----
+
+The query above says ``give me all my friends, and their children, if they have any.''
+
+Optionality is transitive -- if a part of the pattern can only be reached from a bound point through an optional relationship,
+that part is also optional. In the pattern above, the only bound point in the pattern is `me`. Since the relationship
+between `friend` and `children` is optional, `children` is an optional part of the graph.
+
+Also, named paths that contain optional parts are also optional -- if any part of the path is
+`null`, the whole path is `null`.
+
+In these examples, `b` and `p` are all optional and can contain `null`:
+
+[source,cypher]
+----
+"""
+ + intro_q2 +
+
+"""
+----
+
+[source,cypher]
+----
+"""
+ + intro_q3 +
+
+"""
+----
+
+[source,cypher]
+----
+"""
+ + intro_q4 +
+
+"""
+----
+
+[source,cypher]
+----
+"""
+ + intro_q5 +
+
+"""
+----
+
+As a simple example, let's take the following query, executed on the graph pictured below.
+""",
+ queryText = intro_q1,
+ returns = "This returns the a +friend+ node, and no +children+, since there are no such relationships in the graph.",
+ assertions = p => assertTrue(true)
+ )
+ }
+
+ @Test def runQueries() {
+ testWithoutDocs(intro_q2)
+ testWithoutDocs(intro_q3)
+ testWithoutDocs(intro_q4)
+ testWithoutDocs(intro_q5)
+ }
+
+}
View
32 cypher/src/test/scala/org/neo4j/cypher/docgen/WhereTest.scala
@@ -121,8 +121,8 @@ class WhereTest extends DocumentingTestBase {
@Test def filter_on_null() {
testQuery(
title = "Filter on null values",
- text = "Sometimes you might want to test if a value or an identifier is null. This is done just like SQL does it, with IS NULL." +
- " Also like SQL, the negative is IS NOT NULL, althought NOT(IS NULL x) also works.",
+ text = "Sometimes you might want to test if a value or an identifier is null. This is done just like SQL does it, " +
+ "with IS NULL. Also like SQL, the negative is `IS NOT NULL`, althought `NOT(IS NULL x)` also works.",
queryText = """start a=node(%Tobias%), b=node(%Andres%, %Peter%) match a<-[r?]-b where r is null return b""",
returns = "Nodes that Tobias is not connected to",
assertions = (p) => assertEquals(List(Map("b" -> node("Peter"))), p.toList))
@@ -130,21 +130,19 @@ class WhereTest extends DocumentingTestBase {
@Test def has_relationship_to() {
testQuery(
- title = "Filter on relationships",
- text = """To filter out subgraphs based on relationships between nodes, you use a limited part of the iconigraphy in the match clause. You can only describe the relationship with direction and optional type. These are all valid expressions:
-[source,cypher]
-----
-WHERE a-->b
-WHERE a<--b
-WHERE a<-[:KNOWS]-b
-WHERE a-[:KNOWS]-b
-----
-
-Note that you can not introduce new identifiers here. Although it might look very similar to the `MATCH` clause, the
-`WHERE` clause is all about eliminating matched subgraphs. `MATCH a-->b` is very different from `WHERE a-->b`; the first will
-produce a subgraph for every relationship between `a` and `b`, and the latter will eliminate any matched subgraphs where `a` and `b`
-do not have a relationship between them.
- """,
+ title = "Filter on patterns",
+ text = """Patterns are expressions in Cypher, expressions that return a collection of paths. Collection
+expressions are also predicates - an empty collection represents `false`, and a non-empty represents `true`.
+
+So, patterns are not only expressions, they are also predicates. The only limitation to your pattern is that you must be
+able to express it in a single path. You can't use comas between multiple paths like you do in MATCH. You can achieve
+the same effect by combining multiple patterns with AND.
+
+Note that you can not introduce new identifiers here. Although it might look very similar to the `MATCH` patterns, the
+`WHERE` clause is all about eliminating matched subgraphs. `MATCH a-[*]->b` is very different from `WHERE a-[*]->b`; the
+first will produce a subgraph for every path it can find between `a` and `b`, and the latter will eliminate any matched
+subgraphs where `a` and `b` do not have a directed relationship chain between them.
+ """,
queryText = """start a=node(%Tobias%), b=node(%Andres%, %Peter%) where a<--b return b""",
returns = "Nodes that Tobias is not connected to",
assertions = (p) => assertEquals(List(Map("b" -> node("Andres"))), p.toList))
Please sign in to comment.
Something went wrong with that request. Please try again.