Current section

Files

Jump to
ex_cypher lib ex_cypher.ex
Raw

lib/ex_cypher.ex

defmodule ExCypher do
@moduledoc """
A DSL to make it easy to integrate other apps to Neo4J applications.
This module provide a clean API to build queries using CYPHER query language,
which is largely used by Neo4j
### Usage:
First of all, add `import ExCpyher` to the top of your module, so that
`cypher` macro is available to your functions, then you can run it to generate
you cypher queries.
### Examples:
##### MATCH statements:
1. Matching nodes without returning a specific property
```
iex> cypher do: match(node(:n))
"MATCH (n)"
iex> cypher do: match(node(:p, [:Person]))
"MATCH (p:Person)"
iex> cypher do
...> match(node(:p, [:Person], %{name: "bob"}))
...> end
~S[MATCH (p:Person {"name":"bob"})]
```
We already support associations too:
```
iex> cypher do
...> match node(:p, [:Person]) -- node(:c, [:Company])
...> end
"MATCH (p:Person)--(c:Company)"
iex> cypher do
...> match node(:p, [:Person]) -- rel(:WORKS_IN) -- node(:c, [:Company])
...> return :p
...> end
"MATCH (p:Person)-[WORKS_IN]-(c:Company) RETURN p"
```
Can use arrow-syntax to represent directed associations, but must use
parenthesis arround the `match` arguments so that elixir correctly
builds the AST with the correct precedence structure
```
iex> cypher do
...> match (node(:p, [:Person]) -- rel(:WORKS_IN) -> node(:c, [:Company]))
...> end
"MATCH (p:Person)-[WORKS_IN]->(c:Company)"
iex> cypher do
...> match (node(:c, [:Company]) <- rel(:WORKS_IN) -- node(:p, [:Person]))
...> end
"MATCH (c:Company)<-[WORKS_IN]-(p:Person)"
```
##### ORDERing and LIMITing queries:
You can order and/or limit your query results with `order` and `limit`
commands:
```
iex> cypher do
...> match node(:s, [:Sharks])
...> order s.name
...> limit 10
...> return s.name, s.population
...> end
"MATCH (s:Sharks) ORDER BY s.name LIMIT 10 RETURN s.name, s.population"
```
Also, you can choose the ordering direction of your matched nodes with a
tuple syntax:
```
iex> cypher do
...> match node(:s, [:Sharks])
...> order {s.name, :asc}, {s.age, :desc}
...> return :s
...> end
"MATCH (s:Sharks) ORDER BY s.name ASC, s.age DESC RETURN s"
```
##### CREATE and MERGE stuff
In order to create new nodes into your database, you can use the
following syntax:
```
iex> cypher do
...> create node(:p, [:Player], %{nick: "like4boss", score: "100"})
...> return p.name
...> end
~S[CREATE (p:Player {"nick":"like4boss","score":"100"}) RETURN p.name]
```
However, if you want to match pre-existing nodes instead of creating new
ones, you can use merge, as follows:
```
iex> cypher do
...> merge node(:p, [:Player], %{nick: "like4boss"})
...> merge node(:p2, [:Player], %{nick: "marioboss"})
...> return p.name
...> end
~S|MERGE (p:Player {"nick":"like4boss"}) MERGE (p2:Player {"nick":"marioboss"}) RETURN p.name|
iex> cypher do
...> merge node(:p, [:Player], %{nick: "like4boss"})
...> merge node(:p2, [:Player], %{nick: "marioboss"})
...> merge (node(:p) -- rel([:IN_LOBBY]) -> node(:p2))
...> return p.name
...> end
~S|MERGE (p:Player {"nick":"like4boss"}) MERGE (p2:Player {"nick":"marioboss"}) MERGE (p)-[:IN_LOBBY]->(p2) RETURN p.name|
```
"""
alias ExCypher.{Buffer, Statement}
@supported_statements [:match, :create, :merge, :return, :where, :pipe_with, :order, :limit]
@doc """
Wraps contents of a Cypher query and returns the query string.
"""
defmacro cypher(do: block) do
quote do
{:ok, var!(buffer, ExCypher)} = Buffer.new_query()
unquote(Macro.prewalk(block, &parse/1))
query = Buffer.generate_query(var!(buffer, ExCypher))
Buffer.stop_buffer(var!(buffer, ExCypher))
query
end
end
@doc """
Saves a query line into the query buffer (where the other query lines)
are being kept isolated.
Since those query lines break with elixir's language syntax, we cannot
call `unquote` on them, thus making the macro approach necessary.
Also, `buffer` pid is on another scope, and cannot be accessed directly
by the `parse` function.
"""
defmacro command(args) do
quote do
Buffer.put_buffer(var!(buffer, ExCypher), unquote(args))
end
end
@doc """
Parses each command-line from the `cypher` macro's block
"""
def parse({command, _ctx, args}) when command in @supported_statements do
params = Statement.parse(command, args)
quote do
command(unquote(params))
end
end
def parse(stmt), do: stmt
end