Commit d0371171 authored by Louise Söderström's avatar Louise Söderström
Browse files

Fix shortest path in with clause bug

Semantic check claimed shortest path returned a list
Unwind didn't work for the Scala iterator returned by allShortestPaths
parent a76f153b
No related merge requests found
Showing with 140 additions and 2 deletions
+140 -2
......@@ -31,7 +31,7 @@ class ShortestPathAcceptanceTest extends ExecutionEngineFunSuite with NewPlanner
var nodeC: Node = _
var nodeD: Node = _
override def databaseConfig = Map(GraphDatabaseSettings.forbid_shortestpath_common_nodes -> "false")
override def databaseConfig() = Map(GraphDatabaseSettings.forbid_shortestpath_common_nodes -> "false")
override protected def initTest(): Unit = {
super.initTest()
......@@ -41,6 +41,72 @@ class ShortestPathAcceptanceTest extends ExecutionEngineFunSuite with NewPlanner
nodeD = createLabeledNode("D")
}
test("shortest path in a with clause") {
relate(nodeA, nodeB)
val query =
"""
| MATCH (a:A), (b:B)
| WITH shortestPath((a)-[:REL]->(b)) AS x
| RETURN nodes(x)
""".stripMargin
val result = executeWithAllPlanners(query).columnAs[List[Node]]("nodes(x)").toList
result should equal(List(List(nodeA, nodeB)))
}
test("shortest path in a with clause and no paths found") {
relate(nodeA, nodeB)
val query =
"""
| MATCH (a:A), (b:B)
| WITH shortestPath((a)-[:XXX]->(b)) AS x
| RETURN nodes(x)
""".stripMargin
val result = executeWithAllPlanners(query).columnAs[List[Node]]("nodes(x)").toList
result should equal(List(null))
}
test("all shortest paths in a with clause") {
relate(nodeA, nodeB)
val query =
"""
| MATCH (a:A), (b:B)
| WITH allShortestPaths((a)-[:REL]->(b)) AS p
| UNWIND p AS x
| RETURN nodes(x)
""".stripMargin
val result = executeWithAllPlanners(query).columnAs[List[Node]]("nodes(x)").toList
result should equal(List(List(nodeA, nodeB)))
}
test("all shortest paths in a with clause and no paths found") {
relate(nodeA, nodeB)
val query =
"""
| MATCH (a:A), (b:B)
| WITH allShortestPaths((a)-[:XXX]->(b)) AS p
| UNWIND p AS x
| RETURN nodes(x)
""".stripMargin
val result = executeWithAllPlanners(query).columnAs[List[Node]]("nodes(x)").toList
result should equal(List.empty)
}
// THESE NEED TO BE REVIEWED FOR SEMANTIC CORRECTNESS
test("unnamed shortest path with fallback-required predicate should work") {
val r1 = relate(nodeA, nodeB, "bar" -> 1)
......
......@@ -89,6 +89,7 @@ trait ListSupport {
case x: Map[_, _] => Iterable(x)
case x: JavaMap[_, _] => Iterable(x.asScala)
case x: Traversable[_] => x.toIterable
case x: Iterator[_] => x.toIterable
case x: JavaIterable[_] => x.asScala.map {
case y: JavaMap[_, _] => y.asScala
case y => y
......
......@@ -24,7 +24,7 @@ import org.neo4j.cypher.internal.frontend.v3_1.symbols._
case class ShortestPathExpression(pattern: ShortestPaths) extends Expression with SimpleTyping {
def position = pattern.position
protected def possibleTypes = CTList(CTPath)
protected def possibleTypes = if (pattern.single) CTPath else CTList(CTPath)
override def semanticCheck(ctx: SemanticContext) =
pattern.declareVariables(Pattern.SemanticContext.Expression) chain
......
/*
* Copyright (c) 2002-2017 "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.frontend.v3_1.ast
import org.neo4j.cypher.internal.frontend.v3_1.symbols._
import org.neo4j.cypher.internal.frontend.v3_1.{SemanticDirection, SemanticState}
import org.neo4j.cypher.internal.frontend.v3_1.test_helpers.CypherFunSuite
class ShortestPathExpressionTest extends CypherFunSuite with AstConstructionTestSupport {
test("should get correct types for shortestPath") {
// Given
val (exp, state) = makeShortestPathExpression(true)
// When
val result = exp.semanticCheck(Expression.SemanticContext.Simple)(state)
// Then
result.errors shouldBe empty
exp.types(result.state) should equal(TypeSpec.exact(CTPath))
}
test("should get correct types for allShortestPath") {
// Given
val (exp, state) = makeShortestPathExpression(false)
// When
val result = exp.semanticCheck(Expression.SemanticContext.Simple)(state)
// Then
result.errors shouldBe empty
exp.types(result.state) should equal(TypeSpec.exact(CTList(CTPath)))
}
private def makeShortestPathExpression(single: Boolean): (ShortestPathExpression, SemanticState) = {
val state = Seq("n", "k").foldLeft(SemanticState.clean) { (acc, n) =>
acc.specifyType(varFor(n), TypeSpec.exact(CTNode)).right.get
}
val pattern = chain(node(Some(varFor("n"))), relationship(None), node(Some(varFor("k"))))
(ShortestPathExpression(ShortestPaths(pattern, single) _), state)
}
private def chain(left: PatternElement, rel: RelationshipPattern, right: NodePattern): RelationshipChain = {
RelationshipChain(left, rel, right)_
}
private def relationship(id: Option[Variable]): RelationshipPattern = {
RelationshipPattern(id, optional = false, Seq.empty, None, None, SemanticDirection.OUTGOING)_
}
private def node(id: Option[Variable]): NodePattern = {
NodePattern(id, Seq.empty, None)_
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment