Graph API

OrientDB adheres to the TinkerPop Blueprints standard and uses it as default Graph Java API.

Requirements

To use the Graph API include the following jars in your classpath:

  1. orientdb-core-*.jar
  2. blueprints-core-*.jar
  3. orientdb-graphdb-*.jar

Also include the following 3rd party jars:

  1. jna-*.jar
  2. jna-platform-*.jar
  3. concurrentlinkedhashmap-lru-*.jar

If you’re connected to a remote server (not local/plocal/memory modes) include also:

  1. orientdb-client-*.jar
  2. orientdb-enterprise-*.jar

To also use the TinkerPop Pipes tool include also:

  1. pipes-*.jar

To also use the TinkerPop Gremlin language include also:

  1. gremlin-java-*.jar
  2. gremlin-groovy-*.jar
  3. groovy-*.jar

NOTE: Starting from v2.0, Lightweight Edges are disabled by default when new database are created.

Introduction

Tinkerpop is a complete stack of projects to handle Graphs:

  • Blueprints provides a collection of interfaces and implementations to common, complex data structures. In short, Blueprints provides a one stop shop for implemented interfaces to help developers create software without being tied to particular underlying data management systems.
  • Pipes is a graph-based data flow framework for Java 1.6+. A process graph is composed of a set of process vertices connected to one another by a set of communication edges. Pipes supports the splitting, merging, and transformation of data from input to output.
  • Gremlin is a Turing-complete, graph-based programming language designed for key/value-pair multi-relational graphs. Gremlin makes use of an XPath-like syntax to support complex graph traversals. This language has application in the areas of graph query, analysis, and manipulation.
  • Rexster is a RESTful graph shell that exposes any Blueprints graph as a standalone server. Extensions support standard traversal goals such as search, score, rank, and, in concert, recommendation. Rexster makes extensive use of Blueprints, Pipes, and Gremlin. In this way its possible to run Rexster over various graph systems. To configure Rexster to work with OrientDB follow this guide: configuration.
  • Sail Ouplementation to use OrientDB as a RDF Triple Store.

Get started with Blueprints

OrientDB supports different kind of storages and depends by the Database URL used:

  • Persistent embedded GraphDB. OrientDB is linked to the application as JAR (No network transfer). Use plocal as prefix. Example “plocal:/tmp/graph/test”
  • In-Memory embedded GraphDB. Keeps all the data only in memory. Use memory as prefix. Example “memory:test”
  • Persistent remote GraphDB. Uses a binary protocol to send and receive data from a remote OrientDB server. Use remote as prefix. Example “remote:localhost/test”. It requires a OrientDB Server instance is up and running at the specified address (localhost in this case). Remote database can be persistent or in-memory as well.

Working with the GraphDB

Before working with a graph you need an instance of OrientGraph class. The constructor gets a URL that is the location of the database. If the database already exists, it will be opened, otherwise it will be created. However a new database can only be created in plocal or memory mode, not in remote mode. In multi-threaded applications use one OrientGraph instance per thread. Also all the graph components (Vertices and Edges) are not thread-safe, so sharing them between threads could cause unpredictable errors.

Remember to always close the graph once done using the .shutdown() method.

Example:

  1. OrientGraph graph = new OrientGraph("plocal:C:/temp/graph/db");
  2. try {
  3. ...
  4. } finally {
  5. graph.shutdown();
  6. }

Use the factory

Starting from v1.7 the best way to get a Graph instance is through the OrientGraphFactory. To know more: Use the Graph Factory. Example:

  1. // AT THE BEGINNING
  2. OrientGraphFactory factory = new OrientGraphFactory("plocal:C:/temp/graph/db").setupPool(1,10);
  3. // EVERY TIME YOU NEED A GRAPH INSTANCE
  4. OrientGraph graph = factory.getTx();
  5. try {
  6. ...
  7. } finally {
  8. graph.shutdown();
  9. }

Transactions

Before v2.1.7, every time the graph is modified an implicit transaction is started automatically if no previous transaction was running. Transactions are committed automatically when the graph is closed by calling the shutdown() method or by explicit commit(). To rollback changes call the rollback() method.

After v2.1.7, you can setup the consistency level.

Changes inside a transaction will be temporary until the commit or the close of the graph instance. Concurrent threads or external clients can see the changes only when the transaction has been fully committed.

Full example:

  1. try{
  2. Vertex luca = graph.addVertex(null); // 1st OPERATION: IMPLICITLY BEGIN A TRANSACTION
  3. luca.setProperty( "name", "Luca" );
  4. Vertex marko = graph.addVertex(null);
  5. marko.setProperty( "name", "Marko" );
  6. Edge lucaKnowsMarko = graph.addEdge(null, luca, marko, "knows");
  7. graph.commit();
  8. } catch( Exception e ) {
  9. graph.rollback();
  10. }

Surrounding the transaction between a try/catch assures that any errors will rollback the transaction to the previous status for all the involved elements. For more information, look at Concurrency.

NOTE: Before v2.1.7, to work against a graph always use transactional OrientGraph instances and never non-transactional ones to avoid graph corruption from multi-threaded changes. A non-transactional graph instance created with OrientGraphNoTx graph = factory.getNoTx(); is only useful if you don’t work with data but want to define the database schema or for bulk inserts.

Optimistic approach

OrientDB supports optimistic transactions, so no lock is kept when a transaction is running, but at commit time each graph element version is checked to see if there has been an update by another client. This is the reason why you should write your code to be concurrency-proof by handling the concurrent updating case:

  1. for (int retry = 0; retry < maxRetries; ++retry) {
  2. try {
  3. // LOOKUP FOR THE INVOICE VERTEX
  4. Iterable<Vertex> invoices = graph.getVertices("invoiceId", 2323);
  5. Vertex invoice = invoices.iterator().next();
  6. // CREATE A NEW ITEM
  7. Vertex invoiceItem = graph.addVertex("class:InvoiceItem");
  8. invoiceItem.field("price", 1000);
  9. // ADD IT TO THE INVOICE
  10. invoice.addEdge(invoiceItem);
  11. graph.commit();
  12. // OK, EXIT FROM RETRY LOOP
  13. break;
  14. } catch( ONeedRetryException e ) {
  15. // SOMEONE HAVE UPDATE THE INVOICE VERTEX AT THE SAME TIME, RETRY IT
  16. }
  17. }

Working with Vertices and Edges

Create a vertex

To create a new Vertex in the current Graph call the Vertex OrientGraph.addVertex(Object id)) method. Note that the id parameter is ignored since OrientDB implementation assigns a unique-id once the vertex is created. To return it use Vertex.getId()). Example:

  1. Vertex v = graph.addVertex(null);
  2. System.out.println("Created vertex: " + v.getId());

Create an edge

An Edge links two vertices previously created. To create a new Edge in the current Graph call the Edge OrientGraph.addEdge(Object id, Vertex outVertex, Vertex inVertex, String label )) method. Note that the id parameter is ignored since OrientDB implementation assigns a unique-id once the Edge is created. To return it use Edge.getId()). outVertex is the Vertex instance where the Edge starts and inVertex is the Vertex instance where the Edge ends. label is the Edge’s label. Specify null to not assign it. Example:

  1. Vertex luca = graph.addVertex(null);
  2. luca.setProperty("name", "Luca");
  3. Vertex marko = graph.addVertex(null);
  4. marko.setProperty("name", "Marko");
  5. Edge lucaKnowsMarko = graph.addEdge(null, luca, marko, "knows");
  6. System.out.println("Created edge: " + lucaKnowsMarko.getId());

If you’re interested on optimizing creation of edges by concurrent threads/clients, look at Concurrency on adding edges.

Retrieve all the Vertices

To retrieve all the vertices use the getVertices() method:

  1. for (Vertex v : graph.getVertices()) {
  2. System.out.println(v.getProperty("name"));
  3. }

Retrieve all the Edges

To retrieve all the vertices use the getEdges()) method:

  1. for (Edge e : graph.getEdges()) {
  2. System.out.println(e.getProperty("age"));
  3. }

NOTE: When Lightweight Edges are enabled (starting from v2.0 are disabled by default), edges are stored as links not as records. This is to improve performance. As a consequence, getEdges() will only retrieve records of class E. With useLightweightEdges=true, records of class E are only created under certain circumstances (e.g. if the Edge has properties) otherwise they will be links on the in and out vertices. If you really want getEdges() to return all edges, disable the Lightweight Edges feature by executing this command once: alter database custom useLightweightEdges=false. This will only take effect for new edges so you’ll have to convert the links to actual edges before getEdges will return all edges. For more information look at: Troubleshooting: Why can’t I see all the edges.

Removing a Vertex

To remove a vertex from the current Graph call the OrientGraph.removeVertex(Vertex vertex)) method. The vertex will be disconnected from the graph and then removed. Disconnection means that all the vertex’s edges will be deleted as well. Example:

  1. graph.removeVertex(luca);

Removing an Edge

To remove an edge from the current Graph call the OrientGraph.removeEdge(Edge edge)) method. The edge will be removed and the two vertices will not be connected anymore. Example:

  1. graph.removeEdge(lucaKnowsMarko);

Set and get properties

Vertices and Edges can have multiple properties where the key is a String and the value can be any supported OrientDB types.

Example:

  1. vertex2.setProperty("x", 30.0f);
  2. vertex2.setProperty("y", ((float) vertex1.getProperty( "y" )) / 2);
  3. for (String property : vertex2.getPropertyKeys()) {
  4. System.out.println("Property: " + property + "=" + vertex2.getProperty(property));
  5. }
  6. vertex1.removeProperty("y");

Setting Multiple Properties

Blueprints Extension OrientDB Blueprints implementation supports setting of multiple properties in one shot against Vertices and Edges. This improves performance avoiding to save the graph element at every property set: setProperties(Object …)). Example:

  1. vertex.setProperties( "name", "Jill", "age", 33, "city", "Rome", "born", "Victoria, TX" );

You can also pass a Map of values as first argument. In this case all the map entries will be set as element properties:

  1. Map<String,Object> props = new HashMap<String,Object>();
  2. props.put("name", "Jill");
  3. props.put("age", 33);
  4. props.put("city", "Rome");
  5. props.put("born", "Victoria, TX");
  6. vertex.setProperties(props);

Creating Element and Properties all together

If you want to create a vertex or an edge while setting the initial properties, the OrientDB Blueprints implementation offers new methods to do it:

  1. graph.addVertex("class:Customer", "name", "Jill", "age", 33, "city", "Rome", "born", "Victoria, TX");

This creates a new Vertex of class Customer with the properties: name, age, city, and born. The same is for Edges:

  1. person1.addEdge("class:Friend", person2, null, null, "since", "2013-07-30");

This creates a new Edge of class Friend between vertices person1 and person2 with the property since.

Both methods accept a Map<String, Object> as a parameter to set one property per map entry (see above for the example).

These methods are especially useful if you’ve declared constraints in the schema. For example, a property cannot be null, and only using these methods will the validation checks succeed.

Using Indices

OrientDB allows execution of queries against any field of vertices and edges, indexed and not-indexed. The first rule to speed up queries is to setup indices on the key properties you use in the query. For example, if you have a query that is looking for all the vertices with the name ‘OrientDB’ you do this:

  1. graph.getVertices("name", "OrientDB");

Without an index against the property “name” this execution could take a lot of time. So let’s create a new index against the “name” property:

  1. graph.createKeyIndex("name", Vertex.class);

If the name MUST be unique you can enforce this constraint by setting the index as “UNIQUE” (this is an OrientDB only feature):

  1. graph.createKeyIndex("name", Vertex.class, new Parameter("type", "UNIQUE"));

This constraint will be applied to all the Vertex and sub-type instances. To specify an index against a custom type like the “Customer” vertices use the additional parameter “class”:

  1. graph.createKeyIndex("name", Vertex.class, new Parameter("class", "Customer"));

You can also have both UNIQUE index against custom types:

  1. graph.createKeyIndex("name", Vertex.class, new Parameter("type", "UNIQUE"), new Parameter("class", "Customer"));

To create a case insensitive index use the additional parameter “collate”:

  1. graph.createKeyIndex("name", Vertex.class, new Parameter("type", "UNIQUE"), new Parameter("class", "Customer"),new Parameter("collate", "ci"));

To get a vertex or an edge by key prefix use the class name before the property. For the example above use Customer.name in place of only name to use the index created against the field name of class Customer:

  1. for (Vertex v : graph.getVertices("Customer.name", "Jay")) {
  2. System.out.println("Found vertex: " + v);
  3. }

If the class name is not passed, then “V” is taken for vertices and “E” for edges:

  1. graph.getVertices("name", "Jay");
  2. graph.getEdges("age", 20);

For more information about indices look at Index guide.

Using Non-Transactional Graphs

To speed up operations like on massive insertions you can avoid transactions by using a different class than OrientGraph: OrientGraphNoTx. In this case each operation is atomic and data is updated at each operation. When the method returns, the underlying storage is updated. Use this for bulk inserts and massive operations or for schema definition.

NOTE: Using non-transactional graphs could create corruption in the graph if changes are made in multiple threads at the same time. So use non-transactional graph instances only for non multi-threaded operations.

Configure the Graph

Starting from v1.6 OrientDB supports configuration of the graph by setting all the properties during construction:

Name Description Default value
blueprints.orientdb.url Database URL -
blueprints.orientdb.username User name admin
blueprints.orientdb.password User password admin
blueprints.orientdb.saveOriginalIds Saves the original element IDs by using the property id. This could be useful on import of a graph to preserve original ids. false
blueprints.orientdb.keepInMemoryReferences Avoids keeping records in memory by using only RIDs false
blueprints.orientdb.useCustomClassesForEdges Uses the Edge’s label as OrientDB class. If it doesn’t exist create it under the hood. true
blueprints.orientdb.useCustomClassesForVertex Uses Vertex’s label as OrientDB class. If it doesn’t exist create it under the hood. true
blueprints.orientdb.useVertexFieldsForEdgeLabels Stores the Edge’s relationships in the Vertex by using the Edge’s class. This allows using multiple fields and makes faster traversal by edge’s label (class). true
blueprints.orientdb.lightweightEdges Uses Lightweight Edges. This avoids creating a physical document per edge. Documents are created only when the Edges have properties. false
blueprints.orientdb.autoStartTx Auto starts a transaction as soon as the graph is changed by adding/remote vertices and edges and properties. true

Gremlin usage

If you use GREMLIN language with OrientDB remember to initialize it with:

  1. OGremlinHelper.global().create()

Look at these pages about GREMLIN usage:

Multi-Threaded Applications

Multi-threaded applications must use one OrientGraph instance per thread. For more information about multi-threading look at Java Multi Threading. Also all the graph components (Vertices and Edges) are not thread-safe, so sharing them between threads could cause unpredictable errors.

Blueprints Extensions

OrientDB is a Graph Database on steroids because it merges the graph, document, and object-oriented worlds together. Below are some of the features exclusive to OrientDB.

Custom types

OrientDB supports custom types for vertices and edges in an Object Oriented manner. Even if this isn’t supported directly by Blueprints there are some tricks to use them. Look at the Graph Schema page to know how to create a schema and work against types.

OrientDB added a few variants to the Blueprints methods to work with types.

Creating vertices and edges in specific clusters

By default each class has one cluster with the same name. You can add multiple clusters to the class to allow OrientDB to write vertices and edges on multiple files. Furthermore working in Distributed Mode each cluster can be configured to be managed by a different server.

Example:

  1. // SAVE THE VERTEX INTO THE CLUSTER 'PERSON_USA' ASSIGNED TO THE NODE 'USA'
  2. graph.addVertex("class:Person,cluster:Person_usa");

Retrieve vertices and edges by type

To retrieve all the vertices of Person class use the special getVerticesOfClass(String className) method:

  1. for (Vertex v : graph.getVerticesOfClass("Person")) {
  2. System.out.println(v.getProperty("name"));
  3. }

All the vertices of class Person and all subclasses will be retrieved. This is because by default polymorphism is used. If you’re interested ONLY into Person vertices (excluding any sub-types) use the getVerticesOfClass(String className, boolean polymorphic) method specifying false in the second argument polymorphic:

  1. for (Vertex v : graph.getVerticesOfClass("Person", false)) {
  2. System.out.println(v.getProperty("name"));
  3. }

The same variants also apply to the getEdges() method as:

  • getEdgesOfClass(String className) and
  • getEdgesOfClass(String className, boolean polymorphic)

Ordered Edges

OrientDB, by default, uses a set to handle the edge collection. Sometimes it’s better having an ordered list to access the edge by an offset. Example:

  1. person.createEdgeProperty(Direction.OUT, "Photos").setOrdered(true);

Every time you access the edge collection the edges are ordered. Below is an example to print all the photos in an ordered way.

  1. for (Edge e : loadedPerson.getEdges(Direction.OUT, "Photos")) {
  2. System.out.println( "Photo name: " + e.getVertex(Direction.IN).getProperty("name") );
  3. }

To access the underlying edge list you have to use the Document Database API. Here’s an example to swap the 10th photo with the last.

  1. // REPLACE EDGE Photos
  2. List<ODocument> photos = loadedPerson.getRecord().field("out_Photos");
  3. photos.add(photos.remove(9));

To have the same result by using SQL, execute the following commands:

  1. create property out_Photos LINKLIST
  2. alter property User.out_Photos custom ordered=true

Working on detached elements

When you work with web applications, it’s very common to query elements and render them to the user to let him apply some changes. Once the user updates some fields and presses the “save” button, what happens?

Before now the developer had to track the changes in a separate structure, load the vertex/edge from the database, and apply the changes to the element.

Starting with OrientDB v1.7 we added two new methods to the Graph API on the OrientElement and OrientBaseGraph classes:

  • OrientElement.detach()
  • OrientElement.attach()
  • OrientBaseGraph.detach(OrientElement)
  • OrientBaseGraph.attach(OrientElement)

Detach

Detach methods fetch all the record content in RAM and reset the connection to the Graph instance. This allows you to modify the element off-line and to re-attach it once finished.

Attach

Once the detached element has been modified, to save it back to the database you need to call the attach() method. It restores the connection between the Graph Element and the Graph Instance.

Example

The first step is load a vertex and detach it.

  1. OrientGraph g = OrientGraph("plocal:/temp/db");
  2. try {
  3. Iterable<OrientVertex> results = g.query().has("name", EQUALS, "fast");
  4. for (OrientVertex v : results)
  5. v.detach();
  6. } finally {
  7. g.shutdown();
  8. }

After a while the element is updated (from GUI or by application)

  1. v.setProperty("name", "super fast!");

On “save” re-attach the element and save it to the database.

  1. OrientGraph g = OrientGraph("plocal:/temp/db");
  2. try {
  3. v.attach(g);
  4. v.save();
  5. } finally {
  6. g.shutdown();
  7. }

FAQ

Does detach go recursively to detach all connected elements? No, it works only at the current element level.

Can I add an edge against detached elements? No, you can only get/set/remove a property while is detached. Any other operation that requires the database will throw an IllegalStateException.

Execute commands

The OrientDB Blueprints implementation allows you to execute commands using SQL, Javascript, and all the other supported languages.

SQL queries

  1. for (Vertex v : (Iterable<Vertex>) graph.command(
  2. new OCommandSQL("SELECT EXPAND( out('bought') ) FROM Customer WHERE name = 'Jay'")).execute()) {
  3. System.out.println("- Bought: " + v);
  4. }

It is possible to have parameters in a query using prepared queries.

To execute an asynchronous query:

  1. graph.command(
  2. new OSQLAsynchQuery<Vertex>("SELECT FROM Member",
  3. new OCommandResultListener() {
  4. int resultCount =0;
  5. @Override
  6. public boolean result(Object iRecord) {
  7. resultCount++;
  8. Vertex doc = graph.getVertex( iRecord );
  9. return resultCount < 100;
  10. }
  11. } ).execute();

SQL commands

Along with queries, you can execute any SQL command like CREATE VERTEX, UPDATE, or DELETE VERTEX. In the example below it sets a new property called “local” to true on all the Customers that live in Rome:

  1. int modified = graph.command(
  2. new OCommandSQL("UPDATE Customer SET local = true WHERE 'Rome' IN out('lives').name")).execute());

If the command modifies the schema (like create/alter/drop class and create/alter/drop property commands), remember to force updating of the schema of the database instance you’re using by calling reload():

  1. graph.getRawGraph().getMetadata().getSchema().reload();

For more information look at the available SQL commands.

SQL batch

To execute multiple SQL commands in a batch, use the OCommandScript and SQL as the language. This is recommended when creating edges on the server side, to minimize the network roundtrip:

  1. String cmd = "BEGIN\n";
  2. cmd += "LET a = CREATE VERTEX SET script = true\n";
  3. cmd += "LET b = SELECT FROM V LIMIT 1\n";
  4. cmd += "LET e = CREATE EDGE FROM $a TO $b RETRY 100\n";
  5. cmd += "COMMIT\n";
  6. cmd += "return $e";
  7. OIdentifiable edge = graph.command(new OCommandScript("sql", cmd)).execute();

For more information look at SQL Batch.

Database functions

To execute a database function it must be written in Javascript or any other supported languages. In the example below we imagine having written the function updateAllTheCustomersInCity(cityName) that executes the same update like above. Note the ‘Rome’ attribute passed in the execute() method:

  1. graph.command(
  2. new OCommandFunction("updateAllTheCustomersInCity")).execute("Rome"));

Code

To execute code on the server side you can select between the supported language (by default Javascript):

  1. graph.command(
  2. new OCommandScript("javascript", "for(var i=0;i<10;++i){ print('\nHello World!'); }")).execute());

This prints the line “Hello World!” ten times in the server console or in the local console if the database has been opened in “plocal” mode.

Access to the underlying Graph

Since the TinkerPop Blueprints API is quite raw and doesn’t provide ad-hoc methods for very common use cases, you might need to access the underlying ODatabaseGraphTx object to better use the graph-engine under the hood. Commons operations are:

  • Count incoming and outgoing edges without browsing them all
  • Get incoming and outgoing vertices without browsing the edges
  • Execute a query using SQL-like language integrated in the engine

The OrientGraph class provides the method .getRawGraph() to return the underlying database: [Document Database].

Example:

  1. final OrientGraph graph = new OrientGraph("plocal:C:/temp/graph/db");
  2. try {
  3. List<ODocument> result = graph.getRawGraph().query(
  4. new OSQLSynchQuery("SELECT FROM V WHERE color = 'red'"));
  5. } finally {
  6. graph.shutdown();
  7. }

Security

If you want to use OrientDB security, use the constructor that retrieves the URL, user and password. To know more about OrientDB security visit Security. By default the “admin” user is used.

Tuning

Look at the Performance Tuning Blueprints page.