Skip to content

Commit 301dbc6

Browse files
authored
Merge pull request #273 from mpollmeier/project-by
type safe `project` step by To-om
2 parents e9f20df + 53ac07a commit 301dbc6

File tree

3 files changed

+72
-0
lines changed

3 files changed

+72
-0
lines changed

gremlin-scala/src/main/scala/gremlin/scala/GremlinScala.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import org.apache.tinkerpop.gremlin.structure.{Direction, T}
3333
import shapeless.{::, HList, HNil}
3434
import shapeless.ops.hlist.{IsHCons, Mapper, Prepend, RightFolder, ToTraversable, Tupler}
3535
import shapeless.ops.product.ToHList
36+
import shapeless.syntax.std.tuple._
3637
import scala.concurrent.duration.FiniteDuration
3738
import scala.reflect.runtime.{universe => ru}
3839
import scala.collection.{immutable, mutable}
@@ -116,6 +117,10 @@ class GremlinScala[End](val traversal: GraphTraversal[_, End]) {
116117
otherProjectKeys: String*): GremlinScala.Aux[JMap[String, A], Labels] =
117118
GremlinScala[JMap[String, A], Labels](traversal.project(projectKey, otherProjectKeys: _*))
118119

120+
def project[H <: Product](
121+
builder: ProjectionBuilder[Nil.type] => ProjectionBuilder[H]): GremlinScala[H] =
122+
builder(ProjectionBuilder()).build(this)
123+
119124
/** You might think that predicate should be `GremlinScala[End] => GremlinScala[Boolean]`,
120125
* but that's not how tp3 works: e.g. `.value(Age).is(30)` returns `30`, not `true`
121126
*/
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package gremlin.scala
2+
3+
import java.util.{Map => JMap, UUID}
4+
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal
5+
import shapeless.ops.tuple.{Prepend => TuplePrepend}
6+
import shapeless.syntax.std.tuple._
7+
8+
class ProjectionBuilder[T <: Product] private[gremlin] (
9+
labels: Seq[String],
10+
addBy: GraphTraversal[_, JMap[String, Any]] => GraphTraversal[_, JMap[String, Any]],
11+
buildResult: JMap[String, Any] => T) {
12+
13+
def apply[U, TR <: Product](by: By[U])(
14+
implicit prepend: TuplePrepend.Aux[T, Tuple1[U], TR]): ProjectionBuilder[TR] = {
15+
val label = UUID.randomUUID().toString
16+
new ProjectionBuilder[TR](labels :+ label,
17+
addBy.andThen(by.apply),
18+
map => buildResult(map) :+ map.get(label).asInstanceOf[U])
19+
}
20+
21+
def and[U, TR <: Product](by: By[U])(
22+
implicit prepend: TuplePrepend.Aux[T, Tuple1[U], TR]): ProjectionBuilder[TR] = apply(by)
23+
24+
private[gremlin] def build(g: GremlinScala[_]): GremlinScala[T] = {
25+
GremlinScala(addBy(g.traversal.project(labels.head, labels.tail: _*))).map(buildResult)
26+
}
27+
}
28+
29+
object ProjectionBuilder {
30+
def apply() = new ProjectionBuilder[Nil.type](Nil, scala.Predef.identity, _ => Nil)
31+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package gremlin.scala
2+
3+
import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory
4+
import org.scalatest.{Matchers, WordSpec}
5+
6+
class ProjectSpec extends WordSpec with Matchers {
7+
8+
"projecting by two traversals" in {
9+
val result: (java.lang.Long, java.lang.Long) =
10+
graph.V
11+
.has(name.of("marko"))
12+
.project(_(By(__.outE.count)).and(By(__.inE.count)))
13+
.head
14+
15+
result shouldBe (3, 0)
16+
}
17+
18+
"projecting by property and traversal" in {
19+
val result: List[(String, java.lang.Long)] =
20+
graph.V
21+
.out("created")
22+
.project(_(By(name)).and(By(__.in("created").count)))
23+
.toList
24+
25+
result shouldBe List(
26+
("lop", 3),
27+
("lop", 3),
28+
("lop", 3),
29+
("ripple", 1)
30+
)
31+
}
32+
33+
def graph: ScalaGraph = TinkerFactory.createModern.asScala
34+
val name = Key[String]("name")
35+
36+
}

0 commit comments

Comments
 (0)