Clojuresque client to Neo4j database, based upon the bolt protocol.
neo4j-clj is a clojure client library to the Neo4j graph database, relying on the Bolt protocol.
This library provides a clojuresque way to deal with connections, sessions, transactions and Cypher queries.
neo4j-clj is in active use at our own projects, but it's not a feature complete client library in every possible sense.
You might choose to issue new feature requests, or clone the repo to add the feature and create a PR.
We appreciate any help on the open source projects we provide. See Development section below for more info on how to build your own version.
Throughout the examples, we assume you're having a Neo4j instance up and running. See our Neo4j survival guide for help on that.
You can clone our repository and run the example for yourself.
(ns example.core
(:require [neo4j-clj.core :as db])
(:import (java.net URI)))
(def local-db
(db/connect (URI. "bolt://localhost:7687")
"neo4j"
"YA4jI)Y}D9a+y0sAj]T5s|C5qX!w.T0#u<be5w6X[p"))
(db/defquery create-user
"CREATE (u:user $user)")
(db/defquery get-all-users
"MATCH (u:user) RETURN u as user")
(defn -main
"Example usage of neo4j-clj"
[& args]
;; Using a session
(with-open [session (db/get-session local-db)]
(create-user session {:user {:first-name "Luke" :last-name "Skywalker"}}))
;; Using a transaction
(db/with-transaction local-db tx
;; print, as query result has to be consumed inside session
(println (get-all-users tx))))
If you do run stuff on the REPL, make sure to consume the results from the query before closing session/transaction - like I did with
println. Use doall
or whatever suits you, because otherwise you'll run into issues.
First of all, you need to connect to the database.
(db/connect (URI. "bolt://localhost:7687") ; bolt API URI
"neo4j" ; username
"YA4jI)Y}D9a+y0sAj]T5s|C5qX!w.T0#u<be5w6X[p" ; password
)
Everything on the Bolt protocol is organized in a session. neo4j-clj uses the
standard with-open
to handle lexical-scoped sessions.
(with-open [session (db/get-session local-db)]
;; ... use session here
)
neo4j-clj can also handle transactions (on top of sessions) by utilizing a
with-transaction
-macro.
(db/with-transaction local-db tx
;; run queries within transaction
)
local-db
is the Neo4j instance you're connected to.
tx
is the symbol the transaction is bound to. You might use
the tx
transaction for query functions.
You do not need to have everything inside a with-transaction
block,
as all statements in a session are wrapped in transactions anyhow
(see Sessions and Transactions).
You fail transactions by throwing an Exception.
You create new queries using defquery
. We strongly belief that using Cypher
as a String directly in your code comes with a couple of advantages.
Both paramaters and return values are transformed from Clojure datastructures into Neo4j types. That transformation is done transparently, so you won't have to deal with that.
For some / most queries you need to supply some parameters. Just include a
variable into your Cypher query using the $
notation or the {}
notation,
and provide a Clojure map of variables to the query:
(db/defquery users-by-age "MATCH (u:User {age: $age}) RETURN u as user")
(users-by-age tx {:age 42})
is equivalent to
(db/defquery users-by-age "MATCH (u:User {age: {age}}) RETURN u as user")
(users-by-age tx {:age 42})
A query will always return a collection. Here, it will return ({:user {...}})
,
i.e. a collection of maps with the user-key populated.
Think of it as a map per result row.
You can have more than one parameter in your map:
(db/defquery create-user "CREATE (u:User {name: $name, age: $age})")
(create-user tx {:name "..." :age 42})
And you can also nest paramters:
(db/defquery create-user "CREATE (u:User $user)")
(create-user tx {:user {:name "..." :age 42}})
and un-nest them as necessary:
(db/defquery create-user "CREATE (u:User {name : {user}.name})")
(create-user tx {:user {:name "..." :age 42}})
The result of a query is a list, even if your query returns a single item. Each "result row" is one map in that sequence returned.
The values are provided using the ususal Clojure datastructures, no need to wrap/unwrap stuff. That's handled for you by neo4j-clj.
I'd like to elaborarte a little on the handling of node/edge labels. You can run a query labels like this:
(db/defquery get-users "MATCH (u:User) RETURN u as user,labels(u) as labels")
(get-users tx)
and this will return a collection of maps with two keys: user
and labels
where labels
are a collection of labels
associated with the nodes. At the moment, labels
are not sets! It's up to you to convert collections into appropriate
types yourself (because we just do not know on the neo4j-clj level), and this is especially true for labels
.
neo4j-clj comes equipped with support for Joplin for datastore migration and seeding.
As we do not force our users into Joplin dependencies, you have to add [joplin.core "0.3.10"] to your projects dependencies yourself.
Neo4j cannot cope with dashes really well (you need to escape them), so the Clojure kebab-case style is not really acceptable.
We appreciate any help on our open source projects. So, feel free to fork and clone this repository.
We use leiningen. So, after you've cloned your repo you should be able to run 'lein test' to run the tests sucessfully.
For testing purposes, we provide access to the Neo4j in-memory database feature, which we address using the bolt protocol.
To do so, you need to add a dependency to [org.neo4j.test/neo4j-harness "4.0.0"]
to your project and require the
neo4j-clj.in-memory
namespace.
(def test-db
(neo4j-clj.in-memory/create-in-memory-connection))
;; instead of (neo4j/connect url user password)
So, you can easily run tests on your stuff without requiring an external database.