Skip to content

Instantly share code, notes, and snippets.

@venkatd
Last active April 6, 2016 04:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save venkatd/f8c13500f45702728d1ad14d3f85bea7 to your computer and use it in GitHub Desktop.
Save venkatd/f8c13500f45702728d1ad14d3f85bea7 to your computer and use it in GitHub Desktop.
defmodule Neo.Query do
defstruct [
piped_queries: [],
optional_queries: [],
labels: [],
match_clauses: [],
merge_clauses: [],
where_clauses: [],
create_clauses: [],
set_clauses: [],
delete_clauses: [],
with_columns: [],
return_columns: [],
order_clause: nil,
limit: nil
]
alias Neo.Query, as: Query
alias Neo.Cypher, as: Cypher
def new_query do
%Query{}
end
def match(query = %Query{}, nil) do
query
end
def match(query = %Query{}, labels) when is_list(labels) do
new_labels = Keyword.merge(query.labels, labels)
%Query{ query | labels: new_labels }
end
def match(query = %Query{}, cypher, params \\ []) when is_binary(cypher) and is_list(params) do
new_match_clauses = query.match_clauses ++ [ %Cypher{value: cypher, params: params} ]
%Query{ query | match_clauses: new_match_clauses }
end
def where(query = %Query{}, cypher, params \\ []) when is_list(params) and is_list(params) do
where_clauses = query.where_clauses ++ [ %Cypher{value: cypher, params: params} ]
%Query{ query | where_clauses: where_clauses }
end
def optional_match(base_query = %Query{}, optional_query = %Query{}) do
new_optional_queries = base_query.optional_queries ++ [optional_query]
%Query{ base_query | optional_queries: new_optional_queries }
end
def merge(query = %Query{}, cypher, params \\ []) do
new_merge_clauses = query.merge_clauses ++ [ %Cypher{value: cypher, params: params} ]
%Query{ query | merge_clauses: new_merge_clauses }
end
def create(query = %Query{}, cypher, params \\ []) when is_list(params) do
new_merge_clauses = query.create_clauses ++ [ %Cypher{value: cypher, params: params} ]
%Query{ query | create_clauses: new_merge_clauses }
end
def set(query = %Query{}, cypher, params \\ []) when is_list(params) do
new_set_clauses = query.set_clauses ++ [ %Cypher{value: cypher, params: params} ]
%Query{ query | set_clauses: new_set_clauses }
end
def delete(query = %Query{}, cypher) do
new_delete_clauses = query.delete_clauses ++ [ %Cypher{value: cypher} ]
%Query{ query | delete_clauses: new_delete_clauses }
end
def return(query = %Query{}, values) when is_list(values) do
new_return_columns = Enum.uniq(query.return_columns ++ values)
%Query{ query | return_columns: new_return_columns }
end
def return(query = %Query{}, value), do: return(query, [value])
def as(query = %Query{piped_queries: piped_queries}, values) when is_list(values) do
piped_queries = query.piped_queries
query = %{query | piped_queries: [], with_columns: Enum.uniq(query.with_columns ++ values), return_columns: []}
%{new_query | piped_queries: piped_queries ++ [query]}
end
def order(query = %Query{}, order_clause) do
%Query{ query | order_clause: order_clause }
end
def limit(query = %Query{}, count) do
%Query{ query | limit: count }
end
end
defmodule Neo.QueryTest do
use ExUnit.Case
import Neo.Query
import Neo.QueryClause
alias Neo.Cypher, as: Cypher
# import Turtle.TestHelpers, only: [compare: 2]
test "generates a simple match + return query" do
query = new_query
|> match(yo: "yeah/man", dude: "nah")
|> return([:yo])
%Cypher{value: cypher, params: params} = to_cypher(query)
expected_cypher = """
MATCH
(yo:`yeah/man`),
(dude:`nah`)
RETURN yo
""" |> String.strip
assert cypher == expected_cypher
assert params == []
end
test "aliasing return values" do
query = new_query
|> match(main_user: :user, secondary_user: :user)
|> return(main_user: :start_user, secondary_user: :end_user)
expected_cypher = """
MATCH
(main_user:`user`),
(secondary_user:`user`)
RETURN main_user AS start_user, secondary_user AS end_user
""" |> String.strip
%Cypher{value: cypher, params: params} = to_cypher(query)
assert cypher == expected_cypher
assert params == []
end
test "piping queries together" do
# return clause is ignored once you pipe with as(...)
query = new_query
|> match(user: :user)
|> where("user.id = {user_id}", user_id: "u123")
|> return([:user])
|> as(user: :start_point)
|> match(child: :card)
|> match("(start_point)-[:has_child]->(child)")
|> return([:child, :start_point])
expected_cypher = """
MATCH
(user:`user`)
WHERE
user.id = {user_id}
WITH user AS start_point
MATCH
(child:`card`),
(start_point)-[:has_child]->(child)
RETURN child, start_point
""" |> String.strip
%Cypher{value: cypher, params: params} = to_cypher(query)
assert cypher == expected_cypher
assert params == [user_id: "u123"]
end
test "match with multiple types of labels" do
query = new_query
|> match(yo: "yeah/man", dude: [:nah, :yo], string: "cheese")
|> return([:yo])
%Cypher{value: cypher} = to_cypher(query)
expected_cypher = """
MATCH
(yo:`yeah/man`),
(dude:`nah`:`yo`),
(string:`cheese`)
RETURN yo
""" |> String.strip
assert cypher == expected_cypher
end
test "adding set clauses" do
query = new_query
|> match(user: "user")
|> where("user.id = {id}", id: 123)
|> return(:user)
|> set("user.email = {email}", email: "john@hotmail.com")
%Cypher{value: cypher, params: params} = to_cypher(query)
expected_cypher = """
MATCH
(user:`user`)
WHERE
user.id = {id}
SET
user.email = {email}
RETURN user
""" |> String.strip
assert cypher == expected_cypher
assert params == [id: 123, email: "john@hotmail.com"]
end
test "ordering queries" do
query = new_query
|> match(user: "user")
|> return(:user)
|> order("user.age")
%Cypher{value: cypher} = to_cypher(query)
expected_cypher = """
MATCH
(user:`user`)
RETURN user
ORDER BY user.age
""" |> String.strip
assert cypher == expected_cypher
end
test "adding a limit" do
query = new_query
|> match(user: "user")
|> return(:user)
|> limit(5)
|> order("user.age")
%Cypher{value: cypher} = to_cypher(query)
expected_cypher = """
MATCH
(user:`user`)
RETURN user
ORDER BY user.age
LIMIT 5
""" |> String.strip
assert cypher == expected_cypher
end
test "match and where conditions" do
query = new_query
|> match(user: "user", enemy: "user")
|> where("user.id = {user_id}", user_id: "abc123")
|> where("enemy.age > {enemy_age}", enemy_age: 40)
|> match("(user)-[r:hates]->(enemy)")
|> match("(enemy)-[:loves]->(user)")
|> return([:user, :r, :enemy])
%Cypher{value: cypher, params: params} = to_cypher(query)
expected_cypher = """
MATCH
(user:`user`),
(enemy:`user`),
(user)-[r:hates]->(enemy),
(enemy)-[:loves]->(user)
WHERE
user.id = {user_id}
AND enemy.age > {enemy_age}
RETURN user, r, enemy
""" |> String.strip
assert cypher == expected_cypher
assert params == [user_id: "abc123", enemy_age: 40]
end
test "where conditions with no params" do
query = new_query
|> match(user: "user")
|> where("user.age = 99")
|> return(:user)
%Cypher{value: cypher, params: params} = to_cypher(query)
expected_cypher = """
MATCH
(user:`user`)
WHERE
user.age = 99
RETURN user
""" |> String.strip
assert cypher == expected_cypher
assert params == []
end
test "create statement" do
query = new_query
|> create("(n {attrs})", attrs: %{name: "bob", age: 25})
|> return(:n)
%Cypher{value: cypher, params: params} = to_cypher(query)
expected_cypher = """
CREATE
(n {attrs})
RETURN n
""" |> String.strip
assert cypher == expected_cypher
assert params == [attrs: %{name: "bob", age: 25}]
end
test "delete clause" do
query = new_query
|> match(user: "user")
|> where("user.id = {user_id}", user_id: "abc123")
|> delete("user")
%Cypher{value: cypher, params: params} = to_cypher(query)
expected_cypher = """
MATCH
(user:`user`)
WHERE
user.id = {user_id}
DELETE
user
""" |> String.strip
assert cypher == expected_cypher
assert params == [user_id: "abc123"]
end
test "optional matches" do
user_query = new_query
|> match(user: "user")
|> where("user.id = {user_id}", user_id: "abc123")
|> return(:user)
friend_query = new_query
|> match(friend: "user")
|> match("(user)-[:friends_with]->(friend)")
|> where("friend.status = {friend_status}", friend_status: "active")
|> return(:friend)
combined_query = optional_match(user_query, friend_query)
%Cypher{value: cypher, params: params} = to_cypher(combined_query)
expected_cypher = """
MATCH
(user:`user`)
WHERE
user.id = {user_id}
OPTIONAL MATCH
(friend:`user`),
(user)-[:friends_with]->(friend)
WHERE
friend.status = {friend_status}
RETURN user, friend
""" |> String.strip
assert cypher == expected_cypher
assert params == [user_id: "abc123", friend_status: "active"]
end
test "merge clause" do
params = [
create_props: [id: "123", priority: 0],
match_props: [priority: 10]
]
merge_query = new_query
|> match(user: :user, card: :card)
|> merge("(user)-[r:member_of]->(card) ON CREATE SET r += {create_props} ON MATCH SET r += {match_props}", params)
expected_cypher = """
MATCH
(user:`user`),
(card:`card`)
MERGE
(user)-[r:member_of]->(card) ON CREATE SET r += {create_props} ON MATCH SET r += {match_props}
""" |> String.strip
%Cypher{value: cypher, params: params} = to_cypher(merge_query)
assert cypher == expected_cypher
assert params == [create_props: [id: "123", priority: 0], match_props: [priority: 10]]
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment