diff --git a/cypher/CHANGES.txt b/cypher/CHANGES.txt index 3574df502..1403ec7e4 100644 --- a/cypher/CHANGES.txt +++ b/cypher/CHANGES.txt @@ -1,3 +1,7 @@ +1.8 +-------------------- +o Removed the /../ literal for regular expressions. Now a normal string literal is used instead + 1.8.M07 (2012-08-08) -------------------- o Added escape characters to string literals diff --git a/cypher/src/main/scala/org/neo4j/cypher/internal/parser/v1_8/Predicates.scala b/cypher/src/main/scala/org/neo4j/cypher/internal/parser/v1_8/Predicates.scala index c97429b75..6f47b9640 100644 --- a/cypher/src/main/scala/org/neo4j/cypher/internal/parser/v1_8/Predicates.scala +++ b/cypher/src/main/scala/org/neo4j/cypher/internal/parser/v1_8/Predicates.scala @@ -22,7 +22,7 @@ package org.neo4j.cypher.internal.parser.v1_8 import org.neo4j.cypher.internal.commands._ -trait Predicates extends Base with ParserPattern { +trait Predicates extends Base with ParserPattern with StringLiteral { def predicate: Parser[Predicate] = predicateLvl1 ~ rep( ignoreCase("or") ~> predicateLvl1 ) ^^ { case head ~ rest => rest.foldLeft(head)((a,b) => Or(a,b)) } @@ -66,7 +66,7 @@ trait Predicates extends Base with ParserPattern { 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 ~ "=~" ~ stringLit ^^ { 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 <>")) diff --git a/cypher/src/main/scala/org/neo4j/cypher/internal/parser/v1_8/StringLiteral.scala b/cypher/src/main/scala/org/neo4j/cypher/internal/parser/v1_8/StringLiteral.scala index 99a4ac885..448ec80e8 100644 --- a/cypher/src/main/scala/org/neo4j/cypher/internal/parser/v1_8/StringLiteral.scala +++ b/cypher/src/main/scala/org/neo4j/cypher/internal/parser/v1_8/StringLiteral.scala @@ -19,10 +19,10 @@ */ package org.neo4j.cypher.internal.parser.v1_8 -import org.neo4j.cypher.internal.commands.{Expression, Literal} +import org.neo4j.cypher.internal.commands.Literal trait StringLiteral extends Base { - def stringLit: Parser[Expression] = Parser { + def stringLit: Parser[Literal] = Parser { case in if in.atEnd => Failure("out of string", in) case in => val start = handleWhiteSpace(in.source, in.offset) @@ -35,7 +35,7 @@ trait StringLiteral extends Base { var ls = string.toList.tail val sb = new StringBuilder(ls.length) var idx = start - var result: Option[ParseResult[Expression]] = None + var result: Option[ParseResult[Literal]] = None while (!ls.isEmpty && result.isEmpty) { val (pref, suf) = ls span { @@ -77,7 +77,7 @@ trait StringLiteral extends Base { } } - case class EscapeProduct(result: Option[ParseResult[Expression]]) + case class EscapeProduct(result: Option[ParseResult[Literal]]) private def parseEscapeChars(suf: List[Char], in:Input): Either[Char, Failure] = suf match { case '\\' :: tail => Left('\\') diff --git a/cypher/src/test/java/org/neo4j/cypher/javacompat/IntroExamplesTest.java b/cypher/src/test/java/org/neo4j/cypher/javacompat/IntroExamplesTest.java index ef510cf00..ce49cd4e0 100644 --- a/cypher/src/test/java/org/neo4j/cypher/javacompat/IntroExamplesTest.java +++ b/cypher/src/test/java/org/neo4j/cypher/javacompat/IntroExamplesTest.java @@ -19,25 +19,22 @@ */ package org.neo4j.cypher.javacompat; -import java.io.IOException; -import java.io.Writer; -import java.util.Map; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; -import org.neo4j.test.AsciiDocGenerator; -import org.neo4j.test.GraphDescription; +import org.neo4j.test.*; import org.neo4j.test.GraphDescription.Graph; -import org.neo4j.test.GraphHolder; -import org.neo4j.test.ImpermanentGraphDatabase; -import org.neo4j.test.JavaTestDocsGenerator; -import org.neo4j.test.TestData; import org.neo4j.visualization.asciidoc.AsciidocHelper; -import static org.neo4j.visualization.asciidoc.AsciidocHelper.*; +import java.io.IOException; +import java.io.Writer; +import java.util.Map; + +import static org.neo4j.visualization.asciidoc.AsciidocHelper.createCypherSnippet; +import static org.neo4j.visualization.asciidoc.AsciidocHelper.createQueryResultSnippet; public class IntroExamplesTest implements GraphHolder { @@ -84,7 +81,7 @@ public void intro_examples() throws Exception + data.get().get( "Maria" ).getId() + "," + data.get().get( "Steve" ).getId() - + ") MATCH user-[:friend]->follower WHERE follower.name =~ /S.*/ RETURN user, follower.name "; + + ") MATCH user-[:friend]->follower WHERE follower.name =~ 'S.*' RETURN user, follower.name "; fw.append( "\n" ); fw.append( createCypherSnippet( query ) ); fw.append( "\nResulting in\n" ); diff --git a/cypher/src/test/scala/org/neo4j/cypher/CypherParserTest.scala b/cypher/src/test/scala/org/neo4j/cypher/CypherParserTest.scala index abb3bcbea..4f9fe9b87 100644 --- a/cypher/src/test/scala/org/neo4j/cypher/CypherParserTest.scala +++ b/cypher/src/test/scala/org/neo4j/cypher/CypherParserTest.scala @@ -277,8 +277,8 @@ class CypherParserTest extends JUnitSuite with Assertions { returns(ReturnItem(Entity("a"), "a"))) } - @Test def shouldHandleRegularComparison() { - testFrom_1_7( + @Test def shouldHandleRegularComparisonOld() { + test_1_7( "start a = node(1) where \"Andres\" =~ /And.*/ return a", Query. start(NodeById("a", 1)). @@ -287,8 +287,18 @@ class CypherParserTest extends JUnitSuite with Assertions { ) } - @Test def shouldHandleMultipleRegularComparison1_6() { - testFrom_1_7( + @Test def shouldHandleRegularComparison() { + testFrom_1_8( + "start a = node(1) where \"Andres\" =~ 'And.*' return a", + Query. + start(NodeById("a", 1)). + where(LiteralRegularExpression(Literal("Andres"), Literal("And.*"))). + returns(ReturnItem(Entity("a"), "a")) + ) + } + + @Test def shouldHandleMultipleRegularComparison1_7() { + test_1_7( """start a = node(1) where a.name =~ /And.*/ AnD a.name =~ /And.*/ return a""", Query. start(NodeById("a", 1)). @@ -298,6 +308,16 @@ class CypherParserTest extends JUnitSuite with Assertions { } @Test def shouldHandleMultipleRegularComparison() { + testFrom_1_8( + """start a = node(1) where a.name =~ 'And.*' AnD a.name =~ 'And.*' return a""", + Query. + start(NodeById("a", 1)). + where(And(LiteralRegularExpression(Property("a", "name"), Literal("And.*")), LiteralRegularExpression(Property("a", "name"), Literal("And.*")))). + returns(ReturnItem(Entity("a"), "a")) + ) + } + + @Test def shouldHandleMultipleRegularComparison1_6() { test_1_6( """start a = node(1) where a.name =~ /And.*/ AnD a.name =~ /And.*/ return a""", Query. @@ -317,8 +337,8 @@ class CypherParserTest extends JUnitSuite with Assertions { ) } - @Test def shouldHandleEscapedRegexs() { - testFrom_1_7( + @Test def shouldHandleEscapedRegexs1_7() { + test_1_7( """start a = node(1) where a.name =~ /And\/.*/ return a""", Query. start(NodeById("a", 1)). @@ -327,6 +347,16 @@ class CypherParserTest extends JUnitSuite with Assertions { ) } + @Test def shouldHandleEscapedRegexs() { + testFrom_1_8( + """start a = node(1) where a.name =~ 'And\\/.*' return a""", + Query. + start(NodeById("a", 1)). + where(LiteralRegularExpression(Property("a", "name"), Literal("And\\/.*"))). + returns(ReturnItem(Entity("a"), "a")) + ) + } + @Test def shouldHandleGreaterThanOrEqual() { testAll( "start a = node(1) where a.name >= \"andres\" return a", diff --git a/cypher/src/test/scala/org/neo4j/cypher/ExecutionEngineTest.scala b/cypher/src/test/scala/org/neo4j/cypher/ExecutionEngineTest.scala index c3be7a010..1cdc9d893 100644 --- a/cypher/src/test/scala/org/neo4j/cypher/ExecutionEngineTest.scala +++ b/cypher/src/test/scala/org/neo4j/cypher/ExecutionEngineTest.scala @@ -1214,7 +1214,7 @@ return x, p""") val result = parseAndExecute( """ start a = node(1) -where a.name =~ /And.*/ AND a.name =~ /And.*/ +where a.name =~ 'And.*' AND a.name =~ 'And.*' return a""") assert(List(a) === result.columnAs[Node]("a").toList) @@ -1422,7 +1422,7 @@ order by a.COL1 @Test def shouldAllowStringComparisonsInArray() { val a = createNode("array" -> Array("Cypher duck", "Gremlin orange", "I like the snow")) - val result = parseAndExecute("start a = node(1) where single(x in a.array where x =~ /.*the.*/) return a") + val result = parseAndExecute("start a = node(1) where single(x in a.array where x =~ '.*the.*') return a") assert(List(Map("a" -> a)) === result.toList) } @@ -1642,7 +1642,7 @@ RETURN x0.name? @Test def shouldHandleAllOperatorsWithNull() { val a = createNode() - val result = parseAndExecute("start a=node(1) where a.x? =~ /.*?blah.*?/ and a.x? = 13 and a.x? <> 13 and a.x? > 13 return a") + val result = parseAndExecute("start a=node(1) where a.x? =~ '.*?blah.*?' and a.x? = 13 and a.x? <> 13 and a.x? > 13 return a") assert(List(Map("a" -> a)) === result.toList) } @@ -2119,17 +2119,6 @@ RETURN x0.name? assert(result.toList === List(Map("sum(foo)" -> 8))) } - @Test - def with_should_not_forget_parameters() { - graph.index().forNodes("test") - val id = "bar" - val result = parseAndExecute("start n=node:test(name={id}) with count(*) as c where c=0 create x={name:{id}} return c, x", "id" -> id).toList - - assert(result.size === 1) - assert(result(0)("c").asInstanceOf[Long] === 0) - assert(result(0)("x").asInstanceOf[Node].getProperty("name") === id) - } - @Ignore("This pattern is currently not supported. Revisit when we do support it.") @Test def two_double_optional_paths_with_shared_relationships() { diff --git a/cypher/src/test/scala/org/neo4j/cypher/docgen/WhereTest.scala b/cypher/src/test/scala/org/neo4j/cypher/docgen/WhereTest.scala index 0ca4b9503..2d210d34b 100644 --- a/cypher/src/test/scala/org/neo4j/cypher/docgen/WhereTest.scala +++ b/cypher/src/test/scala/org/neo4j/cypher/docgen/WhereTest.scala @@ -55,8 +55,8 @@ class WhereTest extends DocumentingTestBase { @Test def regular_expressions() { testQuery( title = "Regular expressions", - text = "You can match on regular expressions by using `=~ /regexp/`, like this:", - queryText = """start n=node(%Andres%, %Tobias%) where n.name =~ /Tob.*/ return n""", + text = "You can match on regular expressions by using `=~ \"regexp\"`, like this:", + queryText = """start n=node(%Andres%, %Tobias%) where n.name =~ 'Tob.*' return n""", returns = """The "+Tobias+" node will be returned.""", assertions = (p) => assertEquals(List(node("Tobias")), p.columnAs[Node]("n").toList)) } @@ -64,8 +64,9 @@ class WhereTest extends DocumentingTestBase { @Test def regular_expressions_escaped() { testQuery( title = "Escaping in regular expressions", - text = "If you need a forward slash inside of your regular expression, escape it using a backslash (+\\+).", - queryText = """start n=node(%Andres%, %Tobias%) where n.name =~ /Some\/thing/ return n""", + text = "If you need a forward slash inside of your regular expression, escape it. Remember that back slash needs " + + "to be escaped in string literals", + queryText = """start n=node(%Andres%, %Tobias%) where n.name =~ 'Some\\/thing' return n""", returns = """No nodes match this regular expression.""", assertions = (p) => assertEquals(List(), p.toList)) } @@ -74,7 +75,7 @@ class WhereTest extends DocumentingTestBase { testQuery( title = "Case insensitive regular expressions", text = "By pre-pending a regular expression with `(?i)`, the whole expression becomes case insensitive.", - queryText = """start n=node(%Andres%, %Tobias%) where n.name =~ /(?i)ANDR.*/ return n""", + queryText = """start n=node(%Andres%, %Tobias%) where n.name =~ '(?i)ANDR.*' return n""", returns = """The node with name "+Andres+" is returned.""", assertions = (p) => assertEquals(List(Map("n" -> node("Andres"))), p.toList)) } @@ -113,7 +114,7 @@ class WhereTest extends DocumentingTestBase { text = "You can put the exact relationship type in the `MATCH` pattern, but sometimes you want to be able to do more " + "advanced filtering on the type. You can use the special property `TYPE` to compare the type with something else. " + "In this example, the query does a regular expression comparison with the name of the relationship type.", - queryText = """start n=node(%Andres%) match (n)-[r]->() where type(r) =~ /K.*/ return r""", + queryText = """start n=node(%Andres%) match (n)-[r]->() where type(r) =~ 'K.*' return r""", returns = """This returns relationships that has a type whose name starts with K.""", assertions = (p) => assertEquals("KNOWS", p.columnAs[Relationship]("r").toList.head.getType.name())) }