Last active
April 6, 2016 04:37
-
-
Save venkatd/f8c13500f45702728d1ad14d3f85bea7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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