diff --git a/core/src/main/scala/org/graphframes/pattern/patterns.scala b/core/src/main/scala/org/graphframes/pattern/patterns.scala index 0d9c3f345..331947759 100644 --- a/core/src/main/scala/org/graphframes/pattern/patterns.scala +++ b/core/src/main/scala/org/graphframes/pattern/patterns.scala @@ -31,13 +31,15 @@ private[graphframes] object PatternParser extends RegexParsers { private val anonymousVertex: Parser[Vertex] = "" ^^ { _ => AnonymousVertex } private val vertex: Parser[Vertex] = "(" ~> (vertexName | anonymousVertex) <~ ")" private val namedEdge: Parser[Edge] = - vertex ~ "-" ~ "[" ~ "[a-zA-Z0-9_]+".r ~ "]" ~ "->" ~ vertex ^^ { + vertex ~ ("<-" | "-") ~ "[" ~ "[a-zA-Z0-9_]+".r ~ "]" ~ ("->" | "-") ~ vertex ^^ { case src ~ "-" ~ "[" ~ name ~ "]" ~ "->" ~ dst => NamedEdge(name, src, dst) + case dst ~ "<-" ~ "[" ~ name ~ "]" ~ "-" ~ src => NamedEdge(name, src, dst) case _ => throw new GraphFramesUnreachableException() } val anonymousEdge: Parser[Edge] = - vertex ~ "-" ~ "[" ~ "]" ~ "->" ~ vertex ^^ { + vertex ~ ("<-" | "-") ~ "[" ~ "]" ~ ("->" | "-") ~ vertex ^^ { case src ~ "-" ~ "[" ~ "]" ~ "->" ~ dst => AnonymousEdge(src, dst) + case dst ~ "<-" ~ "[" ~ "]" ~ "-" ~ src => AnonymousEdge(src, dst) case _ => throw new GraphFramesUnreachableException() } private val edge: Parser[Edge] = namedEdge | anonymousEdge @@ -45,28 +47,32 @@ private[graphframes] object PatternParser extends RegexParsers { "!" ~ edge ^^ { case _ ~ e => Negation(e) } - private val fixedLengthPattern: Parser[List[Edge]] = - vertex ~ "-" ~ "[" ~ "[a-zA-Z0-9_]*".r ~ "*" ~ "[0-9]+".r ~ "]" ~ "->" ~ vertex ^^ { - case src ~ "-" ~ "[" ~ name ~ "*" ~ num ~ "]" ~ "->" ~ dst => { - val hop: Int = num.toInt - if (hop == 1) { - List(if (name.isEmpty) AnonymousEdge(src, dst) else NamedEdge(name, src, dst)) - } else if (hop > 1) { - val midVertices = (1 until hop).map(i => NamedVertex(s"_v$i")) - val vertices = src +: midVertices :+ dst - vertices - .sliding(2) - .zipWithIndex - .map { - case (Seq(v1, v2), i) => - if (name.isEmpty) AnonymousEdge(v1, v2) else NamedEdge(s"_$name${i + 1}", v1, v2) - case _ => throw new GraphFramesUnreachableException() - } - .toList - } else { - throw new GraphFramesUnreachableException() + + def generateFixedLengthPattern(src: Vertex, name: String, hop: Int, dst: Vertex): List[Edge] = { + if (hop == 1) { + List(if (name.isEmpty) AnonymousEdge(src, dst) else NamedEdge(name, src, dst)) + } else if (hop > 1) { + val midVertices = (1 until hop).map(i => NamedVertex(s"_v$i")) + val vertices = src +: midVertices :+ dst + vertices + .sliding(2) + .zipWithIndex + .map { + case (Seq(v1, v2), i) => + if (name.isEmpty) AnonymousEdge(v1, v2) else NamedEdge(s"_$name${i + 1}", v1, v2) + case _ => throw new GraphFramesUnreachableException() } - } + .toList + } else { + throw new GraphFramesUnreachableException() + } + } + private val fixedLengthPattern: Parser[List[Edge]] = + vertex ~ ("<-" | "-") ~ "[" ~ "[a-zA-Z0-9_]*".r ~ "*" ~ "[0-9]+".r ~ "]" ~ ("->" | "-") ~ vertex ^^ { + case src ~ "-" ~ "[" ~ name ~ "*" ~ num ~ "]" ~ "->" ~ dst => + generateFixedLengthPattern(src, name, num.toInt, dst) + case dst ~ "<-" ~ "[" ~ name ~ "*" ~ num ~ "]" ~ "-" ~ src => + generateFixedLengthPattern(src, name, num.toInt, dst) case _ => throw new GraphFramesUnreachableException() } private val pattern: Parser[Pattern] = edge | vertex | negatedEdge diff --git a/core/src/test/scala/org/graphframes/pattern/PatternSuite.scala b/core/src/test/scala/org/graphframes/pattern/PatternSuite.scala index 301368cc2..3071ad700 100644 --- a/core/src/test/scala/org/graphframes/pattern/PatternSuite.scala +++ b/core/src/test/scala/org/graphframes/pattern/PatternSuite.scala @@ -84,6 +84,16 @@ class PatternSuite extends SparkFunSuite { AnonymousEdge(NamedVertex("_v9"), NamedVertex("v")))) } + test("good parses - reverse edge pattern") { + assert(Pattern.parse("(u)-[e]->(v)") === Pattern.parse("(v)<-[e]-(u)")) + assert(Pattern.parse("(u)-[]->(v)") === Pattern.parse("(v)<-[]-(u)")) + assert(Pattern.parse("(u)-[*5]->(v)") === Pattern.parse("(v)<-[*5]-(u)")) + assert(Pattern.parse("()-[e]->()") === Pattern.parse("()<-[e]-()")) + assert( + Pattern.parse("(u)-[]->(v); (v)-[]->(w); !(u)-[]->(w)") === Pattern.parse( + "(u)-[]->(v); (w)<-[]-(v); !(w)<-[]-(u)")) + } + test("bad parses") { withClue("Failed to catch parse error with lone anonymous vertex") { intercept[InvalidParseException] {