-
Notifications
You must be signed in to change notification settings - Fork 23
Enhanced API
The standard Neo4J API provides methods to manipulate the underlying graph datastore. When building applications on top of Neo4J, a little more abstraction and some added features can be useful, hence an Enhanced API.
A Neo4J graph consists of the following element types:
- Node
- Relationship
- RelationshipType
- Property name
- Property value
These five types of elements don't share a common interface, except for Node and Relationship, which both extend the PropertyContainer interface.
The Enhanced API unifies all Neo4j elements under the common interface Vertex.
The basic building block in the Enhanced API is the Vertex. Properties can be created on a Vertex and Vertices can connect to Edges.
Edges are a generalization of Relationships in the standard Neo4j API and make associations between Vertices possible.
Relationships in the standard Neo4j API, can have only two sides (start and end), and only one Node can be attached to each side of a Relationship.
Edges generalize Relationships, by having Connectors. An Edge can have an unlimited number of Connectors and each Connector can have an unlimited number of attached Vertices.
Every Edge has a type, which defines the number and the type of the Connectors an Edge has. The type of the Connector is primarily used to set the ConnectionMode of a Connector.
A Connector(Type) can have one of four ConnectionModes:
These modes have the following meaning:
- Unrestricted: A Vertex can connect to an unlimited number of Connectors of the same type, and the Connector can be connect to by an unlimited number of Vertices.
- Surjective: A Vertex can connect to an unlimited number of Connectors of the same type, but the Connector can only be connected to by one Vertex.
- Injective: A Vertex can only connect to one Connector of the same type, but the Connector can be connected to by an unlimited number of Vertices.
- Bijective: A Vertex can only connect to one Connector of the same type, and the Connector can only be connected to by one Vertex.
Let's first look at a familiar type of Edge, the BinaryEdge, which mimics the behaviour of a Relationship in the standard Neo4j API. It has two connectors:
- StartConnector
- EndConnector
Both StartConnector and EndConnector have a surjective ConnectionMode, meaning that just like Relationship in the standard Neo4j API connects two Nodes, a BinaryEdge connects two Verices. Just like Nodes can have many other Relationships with that same RelationshipType, Vertices can have many BinaryEdges with the same EdgeType.
Let's look at an example, where we state that John is a neighbour of Paula.
There are two regular vertices (presented in black), denoting the persons: John and Paula. There is one Edge (presented in red), the connections are presented in blue.
Note that an BinaryEdge is a Vertex with two additional Connectors.
A Vertex has several methods to manipulate BinaryEdges. All of them very similar to the Relationship related methods on Node in the standard API:
public Vertex addEdge(Vertex vertex, RelationshipType relationshipType);
public BinaryEdge createBinaryEdgeTo(Vertex vertex, RelationshipType type);
public Iterable<BinaryEdge> getBinaryEdges();
public Iterable<BinaryEdge> getBinaryEdges(Direction dir);
public Iterable<BinaryEdge> getBinaryEdges(Direction direction, RelationshipType... types);
public Iterable<BinaryEdge> getBinaryEdges(RelationshipType... types);
public Iterable<BinaryEdge> getBinaryEdges(RelationshipType type, Direction dir);
public boolean hasBinaryEdge();
public boolean hasBinaryEdge(Direction dir);
public boolean hasBinaryEdge(Direction dir, RelationshipType... relTypes);
public boolean hasBinaryEdge(RelationshipType... relTypes);
public boolean hasBinaryEdge(RelationshipType relTypes, Direction dir);
The BinaryEdge interface defines the following methods:
public Vertex getEndVertex();
public Vertex getOtherVertex(Vertex element);
public Vertex getStartVertex();
Properties are also a special case of a general Edge, just like BinaryEdges.
A Property is a unary edge: an Edge with only one connector. That connector is bijective. After all a Vertex can have only one Property of a certain PropertyType and a Property can belong to only one Vertex (this is analogous to how properties work in the standard Neo4j API).
Let's say we want to state the fact that John is 49 years old:
There is one Vertex denoting John, and there is one Property (presented in red). The connection is presented in blue.
Note that Property is an Vertex, with one additional Connector.
Each Property has a PropertyType, which has a name, defines the Connector of the Property and parameterized is with the data type of the Property.
PropertyTypes are a closed set of classes, one for each of the data types supported by the Neo4j database:
- BooleanPropertyType
- BooleanArrayPropertyType
- BytePropertyType
- ByteArrayPropertyType
- DoublePropertyType
- DoubleArrayPropertyType
- FloatPropertyType
- FloatArrayPropertyType
- IntegerPropertyType
- IntegerArrayPropertyType
- LongPropertyType
- LongArrayPropertyType
- ShortPropertyType
- ShortArrayPropertyType
- StringPropertyType
- StringArrayPropertyType
No new PropertyType can be instantiated, nor can PropertyType be subclassed. All datatype specializations of PropertyType (BooleanPropertyType etc.) can be instantiated and can also be subclassed.
All non-array versions of PropertyType extend the ComparablePropertyType interface, which defines the following methods:
public int compare(Node node1, Node node2)
public int compare(T value, Node node)
This allows for the comparison of property values of two Vertices with the same PropertyType, and the comparison of the property value of a Vertex to a value having the same data type.
These methods are an integral part of IndexedRelationships, which is transparently supported by the Enhanced API under the name SortedBinaryRelationships.
Note
Properties in Neo4j can have different data types on different Nodes. It is legal in standard Neo4j to have one Node with a property "id", being a Long, while another Node in the database also has a property "id", being an Integer.
With Enhanced API this is no longer possible. Properies are no longer set by name, but by PropertyType. A PropertyType, just like any other EdgeType is a global definition, which contains the data type of the Property.
When using Enhanced API on an existing database, make sure that all properties with the same name have the same data type. ClassCastExceptions will be thrown if existing data does not conform to the the data type of the PropertyType.
The Vertex interface supports several methods to manipulate Properties. The methods are largely based upon the PropertyContainer interface in the standard API.
public <T> Property<T> getProperty(PropertyType<T> pt);
public <T> T getPropertyValue(PropertyType<T> pt);
public <T> boolean hasProperty(PropertyType<T> pt);
public Vertex removeProperty(PropertyType<T> pt);
public Vertex setProperty(PropertyType<T> pt, T value);
public Iterable<PropertyType<?>> getPropertyTypes();
Apart from the obvious use of PropertyType instead of a name, the methods defined on Vertex differ from those defined on PropertyContainer in one more aspect. The setProperty and removeProperty return the Vertex, so it is possible to write code in fluent style:
db().createVertext
.setProperty(Name, "John")
.setAge(Age, 29)
.addEdge(NEIGHBOUR, Paula);
The Vertex interface also supports methods for the manipulation of general edges:
public Iterable<Edge> getEdges(EdgeType... edgeTypes);
public Iterable<Edge> getEdges(EdgeType edgeType, ConnectorType<?>... role);
public boolean hasEdge(EdgeType... edgeTypes);
public boolean hasEdge(EdgeType edgeType, ConnectorType<?>... role);
The Edge interface defines the following methods:
public Iterable<Connector<?>> getConnectors();
public Iterable<Connector<?>> getConnectors(ConnectorType<?>... connectorTypes);
public <T extends ConnectionMode> Connector<T> getConnector(ConnectorType<T> connectorType);
public <T extends ConnectionMode> Iterable<Vertex> getVertices(ConnectorType<T> connectorType);
public <T extends ConnectionMode> Iterable<Connection> getConnections(ConnectorType<T> connectorType);
public <T extends LeftRestrictedConnectionMode>Vertex getVertex(ConnectorType<T> connectorType);
So far we have only looked at Edges with one connector (Properties), and with two connectors (BinaryEdges), but a generic Edge can have an unlimited number of connector.
Let's look at an example. Suppose we want to state that Tom, Dick and Harry give Flo and Eddie a Book and a Bicycle.
There is an edge (presented in red) of type "GIVE", with three Connectors:
- giver
- recipient
- gift
Each of these connectors has several connection (presented in blue) to Vertices.
To setup this example, we first need to create the Vertices. It is assumed a DatabaseService has been created. This DatabaseService is a wrapper around GraphDatabaseService in the standard Neo4j API, extended with methods for the Enhanced API.
Vertex tom = db.createVertex();
Vertex dick = db.createVertex();
Vertex harry = db.createVertex();
Vertex flo = db.createVertex();
Vertex eddie = db.createVertex();
Vertex book = db.createVertex();
Vertex bicycle = db.createVertex();
Now we are going to define the ConnectorTypes: giver, recipient and gift. This is done by means of a ConnectorTypeDescription.
ConnectorTypeDescription giverDescr = new ConnectorTypeDescription("giver", ConnectionMode.UNRESTRICTED);
ConnectorTypeDescription recipientDescr = new ConnectorTypeDescription("recipient", ConnectionMode.UNRESTRICTED);
ConnectorTypeDescription giftDescr = new ConnectorTypeDescription("gift", ConnectionMode.UNRESTRICTED);
Now that we have the ConnectorTypeDescriptions we can create the EdgeType.
EdgeType edgeType = db.createEdgeType("GIVES", giverDescr, recipientDescr, giftDescr);
We can now get the actual instances of the ConnectorTypes by calling edgeType#getConnectorType(String).
ConnectorType<?> giver = edgeType.getConnectorType("giver");
ConnectorType<?> recipient = edgeType.getConnectorType("recipient");
ConnectorType<?> gift = edgeType.getConnectorType("gift");
Now we will define the Connectors for each ConnectorType of the Edge we want to create. This is done by means of a ConnectorDescription:
ConnectorDescription givers = new ConnectorDescription(giver, tom, dick, harry);
ConnectorDescription recipients = new ConnectorDescription(recipient, flo, eddie);
ConnectorDescription gifts = new ConnectorDescription(gift, book, bicycle);
Finally we can create the Edge.
Edge edge = db.createEdge(egdeType, givers, recipients, gifts);
Once the edge has been created, we can iterate over the connections like this:
for(Vertex vertex: edge.getVertices(giver){
//Do something with the vertex;
}
ConnectorTypeDescriptors are necessary to make sure that an EdgeType is created in one step. ConnectorTypes cannot be added or removed from an EdgeType.
ConnectorDescriptors are necessary to make sure that an Edge is created in one step. Connectors cannot be added or removed from an Edge.
The Vertex interface support methods for the manipulation of Properties and Edges, thereby providing all methods normally associated with Nodes. Properties and Edges (including their Types) are Vertices too. This allows for the generalization of all Neo4j database elements as if they were Nodes.
Due to generalization it is possible to create Edges involving regular Vertices, Edges of any kind, including BinaryEdges and Properties.
The generalization also makes it possible to set Properties on all Vertices. So it even becomes possible to set a Property on a Property.