Skip to content

Var length path refactoring #102

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,18 @@ public void varPathPredicate() throws Exception {

assertThat(results)
.extracting("n.name", "m.name")
.containsExactlyInAnyOrder( tuple("marko", "josh"),
.containsExactlyInAnyOrder(tuple("marko", "josh"),
tuple("marko", "ripple"),
tuple("josh", "ripple"));
}

@Test
public void multipleVarLengthRelationships() throws Exception {
List<Map<String, Object>> results = submitAndGet(
"MATCH p = (a {name: 'marko'})-[:knows*0..1]->(b)-[:created*0..1]->(c)\n" +
"RETURN p");

assertThat(results)
.hasSize(6);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ GremlinSteps<T, P> choose(GremlinSteps<T, P> traversalPredicate,

GremlinSteps<T, P> sideEffect(GremlinSteps<T, P> sideEffectTraversal);

GremlinSteps<T, P> simplePath();

GremlinSteps<T, P> skip(long skip);

GremlinSteps<T, P> sum();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ private Tokens() {
public static final String NULL = " cypher.null";
public static final String UNUSED = " cypher.unused";
public static final String PATH_EDGE = " cypher.path.edge.";
public static final String PATH_START = " cypher.path.start.";

public static final String PROJECTION_RELATIONSHIP = " cypher.relationship";
public static final String PROJECTION_ELEMENT = " cypher.element";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,12 @@ public GremlinSteps<Bytecode, P> sideEffect(GremlinSteps<Bytecode, P> sideEffect
return this;
}

@Override
public GremlinSteps<Bytecode, P> simplePath() {
bytecode.addStep(Symbols.simplePath);
return this;
}

@Override
public GremlinSteps<Bytecode, P> skip(long skip) {
bytecode.addStep(Symbols.skip, skip);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,12 @@ public GremlinSteps<String, GroovyPredicate> sideEffect(GremlinSteps<String, Gro
return this;
}

@Override
public GremlinSteps<String, GroovyPredicate> simplePath() {
g.append(chain("simplePath"));
return this;
}

@Override
public GremlinSteps<String, GroovyPredicate> skip(long skip) {
g.append(chain("skip", skip));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,12 @@ public GremlinSteps<GraphTraversal, P> sideEffect(GremlinSteps<GraphTraversal, P
return this;
}

@Override
public GremlinSteps<GraphTraversal, P> simplePath() {
g.simplePath();
return this;
}

@Override
public GremlinSteps<GraphTraversal, P> skip(long skip) {
g.skip(skip);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/
package org.opencypher.gremlin.translation.context

import org.opencypher.gremlin.translation.GremlinSteps
import org.opencypher.gremlin.translation.translator.Translator
import org.opencypher.v9_0.expressions.Expression
import org.opencypher.v9_0.util.symbols.CypherType
Expand Down Expand Up @@ -65,17 +64,6 @@ sealed class StatementContext[T, P](
parameters.contains(name)
}

private var midTraversals = 0

def midTraversal(g: GremlinSteps[T, P]): Unit = {
midTraversals += 1
g.V()
}

def lowerBound(edges: Int): Int = if (edges == 0) 0 else edges * 2 + 1

def upperBound(edges: Int): Int = lowerBound(edges) + midTraversals

def unsupported(description: String, node: Any): Nothing = {
throw new UnsupportedOperationException(s"Unsupported $description: $node")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ sealed private[ir] class TranslationGenerator[T, P](translator: Translator[T, P]
g.select(column)
case SideEffect(sideEffectTraversal) =>
g.sideEffect(generateSteps(sideEffectTraversal))
case SimplePath =>
g.simplePath()
case Skip(skip) =>
g.skip(skip)
case Sum =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,11 @@ class IRGremlinSteps extends GremlinSteps[Seq[GremlinStep], GremlinPredicate] {
this
}

override def simplePath(): GremlinSteps[Seq[GremlinStep], GremlinPredicate] = {
buf += SimplePath
this
}

override def skip(skip: Long): GremlinSteps[Seq[GremlinStep], GremlinPredicate] = {
buf += Skip(skip)
this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,8 @@ case class SideEffect(sideEffectTraversal: Seq[GremlinStep]) extends GremlinStep
}
}

case object SimplePath extends GremlinStep

case class Skip(skip: Long) extends GremlinStep

case object Sum extends GremlinStep
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
package org.opencypher.gremlin.translation.walker

import org.opencypher.gremlin.translation.GremlinSteps
import org.opencypher.gremlin.translation.Tokens.START
import org.opencypher.gremlin.translation.context.StatementContext
import org.opencypher.gremlin.translation.walker.NodeUtils._
import org.opencypher.v9_0.ast._
Expand All @@ -39,10 +38,7 @@ object CallWalker {
private class CallWalker[T, P](context: StatementContext[T, P], g: GremlinSteps[T, P]) {

def walk(node: UnresolvedCall): Unit = {
if (context.isFirstStatement) {
context.markFirstStatement()
g.inject(START)
}
ensureFirstStatement(g, context)

node match {
case UnresolvedCall(Namespace(namespaceParts), ProcedureName(name), argumentOption, results) =>
Expand Down Expand Up @@ -78,10 +74,7 @@ private class CallWalker[T, P](context: StatementContext[T, P], g: GremlinSteps[
}

def walkStandalone(node: UnresolvedCall): Unit = {
if (context.isFirstStatement) {
context.markFirstStatement()
g.inject(START)
}
ensureFirstStatement(g, context)

val procedures = context.dsl.procedures()
node match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/
package org.opencypher.gremlin.translation.walker

import org.opencypher.gremlin.translation.Tokens.{NULL, START}
import org.opencypher.gremlin.translation.Tokens.NULL
import org.opencypher.gremlin.translation._
import org.opencypher.gremlin.translation.context.StatementContext
import org.opencypher.gremlin.translation.walker.NodeUtils._
Expand Down Expand Up @@ -49,10 +49,7 @@ private class MatchWalker[T, P](context: StatementContext[T, P], g: GremlinSteps
}

private def walkOptionalMatch(patternParts: Seq[PatternPart], whereOption: Option[Where]): Unit = {
if (context.isFirstStatement) {
context.markFirstStatement()
g.inject(START)
}
ensureFirstStatement(g, context)

val subG = g.start()
val contextSubG = context.copy()
Expand Down Expand Up @@ -87,12 +84,8 @@ private class MatchWalker[T, P](context: StatementContext[T, P], g: GremlinSteps
}

private def foldPatternElement(maybeName: Option[String], patternElement: PatternElement): Unit = {
if (!context.isFirstStatement) {
context.midTraversal(g)
} else {
g.V()
context.markFirstStatement()
}
context.markFirstStatement()
g.V()

PatternWalker.walkMatch(context, g, patternElement, maybeName)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@
package org.opencypher.gremlin.translation.walker

import org.opencypher.gremlin.translation.GremlinSteps
import org.opencypher.gremlin.translation.Tokens.START
import org.opencypher.gremlin.translation.context.StatementContext
import org.opencypher.gremlin.translation.walker.NodeUtils.{asUniqueName, getPathTraversalAliases}
import org.opencypher.gremlin.translation.walker.NodeUtils.{getPathTraversalAliases, _}
import org.opencypher.v9_0.ast._
import org.opencypher.v9_0.expressions._
import org.opencypher.v9_0.util.InputPosition.NONE
Expand All @@ -45,11 +44,7 @@ private class MergeWalker[T, P](context: StatementContext[T, P], g: GremlinSteps
g: GremlinSteps[T, P],
patternParts: Seq[PatternPart],
actions: Seq[MergeAction]): GremlinSteps[T, P] = {

if (context.isFirstStatement) {
g.inject(START)
context.markFirstStatement()
}
ensureFirstStatement(g, context)

val matchSubG = g.start()
MatchWalker.walkPatternParts(context.copy(), matchSubG, patternParts, None)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,11 @@ object NodeUtils {
expressions.map(ExpressionWalker.walkLocal(context, g, _)).foreach(traversal.by)
traversal.select(Column.values)
}

def ensureFirstStatement[T, P](traversal: GremlinSteps[T, P], context: StatementContext[T, P]): Unit = {
if (context.isFirstStatement) {
traversal.inject(Tokens.START)
context.markFirstStatement()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
package org.opencypher.gremlin.translation.walker

import org.apache.tinkerpop.gremlin.process.traversal.Scope
import org.opencypher.gremlin.translation.GremlinSteps
import org.opencypher.gremlin.translation.Tokens.PATH_EDGE
import org.opencypher.gremlin.translation.context.StatementContext
import org.opencypher.gremlin.translation.walker.NodeUtils._
import org.opencypher.gremlin.translation.{GremlinSteps, Tokens}
import org.opencypher.v9_0.expressions.SemanticDirection._
import org.opencypher.v9_0.expressions.{UnsignedDecimalIntegerLiteral => UDIL, _}
import org.opencypher.v9_0.util.InputPosition.NONE
Expand Down Expand Up @@ -79,7 +79,7 @@ class PatternWalker[T, P](context: StatementContext[T, P], g: GremlinSteps[T, P]
properties.map(hasProperties(variable, _)).foreach(g.map)
}

val traversalStepsHardLimit: Int = context.lowerBound(10)
val traversalStepsHardLimit: Int = gremlinPathLength(10)

private def walkRelationship(pathName: Option[String], relationship: RelationshipPattern): Unit = {
val RelationshipPattern(variableOption, types, length, properties, direction, _) = relationship
Expand All @@ -103,7 +103,10 @@ class PatternWalker[T, P](context: StatementContext[T, P], g: GremlinSteps[T, P]
case OUTGOING => directionT.inV()
}

def pathLengthT = g.start().path().count(Scope.local)
val pathStart = Tokens.PATH_START + pathName.getOrElse(context.generateName().trim())

g.as(pathStart)
def pathLengthT = g.start().path().from(pathStart).count(Scope.local)

val p = context.dsl.predicates()
length match {
Expand All @@ -120,30 +123,38 @@ class PatternWalker[T, P](context: StatementContext[T, P], g: GremlinSteps[T, P]
range match {
case Range(Some(UDIL(lower)), None) =>
// -[*m..]->
val lowerBound = context.lowerBound(lower.toInt)
val lowerBound = gremlinPathLength(lower.toInt)
g.emit()
.repeat(directionT)
.until(pathLengthT.is(p.gte(traversalStepsHardLimit)))
.where(pathLengthT.is(p.gte(lowerBound)))
.simplePath()
.from(pathStart)
case Range(None, Some(UDIL(upper))) =>
// -[*..n]->
val upperBound = context.upperBound(upper.toInt)
val upperBound = gremlinPathLength(upper.toInt)
g.repeat(directionT)
.emit()
.until(pathLengthT.is(p.gte(upperBound)))
.where(pathLengthT.is(p.lte(upperBound)))
.simplePath()
.from(pathStart)
case Range(Some(UDIL(lower)), Some(UDIL(upper))) if lower == upper =>
// -[*n]->
g.times(lower.toInt)
.repeat(directionT)
.simplePath()
.from(pathStart)
case Range(Some(UDIL(lower)), Some(UDIL(upper))) =>
// -[*m..n]->
val lowerBound = context.lowerBound(lower.toInt)
val upperBound = context.upperBound(upper.toInt)
val lowerBound = gremlinPathLength(lower.toInt)
val upperBound = gremlinPathLength(upper.toInt)
g.emit()
.repeat(directionT)
.until(pathLengthT.is(p.gte(upperBound)))
.where(pathLengthT.is(p.between(lowerBound, upperBound + 1)))
.simplePath()
.from(pathStart)
}
case _ =>
context.unsupported("path pattern length", length)
Expand Down Expand Up @@ -175,4 +186,6 @@ class PatternWalker[T, P](context: StatementContext[T, P], g: GremlinSteps[T, P]
context.unsupported("property map", propertyMap)
}
}

private def gremlinPathLength(edges: Int): Int = if (edges == 0) 0 else edges * 2 + 1
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,7 @@ private class ProjectionWalker[T, P](context: StatementContext[T, P], g: Gremlin
skip: Option[Skip],
limit: Option[Limit],
finalize: Boolean): Unit = {
if (context.isFirstStatement) {
context.markFirstStatement()
g.inject(START)
}
ensureFirstStatement(g, context)

val subTraversals = returnSubTraversals(items, finalize)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
*/
package org.opencypher.gremlin.translation.walker

import org.opencypher.gremlin.translation.Tokens.START
import org.opencypher.gremlin.translation._
import org.opencypher.gremlin.translation.context.StatementContext
import org.opencypher.gremlin.translation.walker.NodeUtils._
import org.opencypher.v9_0.ast._

import scala.collection.mutable
Expand Down Expand Up @@ -47,10 +47,7 @@ class StatementWalker[T, P](context: StatementContext[T, P], g: GremlinSteps[T,
}

def walkUnion(node: Union): Unit = {
if (context.isFirstStatement) {
context.markFirstStatement()
g.inject(START)
}
ensureFirstStatement(g, context)

val subGs = mutable.ArrayBuffer.empty[GremlinSteps[T, P]]
for (query <- flattenUnion(Vector(), node)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ package org.opencypher.gremlin.translation.walker
import java.util.Collections

import org.opencypher.gremlin.translation.GremlinSteps
import org.opencypher.gremlin.translation.Tokens._
import org.opencypher.gremlin.translation.context.StatementContext
import org.opencypher.gremlin.translation.walker.NodeUtils._
import org.opencypher.v9_0.ast._
import org.opencypher.v9_0.expressions._

Expand All @@ -42,10 +42,7 @@ private class UnwindWalker[T, P](context: StatementContext[T, P], g: GremlinStep
case Null() =>
g.inject(Collections.emptyList).unfold().as(varName)
case _: Expression =>
if (context.isFirstStatement) {
context.markFirstStatement()
g.inject(START)
}
ensureFirstStatement(g, context)
ExpressionWalker.walk(context, g, expression)
g.unfold().as(varName)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public void adjacentMap() {
))
.withFlavor(TranslatorFlavor.empty())
.rewritingWith(InlineMapTraversal$.MODULE$)
.removes(__().select("n").map(__()).map(__().outE().inV()))
.adds(__().select("n").outE().inV());
.removes(__().select("n").map(__()).as(" cypher.path.start.GENERATED1").map(__().outE().inV()))
.adds(__().select("n").as(" cypher.path.start.GENERATED1").outE().inV());
}
}