Permalink
Browse files

Lots of documentation changes to Cypher

  • Loading branch information...
1 parent 98bf873 commit 55863d0dcbadca30e277b9a3bf5c9ca1f0eb809a @systay committed Jun 26, 2012
@@ -10,7 +10,7 @@ and old ones changed to fit into new possibilities. To guard you from having to
Cypher allows you to use an older parser, but still gain the speed from new optimizations.
There are two ways you can select which parser to use. You can configure your database with the configuration parameter
-+cypher_parser_version+, and enter which parser you'd like to use (1.6 and 1.7 are supported now). Any Cypher query
++cypher_parser_version+, and enter which parser you'd like to use (1.6, 1.7 and 1.8 are supported now). Any Cypher query
that doesn't explicitly say anything else, will get the parser you have configured.
The other way is on a query by query basis. By simply pre-pending your query with +"CYPHER 1.6"+, that particular query
@@ -14,7 +14,8 @@ START n=node(1) MATCH n-->b RETURN b
The identifiers are +n+ and +b+.
-Identifiers can be lower or upper case, and may contain underscore.
-If other characters are needed, you can use the +`+ sign.
+Identifier names are case sensitive, and can contain any underscore and alphanumeric character (a-z, 0-9), but must
+start with a letter. If other characters are needed, you can use the backquote (+`+) sign.
+
The same rules apply to property names.
@@ -20,9 +20,9 @@ include::updating.txt[]
include::transactions.txt[]
-:leveloffset: 1
+:leveloffset: 2
-include::ql/pattern/patterns.txt[]
+include::ql/introduction/pattern.txt[]
:leveloffset: 2
@@ -159,9 +159,7 @@ This section is showing some graph-related update operations that require advanc
:leveloffset: 2
-include::insert-a-new-value-into-a-linked-list.txt[]
-
-include::cypher-linkedlist-graph.txt[]
+include::linked-list.txt[]
:leveloffset: 1
@@ -3,6 +3,10 @@ Create
======
Creating graph elements - nodes and relationships, is done with +CREATE+.
+[TIP]
+In the `CREATE` clause, patterns are used a lot.
+Read <<introduction-pattern>> for an introduction.
+
:leveloffset: 2
include::create-single-node.txt[]
@@ -4,6 +4,12 @@ Foreach
Collections and paths are key concepts in Cypher. To use them for updating data, you can use the FOREACH construct.
It allows you to do updating commands on elements in a collection - a path, or a collection created by aggregation.
+The identifier context inside of the foreach parenthesis is separate from the one outside it, i.e. if you `CREATE` a
+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`.
+
:leveloffset: 2
include::mark-all-nodes-along-a-path.txt[]
@@ -2,9 +2,13 @@
Match
=====
-include::introduction.txt[]
+== Introduction ==
-For the examples given in the sections below, the follwoing graph is the base:
+[TIP]
+In the `MATCH` clause, patterns are used a lot.
+Read <<introduction-pattern>> for an introduction.
+
+The following graph is used for the examples below:
include::cypher-match-graph.txt[]
@@ -1,9 +1,2 @@
-[[match-introduction]]
-== Introduction ==
-In the `MATCH` clause, patterns are used a lot.
-Read <<pattern-patterns>> for an introduction.
-The following graph is used for the examples below:
-
-include::cypher-match-graph.txt[]
@@ -7,6 +7,10 @@ will always make the least change possible to the graph - if it can use parts of
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[]
@@ -121,7 +121,7 @@ class PipeExecutionResult(r: => Traversable[Map[String, Any]], val symbols: Symb
writer.println("%s ms".format(timeTaken))
}
- def dumpToString(): String = {
+ lazy val dumpToString: String = {
val stringWriter = new StringWriter()
val writer = new PrintWriter(stringWriter)
dumpToString(writer)
@@ -31,6 +31,7 @@ object PatternWithEnds {
case ParsedVarLengthRelation(name, _, start, end, typ, dir, optional, predicate, None, maxHops, relIterator) => Some((start, end, typ, dir, optional, maxHops, relIterator, predicate))
case ParsedVarLengthRelation(_, _, _, _, _, _, _, _, Some(x), _, _) => throw new SyntaxException("Shortest path does not support a minimal length")
case ParsedRelation(name, _, start, end, typ, dir, optional, predicate) => Some((start, end, typ, dir, optional, Some(1), Some(name), predicate))
+ case _ => None
}
}
@@ -21,6 +21,7 @@ package org.neo4j.cypher.internal.parser.v1_8
import org.neo4j.graphdb.Direction
import org.neo4j.cypher.internal.commands.{True, Entity, Expression}
+import org.neo4j.helpers.ThisShouldNotHappenError
trait ParserPattern extends Base {
@@ -53,6 +54,7 @@ trait ParserPattern extends Base {
concretePattern.find(!_.success) match {
case Some(No(msg)) => Failure(msg.mkString("\n"), rest)
case None => Success(concretePattern.flatMap(_.values), rest)
+ case _ => throw new ThisShouldNotHappenError("Andres", "This is here to stop compiler warnings.")
}
case Failure(msg, rest) => Failure(msg, rest)
@@ -0,0 +1,232 @@
+/**
+ * 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.neo4j.graphdb.index.Index
+import org.junit.Test
+import scala.collection.JavaConverters._
+import java.io.{File, PrintWriter}
+import org.neo4j.graphdb._
+import org.neo4j.visualization.asciidoc.AsciidocHelper
+import org.neo4j.cypher.CuteGraphDatabaseService.gds2cuteGds
+import org.neo4j.cypher.javacompat.GraphImpl
+import org.neo4j.cypher._
+import org.neo4j.test.{GeoffService, ImpermanentGraphDatabase, TestGraphDatabaseFactory, GraphDescription}
+import org.scalatest.Assertions
+
+/*
+Use this base class for tests that are more flowing text with queries intersected in the middle of the text.
+ */
+abstract class ArticleTest extends Assertions with DocumentationHelper {
+
+ var db: GraphDatabaseService = null
+ val parser: CypherParser = new CypherParser
+ implicit var engine: ExecutionEngine = null
+ var nodes: Map[String, Long] = null
+ var nodeIndex: Index[Node] = null
+ var relIndex: Index[Relationship] = null
+ val properties: Map[String, Map[String, Any]] = Map()
+ var generateConsole: Boolean = true
+
+ def title: String
+ def section: String
+ def assert(name: String, result: ExecutionResult)
+ def graphDescription: List[String]
+ def indexProps: List[String] = List()
+
+ def executeQuery(queryText: String)(implicit engine: ExecutionEngine): ExecutionResult = try {
+ val result = engine.execute(replaceNodeIds(queryText))
+ result.toList //Let's materialize the result
+ result.dumpToString()
+ result
+ } catch {
+ case e: CypherException => throw new InternalException(queryText, e)
+ }
+
+ def replaceNodeIds(_query: String): String = {
+ var query = _query
+ nodes.keySet.foreach((key) => query = query.replace("%" + key + "%", node(key).getId.toString))
+ query
+ }
+
+ 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)) {
+ val value = n.getProperty(property)
+ index.add(n, property, value)
+ }
+ })
+ }
+
+ def node(name: String): Node = db.getNodeById(nodes.getOrElse(name, throw new NotFoundException(name)))
+
+ def rel(id: Long): Relationship = db.getRelationshipById(id)
+
+
+ def text: String
+
+ def expandQuery(query: String, includeResults: Boolean, emptyGraph: Boolean, possibleAssertion: Seq[String]) = {
+ val querySnippet = AsciidocHelper.createCypherSnippet(replaceNodeIds(query))
+ val consoleText = consoleSnippet(replaceNodeIds(query), emptyGraph)
+ val queryOutput = runQuery(emptyGraph, query, possibleAssertion)
+ val resultSnippet = AsciidocHelper.createQueryResultSnippet(queryOutput)
+
+ val queryText = """_Query_
+
+%s
+
+""".format(querySnippet)
+
+ val resultText = """.Result
+%s
+""".format(resultSnippet)
+
+ if (includeResults)
+ queryText + resultText + consoleText
+ else
+ queryText + consoleText
+ }
+
+
+ def runQuery(emptyGraph: Boolean, query: String, possibleAssertion: Seq[String]): String = {
+ val result = if (emptyGraph) {
+ val db = new ImpermanentGraphDatabase()
+ val engine = new ExecutionEngine(db)
+ val result = executeQuery(query)(engine)
+ db.shutdown()
+ result
+ }
+ else
+ executeQuery(query)
+
+ possibleAssertion.foreach(assert(_, result))
+
+ result.dumpToString()
+ }
+
+ private def consoleSnippet(query: String, empty: Boolean): String = {
+ if (generateConsole) {
+ val create = if (!empty) new GeoffService(db).toGeoff.trim else "start n=node(*) match n-[r?]->() delete n, r;"
+ """.Try this query live
+[console]
+----
+%s
+
+%s
+----
+""".format(create, query)
+ } else ""
+ }
+
+ def header = "[[%s-%s]]".format(section.toLowerCase, title.toLowerCase.replace(" ", "-"))
+
+ @Test
+ def produceDocumentation() {
+ val db = init()
+ try {
+ val (dir: File, writer: PrintWriter) = createWriter(title, section)
+
+ val queryText = includeQueries(text, dir)
+
+ writer.println(header)
+ writer.println(queryText)
+ writer.close()
+ } finally {
+ db.shutdown()
+ }
+ }
+
+ val assertiongRegEx = "assertion=([^\\s]*)".r
+
+ private def includeGraphviz(startText: String, dir: File):String = {
+ val graphVizLine = "include::" + graphvizFileName + "[]"
+
+ val txt = startText.replaceAllLiterally("###graph-image###", graphVizLine)
+ if (txt != startText) {
+ dumpGraphViz(dir)
+ }
+
+ txt
+ }
+
+ private def includeQueries(query: String, dir: File) = {
+ val startText = includeGraphviz(query, dir)
+ val regex = ("(?s)###(.*?)###").r
+ val queries = (regex findAllIn startText).toList
+
+ var producedText = startText
+ queries.foreach {
+ query => {
+ val firstLine = query.split("\n").head
+
+ val includeResults = !firstLine.contains("no-results")
+ val emptyGraph = firstLine.contains("empty-graph")
+ val asserts: Seq[String] = assertiongRegEx.findFirstMatchIn(firstLine).toSeq.flatMap(_.subgroups)
+
+ val rest = query.split("\n").tail.mkString("\n")
+ val q = rest.replaceAll("#", "")
+ producedText = producedText.replace(query, expandQuery(q, includeResults, emptyGraph, asserts))
+ }
+ }
+
+ producedText
+ }
+
+ private def init() = {
+ db = new TestGraphDatabaseFactory().newImpermanentDatabaseBuilder().newGraphDatabase()
+
+ db.asInstanceOf[ImpermanentGraphDatabase].cleanContent(false)
+
+ db.inTx(() => {
+ nodeIndex = db.index().forNodes("nodes")
+ relIndex = db.index().forRelationships("rels")
+ val g = new GraphImpl(graphDescription.toArray[String])
+ val description = GraphDescription.create(g)
+
+ nodes = description.create(db).asScala.map {
+ case (name, node) => name -> node.getId
+ }.toMap
+
+ db.getAllNodes.asScala.foreach((n) => {
+ indexProperties(n, nodeIndex)
+ n.getRelationships(Direction.OUTGOING).asScala.foreach(indexProperties(_, relIndex))
+ })
+
+ properties.foreach((n) => {
+ val nod = node(n._1)
+ n._2.foreach((kv) => nod.setProperty(kv._1, kv._2))
+ })
+ })
+ engine = new ExecutionEngine(db)
+ db
+ }
+}
+
+
+
+
@@ -22,7 +22,7 @@ package org.neo4j.cypher.docgen
import org.junit.Test
import org.neo4j.cypher.CuteGraphDatabaseService.gds2cuteGds
import org.neo4j.graphdb.{Node, Relationship}
-import org.neo4j.cypher.{StatisticsChecker, QueryStatistics}
+import org.neo4j.cypher.StatisticsChecker
class CreateTest extends DocumentingTestBase with StatisticsChecker {
def graphDescription = List()
@@ -123,9 +123,9 @@ for this to work.""",
text =
"""When you use CREATE and a pattern, all parts of the pattern that are not already in scope at this time
will be created. """,
- queryText = "create (andres {name:'Andres'})-[:WORKS_AT]->neo<-[:WORKS_AT]-(michael {name:'Michael'}) return andres,michael",
- returns = "This query creates three nodes and two relationships in one go, and returns the end point " +
- "of the created path",
+ queryText = "create p = (andres {name:'Andres'})-[:WORKS_AT]->neo<-[:WORKS_AT]-(michael {name:'Michael'}) return p",
+ returns = "This query creates three nodes and two relationships in one go, assigns it to a path identifier, " +
+ "and returns it",
assertions = (p) => assertStats(p, nodesCreated = 3, relationshipsCreated = 2, propertiesSet = 2))
}
Oops, something went wrong.

0 comments on commit 55863d0

Please sign in to comment.