29 June 2016
29 June 2016,
 Off

By OrientDB Software Engineer Andrea Iacono

There’s no specific Scala API for OrientDB but we can use the Java API, paying attention to the different calling conventions between Java and Scala.

Let’s start defining our SBT project. To use OrientDB we just need one library dependency of OrientDB, so the build.sbt file will be like this:

name := "OrientDbScalaExample"
version := "1.0"
scalaVersion := "2.11.8"

libraryDependencies ++= Seq(
"com.orientechnologies" % "orientdb-graphdb" % "2.2.3",
)

 

In this example we’ll only use the Graph API so we only need that dependency in our build.sbt, though a list of all the available OrientDB libraries is present in the Supported Libraries documentation page.

Once we have our build.sbt we can start using OrientDB. Let’s start creating a new database, using the Graph API:

val uri: String = "plocal:target/database/scala_sample"
val factory: OrientGraphFactory = new OrientGraphFactory(uri)
val graph: OrientGraph = factory.getTx()

 

If the database is not existing it will be created and opened, while if it already exists, it will simply be opened. Refer to the GraphFactory documentation

Now that we have access to our graph database, we can define some new classes, extending V and E. Let’s start defining a Person class, extending V that has a first and a last name:

val person: OrientVertexType = graph.createVertexType("Person")
person.createProperty("firstName", OType.STRING)
person.createProperty("lastName", OType.STRING)

 

Then we define other two classes, Company and Project:

val company: OrientVertexType = graph.createVertexType("Company")
company.createProperty("name", OType.STRING)
company.createProperty("revenue", OType.LONG)

val project: OrientVertexType = graph.createVertexType("Project")
project.createProperty("name", OType.STRING)

 

We also want to extend the E class, to create a work relationship between a Person and a Company:

val work: OrientEdgeType = graph.createEdgeType("Work")
work.createProperty("startDate", OType.DATE)
work.createProperty("endDate", OType.DATE)
work.createProperty("projects", OType.LINKSET)

 

This class has three properties:

  • A startDate, which tells us when the person started to work for that company.
  • An endDate, which tells us when the person stopped working for that company. If this property is not present, we assume the person is still working for the company.
  • A LinkSet of projects, which are all the project the person contributed to while working for the company. See Types documentation for more details about the LinkSet.

Now that we’ve defined the classes we need, we can add some vertices; let’s start with the Person John Doe:

val johnDoe: Vertex = graph.addVertex("class:Person", Nil: _*)
johnDoe.setProperty("firstName", "John")
johnDoe.setProperty("lastName", "Doe")

 

The second parameter passed to the addVertex() method is due to the difference in calling conventions between Java and Scala; that parameter is needed to let the Scala compiler understand which of the overloaded versions of the method to invoke.

Setting properties can also be done by specifying the property name and value as pairs when calling the addVertex() method itself, as seen in the following examples where we create two other people, a company and two projects:

val johnSmith: Vertex = graph.addVertex("class:Person", "firstName", "John", "lastName", "Smith")
val janeDoe: Vertex = graph.addVertex("class:Person", "firstName", "Jane", "lastName", "Doe")
val acme: Vertex = graph.addVertex("class:Company", "name", "ACME", "revenue", "10000000")
val acmeGlue: Vertex = graph.addVertex("class:Project", "name", "ACME Glue")
val acmeRocket: Vertex = graph.addVertex("class:Project", "name", "ACME Rocket")

 

Now we’ll create a couple of edges to connect our vertices; we can do it using the vertex itself:

val johnSmithAcme: Edge = johnSmith.addEdge("Work", acme) 
johnSmithAcme.setProperty("startDate", "2009-01-01")

 

Or we can use the instance of the OrientGraph and the method addEdge():

val johnDoeAcme: Edge = graph.addEdge(null, johnDoe, acme, "Work")
johnDoeAcme.setProperty("startDate", "2010-01-01")
johnDoeAcme.setProperty("endDate", "2013-04-21")
johnDoeAcme.setProperty("projects", Set(acmeGlue, acmeRocket))

 

In this case, beside the start and end of the work properties, we’ve also set the projects that the person contributed to while working for that company.
For more details about managing vertices and edges, have a look at the Vertices and Edges documentation.

Now we have a graph database like this:

OrientDB graph database-scala

Note that in the image there are no edges among the projects and the people because we defined the relationship “person contributed to a project” not as an edge but as a LinkSet in the “work relationship” edge.

Now that our database contains some data we can use OrientDB’s extended SQL to query that data. Let’s suppose we want to find all the people who work for ACME. The query will be something like this:

SELECT expand(in('Work')) FROM Company WHERE name='ACME'

 

So, we can launch the query with the command() and then the execute() method:

val res: OrientDynaElementIterable = graph
          .command(new OCommandSQL(s"SELECT expand(in('Work')) FROM Company WHERE name='ACME'"))
          .execute()

 

which returns an Iterable that we can use to loop over the results and to print some info:

res.foreach(v => {

            // casts the element in the Iterable to an OrientVertex
            val person = v.asInstanceOf[OrientVertex]

            // gets the "Work" out edge of the Vertex
            val workEdgeIterator = person.getEdges(Direction.OUT, "Work").iterator()
            val edge = workEdgeIterator.next()

            // retrieves info about person analyzing the "Work" edge
            val status = if (edge.getProperty("endDate") != null) "retired" else "active"
            val projects =
                if (edge.getProperty("projects") != null)
                  edge
                    .getProperty("projects")
                    .asInstanceOf[Set[Vertex]]
                    .map(v=>v.getProperty[String]("name"))
                    .mkString(", ")
                else
                    "Any project"

            // and prints them
            println(s"Name: ${person.getProperty("lastName")}, " +
                 s"${person.getProperty("firstName")} " +
                 s"[${status}]. Worked on: ${projects}.")
})

 

In the loop we cast the element of the Iterable to an OrientVertex (which is OrientDB’s implementation of the interface Vertex); we then call the getEdges() method on the vertex (with the Direction.OUT and “Work” params) to retrieve the outgoing edges with label “Work” of that vertex; since we populated our Person vertices with only one edge, we get that edge directly calling the next() method on the Iterator.
We use the getProperty() method to retrieve the value of a specific property; in the case of the “projects” property, which is of type LinkSet, we have to cast it to a Set of Vertices to create a list of the names of the projects using map.

Running this sample will produce this output:

ACME people:
Name: Doe, John [retired]. Worked on: ACME Glue, ACME Rocket.
Name: Smith, John [active]. Worked on: Any project.

 

This is a very simple example on how to start using Scala: if you want to fully exploit the potential of OrientDB, refer to the Official Java API documentation.

Here is the full code:

import com.orientechnologies.orient.core.metadata.schema.OType
import com.orientechnologies.orient.core.sql.OCommandSQL
import com.tinkerpop.blueprints.{Direction, Edge, Vertex}
import com.tinkerpop.blueprints.impls.orient._

import scala.collection.JavaConversions._

object OrientDbSample extends App {

   val WorkEdgeLabel = "Work"

   // opens the DB (if not existing, it will create it)
   val uri: String = "plocal:target/database/scala_sample"
   val factory: OrientGraphFactory = new OrientGraphFactory(uri)
   val graph: OrientGraph = factory.getTx()

   try {

       // if the database does not contain the classes we need (it was just created),
       // then adds them
       if (graph.getVertexType("Person") == null) {

           // we now extend the Vertex class for Person and Company
           val person: OrientVertexType = graph.createVertexType("Person")
           person.createProperty("firstName", OType.STRING)
           person.createProperty("lastName", OType.STRING)

           val company: OrientVertexType = graph.createVertexType("Company")
           company.createProperty("name", OType.STRING)
           company.createProperty("revenue", OType.LONG)

           val project: OrientVertexType = graph.createVertexType("Project")
           project.createProperty("name", OType.STRING)

           // we now extend the Edge class for a "Work" relationship
           // between Person and Company
           val work: OrientEdgeType = graph.createEdgeType(WorkEdgeLabel)
           work.createProperty("startDate", OType.DATE)
           work.createProperty("endDate", OType.DATE)
           work.createProperty("projects", OType.LINKSET)
       }
       else {

           // cleans up the DB since it was already created in a preceding run
           graph.command(new OCommandSQL("DELETE VERTEX V")).execute()
           graph.command(new OCommandSQL("DELETE EDGE E")).execute()
       }

       // adds some people
       // (we have to force a vararg call in addVertex() method to avoid ambiguous
       // reference compile error, which is pretty ugly)
       val johnDoe: Vertex = graph.addVertex("class:Person", Nil: _*)
       johnDoe.setProperty("firstName", "John")
       johnDoe.setProperty("lastName", "Doe")

       // we can also set properties directly in the constructor call
       val johnSmith: Vertex = graph.addVertex("class:Person", "firstName", "John", "lastName", "Smith")
       val janeDoe:Vertex=graph.addVertex("class:Person","firstName","Jane","lastName","Doe")

       // creates a Company
       val acme : Vertex = graph.addVertex("class:Company","name","ACME","revenue","10000000")

       // creates a couple of projects
       val acmeGlue: Vertex = graph.addVertex("class:Project", "name", "ACME Glue")
       val acmeRocket: Vertex = graph.addVertex("class:Project", "name", "ACME Rocket")

       // creates edge JohnDoe worked for ACME
       val johnDoeAcme: Edge = graph.addEdge(null, johnDoe, acme, WorkEdgeLabel)
       johnDoeAcme.setProperty("startDate", "2010-01-01")
       johnDoeAcme.setProperty("endDate", "2013-04-21")
       johnDoeAcme.setProperty("projects", Set(acmeGlue, acmeRocket))

       // another way to create an edge, starting from the source vertex
       val johnSmithAcme: Edge = johnSmith.addEdge(WorkEdgeLabel, acme)
       johnSmithAcme.setProperty("startDate", "2009-01-01")

       // prints all the people who works/worked for ACME
       val res: OrientDynaElementIterable = graph
         .command(new OCommandSQL(s"SELECT expand(in('${WorkEdgeLabel}')) FROM Company WHERE name='ACME'"))
         .execute()

       println("ACME people:")
       res.foreach(v => {

           // gets the person
           val person : OrientVertex = v.asInstanceOf[OrientVertex]

           // gets the "Work" edge
           val workEdgeIterator = person.getEdges(Direction.OUT, WorkEdgeLabel).iterator()
           val edge = workEdgeIterator.next()

           // retrieves worker's info
           val status = if (edge.getProperty("endDate") != null) "retired" else "active"
           val projects =
             if (edge.getProperty("projects") != null)
               edge
                 .getProperty("projects")
                 .asInstanceOf[Set[Vertex]]
                 .map(v=>v.getProperty[String]("name"))
                 .mkString(", ")
             else
               "Any project"

           // and prints them
           println(s"Name: ${person.getProperty("lastName")}, " +
                         s"${person.getProperty("firstName")} " +
                         s"[${status}]. Worked on: ${projects}.")
       })
   }
   finally {
       graph.shutdown()
   }
}

Comments are closed.