From bc034f0bd3b49b2111c90f332b760e3b8750b22f Mon Sep 17 00:00:00 2001 From: To-om Date: Sun, 24 Feb 2019 10:49:57 +0100 Subject: [PATCH 1/4] Add type safe projection --- .../scala/gremlin/scala/GremlinScala.scala | 28 +++++++++++++- .../scala/gremlin/scala/ProjectSpec.scala | 38 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 gremlin-scala/src/test/scala/gremlin/scala/ProjectSpec.scala diff --git a/gremlin-scala/src/main/scala/gremlin/scala/GremlinScala.scala b/gremlin-scala/src/main/scala/gremlin/scala/GremlinScala.scala index f35f9ec1..608988ff 100644 --- a/gremlin-scala/src/main/scala/gremlin/scala/GremlinScala.scala +++ b/gremlin-scala/src/main/scala/gremlin/scala/GremlinScala.scala @@ -13,7 +13,8 @@ import java.util.{ Map => JMap, Collection => JCollection, Iterator => JIterator, - Set => JSet + Set => JSet, + UUID } import java.util.stream.{Stream => JStream} @@ -116,6 +117,31 @@ class GremlinScala[End](val traversal: GraphTraversal[_, End]) { otherProjectKeys: String*): GremlinScala.Aux[JMap[String, A], Labels] = GremlinScala[JMap[String, A], Labels](traversal.project(projectKey, otherProjectKeys: _*)) + def project[H <: HList]( + builder: ProjectionBuilder[End, HNil] ⇒ ProjectionBuilder[End, H]): GremlinScala[H] = + builder(new ProjectionBuilder(Nil, scala.Predef.identity, _ ⇒ HNil)).build(this) + + class ProjectionBuilder[T, H <: HList] private[gremlin] ( + labels: Seq[String], + addBy: GraphTraversal[_, JMap[String, Any]] ⇒ GraphTraversal[_, JMap[String, Any]], + buildResult: JMap[String, Any] ⇒ H) { + + def apply[U, HR <: HList](f: GremlinScala[T] ⇒ GremlinScala[U])( + implicit prepend: Prepend.Aux[H, U :: HNil, HR]): ProjectionBuilder[T, HR] = { + val label = UUID.randomUUID().toString + new ProjectionBuilder[T, HR](labels :+ label, + addBy.andThen(_.by(f(__[T]()).traversal)), + map ⇒ buildResult(map) :+ map.get(label).asInstanceOf[U]) + } + + def and[U, HR <: HList](f: GremlinScala[T] ⇒ GremlinScala[U])( + implicit prepend: Prepend.Aux[H, U :: HNil, HR]): ProjectionBuilder[T, HR] = apply(f) + + private[gremlin] def build(g: GremlinScala[T]): GremlinScala[H] = { + GremlinScala(addBy(g.traversal.project(labels.head, labels.tail: _*))).map(buildResult) + } + } + /** You might think that predicate should be `GremlinScala[End] => GremlinScala[Boolean]`, * but that's not how tp3 works: e.g. `.value(Age).is(30)` returns `30`, not `true` */ diff --git a/gremlin-scala/src/test/scala/gremlin/scala/ProjectSpec.scala b/gremlin-scala/src/test/scala/gremlin/scala/ProjectSpec.scala new file mode 100644 index 00000000..01edbfae --- /dev/null +++ b/gremlin-scala/src/test/scala/gremlin/scala/ProjectSpec.scala @@ -0,0 +1,38 @@ +package gremlin.scala + +import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory +import org.scalatest.{Matchers, WordSpec} +import shapeless.HNil + +class ProjectSpec extends WordSpec with Matchers { + def graph: ScalaGraph = TinkerFactory.createModern.asScala() + + "project steps" should { + "provide type safe result" in { + val result = graph + .V() + .out("created") + .project(_(_.value(Key[String]("name"))) + .and(_.in("created").count())) + .toList() + + result shouldBe List( + "lop" :: 3 :: HNil, + "lop" :: 3 :: HNil, + "lop" :: 3 :: HNil, + "ripple" :: 1 :: HNil + ) + } + + "provide other type safe result" in { + val result = graph + .V() + .has(Key("name").of("marko")) + .project(_(_.outE().count()) + .and(_.inE().count())) + .head() + + result shouldBe (3 :: 0 :: HNil) + } + } +} From 4e99805656724fdf4e1c80439c17c4c048312719 Mon Sep 17 00:00:00 2001 From: To-om Date: Sun, 24 Feb 2019 19:21:17 +0100 Subject: [PATCH 2/4] Use By instead of function --- .../src/main/scala/gremlin/scala/GremlinScala.scala | 10 +++++----- .../src/test/scala/gremlin/scala/ProjectSpec.scala | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/gremlin-scala/src/main/scala/gremlin/scala/GremlinScala.scala b/gremlin-scala/src/main/scala/gremlin/scala/GremlinScala.scala index 608988ff..417abe25 100644 --- a/gremlin-scala/src/main/scala/gremlin/scala/GremlinScala.scala +++ b/gremlin-scala/src/main/scala/gremlin/scala/GremlinScala.scala @@ -126,16 +126,16 @@ class GremlinScala[End](val traversal: GraphTraversal[_, End]) { addBy: GraphTraversal[_, JMap[String, Any]] ⇒ GraphTraversal[_, JMap[String, Any]], buildResult: JMap[String, Any] ⇒ H) { - def apply[U, HR <: HList](f: GremlinScala[T] ⇒ GremlinScala[U])( + def apply[U, HR <: HList](by: By[U])( implicit prepend: Prepend.Aux[H, U :: HNil, HR]): ProjectionBuilder[T, HR] = { val label = UUID.randomUUID().toString new ProjectionBuilder[T, HR](labels :+ label, - addBy.andThen(_.by(f(__[T]()).traversal)), - map ⇒ buildResult(map) :+ map.get(label).asInstanceOf[U]) + addBy.andThen(by.apply), + map ⇒ buildResult(map) :+ map.get(label).asInstanceOf[U]) } - def and[U, HR <: HList](f: GremlinScala[T] ⇒ GremlinScala[U])( - implicit prepend: Prepend.Aux[H, U :: HNil, HR]): ProjectionBuilder[T, HR] = apply(f) + def and[U, HR <: HList](by: By[U])( + implicit prepend: Prepend.Aux[H, U :: HNil, HR]): ProjectionBuilder[T, HR] = apply(by) private[gremlin] def build(g: GremlinScala[T]): GremlinScala[H] = { GremlinScala(addBy(g.traversal.project(labels.head, labels.tail: _*))).map(buildResult) diff --git a/gremlin-scala/src/test/scala/gremlin/scala/ProjectSpec.scala b/gremlin-scala/src/test/scala/gremlin/scala/ProjectSpec.scala index 01edbfae..8072d414 100644 --- a/gremlin-scala/src/test/scala/gremlin/scala/ProjectSpec.scala +++ b/gremlin-scala/src/test/scala/gremlin/scala/ProjectSpec.scala @@ -12,8 +12,8 @@ class ProjectSpec extends WordSpec with Matchers { val result = graph .V() .out("created") - .project(_(_.value(Key[String]("name"))) - .and(_.in("created").count())) + .project(_(By(Key[String]("name"))) + .and(By(__.in("created").count()))) .toList() result shouldBe List( @@ -28,8 +28,8 @@ class ProjectSpec extends WordSpec with Matchers { val result = graph .V() .has(Key("name").of("marko")) - .project(_(_.outE().count()) - .and(_.inE().count())) + .project(_(By(__.outE().count())) + .and(By(__.inE().count()))) .head() result shouldBe (3 :: 0 :: HNil) From f50d1a0be8ac0dfe90f610b702f584a867707a5a Mon Sep 17 00:00:00 2001 From: To-om Date: Sun, 24 Feb 2019 19:55:37 +0100 Subject: [PATCH 3/4] Remove useless type parameter T in ProjectionBuilder Make initial ProjectionBuilder instanciable outside gremlin.scala. --- .../scala/gremlin/scala/GremlinScala.scala | 50 ++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/gremlin-scala/src/main/scala/gremlin/scala/GremlinScala.scala b/gremlin-scala/src/main/scala/gremlin/scala/GremlinScala.scala index 417abe25..ce55fb83 100644 --- a/gremlin-scala/src/main/scala/gremlin/scala/GremlinScala.scala +++ b/gremlin-scala/src/main/scala/gremlin/scala/GremlinScala.scala @@ -118,29 +118,8 @@ class GremlinScala[End](val traversal: GraphTraversal[_, End]) { GremlinScala[JMap[String, A], Labels](traversal.project(projectKey, otherProjectKeys: _*)) def project[H <: HList]( - builder: ProjectionBuilder[End, HNil] ⇒ ProjectionBuilder[End, H]): GremlinScala[H] = - builder(new ProjectionBuilder(Nil, scala.Predef.identity, _ ⇒ HNil)).build(this) - - class ProjectionBuilder[T, H <: HList] private[gremlin] ( - labels: Seq[String], - addBy: GraphTraversal[_, JMap[String, Any]] ⇒ GraphTraversal[_, JMap[String, Any]], - buildResult: JMap[String, Any] ⇒ H) { - - def apply[U, HR <: HList](by: By[U])( - implicit prepend: Prepend.Aux[H, U :: HNil, HR]): ProjectionBuilder[T, HR] = { - val label = UUID.randomUUID().toString - new ProjectionBuilder[T, HR](labels :+ label, - addBy.andThen(by.apply), - map ⇒ buildResult(map) :+ map.get(label).asInstanceOf[U]) - } - - def and[U, HR <: HList](by: By[U])( - implicit prepend: Prepend.Aux[H, U :: HNil, HR]): ProjectionBuilder[T, HR] = apply(by) - - private[gremlin] def build(g: GremlinScala[T]): GremlinScala[H] = { - GremlinScala(addBy(g.traversal.project(labels.head, labels.tail: _*))).map(buildResult) - } - } + builder: ProjectionBuilder[HNil] ⇒ ProjectionBuilder[H]): GremlinScala[H] = + builder(ProjectionBuilder()).build(this) /** You might think that predicate should be `GremlinScala[End] => GremlinScala[Boolean]`, * but that's not how tp3 works: e.g. `.value(Age).is(30)` returns `30`, not `true` @@ -1053,3 +1032,28 @@ class GremlinScala[End](val traversal: GraphTraversal[_, End]) { travs.map(_.apply(start).traversal) } + +class ProjectionBuilder[H <: HList] private[gremlin] ( + labels: Seq[String], + addBy: GraphTraversal[_, JMap[String, Any]] ⇒ GraphTraversal[_, JMap[String, Any]], + buildResult: JMap[String, Any] ⇒ H) { + + def apply[U, HR <: HList](by: By[U])( + implicit prepend: Prepend.Aux[H, U :: HNil, HR]): ProjectionBuilder[HR] = { + val label = UUID.randomUUID().toString + new ProjectionBuilder[HR](labels :+ label, + addBy.andThen(by.apply), + map ⇒ buildResult(map) :+ map.get(label).asInstanceOf[U]) + } + + def and[U, HR <: HList](by: By[U])( + implicit prepend: Prepend.Aux[H, U :: HNil, HR]): ProjectionBuilder[HR] = apply(by) + + private[gremlin] def build(g: GremlinScala[_]): GremlinScala[H] = { + GremlinScala(addBy(g.traversal.project(labels.head, labels.tail: _*))).map(buildResult) + } +} + +object ProjectionBuilder { + def apply() = new ProjectionBuilder[HNil](Nil, scala.Predef.identity, _ ⇒ HNil) +} From f2d3dce808f4bed0077b2382fe27ca21005e789f Mon Sep 17 00:00:00 2001 From: To-om Date: Tue, 26 Feb 2019 10:34:26 +0100 Subject: [PATCH 4/4] Replace HList by Tuple --- .../scala/gremlin/scala/GremlinScala.scala | 24 ++++++++++--------- .../scala/gremlin/scala/ProjectSpec.scala | 11 ++++----- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/gremlin-scala/src/main/scala/gremlin/scala/GremlinScala.scala b/gremlin-scala/src/main/scala/gremlin/scala/GremlinScala.scala index ce55fb83..39cd7b2c 100644 --- a/gremlin-scala/src/main/scala/gremlin/scala/GremlinScala.scala +++ b/gremlin-scala/src/main/scala/gremlin/scala/GremlinScala.scala @@ -33,7 +33,9 @@ import org.apache.tinkerpop.gremlin.process.traversal.{Bytecode, Path, Scope, Tr import org.apache.tinkerpop.gremlin.structure.{Direction, T} import shapeless.{::, HList, HNil} import shapeless.ops.hlist.{IsHCons, Mapper, Prepend, RightFolder, ToTraversable, Tupler} +import shapeless.ops.tuple.{Prepend => TuplePrepend} import shapeless.ops.product.ToHList +import shapeless.syntax.std.tuple._ import scala.concurrent.duration.FiniteDuration import scala.reflect.runtime.{universe => ru} import scala.collection.{immutable, mutable} @@ -117,8 +119,8 @@ class GremlinScala[End](val traversal: GraphTraversal[_, End]) { otherProjectKeys: String*): GremlinScala.Aux[JMap[String, A], Labels] = GremlinScala[JMap[String, A], Labels](traversal.project(projectKey, otherProjectKeys: _*)) - def project[H <: HList]( - builder: ProjectionBuilder[HNil] ⇒ ProjectionBuilder[H]): GremlinScala[H] = + def project[H <: Product]( + builder: ProjectionBuilder[Nil.type] ⇒ ProjectionBuilder[H]): GremlinScala[H] = builder(ProjectionBuilder()).build(this) /** You might think that predicate should be `GremlinScala[End] => GremlinScala[Boolean]`, @@ -1033,27 +1035,27 @@ class GremlinScala[End](val traversal: GraphTraversal[_, End]) { } -class ProjectionBuilder[H <: HList] private[gremlin] ( +class ProjectionBuilder[T <: Product] private[gremlin] ( labels: Seq[String], addBy: GraphTraversal[_, JMap[String, Any]] ⇒ GraphTraversal[_, JMap[String, Any]], - buildResult: JMap[String, Any] ⇒ H) { + buildResult: JMap[String, Any] ⇒ T) { - def apply[U, HR <: HList](by: By[U])( - implicit prepend: Prepend.Aux[H, U :: HNil, HR]): ProjectionBuilder[HR] = { + def apply[U, TR <: Product](by: By[U])( + implicit prepend: TuplePrepend.Aux[T, Tuple1[U], TR]): ProjectionBuilder[TR] = { val label = UUID.randomUUID().toString - new ProjectionBuilder[HR](labels :+ label, + new ProjectionBuilder[TR](labels :+ label, addBy.andThen(by.apply), map ⇒ buildResult(map) :+ map.get(label).asInstanceOf[U]) } - def and[U, HR <: HList](by: By[U])( - implicit prepend: Prepend.Aux[H, U :: HNil, HR]): ProjectionBuilder[HR] = apply(by) + def and[U, TR <: Product](by: By[U]) + (implicit prepend: TuplePrepend.Aux[T, Tuple1[U], TR]): ProjectionBuilder[TR] = apply(by) - private[gremlin] def build(g: GremlinScala[_]): GremlinScala[H] = { + private[gremlin] def build(g: GremlinScala[_]): GremlinScala[T] = { GremlinScala(addBy(g.traversal.project(labels.head, labels.tail: _*))).map(buildResult) } } object ProjectionBuilder { - def apply() = new ProjectionBuilder[HNil](Nil, scala.Predef.identity, _ ⇒ HNil) + def apply() = new ProjectionBuilder[Nil.type](Nil, scala.Predef.identity, _ ⇒ Nil) } diff --git a/gremlin-scala/src/test/scala/gremlin/scala/ProjectSpec.scala b/gremlin-scala/src/test/scala/gremlin/scala/ProjectSpec.scala index 8072d414..a47e72f7 100644 --- a/gremlin-scala/src/test/scala/gremlin/scala/ProjectSpec.scala +++ b/gremlin-scala/src/test/scala/gremlin/scala/ProjectSpec.scala @@ -2,7 +2,6 @@ package gremlin.scala import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory import org.scalatest.{Matchers, WordSpec} -import shapeless.HNil class ProjectSpec extends WordSpec with Matchers { def graph: ScalaGraph = TinkerFactory.createModern.asScala() @@ -17,10 +16,10 @@ class ProjectSpec extends WordSpec with Matchers { .toList() result shouldBe List( - "lop" :: 3 :: HNil, - "lop" :: 3 :: HNil, - "lop" :: 3 :: HNil, - "ripple" :: 1 :: HNil + ("lop", 3), + ("lop",3), + ("lop",3), + ("ripple", 1) ) } @@ -32,7 +31,7 @@ class ProjectSpec extends WordSpec with Matchers { .and(By(__.inE().count()))) .head() - result shouldBe (3 :: 0 :: HNil) + result shouldBe (3, 0) } } }