Querying
Querying nodes
Nothing specific here, we can use Ecto functions the same we do with other adapters. We can:
Repo.get
Repo.get_by
Repo.all
Repo.one
etc.
Preloading
To preload data, we need to use Ecto.Adapters.Neo4j.preload
instead of classic Repo.preload
. This function works the same way except for subqueries and nested preloads which aren't supported.
Considering the graph inserted in the Inserting section, we can do this:
alias Ecto.Adapters.Neo4j
alias GraphApp.Repo
alias GraphApp.Account.User
Repo.get!(User, "12903da6-5d46-417b-9cab-bd82766c868b")
|> Neo4j.preload(:wrote_post)
# Result
%GraphApp.Account.User{
__meta__: #Ecto.Schema.Metadata<:loaded, "User">,
firstName: "John",
has_userprofile: #Ecto.Association.NotLoaded<association :has_userprofile is not loaded>,
lastName: "Doe",
read_post: #Ecto.Association.NotLoaded<association :read_post is not loaded>,
uuid: "12903da6-5d46-417b-9cab-bd82766c868b",
wrote_comment: #Ecto.Association.NotLoaded<association :wrote_comment is not loaded>,
wrote_post: [
%GraphApp.Blog.Post{
__meta__: #Ecto.Schema.Metadata<:built, "Post">,
has_comment: #Ecto.Association.NotLoaded<association :has_comment is not loaded>,
read_post: #Ecto.Association.NotLoaded<association :read_post is not loaded>,
read_post_uuid: nil,
rel_read: nil,
rel_wrote: %{"when" => ~D[2018-02-01]},
text: "This is the second",
title: "Second",
uuid: "727289bc-ec28-4459-a9dc-a51ee6bfd6ab",
wrote_post: #Ecto.Association.NotLoaded<association :wrote_post is not loaded>,
wrote_post_uuid: "12903da6-5d46-417b-9cab-bd82766c868b"
},
%GraphApp.Blog.Post{
__meta__: #Ecto.Schema.Metadata<:built, "Post">,
has_comment: #Ecto.Association.NotLoaded<association :has_comment is not loaded>,
read_post: #Ecto.Association.NotLoaded<association :read_post is not loaded>,
read_post_uuid: nil,
rel_read: nil,
rel_wrote: %{"when" => ~D[2018-01-01]},
text: "This is the first",
title: "First",
uuid: "ae830851-9e93-46d5-bbf7-23ab99846497",
wrote_post: #Ecto.Association.NotLoaded<association :wrote_post is not loaded>,
wrote_post_uuid: "12903da6-5d46-417b-9cab-bd82766c868b"
}
]
}
Querying relationships and relationships' data
To query realtionships and their data, we need to use join
and on
.join
will simply define the relationship and translate in cypher as an unqualified relationship between 2 nodes.
For example:
from u in User,
join: p in Post
will translate to:(:User)-[]->(:Post)
on
allows to qualifies relationship and to had clauses on relationship data.
Remember that relationship data are part of the end node schema. We will use this field to had clause on relationship type and data:
rel_[relationship_type] == %{}
will check that there is a relationship of type [relationship_type] between the two nodesis_nil(rel_[relationship_type])
will check that there is NOT a relationship of type [relationship_type] between the two nodesrel_[relationship_type] == %{rel_property: prop_value}
will check that there is a relationship of type [relationship_type] between the two nodes AND that it has a property named rel_property with the given prop_value
IMPORTANT:
- Always make joins from start node to end node, the opposite way is not supported yet
- Relationship works at 1-level depth only for the moment
- `or` operator in `on` clause is not supported
Examples
alias Ecto.Adapters.Neo4j
import Ecto.Query
alias GraphApp.Repo
alias GraphApp.Account.{User, UserProfile}
alias GraphApp.Blog.{Post, Comment}
Considering the graph inserted in the Inserting section.
Retrieving users related to post in any kind of way:
In Cypher:
MATCH
(u:User)-[]->(:Post)
RETURN
u
In EctoNeo4j:
query = from u in User,
join: p in Post
Repo.all(query)
# Result
[
%GraphApp.Account.User{
__meta__: #Ecto.Schema.Metadata<:loaded, "User">,
firstName: "John",
has_userprofile: #Ecto.Association.NotLoaded<association :has_userprofile is not loaded>,
lastName: "Doe",
read_post: #Ecto.Association.NotLoaded<association :read_post is not loaded>,
uuid: "12903da6-5d46-417b-9cab-bd82766c868b",
wrote_comment: #Ecto.Association.NotLoaded<association :wrote_comment is not loaded>,
wrote_post: #Ecto.Association.NotLoaded<association :wrote_post is not loaded>
},
%GraphApp.Account.User{
__meta__: #Ecto.Schema.Metadata<:loaded, "User">,
firstName: "John",
has_userprofile: #Ecto.Association.NotLoaded<association :has_userprofile is not loaded>,
lastName: "Doe",
read_post: #Ecto.Association.NotLoaded<association :read_post is not loaded>,
uuid: "12903da6-5d46-417b-9cab-bd82766c868b",
wrote_comment: #Ecto.Association.NotLoaded<association :wrote_comment is not loaded>,
wrote_post: #Ecto.Association.NotLoaded<association :wrote_post is not loaded>
},
%GraphApp.Account.User{
__meta__: #Ecto.Schema.Metadata<:loaded, "User">,
firstName: "John",
has_userprofile: #Ecto.Association.NotLoaded<association :has_userprofile is not loaded>,
lastName: "Doe",
read_post: #Ecto.Association.NotLoaded<association :read_post is not loaded>,
uuid: "12903da6-5d46-417b-9cab-bd82766c868b",
wrote_comment: #Ecto.Association.NotLoaded<association :wrote_comment is not loaded>,
wrote_post: #Ecto.Association.NotLoaded<association :wrote_post is not loaded>
}
]
Notice the duplication of node "John Doe" which happens also when you launch the querey in Neo4j...
Retrieving users who wrote post
In Cypher:
MATCH
(u:User)-[:WROTE]->(:Post)
RETURN
u
In EctoNeo4j:
query = from u in User,
join: p in Post,
on: p.rel_wrote == ^%{}
Repo.all(query)
# Result
[
%GraphApp.Account.User{
__meta__: #Ecto.Schema.Metadata<:loaded, "User">,
firstName: "John",
has_userprofile: #Ecto.Association.NotLoaded<association :has_userprofile is not loaded>,
lastName: "Doe",
read_post: #Ecto.Association.NotLoaded<association :read_post is not loaded>,
uuid: "12903da6-5d46-417b-9cab-bd82766c868b",
wrote_comment: #Ecto.Association.NotLoaded<association :wrote_comment is not loaded>,
wrote_post: #Ecto.Association.NotLoaded<association :wrote_post is not loaded>
},
%GraphApp.Account.User{
__meta__: #Ecto.Schema.Metadata<:loaded, "User">,
firstName: "John",
has_userprofile: #Ecto.Association.NotLoaded<association :has_userprofile is not loaded>,
lastName: "Doe",
read_post: #Ecto.Association.NotLoaded<association :read_post is not loaded>,
uuid: "12903da6-5d46-417b-9cab-bd82766c868b",
wrote_comment: #Ecto.Association.NotLoaded<association :wrote_comment is not loaded>,
wrote_post: #Ecto.Association.NotLoaded<association :wrote_post is not loaded>
}
]
Retrieving posts wrote by user but not read:
In Cypher:
MATCH
(:User)-[:WROTE]->(p:Post)
WHERE
NOT (:User)-[:READ]->(p)
RETURN
p
In EctoNeo4j:
query = from u in User,
join: p in Post,
on: p.rel_wrote == ^%{} and is_nil(p.rel_read),
select: p
Repo.all(query)
# Result
[
%GraphApp.Blog.Post{
__meta__: #Ecto.Schema.Metadata<:loaded, "Post">,
has_comment: #Ecto.Association.NotLoaded<association :has_comment is not loaded>,
read_post: #Ecto.Association.NotLoaded<association :read_post is not loaded>,
read_post_uuid: nil,
rel_read: nil,
rel_wrote: nil,
text: "This is the first",
title: "First",
uuid: "ae830851-9e93-46d5-bbf7-23ab99846497",
wrote_post: #Ecto.Association.NotLoaded<association :wrote_post is not loaded>,
wrote_post_uuid: nil
}
]
Retrieving user who wrote a post at a specific date:
In cypher:
MATCH
(u:User)-[rel:WROTE]->(:Post)
WHERE
rel.when = ~D[2018-01-01]
RETURN
u
In EctoNeo4j:
rel_data = %{when: ~D[2018-01-01]}
query = from u in User,
join: p in Post,
on: p.rel_wrote == ^rel_data,
select: [u, p]
Repo.all(query)
# Result
[
[
%GraphApp.Account.User{
__meta__: #Ecto.Schema.Metadata<:loaded, "User">,
firstName: "John",
has_userprofile: #Ecto.Association.NotLoaded<association :has_userprofile is not loaded>,
lastName: "Doe",
read_post: #Ecto.Association.NotLoaded<association :read_post is not loaded>,
uuid: "12903da6-5d46-417b-9cab-bd82766c868b",
wrote_comment: #Ecto.Association.NotLoaded<association :wrote_comment is not loaded>,
wrote_post: #Ecto.Association.NotLoaded<association :wrote_post is not loaded>
},
%GraphApp.Blog.Post{
__meta__: #Ecto.Schema.Metadata<:loaded, "Post">,
has_comment: #Ecto.Association.NotLoaded<association :has_comment is not loaded>,
read_post: #Ecto.Association.NotLoaded<association :read_post is not loaded>,
read_post_uuid: nil,
rel_read: nil,
rel_wrote: nil,
text: "This is the first",
title: "First",
uuid: "ae830851-9e93-46d5-bbf7-23ab99846497",
wrote_post: #Ecto.Association.NotLoaded<association :wrote_post is not loaded>,
wrote_post_uuid: nil
}
]
]
Raw cyher queries
Raw cypher queries can be executed thanks to Ecto.Adapters.Neo4j.query(cql, params, opts)
and Ecto.Adapters.Neo4j.query!(cql, params, opts)
.
They return a Bolt.Sips.Response
if case of success
Example:
iex> my_query = "RETURN {num} AS num"
iex> Ecto.Adapters.Neo4j.query(my_query, %{num: 5})
{:ok,
%Bolt.Sips.Response{
bookmark: nil,
fields: ["num"],
notifications: [],
plan: nil,
profile: nil,
records: [[5]],
results: [%{"num" => 5}],
stats: [],
type: "r"
}}