Ex4j

This library combines the power of Ecto with the Bolt protocol provided by Bolt Sips ♥️

You can use the whole ecto suite you love for validations and structures plus an elegant DSL to retrieve data from Neo4j.

All Ex4j.Node have Ecto.Schema, Ecto.Changeset and Ex4j.Cypher automatically imported for convenience.

Installation

The package can be installed by adding ex4j to your list of dependencies in mix.exs:

def deps do
[
{:ex4j, "~> 0.1.0"}
]
end

Add the connection settings

config :ex4j, Bolt,
url: "bolt://localhost:7687",
basic_auth: [username: "neo4j", password: "zEb0zryxK62NNRXKWxJKd7qeEFkO3mLIgcGwuUA4lvg"],
pool_size: 10,
ssl: false

The docs can be found at https://hexdocs.pm/ex4j.

Usage

All the entities you query must have a schema Node. like below associated in your code:

defmodule Node.User do
use Ex4j.Node
graph do
field(:name, :string)
field(:age, :integer)
field(:email, :string)
end
end
defmodule Node.Has do
use Ex4j.Node
graph do
field(:date, :utc_datetime_usec)
end
end
defmodule Node.Comment do
use Ex4j.Node
graph do
field(:content, :string)
end
end

The module you intend to use for queries must use Ex4j.Cypher:

defmodule App do
use Ex4j.Cypher
def execute do
match(Node.User, as: :u)
|> return(:u)
|> run()
end
end
iex> App.execute()
[
%{"u" => %Node.User{uuid: nil, name: "Tiago", age: 38, email: nil}},
%{"u" => %Node.User{uuid: nil, name: "Davi", age: 35, email: nil}}
]
defmodule App do
use Ex4j.Cypher
def execute do
match(Node.User, as: :u)
|> vertex(Node.Comment, as: :c)
|> edge(Node.Has, as: :h, from: :u, to: :c, type: :out)
|> where(:u, "u.age IN [35,38] AND u.name CONTAINS 'Ti'")
|> where(:c, "c.content CONTAINS 'Article'")
|> where(:h, "h.date > date('2023-12-15')")
|> return(:u)
|> return(:c)
|> run()
end
end
iex> User.execute()
[
%{
"c" => %Node.Comment{uuid: nil, content: "Tiago's Comment"},
"u" => %Node.User{uuid: nil, name: "Tiago", age: 38, email: nil}
},
%{
"c" => %Node.Comment{uuid: nil, content: "Davi's Comment"},
"u" => %Node.User{uuid: nil, name: "Davi", age: 35, email: nil}
}
]
defmodule App do
use Ex4j.Cypher
def execute do
match(Node.User, as: :u)
|> edge(Node.Has, as: :h, from: :u, to: :c, type: :out)
|> vertex(Node.Comment, as: :c)
|> return(:h)
|> run()
end
end
iex> User.execute()
[
%{"h" => %Node.Has{uuid: nil, role: "Tiago's Role"}},
%{"h" => %Node.Has{uuid: nil, role: "Davi's Role"}}
]
defmodule App do
use Ex4j.Cypher
def execute do
match(Node.User, as: :u)
|> edge(Node.Has, as: :h, from: :u, to: :c, type: :out)
|> vertex(Node.Comment, as: :c)
|> return(:u, [:name])
|> return(:c, [:content])
|> run()
end
end
iex> User.execute()
[
%{"c" => %{"content" => "Tiago's Comment"}, "u" => %{"name" => "Tiago"}},
%{"c" => %{"content" => "Davi's Comment"}, "u" => %{"name" => "Davi"}}
]
defmodule App do
use Ex4j.Cypher
def execute do
match(Node.User, as: :u)
|> edge(Node.Has, as: :h, from: :u, to: :c, type: :out)
|> vertex(Node.Comment, as: :c)
|> return(:u, [:name])
|> return(:c, [:content])
|> cypher()
end
end
iex> User.execute()
"MATCH (u:User)-[h:Has]->(c:Comment)\nRETURN c.content,u.name"

You can always execute a query directly like so:

defmodule App do
use Ex4j.Cypher
def execute do
query =
"""
MATCH (n:Comment)
RETURN n
LIMIT 25
"""
run(query)
end
end
iex> App.execute()
{:ok,
%Bolt.Sips.Response{
results: [
%{
"n" => %Bolt.Sips.Types.Node{
id: 2,
properties: %{"text" => "Tiago's Comment"},
labels: ["Comment"]
}
},
%{
"n" => %Bolt.Sips.Types.Node{
id: 3,
properties: %{"content" => "Davi's Comment"},
labels: ["Comment"]
}
},
],
fields: ["n"],
records: [
[
%Bolt.Sips.Types.Node{
id: 2,
properties: %{"text" => "Tiago's Comment"},
labels: ["Comment"]
}
],
[
%Bolt.Sips.Types.Node{
id: 3,
properties: %{"content" => "Davi's Comment"},
labels: ["Comment"]
}
]
],
plan: nil,
notifications: [],
stats: [],
profile: nil,
type: "r",
bookmark: "FB:kcwQOoduCuitRfejxXkLly0aBBiQ"
}}

Plans

License

Ex4j source code is released under Apache License 2.0.

Check NOTICE and LICENSE files for more information.