Gremlin-Scala

repository·master·Indexed 19 days ago

https://github.com/mpollmeier/gremlin-scala

A type-safe Scala DSL for Apache Tinkerpop 3 Gremlin traversals. It provides compiler support to catch invalid traversals, type-safe property access using Key[T] objects, and the ability to map vertices and edges to Scala case classes. The library includes features for building custom DSLs on top of Gremlin-Scala, type-safe 'as' and 'select' steps using shapeless HLists, and implicit conversions between Tinkerpop Java elements and Scala wrappers.

Tokens
13.1K
Snippets
56
Records
59
Agent score
66%

What's inside gremlin-scala

  1. Configure predicates with `gremlin.scala.P`

    master

    When using predicates (like within, gte, etc.), always use import gremlin.scala._ to ensure you are using the Gremlin-Scala wrapper P rather than the raw Tinkerpop org.apache.tinkerpop.gremlin.process.traversal.P.

    This is critical when working with collection types to avoid calling the wrong Java overload (e.g., checking if a value is a set instead of checking if a value is within a set).

    import gremlin.scala._
    
    // Correct: uses Gremlin-Scala's P
    g.V.has("name", P.within(Set("a", "b")))
  2. Use type-safe `as` and `select` steps

    master

    Gremlin-Scala uses the Scala type system (and shapeless HLists) to ensure that when you label steps with .as(StepLabel), the subsequent .select step returns the correct types without manual casting.

    To use this, define StepLabel instances for the types you expect to select.

    import gremlin.scala._
    import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory
    
    val g = TinkerFactory.createModern.asScala.traversal
    
    val a = StepLabel[Vertex]()
    val b = StepLabel[Edge]()
    val c = StepLabel[Double]()
    
    val traversal = g.V(1).as(a).outE("created").as(b).value("weight").as(c)
    
    // Returns a (Edge, Double) tuple automatically
    traversal.select((b, c)).head
  3. Perform simple traversals

    master

    Traversals in Gremlin-Scala are lazy computations. To execute them and retrieve results, use terminal steps like toList, toSet, head, or headOption.

    Common traversal patterns include:

    • g.V: All vertices
    • g.E: All edges
    • g.V(id).outE("label"): Follow outgoing edges
    • g.V(id).out("label"): Follow outgoing edges to the incoming vertex

    Note: Gremlin-Scala is not a monad. While it provides map and flatMap for use in for-comprehensions, it does not fulfill all monad laws because the underlying Tinkerpop GraphTraversal is not a monad.

    import gremlin.scala._
    import org.apache.tinkerpop.gremlin.process.traversal.{Order, P}
    import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory
    
    implicit val graph = TinkerFactory.createModern.asScala
    val g = graph.traversal
    
    // Example: follow edges and collect results
    g.V.hasLabel("person").outE("likes").order(By(weight, Order.decr)).limit(1).inV.toList
  4. Get started with Gremlin-Scala

    master

    To use Gremlin-Scala, add the following dependency to your build.sbt (replace SOME_VERSION with the latest version):

    "com.michaelpollmeier" %% "gremlin-scala" % "SOME_VERSION"

    You also need to add a dependency for the specific graph database you intend to use (e.g., TinkerGraph).

    "com.michaelpollmeier" %% "gremlin-scala" % "SOME_VERSION"
  5. Build a custom DSL on top of Gremlin-Scala

    master

    You can hide the complexity of graph traversals from your users by building a Domain Specific Language (DSL).

    1. Define your domain model using case classes that extend DomainRoot.
    2. Define your DSL steps as classes.
    3. Create an implicit constructor to enable for-comprehension syntax.

    This allows users to write traversals that look like standard Scala code while the compiler manages the underlying Gremlin steps.

    case class Person(name: String, age: Integer) extends DomainRoot
    case class Software(name: String, lang: String) extends DomainRoot
    
    // Assuming PersonSteps is defined as a DSL step provider
    val traversal = for {
      person   <- PersonSteps(graph)
      software <- person.created
    } yield (person.name, software)
    
    traversal.toSet
  6. Create vertices and edges with type-safe properties

    master

    Gremlin-Scala provides a beautiful DSL for graph construction and type-safe property access using Key[T] objects.

    Creating Elements

    • Vertices: Use the + operator. You can add a label or a tuple of (label, property_mapping).
    • Edges: Use arrow syntax like vertex1 --- "label" --> vertex2 or vertex1 <-- "label" --- vertex2.
    • Properties: Use the -> operator within tuples to map keys to values.

    Accessing Properties

    Use Key[T] to ensure type safety when retrieving or setting properties. Methods include:

    • value(key): Returns the value.
    • valueOption(key): Returns an Option[T].
    • setProperty(key, value): Sets a property.
    import gremlin.scala._
    import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph
    import scala.language.postfixOps
    
    implicit val graph = TinkerGraph.open.asScala
    
    // Define typed keys
    val Founded = Key[String]("founded")
    val Distance = Key[Int]("distance")
    
    // Construction
    val paris = graph + "Paris"
    val london = graph + ("London", Founded -> "43 AD")
    paris --- "OneWayRoad" --> london
    paris --- ("Eurostar", Distance -> 495) --> london
    
    // Type-safe access
    paris.out("Eurostar").value(Distance).head // 495
    london.valueOption(Founded) // Some("43 AD")
  7. Map vertices to and from case classes

    master

    Gremlin-Scala allows you to treat case classes as vertices using a blackbox macro. This simplifies saving and loading complex domain objects.

    Key Features

    • @label: Specify the vertex label.
    • Option[A]: Automatically unwrapped. Some(val) is stored as val, None is stored as null.
    • List[A]: Stored as multi-properties (Cardinality.list).
    • @id: Instructs the marshaller to set the element ID (must be an Option when retrieving).
    • @underlying: Instructs the marshaller to set the underlying element.

    Usage

    • Use graph + caseClassInstance to add a vertex.
    • Use v.toCC[MyClass] to convert a vertex back to a case class.
    • Use graph.V.hasLabel[MyClass] to find vertices of that type.
    • Use v.updateAs[MyClass](_.copy(...)) to modify a vertex as if it were a case class.
    @label("my_custom_label")
    case class Example(
      longValue: Long, 
      stringValue: Option[String], 
      @underlying vertex: Option[Vertex] = None
    )
    
    // Adding and retrieving
    val example = Example(123L, Some("test"))
    val v = graph + example
    val recovered = v.toCC[Example]
    
    // Finding by class
    graph.V.hasLabel[Example]
  8. Configure the gremlin-scala runtime via environment variables

    master

    The script respects the following environment variables for configuration:

    • JAVA_HOME: Sets the base directory for the Java installation. The script will look for the executable in $JAVA_HOME/bin/java.
    • JAVA_OPTIONS: Sets JVM options (e.g., heap size). If not set, the script defaults to -Xms32m -Xmx512m.
    • CLASSPATH: Additional classpath entries are appended to the library jars provided by the package.
  9. Manage properties on a ScalaVertex

    master

    The ScalaVertex class provides type-safe methods to manipulate vertex properties. You can set properties using specific Key[A] types, which ensures type safety for the values being stored.

    Key operations include:

    • setProperty(key, value): Sets a single property.
    • setProperties(map): Sets multiple properties from a Map[Key[Any], Any].
    • removeProperty(key, [cardinality]): Removes a property. Defaults to Cardinality.single. For Cardinality.list or Cardinality.set, it removes all instances of that key.
    • setPropertyList(key, values): A convenience method to replace existing list properties with a new list of values.
    // Setting a single property with a type-safe key
    vertex.setProperty(Key[String]("name"), "example")
    
    // Setting multiple properties
    vertex.setProperties(Map(Key[Int]("age") -> 30, Key[String]("city") -> "New York"))
    
    // Removing a property
    vertex.removeProperty(Key[String]("name"))
    
    // Replacing a list property
    vertex.setPropertyList(Key[String]("tags"), List("scala", "gremlin"))
  10. Add vertices and edges with type safety

    master

    You can add new elements to the graph directly from the TraversalSource:

    • addV(): Adds a vertex with no label.
    • addV(label: String): Adds a vertex with the specified label.
    • addE(label: String): Adds an edge with the specified label.

    These methods return GremlinScala.Aux[Vertex, HNil] or GremlinScala.Aux[Edge, HNil] respectively, ensuring the traversal state correctly reflects the type of the element just added.

    // Add a vertex with a label
    g.addV("person")
    
    // Add an edge with a label
    g.addE("knows")