Skip to content

Commit

Permalink
Adding the ability to do ORDER BY, SKIP, LIMIT after WITH, before
Browse files Browse the repository at this point in the history
another optional MATCH.
  • Loading branch information
freeeve authored and systay committed Oct 11, 2012
1 parent fb5909b commit f75e7e9
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 8 deletions.
2 changes: 2 additions & 0 deletions cypher/src/docs/dev/ql/with/index.asciidoc
Expand Up @@ -9,4 +9,6 @@ used to pipe the result from one query to the next.
include::cypher-with-graph.asciidoc[]

include::filter-on-aggregate-function-results.asciidoc[]
include::sort-results-before-using-collect-on-them.asciidoc[]
include::limit-branching-of-your-path-search.asciidoc[]
include::alternative-syntax-of-with.asciidoc[]
Expand Up @@ -75,17 +75,21 @@ Thank you, the Neo4j Team.
val (pattern, namedPaths) = extractMatches(matching)

val returns = Return(List("*"), AllIdentifiers())
BodyWith(updates, pattern, namedPaths, where, returns, None, startItems, paths, nextQ)
BodyWith(updates, pattern, namedPaths, None, where, Seq(), 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 => {
def bodyWith: Parser[Body] = opt(matching) ~ opt(where) ~ WITH ~ opt(order) ~ opt(skip) ~ opt(limit) ~ opt(start) ~ updates ~ body ^^ {
case matching ~ where ~ returns ~ order ~ skip ~ limit ~ start ~ updates ~ nextQ => {
val (pattern, matchPaths) = extractMatches(matching)
val startItems = start.toSeq.flatMap(_._1)
val startPaths = start.toSeq.flatMap(_._2)
val slice = (skip, limit) match {
case (None, None) => None
case (s, l) => Some(Slice(s, l))
}

BodyWith(updates._1, pattern, matchPaths ++ updates._2, where, returns._1, returns._2, startItems, startPaths, nextQ)
BodyWith(updates._1, pattern, matchPaths ++ updates._2, slice, where, order.toSeq.flatten, returns._1, returns._2, startItems, startPaths, nextQ)
}
}

Expand Down Expand Up @@ -113,7 +117,7 @@ Thank you, the Neo4j Team.
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)))
Query(b.returns, start, updates, b.matching, b.where, b.aggregate, b.order, b.slice, b.namedPath ++ namedPaths, Some(expandQuery(b.start, b.startPaths, b.updates, b.next)))
}
case b: BodyReturn => {
checkForAggregates(b.where)
Expand Down Expand Up @@ -193,11 +197,11 @@ If a Body is an intermediate part, either explicitly with WITH, or implicitly wh
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
case class BodyWith(updates:Seq[UpdateAction], matching: Seq[Pattern], namedPath: Seq[NamedPath], slice: Option[Slice], where: Option[Predicate], order:Seq[SortItem], 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
case class NoBody() extends Body
29 changes: 29 additions & 0 deletions cypher/src/test/scala/org/neo4j/cypher/CypherParserTest.scala
Expand Up @@ -1864,7 +1864,36 @@ foreach(x in [1,2,3] :
returns(AllIdentifiers()))
}

@Test def with_limit() {
testFrom_1_9("start n=node(0,1,2) with n limit 2 where ID(n) = 1 return n",
Query.
start(NodeById("n", 0, 1, 2)).
limit(2).
tail(Query.
start().
where(Equals(IdFunction(Identifier("n")), Literal(1))).
returns(ReturnItem(Identifier("n"), "n"))
).
returns(
ReturnItem(Identifier("n"), "n")
))
}

@Test def with_sort_limit() {
testFrom_1_9("start n=node(0,1,2) with n order by ID(n) desc limit 2 where ID(n) = 1 return n",
Query.
start(NodeById("n", 0, 1, 2)).
orderBy(SortItem(IdFunction(Identifier("n")), false)).
limit(2).
tail(Query.
start().
where(Equals(IdFunction(Identifier("n")), Literal(1))).
returns(ReturnItem(Identifier("n"), "n"))
).
returns(
ReturnItem(Identifier("n"), "n")
))
}

@Ignore("slow test") @Test def multi_thread_parsing() {
val q = """start root=node(0) return x"""
Expand Down
21 changes: 20 additions & 1 deletion cypher/src/test/scala/org/neo4j/cypher/docgen/WithTest.scala
Expand Up @@ -19,6 +19,7 @@
*/
package org.neo4j.cypher.docgen

import org.neo4j.cypher.internal.commands.expressions.Literal
import org.junit.Test
import org.neo4j.graphdb.Node
import org.junit.Assert._
Expand All @@ -45,6 +46,24 @@ class WithTest extends DocumentingTestBase {
assertions = (p) => assertEquals(List(node("A")), p.columnAs[Node]("otherPerson").toList))
}

@Test def sort_collect_results() {
testQuery(
title = "Sort results before using collect on them",
text = "You can sort your results before passing them to collect, thus sorting the resulting collection.",
queryText = """start n=node(*) with n order by n.name desc limit 3 return collect(n.name)""",
returns = """A list of the names of people in reverse order, limited to 3, in a collection.""",
assertions = (p) => assertEquals(List(List("Emil", "David", "Cesar")), p.columnAs[Seq[String]]("collect(n.name)").toList))
}

@Test def limit_branching() {
testQuery(
title = "Limit branching of your path search",
text = "You can match paths, limit to a certain number, and then match again using those paths as a base As well as any number of similar limited searches.",
queryText = """start n=node(3) match n--m with m order by m.name desc limit 1 match m--o return o.name""",
returns = """Starting at Anders, find all matching nodes, order by name descending and get the top result, then find all the nodes connected to that top result, and return their names.""",
assertions = (p) => assertEquals(List("Anders", "Bossman"), p.columnAs[String]("o.name").toList))
}

@Test def alternative_way_to_write_with() {
testQuery(
title = "Alternative syntax of WITH",
Expand All @@ -58,4 +77,4 @@ set otherPerson.connection_count = foaf """,
returns = """For persons connected to David, the `connection_count` property is set to their number of outgoing relationships.""",
assertions = (p) => assertEquals(node("A").getProperty("connection_count"), 2L))
}
}
}

0 comments on commit f75e7e9

Please sign in to comment.