Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added the 1.9 parser

  • Loading branch information...
commit 9922161cb42fe7bb14f56dacd6da3c3a56fbdd6c 1 parent dc4fa1c
@systay authored
Showing with 1,729 additions and 35 deletions.
  1. +5 −0 cypher-parent/cypher/pom.xml
  2. +4 −2 cypher-parent/cypher/src/main/scala/org/neo4j/cypher/CypherParser.scala
  3. +1 −2  cypher-parent/cypher/src/main/scala/org/neo4j/cypher/ExecutionEngine.scala
  4. +37 −31 cypher-parent/cypher/src/test/scala/org/neo4j/cypher/CypherParserTest.scala
  5. +57 −0 cypher-parent/parser1_9/pom.xml
  6. +127 −0 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/AbstractPattern.scala
  7. +114 −0 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/Base.scala
  8. +27 −0 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/ConsoleCypherParser.scala
  9. +203 −0 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/CypherParserImpl.scala
  10. +204 −0 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/Expressions.scala
  11. +58 −0 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/MatchClause.scala
  12. +45 −0 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/OrderByClause.scala
  13. +247 −0 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/ParserPattern.scala
  14. +92 −0 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/Predicates.scala
  15. +83 −0 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/ReturnClause.scala
  16. +38 −0 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/SkipLimitClause.scala
  17. +159 −0 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/StartClause.scala
  18. +93 −0 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/StringLiteral.scala
  19. +96 −0 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/Updates.scala
  20. +38 −0 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/WhereClause.scala
  21. +1 −0  cypher-parent/pom.xml
View
5 cypher-parent/cypher/pom.xml
@@ -55,6 +55,11 @@
<artifactId>neo4j-cypher-parser1_8</artifactId>
<version>1.9-SNAPSHOT</version>
</dependency>
+ <dependency>
+ <groupId>org.neo4j</groupId>
+ <artifactId>neo4j-cypher-parser1_9</artifactId>
+ <version>1.9-SNAPSHOT</version>
+ </dependency>
</dependencies>
<distributionManagement>
View
6 cypher-parent/cypher/src/main/scala/org/neo4j/cypher/CypherParser.scala
@@ -23,12 +23,13 @@ import internal.commands.Query
class CypherParser(version: String) {
- def this() = this ("1.8")
+ def this() = this("1.9")
val hasVersionDefined = """(?si)^\s*cypher\s*([^\s]+)\s*(.*)""".r
val v17 = new internal.parser.v1_7.CypherParserImpl
val v18 = new internal.parser.v1_8.CypherParserImpl
+ val v19 = new internal.parser.v1_8.CypherParserImpl
@throws(classOf[SyntaxException])
def parse(queryText: String): Query = {
@@ -41,7 +42,8 @@ class CypherParser(version: String) {
v match {
case "1.7" => v17.parse(q)
case "1.8" => v18.parse(q)
- case _ => throw new SyntaxException("Versions supported are 1.6, 1.7 and 1.8")
+ case "1.9" => v19.parse(q)
+ case _ => throw new SyntaxException("Versions supported are 1.7, 1.8 and 1.9")
}
}
}
View
3  cypher-parent/cypher/src/main/scala/org/neo4j/cypher/ExecutionEngine.scala
@@ -40,14 +40,13 @@ class ExecutionEngine(graph: GraphDatabaseService) {
val database = graph.asInstanceOf[InternalAbstractGraphDatabase]
database.getConfig.getParams.asScala.get("cypher_parser_version") match {
case None => new CypherParser()
- case Some(v) => new CypherParser(v.toString)
+ case Some(v) => new CypherParser(v)
}
}
else {
new CypherParser()
}
-
@throws(classOf[SyntaxException])
def execute(query: String): ExecutionResult = execute(query, Map[String, Any]())
View
68 cypher-parent/cypher/src/test/scala/org/neo4j/cypher/CypherParserTest.scala
@@ -39,7 +39,7 @@ class CypherParserTest extends JUnitSuite with Assertions {
}
@Test def should_return_string_literal() {
- testFrom_1_7("start s = node(1) return \"apa\"",
+ testAll("start s = node(1) return \"apa\"",
Query.
start(NodeById("s", 1)).
returns(ReturnItem(Literal("apa"), "\"apa\"")))
@@ -53,14 +53,14 @@ class CypherParserTest extends JUnitSuite with Assertions {
}
@Test def allTheNodes() {
- testFrom_1_7("start s = NODE(*) return s",
+ testAll("start s = NODE(*) return s",
Query.
start(AllNodes("s")).
returns(ReturnItem(Identifier("s"), "s")))
}
@Test def allTheRels() {
- testFrom_1_7("start r = relationship(*) return r",
+ testAll("start r = relationship(*) return r",
Query.
start(AllRelationships("r")).
returns(ReturnItem(Identifier("r"), "r")))
@@ -173,7 +173,7 @@ class CypherParserTest extends JUnitSuite with Assertions {
}
@Test def shouldReturnLiterals() {
- testFrom_1_7(
+ testAll(
"start a = NODE(1) return 12",
Query.
start(NodeById("a", 1)).
@@ -181,7 +181,7 @@ class CypherParserTest extends JUnitSuite with Assertions {
}
@Test def shouldReturnAdditions() {
- testFrom_1_7(
+ testAll(
"start a = NODE(1) return 12+2",
Query.
start(NodeById("a", 1)).
@@ -189,7 +189,7 @@ class CypherParserTest extends JUnitSuite with Assertions {
}
@Test def arithmeticsPrecedence() {
- testFrom_1_7(
+ testAll(
"start a = NODE(1) return 12/4*3-2*4",
Query.
start(NodeById("a", 1)).
@@ -261,7 +261,7 @@ class CypherParserTest extends JUnitSuite with Assertions {
}
@Test def shouldHandleRegularComparison() {
- testFrom_1_7(
+ testAll(
"start a = node(1) where \"Andres\" =~ /And.*/ return a",
Query.
start(NodeById("a", 1)).
@@ -271,7 +271,7 @@ class CypherParserTest extends JUnitSuite with Assertions {
}
@Test def shouldHandleMultipleRegularComparison() {
- testFrom_1_7(
+ testAll(
"""start a = node(1) where a.name =~ /And.*/ AnD a.name =~ /And.*/ return a""",
Query.
start(NodeById("a", 1)).
@@ -281,7 +281,7 @@ class CypherParserTest extends JUnitSuite with Assertions {
}
@Test def shouldHandleEscapedRegexs() {
- testFrom_1_7(
+ testAll(
"""start a = node(1) where a.name =~ /And\/.*/ return a""",
Query.
start(NodeById("a", 1)).
@@ -854,7 +854,7 @@ class CypherParserTest extends JUnitSuite with Assertions {
}
@Test def exclamationMarkOperator() {
- testFrom_1_7(
+ testAll(
"start a = node(1) where a.prop! = 42 return a",
Query.
start(NodeById("a", 1)).
@@ -1077,7 +1077,7 @@ class CypherParserTest extends JUnitSuite with Assertions {
}
@Ignore @Test def shouldAcceptRelationshipWithPredicate() {
- testFrom_1_7(
+ testAll(
"start a = node(1) match a-[r WHERE r.foo = 'bar']->b return b",
Query.
start(NodeById("a", 1)).
@@ -1094,7 +1094,7 @@ class CypherParserTest extends JUnitSuite with Assertions {
}
@Test def shouldParseMathFunctions() {
- testFrom_1_7("start s = NODE(0) return 5 % 4, abs(-1), round(3.1415), 2 ^ 8, sqrt(16), sign(1)",
+ testAll("start s = NODE(0) return 5 % 4, abs(-1), round(3.1415), 2 ^ 8, sqrt(16), sign(1)",
Query.
start(NodeById("s", 0)).
returns(
@@ -1109,14 +1109,14 @@ class CypherParserTest extends JUnitSuite with Assertions {
}
@Test def shouldAllowCommentAtEnd() {
- testFrom_1_7("start s = NODE(1) return s // COMMENT",
+ testAll("start s = NODE(1) return s // COMMENT",
Query.
start(NodeById("s", 1)).
returns(ReturnItem(Identifier("s"), "s")))
}
@Test def shouldAllowCommentAlone() {
- testFrom_1_7("""start s = NODE(1) return s
+ testAll("""start s = NODE(1) return s
// COMMENT""",
Query.
start(NodeById("s", 1)).
@@ -1124,7 +1124,7 @@ class CypherParserTest extends JUnitSuite with Assertions {
}
@Test def shouldAllowCommentsInsideStrings() {
- testFrom_1_7("start s = NODE(1) where s.apa = '//NOT A COMMENT' return s",
+ testAll("start s = NODE(1) where s.apa = '//NOT A COMMENT' return s",
Query.
start(NodeById("s", 1)).
where(Equals(Property("s", "apa"), Literal("//NOT A COMMENT")))
@@ -1132,7 +1132,7 @@ class CypherParserTest extends JUnitSuite with Assertions {
}
@Test def shouldHandleCommentsFollowedByWhiteSpace() {
- testFrom_1_7("""start s = NODE(1)
+ testAll("""start s = NODE(1)
//I can haz more comment?
return s""",
Query.
@@ -1141,7 +1141,7 @@ class CypherParserTest extends JUnitSuite with Assertions {
}
@Test def first_last_and_rest() {
- testFrom_1_7("start x = NODE(1) match p=x-->z return head(nodes(p)), last(nodes(p)), tail(nodes(p))",
+ testAll("start x = NODE(1) match p=x-->z return head(nodes(p)), last(nodes(p)), tail(nodes(p))",
Query.
start(NodeById("x", 1)).
namedPaths(NamedPath("p", RelatedTo("x", "z", " UNNAMED1", Seq(), Direction.OUTGOING, false, True()))).
@@ -1153,7 +1153,7 @@ class CypherParserTest extends JUnitSuite with Assertions {
}
@Test def filter() {
- testFrom_1_7("start x = NODE(1) match p=x-->z return filter(x in p : x.prop = 123)",
+ testAll("start x = NODE(1) match p=x-->z return filter(x in p : x.prop = 123)",
Query.
start(NodeById("x", 1)).
namedPaths(NamedPath("p", RelatedTo("x", "z", " UNNAMED1", Seq(), Direction.OUTGOING, false, True()))).
@@ -1163,7 +1163,7 @@ class CypherParserTest extends JUnitSuite with Assertions {
}
@Test def collection_literal() {
- testFrom_1_7("start x = NODE(1) return ['a','b','c']",
+ testAll("start x = NODE(1) return ['a','b','c']",
Query.
start(NodeById("x", 1)).
returns(ReturnItem(Collection(Literal("a"), Literal("b"), Literal("c")), "['a','b','c']")
@@ -1171,7 +1171,7 @@ class CypherParserTest extends JUnitSuite with Assertions {
}
@Test def collection_literal2() {
- testFrom_1_7("start x = NODE(1) return []",
+ testAll("start x = NODE(1) return []",
Query.
start(NodeById("x", 1)).
returns(ReturnItem(Collection(), "[]")
@@ -1179,7 +1179,7 @@ class CypherParserTest extends JUnitSuite with Assertions {
}
@Test def collection_literal3() {
- testFrom_1_7("start x = NODE(1) return [1,2,3]",
+ testAll("start x = NODE(1) return [1,2,3]",
Query.
start(NodeById("x", 1)).
returns(ReturnItem(Collection(Literal(1), Literal(2), Literal(3)), "[1,2,3]")
@@ -1187,7 +1187,7 @@ class CypherParserTest extends JUnitSuite with Assertions {
}
@Test def collection_literal4() {
- testFrom_1_7("start x = NODE(1) return ['a',2]",
+ testAll("start x = NODE(1) return ['a',2]",
Query.
start(NodeById("x", 1)).
returns(ReturnItem(Collection(Literal("a"), Literal(2)), "['a',2]")
@@ -1195,7 +1195,7 @@ class CypherParserTest extends JUnitSuite with Assertions {
}
@Test def in_with_collection_literal() {
- testFrom_1_7("start x = NODE(1) where x.prop in ['a','b'] return x",
+ testAll("start x = NODE(1) where x.prop in ['a','b'] return x",
Query.
start(NodeById("x", 1)).
where(AnyInIterable(Collection(Literal("a"), Literal("b")), "-_-INNER-_-", Equals(Property("x", "prop"), Identifier("-_-INNER-_-")))).
@@ -1204,7 +1204,7 @@ class CypherParserTest extends JUnitSuite with Assertions {
}
@Test def mutliple_relationship_type_in_match() {
- testFrom_1_7("start x = NODE(1) match x-[:REL1|REL2|REL3]->z return x",
+ testAll("start x = NODE(1) match x-[:REL1|REL2|REL3]->z return x",
Query.
start(NodeById("x", 1)).
matches(RelatedTo("x", "z", " UNNAMED1", Seq("REL1", "REL2", "REL3"), Direction.OUTGOING, false, True())).
@@ -1213,7 +1213,7 @@ class CypherParserTest extends JUnitSuite with Assertions {
}
@Test def mutliple_relationship_type_in_varlength_rel() {
- testFrom_1_7("start x = NODE(1) match x-[:REL1|REL2|REL3]->z return x",
+ testAll("start x = NODE(1) match x-[:REL1|REL2|REL3]->z return x",
Query.
start(NodeById("x", 1)).
matches(RelatedTo("x", "z", " UNNAMED1", Seq("REL1", "REL2", "REL3"), Direction.OUTGOING, false, True())).
@@ -1222,7 +1222,7 @@ class CypherParserTest extends JUnitSuite with Assertions {
}
@Test def mutliple_relationship_type_in_shortest_path() {
- testFrom_1_7("start x = NODE(1) match x-[:REL1|REL2|REL3]->z return x",
+ testAll("start x = NODE(1) match x-[:REL1|REL2|REL3]->z return x",
Query.
start(NodeById("x", 1)).
matches(RelatedTo("x", "z", " UNNAMED1", Seq("REL1", "REL2", "REL3"), Direction.OUTGOING, false, True())).
@@ -1775,22 +1775,28 @@ foreach(x in [1,2,3] :
returns(ReturnItem(Identifier("p"), "p")))
}
- def test_1_8(query: String, expectedQuery: Query) {
+ def test_1_9(query: String, expectedQuery: Query) {
testQuery(None, query, expectedQuery)
testQuery(None, query + ";", expectedQuery)
}
+ def test_1_8(query: String, expectedQuery: Query) {
+ testQuery(Some("1.9"), query, expectedQuery)
+ testQuery(Some("1.9"), query + ";", expectedQuery)
+ }
+
def test_1_7(query: String, expectedQuery: Query) {
testQuery(Some("1.7"), query, expectedQuery)
}
- def testFrom_1_7(query: String, expectedQuery: Query) {
- test_1_7(query, expectedQuery)
+ def testFrom_1_8(query: String, expectedQuery: Query) {
test_1_8(query, expectedQuery)
+ test_1_9(query, expectedQuery)
}
-
- def testFrom_1_8(query: String, expectedQuery: Query) {
+
+ def testFrom_1_9(query: String, expectedQuery: Query) {
test_1_8(query, expectedQuery)
+ test_1_9(query, expectedQuery)
}
def testAll(query: String, expectedQuery: Query) {
View
57 cypher-parent/parser1_9/pom.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <groupId>org.neo4j.build</groupId>
+ <artifactId>neo4j-cypher-parent</artifactId>
+ <version>1.9-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.neo4j</groupId>
+ <artifactId>neo4j-cypher-parser1_9</artifactId>
+ <packaging>jar</packaging>
+ <name>Neo4j - Cypher Parser v1.9</name>
+ <description>Neo4j query language parser for v1.9</description>
+ <url>http://components.neo4j.org/${project.artifactId}/${project.version}</url>
+
+ <scm>
+ <url>https://github.com/neo4j/community/tree/master/cypher</url>
+ </scm>
+
+ <licenses>
+ <license>
+ <name>GNU General Public License, Version 3</name>
+ <url>http://www.gnu.org/licenses/gpl-3.0-standalone.html</url>
+ <comments>The software ("Software") developed and owned by Network Engine for
+ Objects in Lund AB (referred to in this notice as "Neo Technology") is
+ licensed under the GNU GENERAL PUBLIC LICENSE Version 3 to all third
+ parties and that license is included below.
+
+ However, if you have executed an End User Software License and Services
+ Agreement or an OEM Software License and Support Services Agreement, or
+ another commercial license agreement with Neo Technology or one of its
+ affiliates (each, a "Commercial Agreement"), the terms of the license in
+ such Commercial Agreement will supersede the GNU GENERAL PUBLIC LICENSE
+ Version 3 and you may use the Software solely pursuant to the terms of
+ the relevant Commercial Agreement.
+ </comments>
+ </license>
+ </licenses>
+
+ <distributionManagement>
+ <site>
+ <id>neo4j-site</id>
+ <url>scpexe://static.neo4j.org/var/www/components.neo4j.org/${project.artifactId}/${project.version}</url>
+ </site>
+ </distributionManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.neo4j</groupId>
+ <artifactId>neo4j-cypher-engine</artifactId>
+ <version>1.9-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+
+</project>
View
127 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/AbstractPattern.scala
@@ -0,0 +1,127 @@
+/**
+ * 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_9
+
+import org.neo4j.graphdb.Direction
+import collection.Map
+import org.neo4j.cypher.SyntaxException
+import org.neo4j.cypher.internal.commands.expressions.{Identifier, Expression}
+import org.neo4j.cypher.internal.commands.Predicate
+
+abstract sealed class AbstractPattern {
+ def makeOutgoing:AbstractPattern
+}
+
+object PatternWithEnds {
+ def unapply(p: AbstractPattern): Option[(ParsedEntity, ParsedEntity, Seq[String], Direction, Boolean, Option[Int], Option[String], Predicate)] = p match {
+ 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
+ }
+}
+
+abstract class PatternWithPathName(val pathName: String) extends AbstractPattern {
+ def rename(newName: String): PatternWithPathName
+}
+
+
+case class ParsedEntity(expression: Expression,
+ props: Map[String, Expression],
+ predicate: Predicate) extends AbstractPattern{
+ def makeOutgoing = this
+}
+
+case class ParsedRelation(name: String,
+ props: Map[String, Expression],
+ start: ParsedEntity,
+ end: ParsedEntity,
+ typ: Seq[String],
+ dir: Direction,
+ optional: Boolean,
+ predicate: Predicate) extends PatternWithPathName(name) with Turnable {
+ def rename(newName: String): PatternWithPathName = copy(name = newName)
+
+ def turn(start: ParsedEntity, end: ParsedEntity, dir: Direction): AbstractPattern =
+ copy(start = start, end = end, dir = dir)
+}
+
+trait Turnable {
+ def turn(start: ParsedEntity, end: ParsedEntity, dir: Direction): AbstractPattern
+
+ // It's easier on everything if all relationships are either outgoing or both, but never incoming.
+ // So we turn all patterns around, facing the same way
+ def dir: Direction
+ def start:ParsedEntity
+ def end:ParsedEntity
+
+ def makeOutgoing : AbstractPattern = {
+ dir match {
+ case Direction.INCOMING => turn(start = end, end = start, dir = Direction.OUTGOING)
+ case Direction.OUTGOING => this.asInstanceOf[AbstractPattern]
+ case Direction.BOTH => (start.expression, end.expression) match {
+ case (Identifier(a), Identifier(b)) if a < b => this.asInstanceOf[AbstractPattern]
+ case (Identifier(a), Identifier(b)) if a >= b => turn(start = end, end = start, dir = dir)
+ case _ => this.asInstanceOf[AbstractPattern]
+ }
+ }
+ }
+
+}
+
+
+case class ParsedVarLengthRelation(name: String,
+ props: Map[String, Expression],
+ start: ParsedEntity,
+ end: ParsedEntity,
+ typ: Seq[String],
+ dir: Direction,
+ optional: Boolean,
+ predicate: Predicate,
+ minHops: Option[Int],
+ maxHops: Option[Int],
+ relIterator: Option[String]) extends PatternWithPathName(name) with Turnable {
+ def rename(newName: String): PatternWithPathName = copy(name = newName)
+
+ def turn(start: ParsedEntity, end: ParsedEntity, dir: Direction): AbstractPattern =
+ copy(start = start, end = end, dir = dir)
+}
+
+case class ParsedShortestPath(name: String,
+ props: Map[String, Expression],
+ start: ParsedEntity,
+ end: ParsedEntity,
+ typ: Seq[String],
+ dir: Direction,
+ optional: Boolean,
+ predicate: Predicate,
+ maxDepth: Option[Int],
+ single: Boolean,
+ relIterator: Option[String]) extends PatternWithPathName(name) {
+def rename(newName: String): PatternWithPathName = copy(name = newName)
+
+ def makeOutgoing = this
+}
+
+case class ParsedNamedPath(name: String, pieces: Seq[AbstractPattern]) extends PatternWithPathName(name) {
+ def rename(newName: String): PatternWithPathName = copy(name = newName)
+
+ def makeOutgoing = this
+}
View
114 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/Base.scala
@@ -0,0 +1,114 @@
+/**
+ * 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_9
+
+import scala.util.parsing.combinator._
+import org.neo4j.helpers.ThisShouldNotHappenError
+import org.neo4j.cypher.internal.commands.expressions.{ParameterExpression, Expression, Literal}
+
+abstract class Base extends JavaTokenParsers {
+ var namer = new NodeNamer
+ val keywords = List("start", "create", "set", "delete", "foreach", "match", "where",
+ "with", "return", "skip", "limit", "order", "by", "asc", "ascending", "desc", "descending")
+
+ def ignoreCase(str: String): Parser[String] = ("""(?i)\b""" + str + """\b""").r ^^ (x => x.toLowerCase)
+
+ def onlyOne[T](msg: String, inner: Parser[List[T]]): Parser[T] = Parser {
+ in => inner.apply(in) match {
+ case x: NoSuccess => x
+ case Success(result, pos) => if (result.size > 1)
+ Failure("INNER" + msg, pos)
+ else
+ Success(result.head, pos)
+ }
+ }
+
+ def ignoreCases(strings: String*): Parser[String] = ignoreCases(strings.toList)
+
+ def ignoreCases(strings: List[String]): Parser[String] = strings match {
+ case List(x) => ignoreCase(x)
+ case first :: rest => ignoreCase(first) | ignoreCases(rest)
+ case _ => throw new ThisShouldNotHappenError("Andres", "Something went wrong if we get here.")
+ }
+
+ def commaList[T](inner: Parser[T]): Parser[List[T]] =
+ rep1sep(inner, ",") |
+ rep1sep(inner, ",") ~> opt(",") ~> failure("trailing coma")
+
+ def identity: Parser[String] = nonKeywordIdentifier | escapedIdentity
+
+ def trap[T](inner: Parser[T]): Parser[(T, String)] = Parser {
+ in => {
+ inner.apply(in) match {
+ case Success(result,input) => Success((result, input.source.subSequence(in.offset, input.offset).toString.trim), input )
+ case Failure(msg,input) => Failure(msg,input)
+ case Error(msg,input) => Error(msg,input)
+ }
+ }
+ }
+
+ def nonKeywordIdentifier: Parser[String] =
+ not(ignoreCases(keywords: _*)) ~> ident |
+ ignoreCases(keywords: _*) ~> failure("reserved keyword")
+
+ def lowerCaseIdent = ident ^^ (c => c.toLowerCase)
+
+ def number: Parser[String] = """-?(\d+(\.\d*)?|\d*\.\d+)""".r
+
+ def optParens[U](q: => Parser[U]): Parser[U] = q | parens(q)
+
+ def parens[U](inner: => Parser[U]) =
+ ("(" ~> inner <~ ")"
+ | "(" ~> inner ~> failure("Unclosed parenthesis"))
+
+ def curly[U](inner: => Parser[U]) =
+ ("{" ~> inner <~ "}"
+ | "{" ~> inner ~> failure("Unclosed curly brackets"))
+
+ def escapedIdentity: Parser[String] = ("`(``|[^`])*`").r ^^ (str => stripQuotes(str).replace("``", "`"))
+
+ def stripQuotes(s: String) = s.substring(1, s.length - 1)
+
+ def positiveNumber: Parser[String] = """\d+""".r
+ def anything: Parser[String] = """[.\s]""".r
+
+ def string: Parser[String] = (stringLiteral | apostropheString) ^^ (str => stripQuotes(str))
+
+ def apostropheString: Parser[String] = ("\'" + """([^'\p{Cntrl}\\]|\\[\\/bfnrt]|\\u[a-fA-F0-9]{4})*""" + "\'").r
+
+ def regularLiteral = ("/" + """([^"\p{Cntrl}\\]|\\[\\/bfnrt]|\\u[a-fA-F0-9]{4})*?""" + "/").r ^^ (x => Literal(stripQuotes(x)))
+
+ def parameter: Parser[Expression] = curly(identity | wholeNumber) ^^ (x => ParameterExpression(x))
+
+ override def failure(msg: String): Parser[Nothing] = "" ~> super.failure("INNER" + msg)
+
+ def failure(msg:String, input:Input) = Failure("INNER" + msg, input)
+}
+class NodeNamer {
+ var lastNodeNumber = 0
+
+ def name(s: Option[String]): String = s match {
+ case None => {
+ lastNodeNumber += 1
+ " UNNAMED" + lastNodeNumber
+ }
+ case Some(x) => x
+ }
+}
View
27 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/ConsoleCypherParser.scala
@@ -0,0 +1,27 @@
+/**
+ * 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_9
+
+import org.neo4j.cypher.internal.commands.expressions.Nullable
+import org.neo4j.cypher.internal.parser.ActualParser
+
+class ConsoleCypherParser extends CypherParserImpl with ActualParser {
+ override def createProperty(entity: String, propName: String) = Nullable(super.createProperty(entity, propName))
+}
View
203 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/CypherParserImpl.scala
@@ -0,0 +1,203 @@
+/**
+ * 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_9
+
+import org.neo4j.cypher.SyntaxException
+import org.neo4j.cypher.internal.parser.ActualParser
+import org.neo4j.cypher.internal.commands._
+import expressions.{Property, Expression, AggregationExpression}
+import org.neo4j.cypher.internal.ReattachAliasedExpressions
+import org.neo4j.cypher.internal.mutation.UpdateAction
+
+class CypherParserImpl extends Base
+with StartClause
+with MatchClause
+with WhereClause
+with ReturnClause
+with SkipLimitClause
+with OrderByClause
+with Updates
+with ActualParser {
+ @throws(classOf[SyntaxException])
+ def parse(text: String): Query = {
+ namer = new NodeNamer
+ parseAll(query, text) match {
+ case Success(r, q) => ReattachAliasedExpressions(r.copy(queryString = text))
+ case NoSuccess(message, input) => {
+ if (message.startsWith("INNER"))
+ throw new SyntaxException(message.substring(5), text, input.offset)
+ else
+ throw new SyntaxException(message + """
+
+Think we should have better error message here? Help us by sending this query to cypher@neo4j.org.
+
+Thank you, the Neo4j Team.
+""", text, input.offset)
+ }
+ }
+ }
+
+ def query = start ~ body <~ opt(";") ^^ {
+ case start ~ body => {
+ val q: Query = expandQuery(start._1, start._2, Seq(), body)
+
+ if (q.returns == Return(List()) &&
+ !q.start.forall(_.mutating)) {
+ throw new SyntaxException("Non-mutating queries must return data")
+ }
+
+ q
+ }
+ }
+
+ def body = bodyWith | simpleUpdate | bodyReturn | noBody
+
+ def simpleUpdate: Parser[Body] = opt(matching) ~ opt(where) ~ atLeastOneUpdateCommand ~ body ^^ {
+ case matching ~ where ~ updateCmds ~ nextQ => {
+ val (updates, startItems, paths) = updateCmds
+ val (pattern, namedPaths) = extractMatches(matching)
+
+ val returns = Return(List("*"), AllIdentifiers())
+ BodyWith(updates, pattern, namedPaths, where, returns, None, startItems, paths, nextQ)
+ }
+ }
+
+ def bodyWith: Parser[Body] = opt(matching) ~ opt(where) ~ WITH ~ opt(start) ~ updates ~ body ^^ {
+ case matching ~ where ~ returns ~ start ~ updates ~ nextQ => {
+ val (pattern, matchPaths) = extractMatches(matching)
+ val startItems = start.toSeq.flatMap(_._1)
+ val startPaths = start.toSeq.flatMap(_._2)
+
+ BodyWith(updates._1, pattern, matchPaths ++ updates._2, where, returns._1, returns._2, startItems, startPaths, nextQ)
+ }
+ }
+
+ def bodyReturn: Parser[Body] = opt(matching) ~ opt(where) ~ returns ~ opt(order) ~ opt(skip) ~ opt(limit) ^^ {
+ case matching ~ where ~ returns ~ order ~ skip ~ limit => {
+ val slice = (skip, limit) match {
+ case (None, None) => None
+ case (s, l) => Some(Slice(s, l))
+ }
+
+ val (pattern, namedPaths) = extractMatches(matching)
+ BodyReturn(pattern, namedPaths, slice, where, order.toSeq.flatten, returns._1, returns._2)
+ }
+ }
+
+ def noBody: Parser[Body] = opt(";") ~> "$".r ^^ (x => NoBody())
+
+ def checkForAggregates(where: Option[Predicate]) {
+ where match {
+ case Some(w) => if (w.exists(_.isInstanceOf[AggregationExpression])) throw new SyntaxException("Can't use aggregate functions in the WHERE clause.")
+ case _ =>
+ }
+ }
+
+ private def expandQuery(start: Seq[StartItem], namedPaths: Seq[NamedPath], updates: Seq[UpdateAction], body: Body): Query = body match {
+ case b: BodyWith => {
+ checkForAggregates(b.where)
+ Query(b.returns, start, updates, b.matching, b.where, b.aggregate, Seq(), None, b.namedPath ++ namedPaths, Some(expandQuery(b.start, b.startPaths, b.updates, b.next)))
+ }
+ case b: BodyReturn => {
+ checkForAggregates(b.where)
+ Query(b.returns, start, updates, b.matching, b.where, b.aggregate, b.order, b.slice, b.namedPath ++ namedPaths, None)
+ }
+ case NoBody() => {
+ Query(Return(List()), start, updates, Seq(), None, None, Seq(), None, namedPaths, None)
+ }
+ }
+
+ def createProperty(entity: String, propName: String): Expression = Property(entity, propName)
+
+ override def handleWhiteSpace(source: CharSequence, offset: Int): Int = {
+ if (offset >= source.length())
+ return offset
+
+ val a = source.charAt(offset)
+
+ if ((a == ' ') || (a == '\r') || (a == '\t') || (a == '\n'))
+ handleWhiteSpace(source, offset + 1)
+ else if ((offset + 1) >= source.length())
+ offset
+ else {
+ val b = source.charAt(offset + 1)
+
+ if ((a == '/') && (b == '/')) {
+
+ var loop = 0
+ while ((offset + loop) < source.length() && !(source.charAt(offset + loop) == '\n')) {
+ loop = loop + 1
+ }
+
+ handleWhiteSpace(source, loop + offset)
+ } else {
+ offset
+ }
+ }
+ }
+
+ private def extractMatches(matching: Option[(Seq[Pattern], Seq[NamedPath])]): (Seq[Pattern], Seq[NamedPath]) = matching match {
+ case Some((a,b)) => (a,b)
+ case None => (Seq(), Seq())
+ }
+
+ private def updateCommands: Parser[(Seq[UpdateAction], Seq[StartItem], Seq[NamedPath])] = opt(createStart) ~ updates ^^ {
+ case starts ~ updates =>
+ val createCommands = starts.toSeq.flatMap(_._1)
+ val paths = starts.toSeq.flatMap(_._2) ++ updates._2
+ val updateActions = updates._1
+
+ (updateActions , createCommands, paths)
+ }
+
+ private def atLeastOneUpdateCommand: Parser[(Seq[UpdateAction], Seq[StartItem], Seq[NamedPath])] = Parser {
+ case in => updateCommands(in) match {
+ case Success((changes, starts, paths), rest) if (starts.size + changes.size) == 0 => Failure("", rest)
+ case x => x
+ }
+ }
+}
+
+/*
+A query is split up into a start-part, and one or more Body parts, like a linked list. The start part is either a
+START clause or a CREATE clause, and the body can be one of three: BodyReturn, BodyWith, NoBody
+ */
+
+abstract sealed class Body
+
+/*
+This Body is used when a query ends in a RETURN clause. Once you RETURN, no more query parts are allowed, so this structure
+is one of two possible query tails
+ */
+case class BodyReturn(matching: Seq[Pattern], namedPath: Seq[NamedPath], slice: Option[Slice], where: Option[Predicate], order: Seq[SortItem], returns: Return, aggregate: Option[Seq[AggregationExpression]]) extends Body
+
+/*
+If a Body is an intermediate part, either explicitly with WITH, or implicitly when first MATCHing and then updating the graph, this structure will be used.
+
+This structure has three parts
+ */
+case class BodyWith(updates:Seq[UpdateAction], matching: Seq[Pattern], namedPath: Seq[NamedPath], where: Option[Predicate], returns: Return, aggregate: Option[Seq[AggregationExpression]],// These items belong to the query part before the WITH delimiter
+ start:Seq[StartItem], startPaths:Seq[NamedPath], // These are START or CREATE clauses directly following WITH
+ next: Body) extends Body // This is the pointer to the next query part
+
+/*
+This is the plug used when a query doesn't end in RETURN.
+ */
+case class NoBody() extends Body
View
204 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/Expressions.scala
@@ -0,0 +1,204 @@
+/**
+ * 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_9
+
+import org.neo4j.cypher.internal.commands._
+import expressions._
+
+trait Expressions extends Base with ParserPattern with Predicates with StringLiteral {
+ def expression: Parser[Expression] = term ~ rep("+" ~ term | "-" ~ term) ^^ {
+ case head ~ rest =>
+ var result = head
+ rest.foreach {
+ case "+" ~ f => result = Add(result, f)
+ case "-" ~ f => result = Subtract(result, f)
+ }
+
+ result
+ }
+
+ def term: Parser[Expression] = factor ~ rep("*" ~ factor | "/" ~ factor | "%" ~ factor | "^" ~ factor) ^^ {
+ case head ~ rest =>
+ var result = head
+ rest.foreach {
+ case "*" ~ f => result = Multiply(result, f)
+ case "/" ~ f => result = Divide(result, f)
+ case "%" ~ f => result = Modulo(result, f)
+ case "^" ~ f => result = Pow(result, f)
+ }
+
+ result
+ }
+
+ def factor: Parser[Expression] =
+ (ignoreCase("true") ^^^ Literal(true)
+ | ignoreCase("false") ^^^ Literal(false)
+ | ignoreCase("null") ^^^ Literal(null)
+ | pathExpression
+ | extract
+ | function
+ | aggregateExpression
+ | coalesceFunc
+ | filterFunc
+ | nullableProperty
+ | property
+ | stringLit
+ | numberLiteral
+ | collectionLiteral
+ | parameter
+ | entity
+ | parens(expression)
+ | failure("illegal start of value"))
+
+ def numberLiteral: Parser[Expression] = number ^^ (x => {
+ val value: Any = if (x.contains("."))
+ x.toDouble
+ else
+ x.toLong
+
+ Literal(value)
+ })
+
+ def entity: Parser[Identifier] = identity ^^ (x => Identifier(x))
+
+ def collectionLiteral: Parser[Expression] = "[" ~> repsep(expression, ",") <~ "]" ^^ (seq => Collection(seq: _*))
+
+ def property: Parser[Expression] = identity ~ "." ~ identity ^^ {
+ case v ~ "." ~ p => createProperty(v, p)
+ }
+
+ def createProperty(entity: String, propName: String): Expression
+
+ def nullableProperty: Parser[Expression] = (
+ property <~ "?" ^^ (p => new Nullable(p) with DefaultTrue) |
+ property <~ "!" ^^ (p => new Nullable(p) with DefaultFalse))
+
+ def extract: Parser[Expression] = ignoreCase("extract") ~> parens(identity ~ ignoreCase("in") ~ expression ~ ":" ~ expression) ^^ {
+ case (id ~ in ~ iter ~ ":" ~ expression) => ExtractFunction(iter, id, expression)
+ }
+
+ def coalesceFunc: Parser[Expression] = ignoreCase("coalesce") ~> parens(commaList(expression)) ^^ {
+ case expressions => CoalesceFunction(expressions: _*)
+ }
+
+ def filterFunc: Parser[Expression] = ignoreCase("filter") ~> parens(identity ~ ignoreCase("in") ~ expression ~ (ignoreCase("where") | ":") ~ predicate) ^^ {
+ case symbol ~ in ~ collection ~ where ~ pred => FilterFunction(collection, symbol, pred)
+ }
+
+ def function: Parser[Expression] = Parser {
+ case in => {
+ val inner = identity ~ parens(commaList(expression | entity))
+
+ inner(in) match {
+
+ case Success(name ~ args, rest) => functions.get(name.toLowerCase) match {
+ case None => failure("unknown function", rest)
+ case Some(func) if !func.acceptsTheseManyArguments(args.size) => failure("Wrong number of parameters for function " + name, rest)
+ case Some(func) => Success(func.create(args), rest)
+ }
+
+ case Failure(msg, rest) => Failure(msg, rest)
+ case Error(msg, rest) => Error(msg, rest)
+ }
+ }
+ }
+
+
+ private def func(numberOfArguments: Int, create: List[Expression] => Expression) = new Function(x => x == numberOfArguments, create)
+
+ case class Function(acceptsTheseManyArguments: Int => Boolean, create: List[Expression] => Expression)
+
+ val functions = Map(
+ "type" -> func(1, args => RelationshipTypeFunction(args.head)),
+ "id" -> func(1, args => IdFunction(args.head)),
+ "length" -> func(1, args => LengthFunction(args.head)),
+ "nodes" -> func(1, args => NodesFunction(args.head)),
+ "rels" -> func(1, args => RelationshipFunction(args.head)),
+ "relationships" -> func(1, args => RelationshipFunction(args.head)),
+ "abs" -> func(1, args => AbsFunction(args.head)),
+ "round" -> func(1, args => RoundFunction(args.head)),
+ "sqrt" -> func(1, args => SqrtFunction(args.head)),
+ "sign" -> func(1, args => SignFunction(args.head)),
+ "head" -> func(1, args => HeadFunction(args.head)),
+ "last" -> func(1, args => LastFunction(args.head)),
+ "tail" -> func(1, args => TailFunction(args.head)),
+ "shortestpath" -> Function(x => false, args => null),
+ "range" -> Function(x => x == 2 || x == 3, args => {
+ val step = if (args.size == 2) Literal(1) else args(2)
+ RangeFunction(args(0), args(1), step)
+ })
+ )
+
+ def aggregateExpression: Parser[Expression] = countStar | aggregationFunction
+
+ def aggregateFunctionNames: Parser[String] = ignoreCases("count", "sum", "min", "max", "avg", "collect")
+
+ def aggregationFunction: Parser[Expression] = aggregateFunctionNames ~ parens(opt(ignoreCase("distinct")) ~ expression) ^^ {
+ case function ~ (distinct ~ inner) => {
+
+ val aggregateExpression = function match {
+ case "count" => Count(inner)
+ case "sum" => Sum(inner)
+ case "min" => Min(inner)
+ case "max" => Max(inner)
+ case "avg" => Avg(inner)
+ case "collect" => Collect(inner)
+ }
+
+ if (distinct.isEmpty) {
+ aggregateExpression
+ }
+ else {
+ Distinct(aggregateExpression, inner)
+ }
+ }
+ }
+
+ def countStar: Parser[Expression] = ignoreCase("count") ~> parens("*") ^^^ CountStar()
+
+ def pathExpression: Parser[Expression] = usePath(translate) ^^ {//(pathPattern => PathExpression(pathPattern))
+ case Seq(x:ShortestPath) => ShortestPathExpression(x)
+ case patterns => PathExpression(patterns)
+ }
+
+ private def translate(abstractPattern: AbstractPattern): Maybe[Pattern] = matchTranslator(abstractPattern) match {
+ case Yes(Seq(np)) if np.isInstanceOf[NamedPath] => No(Seq("Can't assign to an identifier in a pattern expression"))
+ case Yes(p@Seq(pattern:Pattern)) => Yes(p.asInstanceOf[Seq[Pattern]])
+ case n: No => n
+ }
+
+ def matchTranslator(abstractPattern: AbstractPattern): Maybe[Any]
+}
+
+trait DefaultTrue
+
+trait DefaultFalse
+
+
+
+
+
+
+
+
+
+
+
+
View
58 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/MatchClause.scala
@@ -0,0 +1,58 @@
+/**
+ * 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_9
+
+import org.neo4j.cypher.internal.commands._
+import expressions.{Identifier, Expression}
+
+trait MatchClause extends Base with ParserPattern {
+ def matching: Parser[(Seq[Pattern], Seq[NamedPath])] = ignoreCase("match") ~> usePattern(matchTranslator) ^^ {
+ case matching =>
+ val namedPaths = matching.filter(_.isInstanceOf[NamedPath]).map(_.asInstanceOf[NamedPath])
+ val unnamedPaths = matching.filter(_.isInstanceOf[List[Pattern]]).map(_.asInstanceOf[List[Pattern]]).flatten ++ matching.filter(_.isInstanceOf[Pattern]).map(_.asInstanceOf[Pattern])
+
+ (unnamedPaths, namedPaths)
+ }
+
+ private def successIfEntities[T](l: Expression, r: Expression)(f: (String, String) => T): Maybe[T] = (l, r) match {
+ case (Identifier(lName), Identifier(rName)) => Yes(Seq(f(lName, rName)))
+ case (x, Identifier(_)) => No(Seq("MATCH end points have to be node identifiers - found: " + x))
+ case (Identifier(_), x) => No(Seq("MATCH end points have to be node identifiers - found: " + x))
+ case (x, y) => No(Seq("MATCH end points have to be node identifiers - found: " + x + " and " + y))
+ }
+
+ def matchTranslator(abstractPattern: AbstractPattern): Maybe[Any] = abstractPattern match {
+ case ParsedNamedPath(name, patterns) =>
+ val namedPathPatterns = patterns.map(matchTranslator)
+ val result = namedPathPatterns.reduce(_ ++ _)
+ result.seqMap(p => Seq(NamedPath(name, p.map(_.asInstanceOf[Pattern]):_*)))
+
+ case ParsedRelation(name, props, ParsedEntity(left, startProps, True()), ParsedEntity(right, endProps, True()), relType, dir, optional, predicate) =>
+ successIfEntities(left, right)((l, r) => RelatedTo(left = l, right = r, relName = name, relTypes = relType, direction = dir, optional = optional, predicate = True()))
+
+ case ParsedVarLengthRelation(name, props, ParsedEntity(left, startProps, True()), ParsedEntity(right, endProps, True()), relType, dir, optional, predicate, min, max, relIterator) =>
+ successIfEntities(left, right)((l, r) => VarLengthRelatedTo(pathName = name, start = l, end = r, minHops = min, maxHops = max, relTypes = relType, direction = dir, relIterator = relIterator, optional = optional, predicate = predicate))
+
+ case ParsedShortestPath(name, props, ParsedEntity(left, startProps, True()), ParsedEntity(right, endProps, True()), relType, dir, optional, predicate, max, single, relIterator) =>
+ successIfEntities(left, right)((l, r) => ShortestPath(pathName = name, start = l, end = r, relTypes = relType, dir = dir, maxDepth = max, optional = optional, single = single, relIterator = relIterator, predicate = predicate))
+
+ case x => No(Seq("failed to parse MATCH pattern"))
+ }
+}
View
45 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/OrderByClause.scala
@@ -0,0 +1,45 @@
+/**
+ * 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_9
+
+import org.neo4j.cypher.internal.commands.SortItem
+
+
+trait OrderByClause extends Base with Expressions {
+ def desc:Parser[String] = ignoreCases("descending", "desc")
+
+ def asc:Parser[String] = ignoreCases("ascending", "asc")
+
+ def ascOrDesc:Parser[Boolean] = opt(asc | desc) ^^ {
+ case None => true
+ case Some(txt) => txt.toLowerCase.startsWith("a")
+ }
+
+ def sortItem :Parser[SortItem] = expression ~ ascOrDesc ^^ { case expression ~ reverse => SortItem(expression, reverse) }
+
+ def order: Parser[Seq[SortItem]] =
+ (ignoreCase("order by") ~> commaList(sortItem)
+ | ignoreCase("order") ~> failure("expected by"))
+}
+
+
+
+
+
View
247 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/ParserPattern.scala
@@ -0,0 +1,247 @@
+/**
+ * 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_9
+
+import org.neo4j.graphdb.Direction
+import org.neo4j.cypher.internal.commands.True
+import org.neo4j.helpers.ThisShouldNotHappenError
+import org.neo4j.cypher.internal.commands.expressions.{Expression, Identifier}
+
+trait ParserPattern extends Base {
+
+ def usePattern[T](translator: AbstractPattern => Maybe[T], acceptable: Seq[T] => Boolean): Parser[Seq[T]] = Parser {
+ case in =>
+ usePattern(translator)(in) match {
+ case Success(patterns, rest) =>
+ if (acceptable(patterns))
+ Success(patterns, rest)
+ else
+ Failure("", rest)
+ case Failure(msg, rest) => Failure(msg, rest)
+ case Error(msg, rest) => Error(msg, rest)
+ }
+ }
+
+ def usePattern[T](translator: AbstractPattern => Maybe[T]): Parser[Seq[T]] = Parser {
+ case in => translate(in, translator, pattern(in))
+ }
+
+ def usePath[T](translator: AbstractPattern => Maybe[T]): Parser[Seq[T]] = Parser {
+ 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))
+
+ 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)
+ case Error(msg, rest) => Error(msg, rest)
+ }
+ }
+
+ private def pattern: Parser[Seq[AbstractPattern]] = commaList(patternBit) ^^ (patterns => patterns.flatten)
+
+ private def patternBit: Parser[Seq[AbstractPattern]] =
+ pathAssignment |
+ pathFacingOut |
+ singleNode
+
+ private def singleNode: Parser[Seq[ParsedEntity]] = {
+ node ^^ (n => Seq(n))
+ }
+
+ private def pathAssignment: Parser[Seq[AbstractPattern]] = optParens(identity) ~ "=" ~ optParens(path) ^^ {
+ case pathName ~ "=" ~ Seq(p: ParsedShortestPath) => Seq(p.rename(pathName))
+ case pathName ~ "=" ~ patterns => Seq(ParsedNamedPath(pathName, patterns))
+ }
+
+ private def node: Parser[ParsedEntity] =
+ parens(nodeFromExpression) | // whatever expression, but inside parenthesis
+ singleNodeEqualsMap | // x = {}
+ nodeIdentifier | // x
+ nodeInParenthesis | // (x {})
+ failure("expected an expression that is a node")
+
+
+ private def singleNodeEqualsMap = identity ~ "=" ~ properties ^^ {
+ case name ~ "=" ~ map => ParsedEntity(Identifier(name), map, True())
+ }
+
+ private def nodeInParenthesis = parens(opt(identity) ~ props) ^^ {
+ case id ~ props => ParsedEntity(Identifier(namer.name(id)), props, True())
+ }
+
+ private def nodeFromExpression = Parser {
+ case in => expression(in) match {
+ case Success(exp, rest) => Success(ParsedEntity(exp, Map[String, Expression](), True()), rest)
+ case x: Error => x
+ case Failure(msg, rest) => failure("expected an expression that is a node", rest)
+ }
+ }
+
+ private def nodeIdentifier = identity ^^ {
+ case name => ParsedEntity(Identifier(name), Map[String, Expression](), True())
+ }
+
+ private def path: Parser[List[AbstractPattern]] = relationship | shortestPath
+ private def pathFacingOut: Parser[List[AbstractPattern]] = relationshipFacingOut | shortestPath
+
+ private def relationshipFacingOut = relationship ^^ (x => x.map(_.makeOutgoing))
+
+ private def relationship: Parser[List[AbstractPattern]] = {
+ node ~ rep1(tail) ^^ {
+ case head ~ tails =>
+ var start = head
+ val links = tails.map {
+ case Tail(dir, relName, relProps, end, None, types, optional) =>
+ val t = ParsedRelation(namer.name(relName), relProps, start, end, types, dir, optional, True())
+ start = end
+ t
+ case Tail(dir, relName, relProps, end, Some((min, max)), types, optional) =>
+ val t = ParsedVarLengthRelation(namer.name(None), relProps, start, end, types, dir, optional, True(), min, max, relName)
+ start = end
+ t
+ }
+
+ List(links: _*)
+ }
+ }
+
+ private def patternForShortestPath: Parser[AbstractPattern] = onlyOne("expected single path segment", relationship)
+
+ private def shortestPath: Parser[List[AbstractPattern]] = (ignoreCase("shortestPath") | ignoreCase("allShortestPaths")) ~ parens(patternForShortestPath) ^^ {
+ case algo ~ relInfo =>
+ val single = algo match {
+ case "shortestpath" => true
+ case "allshortestpaths" => false
+ }
+
+ val PatternWithEnds(start, end, typez, dir, optional, maxDepth, relIterator, predicate) = relInfo
+
+ List(ParsedShortestPath(name = namer.name(None),
+ props = Map(),
+ start = start,
+ end = end,
+ typ = typez,
+ dir = dir,
+ optional = optional,
+ predicate = predicate,
+ maxDepth = maxDepth,
+ single = single,
+ relIterator = relIterator))
+ }
+
+
+ private def tailWithRelData: Parser[Tail] = opt("<") ~ "-" ~ "[" ~ opt(identity) ~ opt("?") ~ opt(":" ~> rep1sep(identity, "|")) ~ variable_length ~ props ~ "]" ~ "-" ~ opt(">") ~ node ^^ {
+ case l ~ "-" ~ "[" ~ rel ~ optional ~ typez ~ varLength ~ properties ~ "]" ~ "-" ~ r ~ end => Tail(direction(l, r), rel, properties, end, varLength, typez.toSeq.flatten.distinct, optional.isDefined)
+ } | linkErrorMessages
+
+ private def linkErrorMessages: Parser[Tail] =
+ opt("<") ~> "-" ~> "[" ~> opt(identity) ~> opt("?") ~> opt(":" ~> rep1sep(identity, "|")) ~> variable_length ~> props ~> "]" ~> failure("expected -") |
+ opt("<") ~> "-" ~> "[" ~> opt(identity) ~> opt("?") ~> opt(":" ~> rep1sep(identity, "|")) ~> variable_length ~> props ~> failure("unclosed bracket") |
+ opt("<") ~> "-" ~> "[" ~> failure("expected relationship information") |
+ opt("<") ~> "-" ~> failure("expected [ or -")
+
+ private def variable_length = opt("*" ~ opt(wholeNumber) ~ opt("..") ~ opt(wholeNumber)) ^^ {
+ case None => None
+ case Some("*" ~ None ~ None ~ None) => Some(None, None)
+ case Some("*" ~ min ~ None ~ None) => Some((min.map(_.toInt), min.map(_.toInt)))
+ case Some("*" ~ min ~ _ ~ max) => Some((min.map(_.toInt), max.map(_.toInt)))
+ }
+
+ private def tailWithNoRelData = opt("<") ~ "--" ~ opt(">") ~ node ^^ {
+ case l ~ "--" ~ r ~ end => Tail(direction(l, r), None, Map(), end, None, Seq(), optional = false)
+ }
+
+ private def tail: Parser[Tail] = tailWithRelData | tailWithNoRelData
+
+ private def props = opt(properties) ^^ {
+ case None => Map[String, Expression]()
+ case Some(x) => x
+ }
+
+ private def properties =
+ expression ^^ (x => Map[String, Expression]("*" -> x)) |
+ "{" ~> repsep(propertyAssignment, ",") <~ "}" ^^ (_.toMap)
+
+
+ private def direction(l: Option[String], r: Option[String]): Direction = (l, r) match {
+ case (None, Some(_)) => Direction.OUTGOING
+ case (Some(_), None) => Direction.INCOMING
+ case _ => Direction.BOTH
+
+ }
+
+ private def propertyAssignment: Parser[(String, Expression)] = identity ~ ":" ~ expression ^^ {
+ case id ~ ":" ~ exp => (id, exp)
+ }
+
+ def expression: Parser[Expression]
+
+ private case class Tail(dir: Direction,
+ relName: Option[String],
+ relProps: Map[String, Expression],
+ end: ParsedEntity,
+ varLength: Option[(Option[Int], Option[Int])],
+ types: Seq[String],
+ optional: Boolean)
+
+ abstract sealed class Maybe[+T] {
+ def values: Seq[T]
+ def success: Boolean
+ def ++[B >: T](other: Maybe[B]): Maybe[B]
+ def map[B](f: T => B): Maybe[B]
+ def seqMap[B](f:Seq[T]=>Seq[B]): Maybe[B]
+ }
+
+ case class Yes[T](values: Seq[T]) extends Maybe[T] {
+ def success = true
+
+ def ++[B >: T](other: Maybe[B]): Maybe[B] = other match {
+ case Yes(otherStuff) => Yes(values ++ otherStuff)
+ case No(msg) => No(msg)
+ }
+
+ def map[B](f: T => B): Maybe[B] = Yes(values.map(f))
+
+ def seqMap[B](f: (Seq[T]) => Seq[B]): Maybe[B] = Yes(f(values))
+ }
+
+ case class No(messages: Seq[String]) extends Maybe[Nothing] {
+ def values = throw new Exception("No values exists")
+ def success = false
+
+ def ++[B >: Nothing](other: Maybe[B]): Maybe[B] = other match {
+ case Yes(_) => this
+ case No(otherMessages) => No(messages ++ otherMessages)
+ }
+
+ def map[B](f: Nothing => B): Maybe[B] = this
+
+ def seqMap[B](f: (Seq[Nothing]) => Seq[B]): Maybe[B] = this
+ }
+}
View
92 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/Predicates.scala
@@ -0,0 +1,92 @@
+/**
+ * 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_9
+
+import org.neo4j.cypher.internal.commands._
+import expressions.{Property, Identifier, Nullable, Expression}
+
+
+trait Predicates extends Base with ParserPattern {
+ def predicate: Parser[Predicate] = predicateLvl1 ~ rep( ignoreCase("or") ~> predicateLvl1 ) ^^ {
+ case head ~ rest => rest.foldLeft(head)((a,b) => Or(a,b))
+ }
+
+ def predicateLvl1: Parser[Predicate] = predicateLvl2 ~ rep( ignoreCase("and") ~> predicateLvl2 ) ^^{
+ case head ~ rest => rest.foldLeft(head)((a,b) => And(a,b))
+ }
+
+ def predicateLvl2: Parser[Predicate] = (
+ expressionOrEntity <~ ignoreCase("is null") ^^ (x => IsNull(x))
+ | expressionOrEntity <~ ignoreCase("is not null") ^^ (x => Not(IsNull(x)))
+ | operators
+ | ignoreCase("not") ~> parens(predicate) ^^ ( inner => Not(inner) )
+ | ignoreCase("not") ~> predicate ^^ ( inner => Not(inner) )
+ | ignoreCase("has") ~> parens(property) ^^ ( prop => Has(prop.asInstanceOf[Property]))
+ | parens(predicate)
+ | sequencePredicate
+ | patternPredicate
+ | aggregateFunctionNames ~> parens(expression) ~> failure("aggregate functions can not be used in the WHERE clause")
+ )
+
+ def sequencePredicate: Parser[Predicate] = allInSeq | anyInSeq | noneInSeq | singleInSeq | in
+
+ def symbolIterablePredicate: Parser[(Expression, String, Predicate)] =
+ (identity ~ ignoreCase("in") ~ expression ~ ignoreCase("where") ~ predicate ^^ { case symbol ~ in ~ iterable ~ where ~ klas => (iterable, symbol, klas) }
+ |identity ~> ignoreCase("in") ~ expression ~> failure("expected where"))
+
+ def in: Parser[Predicate] = expression ~ ignoreCase("in") ~ expression ^^ {
+ case checkee ~ in ~ collection => nullable(AnyInIterable(collection, "-_-INNER-_-", Equals(checkee, Identifier("-_-INNER-_-"))), collection)
+ }
+
+ def allInSeq: Parser[Predicate] = ignoreCase("all") ~> parens(symbolIterablePredicate) ^^ (x => nullable(AllInIterable(x._1, x._2, x._3), x._1))
+ def anyInSeq: Parser[Predicate] = ignoreCase("any") ~> parens(symbolIterablePredicate) ^^ (x => nullable(AnyInIterable(x._1, x._2, x._3), x._1))
+ def noneInSeq: Parser[Predicate] = ignoreCase("none") ~> parens(symbolIterablePredicate) ^^ (x => nullable(NoneInIterable(x._1, x._2, x._3), x._1))
+ def singleInSeq: Parser[Predicate] = ignoreCase("single") ~> parens(symbolIterablePredicate) ^^ (x => nullable(SingleInIterable(x._1, x._2, x._3), x._1))
+
+ def operators:Parser[Predicate] =
+ (expression ~ "=" ~ expression ^^ { case l ~ "=" ~ r => nullable(Equals(l, r),l,r) } |
+ expression ~ ("<"~">") ~ expression ^^ { case l ~ wut ~ r => nullable(Not(Equals(l, r)),l,r) } |
+ expression ~ "<" ~ expression ^^ { case l ~ "<" ~ r => nullable(LessThan(l, r),l,r) } |
+ expression ~ ">" ~ expression ^^ { case l ~ ">" ~ r => nullable(GreaterThan(l, r),l,r) } |
+ expression ~ "<=" ~ expression ^^ { case l ~ "<=" ~ r => nullable(LessThanOrEqual(l, r),l,r) } |
+ expression ~ ">=" ~ expression ^^ { case l ~ ">=" ~ r => nullable(GreaterThanOrEqual(l, r),l,r) } |
+ expression ~ "=~" ~ regularLiteral ^^ { case a ~ "=~" ~ b => nullable(LiteralRegularExpression(a, b),a,b) } |
+ expression ~ "=~" ~ expression ^^ { case a ~ "=~" ~ b => nullable(RegularExpression(a, b),a,b) } |
+ expression ~> "!" ~> failure("The exclamation symbol is used as a nullable property operator in Cypher. The 'not equal to' operator is <>"))
+
+ private def nullable(pred: Predicate, e: Expression*): Predicate = if (!e.exists(_.isInstanceOf[Nullable]))
+ pred
+ else {
+ val map = e.filter(x => x.isInstanceOf[Nullable]).
+ map(x => (x, x.isInstanceOf[DefaultTrue]))
+
+ NullablePredicate(pred, map)
+ }
+
+ def patternPredicate = pathExpression ^^ (NonEmpty(_))
+
+ def expressionOrEntity = expression | entity
+
+ def expression: Parser[Expression]
+ def aggregateFunctionNames:Parser[String]
+ def property: Parser[Expression]
+ def entity: Parser[Identifier]
+ def pathExpression: Parser[Expression]
+}
View
83 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/ReturnClause.scala
@@ -0,0 +1,83 @@
+/**
+ * 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_9
+
+import org.neo4j.cypher.internal.commands._
+import expressions.{Identifier, AggregationExpression}
+import org.neo4j.cypher.SyntaxException
+
+
+trait ReturnClause extends Base with Expressions {
+ def column : Parser[ReturnColumn] = returnItem ~ alias ^^ {
+ case col ~ Some(newName) => col.rename(newName)
+ case col ~ None => col
+ } | "*" ^^^ AllIdentifiers()
+
+ def returnItem: Parser[ReturnItem] = trap(expression) ^^ {
+ case (expression, name) => ReturnItem(expression, name.replace("`", ""))
+ }
+
+ def returns =
+ (returnsClause
+ | ignoreCase("return") ~> failure("return column list expected")
+ | failure("expected return clause"))
+
+ def returnsClause: Parser[(Return, Option[Seq[AggregationExpression]])] = ignoreCase("return") ~> columnList
+
+ def alias: Parser[Option[String]] = opt(ignoreCase("as") ~> identity)
+
+ def columnList:Parser[(Return, Option[Seq[AggregationExpression]])] = opt(ignoreCase("distinct")) ~ commaList(column) ^^ {
+ case distinct ~ returnItems => {
+ val columnName = returnItems.map(_.name).toList
+
+ val none = distinct match {
+ case Some(x) => Some(Seq())
+ case None => None
+ }
+
+ val aggregationExpressions = returnItems.filter(_.isInstanceOf[ReturnItem]).map(_.asInstanceOf[ReturnItem]).
+ flatMap(_.expression.filter(_.isInstanceOf[AggregationExpression])).
+ map(_.asInstanceOf[AggregationExpression])
+
+ val aggregation = aggregationExpressions match {
+ case List() => none
+ case _ => Some(aggregationExpressions)
+ }
+
+ (Return(columnName, returnItems: _*), aggregation)
+ }
+ }
+
+ def withSyntax = ignoreCase("with") ~> columnList | "===" ~> rep("=") ~> columnList <~ "===" <~ rep("=")
+
+ def WITH: Parser[(Return, Option[Seq[AggregationExpression]])] = withSyntax ^^ (columns => {
+
+ val problemColumns = columns._1.returnItems.flatMap {
+ case ReturnItem(_, _, true) => None
+ case ReturnItem(Identifier(_), _, _) => None
+ case ri => Some(ri.name)
+ }
+ if (problemColumns.nonEmpty) {
+ throw new SyntaxException("These columns can't be listen in the WITH statement without renaming: " + problemColumns.mkString(","))
+ }
+
+ columns
+ })
+}
View
38 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/SkipLimitClause.scala
@@ -0,0 +1,38 @@
+/**
+ * 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_9
+
+import org.neo4j.cypher.internal.commands.expressions.{Literal, Expression}
+
+trait SkipLimitClause extends Base {
+ def skip: Parser[Expression] = ignoreCase("skip") ~> numberOrParam ^^ (x => x)
+
+ def limit: Parser[Expression] = ignoreCase("limit") ~> numberOrParam ^^ (x => x)
+
+ private def numberOrParam: Parser[Expression] =
+ (positiveNumber ^^ (x => Literal(x.toInt))
+ | parameter ^^ (x => x)
+ | failure("expected positive integer or parameter"))
+}
+
+
+
+
+
View
159 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/StartClause.scala
@@ -0,0 +1,159 @@
+/**
+ * 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_9
+
+import org.neo4j.cypher.internal.commands._
+import expressions.{Literal, Expression, ParameterExpression, Identifier}
+import org.neo4j.graphdb.Direction
+
+
+trait StartClause extends Base with Expressions {
+ def start: Parser[(Seq[StartItem], Seq[NamedPath])] = createStart | readStart
+
+ def readStart: Parser[(Seq[StartItem], Seq[NamedPath])] = ignoreCase("start") ~> commaList(startBit) ^^ (x => (x, Seq())) | failure("expected START or CREATE")
+
+ def createStart = 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])
+ val namedPaths = pathsAndItems.map(_.path)
+ val pathItems = pathsAndItems.flatMap(_.items)
+
+ ((startItems ++ pathItems), namedPaths)
+ }
+
+ case class NamedPathWStartItems(path:NamedPath, items:Seq[StartItem])
+
+ private def translate(abstractPattern: AbstractPattern): Maybe[Any] = abstractPattern match {
+ case ParsedNamedPath(name, patterns) =>
+ val namedPathPatterns: Maybe[Any] = patterns.map(matchTranslator).reduce(_ ++ _)
+ val startItems = patterns.map(p => translate(p.makeOutgoing)).reduce(_ ++ _)
+
+ startItems match {
+ case No(msg) => No(msg)
+ case Yes(stuff) => namedPathPatterns.seqMap(p => {
+ val namedPath: NamedPath = NamedPath(name, p.map(_.asInstanceOf[Pattern]): _*)
+ Seq(NamedPathWStartItems(namedPath, stuff.map(_.asInstanceOf[StartItem])))
+ })
+ }
+
+ case ParsedRelation(name, props, ParsedEntity(a, startProps, True()), ParsedEntity(b, endProps, True()), relType, dir, map, True()) if relType.size == 1 =>
+ val (from, to) = if (dir == Direction.OUTGOING)
+ (a, b)
+ else
+ (b, a)
+ Yes(Seq(CreateRelationshipStartItem(name, (from, startProps), (to, endProps), relType.head, props)))
+
+ case ParsedEntity(Identifier(name), props, True()) =>
+ Yes(Seq(CreateNodeStartItem(name, props)))
+
+ case ParsedEntity(p, _, True()) if p.isInstanceOf[ParameterExpression] =>
+ Yes(Seq(CreateNodeStartItem(namer.name(None), Map[String, Expression]("*" -> p))))
+
+ case _ => No(Seq(""))
+ }
+
+ def startBit =
+ (identity ~ "=" ~ lookup ^^ {
+ case id ~ "=" ~ l => l(id)
+ }
+ | identity ~> "=" ~> opt("(") ~> failure("expected either node or relationship here")
+ | identity ~> failure("expected identifier assignment"))
+
+ def nodes = ignoreCase("node")
+
+ def rels = (ignoreCase("relationship") | ignoreCase("rel")) ^^^ "rel"
+
+ def typ = nodes | rels | failure("expected either node or relationship here")
+
+ def lookup: Parser[String => StartItem] =
+ nodes ~> parens(parameter) ^^ (p => (column: String) => NodeById(column, p)) |
+ nodes ~> ids ^^ (p => (column: String) => NodeById(column, p)) |
+ nodes ~> idxLookup ^^ nodeIndexLookup |
+ nodes ~> idxString ^^ nodeIndexString |
+ nodes ~> parens("*") ^^ (x => (column: String) => AllNodes(column)) |
+ rels ~> parens(parameter) ^^ (p => (column: String) => RelationshipById(column, p)) |
+ rels ~> ids ^^ (p => (column: String) => RelationshipById(column, p)) |
+ rels ~> idxLookup ^^ relationshipIndexLookup |
+ rels ~> idxString ^^ relationshipIndexString |
+ rels ~> parens("*") ^^ (x => (column: String) => AllRelationships(column)) |
+ nodes ~> opt("(") ~> failure("expected node id, or *") |
+ rels ~> opt("(") ~> failure("expected relationship id, or *")
+
+
+ def relationshipIndexString: ((String, Expression)) => (String) => RelationshipByIndexQuery = {
+ case (idxName, query) => (column: String) => RelationshipByIndexQuery(column, idxName, query)
+ }
+
+ def nodeIndexString: ((String, Expression)) => (String) => NodeByIndexQuery = {
+ case (idxName, query) => (column: String) => NodeByIndexQuery(column, idxName, query)
+ }
+
+ def nodeIndexLookup: ((String, Expression, Expression)) => (String) => NodeByIndex = {
+ case (idxName, key, value) => (column: String) => NodeByIndex(column, idxName, key, value)
+ }
+
+ def relationshipIndexLookup: ((String, Expression, Expression)) => (String) => RelationshipByIndex = {
+ case (idxName, key, value) => (column: String) => RelationshipByIndex(column, idxName, key, value)
+ }
+
+ def ids =
+ (parens(commaList(wholeNumber)) ^^ (x => Literal(x.map(_.toLong)))
+ | parens(commaList(wholeNumber) ~ opt(",")) ~> failure("trailing coma")
+ | "(" ~> failure("expected graph entity id"))
+
+
+ def idxString: Parser[(String, Expression)] = ":" ~> identity ~ parens(parameter | stringLit) ^^ {
+ case id ~ valu => (id, valu)
+ }
+
+ def idxLookup: Parser[(String, Expression, Expression)] =
+ ":" ~> identity ~ parens(idxQueries) ^^ {
+ case a ~ b => (a, b._1, b._2)
+ } |
+ ":" ~> identity ~> "(" ~> (id | parameter) ~> failure("`=` expected")
+
+ def idxQueries: Parser[(Expression, Expression)] = idxQuery
+
+ def indexValue = parameter | stringLit | failure("string literal or parameter expected")
+
+ def idxQuery: Parser[(Expression, Expression)] =
+ ((id | parameter) ~ "=" ~ indexValue ^^ {
+ case k ~ "=" ~ v => (k, v)
+ }
+ | "=" ~> failure("Need index key"))
+
+ def id: Parser[Expression] = identity ^^ (x => Literal(x))
+
+ def andQuery: Parser[String] = idxQuery ~ ignoreCase("and") ~ idxQueries ^^ {
+ case q ~ and ~ qs => q + " AND " + qs
+ }
+
+ def orQuery: Parser[String] = idxQuery ~ ignoreCase("or") ~ idxQueries ^^ {
+ case q ~ or ~ qs => q + " OR " + qs
+ }
+}
+
+
+
+
+
+
+
View
93 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/StringLiteral.scala
@@ -0,0 +1,93 @@
+/**
+ * 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_9
+
+import org.neo4j.cypher.internal.commands.expressions.{Expression, Literal}
+
+trait StringLiteral extends Base {
+ def stringLit: Parser[Expression] = Parser {
+ case in if in.atEnd => Failure("out of string", in)
+ case in =>
+ val start = handleWhiteSpace(in.source, in.offset)
+ val string = in.source.subSequence(start, in.source.length()).toString
+ val startChar = string.charAt(0)
+ if (startChar != '\"' && startChar != '\'')
+ Failure("expected string", in)
+ else {
+
+ var ls = string.toList.tail
+ val sb = new StringBuilder(ls.length)
+ var idx = start
+ var result: Option[ParseResult[Expression]] = None
+
+ while (!ls.isEmpty && result.isEmpty) {
+ val (pref, suf) = ls span {
+ c => c != '\\' && c != startChar
+ }
+ idx += pref.length
+ sb ++= pref
+
+ if (suf.isEmpty) {
+ result = Some(Failure("end of string missing", in))
+ } else {
+
+ val first: Char = suf(0)
+ if (first == startChar) {
+ result = Some(Success(Literal(sb.result()), in.drop(idx - in.offset + 2)))
+ } else {
+ val (escChars, afterEscape) = suf.splitAt(2)
+
+ if (escChars.size == 1) {
+ result = Some(Failure("invalid escape sequence", in.drop(1)))
+ } else {
+
+ ls = afterEscape
+ idx += 2
+
+ parseEscapeChars(escChars.tail, in) match {
+ case Left(c) => sb.append(c)
+ case Right(failure) => result = Some(failure)
+ }
+ }
+ }
+ }
+ }
+
+ result match {
+ case Some(x) => x
+ case None => Failure("end of string missing", in)
+ }
+ }
+ }
+
+ case class EscapeProduct(result: Option[ParseResult[Expression]])
+
+ private def parseEscapeChars(suf: List[Char], in:Input): Either[Char, Failure] = suf match {
+ case '\\' :: tail => Left('\\')
+ case '\'' :: tail => Left('\'')
+ case '"' :: tail => Left('"')
+ case 'b' :: tail => Left('\b')
+ case 'f' :: tail => Left('\f')
+ case 'n' :: tail => Left('\n')
+ case 'r' :: tail => Left('\r')
+ case 't' :: tail => Left('\t')
+ case _ => Right(Failure("invalid escape sequence", in))
+ }
+}
View
96 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/Updates.scala
@@ -0,0 +1,96 @@
+/**
+ * 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_9
+
+import org.neo4j.cypher.internal.mutation._
+import org.neo4j.cypher.internal.commands._
+import expressions.{Property, Identifier}
+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 foreach: Parser[(Seq[UpdateAction], Seq[NamedPath])] = ignoreCase("foreach") ~> "(" ~> identity ~ ignoreCase("in") ~ expression ~ ":" ~ opt(createStart) ~ opt(updates) <~ ")" ^^ {
+ case id ~ in ~ iterable ~ ":" ~ creates ~ innerUpdates => {
+ val createCmds = creates.toSeq.map(_._1.map(_.asInstanceOf[UpdateAction])).flatten
+ val reducedItems: (Seq[UpdateAction], Seq[NamedPath]) = reduce(innerUpdates.toSeq)
+ val updateCmds = reducedItems._1
+ val namedPaths = reducedItems._2 ++ creates.toSeq.flatMap(_._2)
+ if(namedPaths.nonEmpty) throw new SyntaxException("Paths can't be created inside of foreach")
+ (Seq(ForeachAction(iterable, id, createCmds ++ updateCmds)), Seq())
+ }
+ }
+
+ 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 {
+ case Property(entity, property) => DeletePropertyAction(Identifier(entity), property)
+ case x => DeleteEntityAction(x)
+ }
+ (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(links) => namedPathPatterns.seqMap(p => {
+ val namedPath = NamedPath(name, p.map(_.asInstanceOf[Pattern]): _*)
+
+ Seq(PathAndRelateLink(Some(namedPath), links.flatMap(_.links)))
+ })
+ }
+
+ case ParsedRelation(name, props, ParsedEntity(Identifier(startName), startProps, True()), ParsedEntity(Identifier(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
38 cypher-parent/parser1_9/src/main/scala/org/neo4j/cypher/internal/parser/v1_9/WhereClause.scala
@@ -0,0 +1,38 @@
+/**
+ * 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_9
+
+import org.neo4j.cypher.internal.commands.Predicate
+
+
+trait WhereClause extends Base with Expressions {
+ def where: Parser[Predicate] = ignoreCase("where") ~> predicate
+}
+
+
+
+
+
+
+
+
+
+
+
View
1  cypher-parent/pom.xml
@@ -31,6 +31,7 @@
<module>engine</module>
<module>parser1_7</module>
<module>parser1_8</module>
+ <module>parser1_9</module>
<module>cypher</module>
</modules>
Please sign in to comment.
Something went wrong with that request. Please try again.