API: The Essentials

The py2neo top-level package contains the functions and classes that are used directly and ubiquitously across the whole library.

The Graph

class py2neo.Graph[source]

The Graph class provides a wrapper around the REST API exposed by a running Neo4j database server and is identified by the base URI of the graph database. If no URI is specified, a default value is taken from the NEO4J_URI environment variable. If this is not set, a default of http://localhost:7474/db/data/ is assumed. Therefore, the simplest way to connect to a running service is to use:

>>> from py2neo import Graph
>>> graph = Graph()

An explicitly specified graph database URI can also be passed to the constructor as a string:

>>> other_graph = Graph("http://camelot:1138/db/data/")

If the database server requires authorisation, the credentials can also be specified within the URI:

>>> secure_graph = Graph("http://arthur:excalibur@camelot:1138/db/data/")

Once obtained, the Graph instance provides direct or indirect access to most of the functionality available within py2neo.

batch

A py2neo.batch.BatchResource instance attached to this graph. This resource exposes methods for submitting iterable collections of py2neo.batch.Job objects to the server and will often be used indirectly via classes such as py2neo.batch.PullBatch or py2neo.batch.PushBatch.

Return type:py2neo.batch.BatchResource
bind(uri, metadata=None)

Associate this graph with a remote resource.

Parameters:
  • uri – The URI identifying the remote resource to which to bind.
  • metadata – Dictionary of initial metadata to attach to the contained resource.
bound

True if this object is bound to a remote resource, False otherwise.

static cast(obj)[source]

Cast an general Python object to a graph-specific entity, such as a Node or a Relationship.

create(*entities)[source]

Create one or more remote nodes, relationships or paths in a single transaction. The entity values provided must be either existing entity objects (such as nodes or relationships) or values that can be cast to them.

For example, to create a remote node from a local Node object:

from py2neo import Graph, Node
graph = Graph()
alice = Node("Person", name="Alice")
graph.create(alice)

Then, create a second node and a relationship connecting both nodes:

german, speaks = graph.create({"name": "German"}, (alice, "SPEAKS", 0))

This second example shows how dict and tuple objects can also be used to create nodes and relationships respectively. The zero value in the relationship tuple references the zeroth item created within that transaction, i.e. the “German” node.

Note

If an object is passed to this method that is already bound to a remote entity, that argument will be ignored and nothing will be created.

Parameters:entities – One or more existing graph entities or values that can be cast to entities.
Returns:A tuple of all entities created (or ignored) of the same length and order as the arguments passed in.
Return type:tuple

Warning

This method will always return a tuple, even when creating only a single entity. To automatically unpack to a single item, append a trailing comma to the variable name on the left of the assignment operation.

create_unique(*entities)[source]

Create one or more unique paths or relationships in a single transaction. This is similar to create() but uses a Cypher CREATE UNIQUE clause to ensure that only relationships that do not already exist are created.

cypher

The Cypher execution resource for this graph providing access to all Cypher functionality for the underlying database, both simple and transactional.

>>> from py2neo import Graph
>>> graph = Graph()
>>> graph.cypher.execute("CREATE (a:Person {name:{N}})", {"N": "Alice"})
Return type:py2neo.cypher.CypherResource
delete(*entities)[source]

Delete one or more nodes, relationships and/or paths.

delete_all()[source]

Delete all nodes and relationships from the graph.

Warning

This method will permanently remove all nodes and relationships from the graph and cannot be undone.

error_class

alias of GraphError

find(label, property_key=None, property_value=None, limit=None)[source]

Iterate through a set of labelled nodes, optionally filtering by property key and value

find_one(label, property_key=None, property_value=None)[source]

Find a single node by label and optional property. This method is intended to be used with a unique constraint and does not fail if more than one matching node is found.

graph

The graph associated with the remote resource.

Return type:Graph
hydrate(data)[source]

Hydrate a dictionary of data to produce a Node, Relationship or other graph object instance. The data structure and values expected are those produced by the REST API.

Parameters:data – dictionary of data to hydrate
legacy

Sub-resource providing access to legacy functionality.

Return type:py2neo.legacy.LegacyResource
match(start_node=None, rel_type=None, end_node=None, bidirectional=False, limit=None)[source]

Return an iterator for all relationships matching the specified criteria.

For example, to find all of Alice’s friends:

for rel in graph.match(start_node=alice, rel_type="FRIEND"):
    print(rel.end_node.properties["name"])
Parameters:
  • start_nodebound start Node to match or None if any
  • rel_type – type of relationships to match or None if any
  • end_nodebound end Node to match or None if any
  • bidirectionalTrue if reversed relationships should also be included
  • limit – maximum number of relationships to match or None if no limit
Returns:

matching relationships

Return type:

generator

match_one(start_node=None, rel_type=None, end_node=None, bidirectional=False)[source]

Return a single relationship matching the specified criteria. See match() for argument details.

merge(label, property_key=None, property_value=None, limit=None)[source]

Match or create a node by label and optional property and return all matching nodes.

merge_one(label, property_key=None, property_value=None)[source]

Match or create a node by label and optional property and return a single matching node. This method is intended to be used with a unique constraint and does not fail if more than one matching node is found.

>>> graph = Graph()
>>> person = graph.merge_one("Person", "email", "bob@example.com")
neo4j_version

The database software version as a 4-tuple of (int, int, int, str).

node(id_)[source]

Fetch a node by ID. This method creates an object representing the remote node with the ID specified but fetches no data from the server. For this reason, there is no guarantee that the entity returned actually exists.

node_labels

The set of node labels currently defined within the graph.

open_browser()[source]

Open a page in the default system web browser pointing at the Neo4j browser application for this graph.

order

The number of nodes in this graph.

pull(*entities)[source]

Pull data to one or more entities from their remote counterparts.

push(*entities)[source]

Push data from one or more entities to their remote counterparts.

ref

The URI of the remote resource relative to its graph.

Return type:string
relationship(id_)[source]

Fetch a relationship by ID.

relationship_types

The set of relationship types currently defined within the graph.

resource

The remote resource to which this object is bound.

Return type:Resource
Raises:py2neo.BindError
schema

The schema resource for this graph.

Return type:SchemaResource
service_root

The root service associated with the remote resource.

Returns:ServiceRoot
size

The number of relationships in this graph.

supports_cypher_transactions

Indicates whether the server supports explicit Cypher transactions.

supports_foreach_pipe

Indicates whether the server supports pipe syntax for FOREACH.

supports_node_labels

Indicates whether the server supports node labels.

supports_optional_match

Indicates whether the server supports Cypher OPTIONAL MATCH clauses.

supports_schema_indexes

Indicates whether the server supports schema indexes.

supports_start_clause

Indicates whether the server supports the Cypher START clause.

unbind()

Detach this object from any remote resource.

uri

The full URI of the remote resource.

Authentication

Neo4j 2.2 introduces optional authentication for database servers, enabled by default. To use a server with authentication enabled, a user name and password must be specified for the host:port combination. This can either be passed in code using the authenticate() function or specified in the NEO4J_AUTH environment variable. By default the user name and password are neo4j and neo4j respectively. This default password generally requires an initial change before the database can be used.

There are two ways to set up authentication for a new server installation:

  1. Set an initial password for the neo4j user.
  2. Copy auth details from another (initialised) server.

Py2neo provides a command line tool to help with changing user passwords as well as checking whether a password change is required. For a new installation, use:

$ neoauth neo4j neo4j my-p4ssword
Password change succeeded

After a password has been set, the tool can also be used to validate credentials:

$ neoauth neo4j my-p4ssword
Password change not required

Alternatively, authentication can be disabled completely by editing the value of the dbms.security.authorization_enabled setting in the conf/neo4j-server.properties file.

py2neo.authenticate(host_port, user_name, password)[source]

Set HTTP basic authentication values for specified host_port for use with both Neo4j 2.2 built-in authentication as well as if a database server is behind (for example) an Apache proxy. The code below shows a simple example:

from py2neo import authenticate, Graph

# set up authentication parameters
authenticate("camelot:7474", "arthur", "excalibur")

# connect to authenticated graph database
graph = Graph("http://camelot:7474/db/data/")

Note: a host_port can be either a server name or a server name and port number but must match exactly that used within the Graph URI.

Parameters:
  • host_port – the host and optional port requiring authentication (e.g. “bigserver”, “camelot:7474”)
  • user_name – the user name to authenticate as
  • password – the password

Nodes

class py2neo.Node(*labels, **properties)[source]

A graph node that may optionally be bound to a remote counterpart in a Neo4j database. Nodes may contain a set of named properties and may have one or more labels applied to them:

>>> from py2neo import Node
>>> alice = Node("Person", name="Alice")
>>> banana = Node("Fruit", "Food", colour="yellow", tasty=True)

All positional arguments passed to the constructor are interpreted as labels and all keyword arguments as properties. It is also possible to construct Node instances from other data types (such as a dictionary) by using the Node.cast() class method:

>>> bob = Node.cast({"name": "Bob Robertson", "age": 44})

Labels and properties can be accessed and modified using the labels and properties attributes respectively. The former is an instance of LabelSet, which extends the built-in set class, and the latter is an instance of PropertySet which extends dict.

>>> alice.properties["name"]
'Alice'
>>> alice.labels
{'Person'}
>>> alice.labels.add("Employee")
>>> alice.properties["employee_no"] = 3456
>>> alice
<Node labels={'Employee', 'Person'} properties={'employee_no': 3456, 'name': 'Alice'}>

One of the core differences between a PropertySet and a standard dictionary is in how it handles None and missing values. As with actual Neo4j properties, missing values and those equal to None are equivalent.

bind(uri, metadata=None)[source]

Associate this node with a remote node.

Parameters:
  • uri – The URI identifying the remote node to which to bind.
  • metadata – Dictionary of initial metadata to attach to the contained resource.
bound

True if this object is bound to a remote resource, False otherwise.

static cast(*args, **kwargs)[source]

Cast the arguments provided to a Node (or NodePointer). The following combinations of arguments are possible:

>>> Node.cast(None)
>>> Node.cast()
<Node labels=set() properties={}>
>>> Node.cast("Person")
<Node labels={'Person'} properties={}>
>>> Node.cast(name="Alice")
<Node labels=set() properties={'name': 'Alice'}>
>>> Node.cast("Person", name="Alice")
<Node labels={'Person'} properties={'name': 'Alice'}>
>>> Node.cast(123)
<NodePointer address=123>
>>> Node.cast({"name": "Alice"})
<Node labels=set() properties={'name': 'Alice'}>
>>> node = Node("Person", name="Alice")
>>> Node.cast(node)
<Node labels={'Person'} properties={'name': 'Alice'}>
degree

The number of relationships attached to this node.

error_class

alias of GraphError

exists

True if this node exists in the database, False otherwise.

graph

The graph associated with the remote resource.

Return type:Graph
classmethod hydrate(data, inst=None)[source]

Hydrate a dictionary of data to produce a Node instance. The data structure and values expected are those produced by the REST API although only the self value is required.

Parameters:
  • data – dictionary of data to hydrate
  • inst – an existing Node instance to overwrite with new values
labels

The set of labels attached to this node.

match(rel_type=None, other_node=None, limit=None)[source]

Return an iterator for all relationships attached to this node that match the specified criteria. See Graph.match() for argument details.

match_incoming(rel_type=None, start_node=None, limit=None)[source]

Return an iterator for all incoming relationships to this node that match the specified criteria. See Graph.match() for argument details.

match_outgoing(rel_type=None, end_node=None, limit=None)[source]

Return an iterator for all outgoing relationships from this node that match the specified criteria. See Graph.match() for argument details.

properties

The set of properties attached to this node. Properties can also be read from and written to any Node by using the index syntax directly. This means the following statements are equivalent:

node.properties["name"] = "Alice"
node["name"] = "Alice"
pull()[source]

Pull data to this node from its remote counterpart. Consider using Graph.pull() instead for batches of nodes.

push()[source]

Push data from this node to its remote counterpart. Consider using Graph.push() instead for batches of nodes.

ref

The URI of this node relative to its graph.

Return type:string
resource

The remote resource to which this object is bound.

Return type:Resource
Raises:py2neo.BindError
service_root

The root service associated with the remote resource.

Returns:ServiceRoot
unbind()[source]

Detach this node from any remote counterpart.

uri

The full URI of the remote resource.

Relationships

class py2neo.Relationship(*triple, **properties)[source]

A graph relationship that may optionally be bound to a remote counterpart in a Neo4j database. Relationships require a triple of start node, relationship type and end node and may also optionally be given one or more properties:

>>> from py2neo import Node, Relationship
>>> alice = Node("Person", name="Alice")
>>> bob = Node("Person", name="Bob")
>>> alice_knows_bob = Relationship(alice, "KNOWS", bob, since=1999)
append(*others)

Join another path or relationship to the end of this path to form a new path.

Parameters:others – Entities to join to the end of this path
Return type:Path
bind(uri, metadata=None)[source]

Associate this relationship with a remote relationship. The start and end nodes will also be associated with their corresponding remote nodes.

Parameters:
  • uri – The URI identifying the remote relationship to which to bind.
  • metadata – Dictionary of initial metadata to attach to the contained resource.
bound

True if this relationship is bound to a remote counterpart, False otherwise.

static cast(*args, **kwargs)[source]

Cast the arguments provided to a Relationship. The following combinations of arguments are possible:

>>> Relationship.cast(Node(), "KNOWS", Node())
<Relationship type='KNOWS' properties={}>
>>> Relationship.cast((Node(), "KNOWS", Node()))
<Relationship type='KNOWS' properties={}>
>>> Relationship.cast(Node(), "KNOWS", Node(), since=1999)
<Relationship type='KNOWS' properties={'since': 1999}>
>>> Relationship.cast(Node(), "KNOWS", Node(), {"since": 1999})
<Relationship type='KNOWS' properties={'since': 1999}>
>>> Relationship.cast((Node(), "KNOWS", Node(), {"since": 1999}))
<Relationship type='KNOWS' properties={'since': 1999}>
>>> Relationship.cast(Node(), ("KNOWS", {"since": 1999}), Node())
<Relationship type='KNOWS' properties={'since': 1999}>
>>> Relationship.cast((Node(), ("KNOWS", {"since": 1999}), Node()))
<Relationship type='KNOWS' properties={'since': 1999}>
>>> Relationship.cast(Node(), Rel("KNOWS", since=1999), Node())
<Relationship type='KNOWS' properties={'since': 1999}>
>>> Relationship.cast((Node(), Rel("KNOWS", since=1999), Node()))
<Relationship type='KNOWS' properties={'since': 1999}>
end_node

The end node of this relationship.

Returns:Node
exists

True if this relationship exists in the database, False otherwise.

graph

The parent graph of this relationship.

Return type:Graph
classmethod hydrate(data, inst=None)[source]

Hydrate a dictionary of data to produce a Relationship instance. The data structure and values expected are those produced by the REST API.

Parameters:
  • data – dictionary of data to hydrate
  • inst – an existing Relationship instance to overwrite with new values
nodes

A tuple of all nodes in this relationship.

order

The number of nodes in this relationship.

prepend(*others)

Join another path or relationship to the start of this path to form a new path.

Parameters:others – Entities to join to the start of this path
Return type:Path
properties

The set of properties attached to this relationship. Properties can also be read from and written to any Relationship by using the index syntax directly. This means the following statements are equivalent:

relationship.properties["since"] = 1999
relationship["since"] = 1999
pull()[source]

Pull data to this relationship from its remote counterpart.

push()[source]

Push data from this relationship to its remote counterpart.

ref

The URI of this relationship relative to its graph.

Return type:string
rel

The Rel object within this relationship.

relationships

A tuple of all relationships in this relationship.

rels

A tuple of all rels in this relationship.

resource

The resource object wrapped by this relationship, if bound.

service_root

The root service associated with this relationship.

Returns:ServiceRoot
size

The number of relationships in this relationship. This property always equals 1 for a Relationship and is inherited from the more general parent class, Path.

start_node

The start node of this relationship.

Returns:Node
type

The type of this relationship.

unbind()[source]

Detach this relationship and its start and end nodes from any remote counterparts.

uri

The URI of this relationship, if bound.

class py2neo.Rel(*type_, **properties)[source]

A Rel is similar to a Relationship but does not store information about the nodes to which it is attached. This class is used internally to bundle relationship type and property details for Relationship and Path objects but may also be used to denote an explicit forward relationship within a Path.

See also

py2neo.Rev

bind(uri, metadata=None)[source]

Associate this object with a remote relationship.

Parameters:
  • uri – The URI identifying the remote relationship to which to bind.
  • metadata – Dictionary of initial metadata to attach to the contained resource.
bound

True if this object is bound to a remote resource, False otherwise.

static cast(*args, **kwargs)[source]

Cast the arguments provided to a Rel. The following combinations of arguments are possible:

>>> Rel.cast(None)
>>> Rel.cast()
<Rel type=None properties={}>
>>> Rel.cast("KNOWS")
<Rel type='KNOWS' properties={}>
>>> Rel.cast("KNOWS", since=1999)
<Rel type='KNOWS' properties={'since': 1999}>
>>> Rel.cast("KNOWS", {"since": 1999})
<Rel type='KNOWS' properties={'since': 1999}>
>>> Rel.cast(("KNOWS",))
<Rel type='KNOWS' properties={}>
>>> Rel.cast(("KNOWS", {"since": 1999}))
<Rel type='KNOWS' properties={'since': 1999}>
>>> rel = Rel("KNOWS", since=1999)
>>> Rel.cast(rel)
<Rel type='KNOWS' properties={'since': 1999}>
error_class

alias of GraphError

exists

True if this relationship exists in the database, False otherwise.

graph

The graph associated with the remote resource.

Return type:Graph
classmethod hydrate(data, inst=None)[source]

Hydrate a dictionary of data to produce a Rel instance. The data structure and values expected are those produced by the REST API although only the self value is required.

Parameters:
  • data – dictionary of data to hydrate
  • inst – an existing Rel instance to overwrite with new values
properties

The set of properties attached to this relationship. Properties can also be read from and written to any Rel by using the index syntax directly. This means the following statements are equivalent:

rel.properties["since"] = 1999
rel["since"] = 1999
pull()[source]

Pull data to this relationship from its remote counterpart.

push()[source]

Push data from this relationship to its remote counterpart.

ref

The URI of this relationship relative to its graph.

Return type:string
resource

The remote resource to which this object is bound.

Return type:Resource
Raises:py2neo.BindError
service_root

The root service associated with the remote resource.

Returns:ServiceRoot
type

The type of this relationship.

unbind()[source]

Detach this relationship from any remote counterpart.

uri

The full URI of the remote resource.

class py2neo.Rev(*type_, **properties)[source]

A Rev is identical to a Rel but denotes a reversed relationship rather than a forward one. The following example shows how to build a Path with one forward and one reversed relationship:

>>> path = Path(Node(name="A"), Rel("TO"), Node(name="B"), Rev("TO"), Node(name="C"))
>>> for relationship in path.relationships:
...     print(relationship)
({name:"A"})-[:TO]->({name:"B"})
({name:"C"})-[:TO]->({name:"B"})

See also

py2neo.Rel

bind(uri, metadata=None)

Associate this object with a remote relationship.

Parameters:
  • uri – The URI identifying the remote relationship to which to bind.
  • metadata – Dictionary of initial metadata to attach to the contained resource.
bound

True if this object is bound to a remote resource, False otherwise.

cast(*args, **kwargs)

Cast the arguments provided to a Rev. The following combinations of arguments are possible:

>>> Rev.cast(None)
>>> Rev.cast()
<Rev type=None properties={}>
>>> Rev.cast("KNOWS")
<Rev type='KNOWS' properties={}>
>>> Rev.cast("KNOWS", since=1999)
<Rev type='KNOWS' properties={'since': 1999}>
>>> Rev.cast("KNOWS", {"since": 1999})
<Rev type='KNOWS' properties={'since': 1999}>
>>> Rev.cast(("KNOWS",))
<Rev type='KNOWS' properties={}>
>>> Rev.cast(("KNOWS", {"since": 1999}))
<Rev type='KNOWS' properties={'since': 1999}>
>>> rev = Rev("KNOWS", since=1999)
>>> Rev.cast(rev)
<Rev type='KNOWS' properties={'since': 1999}>
error_class

alias of GraphError

exists

True if this relationship exists in the database, False otherwise.

graph

The graph associated with the remote resource.

Return type:Graph
hydrate(data, inst=None)

Hydrate a dictionary of data to produce a Rev instance. The data structure and values expected are those produced by the REST API although only the self value is required.

Parameters:
  • data – dictionary of data to hydrate
  • inst – an existing Rev instance to overwrite with new values
properties

The set of properties attached to this relationship. Properties can also be read from and written to any Rel by using the index syntax directly. This means the following statements are equivalent:

rel.properties["since"] = 1999
rel["since"] = 1999
pull()

Pull data to this relationship from its remote counterpart.

push()

Push data from this relationship to its remote counterpart.

ref

The URI of this relationship relative to its graph.

Return type:string
resource

The remote resource to which this object is bound.

Return type:Resource
Raises:py2neo.BindError
service_root

The root service associated with the remote resource.

Returns:ServiceRoot
type

The type of this relationship.

unbind()

Detach this relationship from any remote counterpart.

uri

The full URI of the remote resource.

Paths

class py2neo.Path(*entities)[source]

A sequence of nodes connected by relationships that may optionally be bound to remote counterparts in a Neo4j database.

>>> from py2neo import Node, Path, Rev
>>> alice, bob, carol = Node(name="Alice"), Node(name="Bob"), Node(name="Carol")
>>> abc = Path(alice, "KNOWS", bob, Rev("KNOWS"), carol)
>>> abc
<Path order=3 size=2>
>>> abc.nodes
(<Node labels=set() properties={'name': 'Alice'}>,
 <Node labels=set() properties={'name': 'Bob'}>,
 <Node labels=set() properties={'name': 'Carol'}>)
>>> abc.rels
(<Rel type='KNOWS' properties={}>, <Rev type='KNOWS' properties={}>)
>>> abc.relationships
(<Relationship type='KNOWS' properties={}>,
 <Relationship type='KNOWS' properties={}>)
>>> dave, eve = Node(name="Dave"), Node(name="Eve")
>>> de = Path(dave, "KNOWS", eve)
>>> de
<Path order=2 size=1>
>>> abcde = Path(abc, "KNOWS", de)
>>> abcde
<Path order=5 size=4>
>>> for relationship in abcde.relationships:
...     print(relationship)
({name:"Alice"})-[:KNOWS]->({name:"Bob"})
({name:"Carol"})-[:KNOWS]->({name:"Bob"})
({name:"Carol"})-[:KNOWS]->({name:"Dave"})
({name:"Dave"})-[:KNOWS]->({name:"Eve"})
append(*others)[source]

Join another path or relationship to the end of this path to form a new path.

Parameters:others – Entities to join to the end of this path
Return type:Path
bound

True if this path is bound to a remote counterpart, False otherwise.

end_node

The end node of this path.

Returns:Node
exists

True if this path exists in the database, False otherwise.

graph

The parent graph of this path.

Return type:Graph
classmethod hydrate(data, inst=None)[source]

Hydrate a dictionary of data to produce a Path instance. The data structure and values expected are those produced by the REST API.

Parameters:
  • data – dictionary of data to hydrate
  • inst – an existing Path instance to overwrite with new values
nodes

A tuple of all nodes in this path.

order

The number of nodes in this path.

prepend(*others)[source]

Join another path or relationship to the start of this path to form a new path.

Parameters:others – Entities to join to the start of this path
Return type:Path
pull()[source]

Pull data to all entities in this path from their remote counterparts.

push()[source]

Push data from all entities in this path to their remote counterparts.

relationships

A tuple of all relationships in this path.

rels

A tuple of all rels in this path.

service_root

The root service associated with this path.

Returns:ServiceRoot
size

The number of relationships in this path.

start_node

The start node of this path.

Returns:Node
unbind()[source]

Detach all entities in this path from any remote counterparts.

Labels & Properties

class py2neo.LabelSet(iterable=None)[source]

A set subclass that can be bound to a remote labels resource.

pull()[source]

Copy the set of remote labels onto the local set.

push()[source]

Copy the set of local labels onto the remote set.

replace(iterable)[source]

Replace all labels with those from the iterable provided.

Parameters:iterable
class py2neo.PropertySet(iterable=None, **kwargs)[source]

A dict subclass that equates None with a non-existent key and can be bound to a remote properties resource.

pull()[source]

Copy the set of remote properties onto the local set.

push()[source]

Copy the set of local properties onto the remote set.

Exceptions

exception py2neo.BindError[source]

Raised when a local graph entity is not or cannot be bound to a remote graph entity.

exception py2neo.Finished(obj)[source]

Raised when actions are attempted against a finished object that is no longer available for use.

exception py2neo.GraphError(*args, **kwargs)[source]

Default exception class for all errors returned by the Neo4j server.

exception py2neo.JoinError[source]

Raised when two graph entities cannot be joined together.