The HelloSparksee sample application is a very simple movie database where we store information about movies, their actors and directors. All of them are nodes in the graph whereas the relationships between them are edges.
In a graph database we can represent movies, actors and directors using two node types:
We can enrich these by adding more information such as the movie title, year of production, or in the case of the people only their names. This information are attributes of the recently created node types:
For each MOVIE we may need the following attributes:
For PEOPLE we need these attributes:
Note that the ID attribute is not required, but it is always useful to have a unique attribute value in order to identify each node, as the other attributes (name and title) can’t be considered unique.
The relationships are represented by edges between the former two types of nodes. We need two types of edges:
The first one (CAST) is going to be an undirected edge and it will have an attribute (CHARACTER) to store the name of the role performed in that movie. In fact it is not a restricted edge, so we could use it between other types of nodes too. For example, later on we could add a new node type “ANIMAL” and use the same “CAST” edge type between “MOVIE” and “ANIMAL” nodes.
The other (DIRECTS) is going to be a restricted directed edge without attributes. It is restricted because it can only be used between PEOPLE and MOVIE nodes.
Let’s now construct the node types MOVIE and PEOPLE and then add the desired attributes to each type definition, which we have defined in our schema.
There are three types of attributes:
Basic: This type of attributes cannot be used for query operations (just get and set attribute values).
Unique: Attributes that work as a primary key, which means that two objects cannot have the same value for an attribute (but NULL). They can be used for query operations.
Indexed: Attributes that can be used for query operations.
Also you have to choose a datatype for the attributes. Available datatypes for attributes are: Boolean
, Integer
, Long
, Double
, Timestamp
, String
, Text
and OID
.
The ID attributes are going to be numeric (Long) unique values. The TITLE and NAME attributes are going to be String
values and both are going to be Indexed
. For the YEAR attribute we are going to use an Integer
type and this is also going to be Indexed
.
// Add a node type for the movies, with a unique identifier and two indexed attributes
int movieType = g.newNodeType("MOVIE");
int movieIdType = g.newAttribute(movieType, "ID", DataType.Long, AttributeKind.Unique);
int movieTitleType = g.newAttribute(movieType, "TITLE", DataType.String, AttributeKind.Indexed);
int movieYearType = g.newAttribute(movieType, "YEAR", DataType.Integer, AttributeKind.Indexed);
// Add a node type for the people, with a unique identifier and an indexed attribute
int peopleType = g.newNodeType("PEOPLE");
int peopleIdType = g.newAttribute(peopleType, "ID", DataType.Long, AttributeKind.Unique);
int peopleNameType = g.newAttribute(peopleType, "NAME", DataType.String, AttributeKind.Indexed);
// Add a node type for the movies, with a unique identifier and two indexed attributes
int movieType = g.NewNodeType("MOVIE");
int movieIdType = g.NewAttribute(movieType, "ID", DataType.Long, AttributeKind.Unique);
int movieTitleType = g.NewAttribute(movieType, "TITLE", DataType.String, AttributeKind.Indexed);
int movieYearType = g.NewAttribute(movieType, "YEAR", DataType.Integer, AttributeKind.Indexed);
// Add a node type for the people, with a unique identifier and an indexed attribute
int peopleType = g.NewNodeType("PEOPLE");
int peopleIdType = g.NewAttribute(peopleType, "ID", DataType.Long, AttributeKind.Unique);
int peopleNameType = g.NewAttribute(peopleType, "NAME", DataType.String, AttributeKind.Indexed);
// Add a node type for the movies, with a unique identifier and two indexed attributes
type_t movieType = g->NewNodeType(L"MOVIE");
attr_t movieIdType = g->NewAttribute(movieType, L"ID", Long, Unique);
attr_t movieTitleType = g->NewAttribute(movieType, L"TITLE", String, Indexed);
attr_t movieYearType = g->NewAttribute(movieType, L"YEAR", Integer, Indexed);
// Add a node type for the people, with a unique identifier and an indexed attribute
type_t peopleType = g->NewNodeType(L"PEOPLE");
attr_t peopleIdType = g->NewAttribute(peopleType, L"ID", Long, Unique);
attr_t peopleNameType = g->NewAttribute(peopleType, L"NAME", String, Indexed);
# Add a node type for the movies, with a unique identifier and two indexed attributes
movie_type = graph.new_node_type(u"MOVIE")
movie_id_type = graph.new_attribute(movie_type, u"ID", sparksee.DataType.LONG, sparksee.AttributeKind.UNIQUE)
movie_title_type = graph.new_attribute(movie_type, u"TITLE", sparksee.DataType.STRING, sparksee.AttributeKind.INDEXED)
movie_year_type = graph.new_attribute(movie_type, "uYEAR", sparksee.DataType.INTEGER, sparksee.AttributeKind.INDEXED)
# Add a node type for the people, with a unique identifier and an indexed attribute
people_type = graph.new_nodetype(u"PEOPLE")
people_id_type = graph.new_attribute(people_type, u"ID", sparksee.DataType.LONG, sparksee.AttributeKind.UNIQUE)
people_name_type = graph.new_attribute(people_type, u"NAME", sparksee.DataType.STRING, sparksee.AttributeKind.INDEXED)
// Add a node type for the movies, with a unique identifier and two indexed attributes
int movieType = [g createNodeType: @"MOVIE"];
int movieIdType = [g createAttribute: movieType name: @"ID" dt: STSLong kind: STSUnique];
int movieTitleType = [g createAttribute: movieType name: @"TITLE" dt: STSString kind: STSIndexed];
int movieYearType = [g createAttribute: movieType name: @"YEAR" dt: STSInteger kind: STSIndexed];
// Add a node type for the people, with a unique identifier and an indexed attribute
int peopleType = [g createNodeType: @"PEOPLE"];
int peopleIdType = [g createAttribute: peopleType name: @"ID" dt: STSLong kind: STSUnique];
int peopleNameType = [g createAttribute: peopleType name: @"NAME" dt: STSString kind: STSIndexed];
Now that we have created our nodes let’s add the relationships we have previously explained in our schema.
We have defined edge types CAST and DIRECTS. We stated during the schema explanation that CAST will be undirected while DIRECTS will be directed. Let’s explain a little bit more about this classification of our edge types.
Directed edges have a node which is the tail (the source of the edge) and a node which is the head (the destination of the edge). In case of undirected edges, each node at the extreme of the edge plays two roles at the same time, head and tail. Whereas undirected edges express navigation in any side, directed edges show the direction of the edge but they can also be navigated through that natural direction or the opposite one.
Also, edges can be classified as restricted or unrestricted. Restricted edges define which must be the type of the tail and head nodes, thus edges will only be allowed between those specified type of nodes. In case of unrestricted edges, there is no restriction, and edges are allowed between nodes belonging to any type. It is important to note that restricted edges must be directed edges.
In addition, in our schema we have decided that CAST will have an attribute called CHARACTER. The CHARACTER attribute is going to be a String
and we are going to set it as Basic
. See the previous section for more information about the attribute types.
// Add an undirected edge type with an attribute for the cast of a movie
int castType = g.newEdgeType("CAST", false, false);
int castCharacterType = g.newAttribute(castType, "CHARACTER", DataType.String, AttributeKind.Basic);
// Add a directed edge type restricted to go from people to movie for the director of a movie
int directsType = g.newRestrictedEdgeType("DIRECTS", peopleType, movieType, false);
// Add an undirected edge type with an attribute for the cast of a movie
int castType = g.NewEdgeType("CAST", false, false);
int castCharacterType = g.NewAttribute(castType, "CHARACTER", DataType.String, AttributeKind.Basic);
// Add a directed edge type restricted to go from people to movie for the director of a movie
int directsType = g.NewRestrictedEdgeType("DIRECTS", peopleType, movieType, false);
// Add an undirected edge type with an attribute for the cast of a movie
type_t castType = g->NewEdgeType(L"CAST", false, false);
attr_t castCharacterType = g->NewAttribute(castType, L"CHARACTER", String, Basic);
// Add a directed edge type restricted to go from people to movie for the director of a movie
type_t directsType = g->NewRestrictedEdgeType(L"DIRECTS", peopleType, movieType, false);
# Add an undirected edge type with an attribute for the cast of a movie
cast_type = graph.new_edge_type(u"CAST", False, False)
cast_character_type = graph.new_attribute(cast_type, u"CHARACTER", sparksee.DataType.STRING, sparksee.AttributeKind.BASIC)
# Add a directed edge type restricted to go from people to movie for the director of a movie
directs_type = graph.new_restricted_edge_type(u"DIRECTS", people_type, movie_type, False)
// Add an undirected edge type with an attribute for the cast of a movie
int castType = [g createEdgeType: @"CAST" directed: FALSE neighbors: FALSE];
int castCharacterType = [g createAttribute: castType name: @"CHARACTER" dt: STSString kind: STSBasic];
// Add a directed edge type restricted to go from people to movie for the director of a movie
int directsType = [g createRestrictedEdgeType: @"DIRECTS" tail: peopleType head: movieType neighbors: FALSE];