neo4j-cypher 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +15 -0
- data/README.rdoc +71 -0
- data/lib/neo4j-cypher.rb +47 -0
- data/lib/neo4j-cypher/argument.rb +39 -0
- data/lib/neo4j-cypher/clause.rb +76 -0
- data/lib/neo4j-cypher/clause_list.rb +101 -0
- data/lib/neo4j-cypher/context.rb +411 -0
- data/lib/neo4j-cypher/create.rb +99 -0
- data/lib/neo4j-cypher/match.rb +343 -0
- data/lib/neo4j-cypher/mixins.rb +43 -0
- data/lib/neo4j-cypher/node_var.rb +65 -0
- data/lib/neo4j-cypher/operator.rb +130 -0
- data/lib/neo4j-cypher/predicate.rb +64 -0
- data/lib/neo4j-cypher/property.rb +106 -0
- data/lib/neo4j-cypher/rel_var.rb +128 -0
- data/lib/neo4j-cypher/result.rb +43 -0
- data/lib/neo4j-cypher/result_wrapper.rb +47 -0
- data/lib/neo4j-cypher/return.rb +124 -0
- data/lib/neo4j-cypher/root.rb +182 -0
- data/lib/neo4j-cypher/start.rb +88 -0
- data/lib/neo4j-cypher/version.rb +5 -0
- data/lib/neo4j-cypher/where.rb +16 -0
- data/lib/neo4j-cypher/with.rb +41 -0
- data/neo4j-cypher.gemspec +26 -0
- metadata +81 -0
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
== neo4j-cypher {<img src="https://secure.travis-ci.org/andreasronge/neo4j-cypher.png" />}[http://travis-ci.org/andreasronge/neo4j-cypher]
|
2
|
+
|
3
|
+
A Ruby DSL for the Neo4j Cypher query language for both MRI and JRuby.
|
4
|
+
The JRuby neo4j-core gem's cypher dsl has been moved to this gem.
|
5
|
+
|
6
|
+
=== Docs
|
7
|
+
|
8
|
+
See {Neo4j Wiki Cypher}[https://github.com/andreasronge/neo4j/wiki/Neo4j::Core-Cypher]
|
9
|
+
|
10
|
+
See the rspecs ! (> 99% test coverage)
|
11
|
+
|
12
|
+
|
13
|
+
=== Random Examples
|
14
|
+
|
15
|
+
Notice the last value returned is the return value (if possible).
|
16
|
+
Matching relationships can be done with operators: <,>, -, and <=> or with the both, incoming and outgoing methods.
|
17
|
+
|
18
|
+
"START v1=node(3) MATCH v2 = (v1)-[:`r`]->(x) RETURN v2"
|
19
|
+
|
20
|
+
Neo4j::Cypher.query do
|
21
|
+
node(3) > :r > :x
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
"START v2=node(1) MATCH (v2)-[v1:`knows`]->(other) WHERE v1.since > 1994 and other.name = "foo" RETURN other"
|
26
|
+
|
27
|
+
Neo4j::Cypher.query do
|
28
|
+
node(1) > (rel(:knows)[:since] > 1994) > (node(:other)[:name] == 'foo'); :other
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
"START v2=node(1) MATCH (v2)-[v1:`friends`]->(v3) WHERE (v1.since = 1994) RETURN v3"
|
33
|
+
|
34
|
+
Neo4j::Cypher.query do
|
35
|
+
node(1).outgoing(rel(:friends).where{|r| r[:since] == 1994})
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
"START v1=node(2,3,4,1) RETURN count(v1.property?"
|
40
|
+
|
41
|
+
Neo4j::Cypher.query do
|
42
|
+
node(2, 3, 4, 1)[:property?].count
|
43
|
+
end
|
44
|
+
|
45
|
+
"START v1=node(42) MATCH (v1)-[:`favorite`]->(stuff)<-[:`favorite`]-(person) WHERE not((v1)-[:`friend`]-(person)) RETURN person.name,count(stuff) ORDER BY count(stuff) DESC"
|
46
|
+
|
47
|
+
Neo4j::Cypher.query do
|
48
|
+
node(42).where_not { |m| m - :friend - :person } > :favorite > :stuff < :favorite < :person
|
49
|
+
ret(node(:person)[:name], count(:stuff).desc)
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
== Complex Example
|
54
|
+
|
55
|
+
"START n=node(42) MATCH (n)-[r]->(m) WITH n,collect(type(r)) as out_types,collect(m) as outgoing MATCH (n)<-[r]-(m) RETURN n,outgoing,out_types,collect(m) as incoming,collect(type(r)) as in_types"
|
56
|
+
|
57
|
+
Neo4j::Cypher.query do
|
58
|
+
n = node(42).as(:n)
|
59
|
+
r = rel('r')
|
60
|
+
m = node(:m)
|
61
|
+
rel_types = r.rel_type.collect
|
62
|
+
end_nodes = m.collect
|
63
|
+
|
64
|
+
n.with_match(rel_types.as(:out_types), end_nodes.as(:outgoing)) { |n, _, _| n < r < m } > r > m
|
65
|
+
|
66
|
+
ret([n,
|
67
|
+
:outgoing,
|
68
|
+
:out_types,
|
69
|
+
end_nodes.as(:incoming),
|
70
|
+
rel_types.as(:in_types)])
|
71
|
+
end
|
data/lib/neo4j-cypher.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'neo4j-cypher/version'
|
2
|
+
|
3
|
+
require 'neo4j-cypher/context'
|
4
|
+
require 'neo4j-cypher/mixins'
|
5
|
+
require 'neo4j-cypher/clause'
|
6
|
+
require 'neo4j-cypher/clause_list'
|
7
|
+
require 'neo4j-cypher/argument'
|
8
|
+
require 'neo4j-cypher/root'
|
9
|
+
require 'neo4j-cypher/start'
|
10
|
+
require 'neo4j-cypher/create'
|
11
|
+
require 'neo4j-cypher/match'
|
12
|
+
require 'neo4j-cypher/return'
|
13
|
+
require 'neo4j-cypher/node_var'
|
14
|
+
require 'neo4j-cypher/rel_var'
|
15
|
+
require 'neo4j-cypher/property'
|
16
|
+
require 'neo4j-cypher/predicate'
|
17
|
+
require 'neo4j-cypher/with'
|
18
|
+
require 'neo4j-cypher/operator'
|
19
|
+
require 'neo4j-cypher/where'
|
20
|
+
require 'neo4j-cypher/result_wrapper'
|
21
|
+
require 'neo4j-cypher/result'
|
22
|
+
|
23
|
+
module Neo4j
|
24
|
+
module Cypher
|
25
|
+
|
26
|
+
# Creates a Cypher DSL query.
|
27
|
+
# To create a new cypher query you must initialize it either an String or a Block.
|
28
|
+
#
|
29
|
+
# @example <tt>START n0=node(3) MATCH (n0)--(x) RETURN x</tt> same as
|
30
|
+
# Cypher.query { start n = node(3); match n <=> :x; ret :x }.to_s
|
31
|
+
#
|
32
|
+
# @example <tt>START n0=node(3) MATCH (n0)-[:`r`]->(x) RETURN r</tt> same as
|
33
|
+
# Cypher.query { node(3) > :r > :x; :r }
|
34
|
+
#
|
35
|
+
# @example <tt>START n0=node(3) MATCH (n0)-->(x) RETURN x</tt> same as
|
36
|
+
# Cypher.query { node(3) >> :x; :x }
|
37
|
+
#
|
38
|
+
# @param args the argument for the dsl_block
|
39
|
+
# @yield the block which will be evaluated in the context of this object in order to create an Cypher Query string
|
40
|
+
# @yieldreturn [Return, Object] If the return is not an instance of Return it will be converted it to a Return object (if possible).
|
41
|
+
# @return [Cypher::Result]
|
42
|
+
def self.query(*args, &dsl_block)
|
43
|
+
Result.new(*args, &dsl_block)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Cypher
|
3
|
+
|
4
|
+
class Argument
|
5
|
+
include Referenceable
|
6
|
+
include Clause
|
7
|
+
|
8
|
+
def initialize(clause_list, expr, var_name)
|
9
|
+
super(clause_list, :argument, EvalContext)
|
10
|
+
var_name ||= self.var_name
|
11
|
+
@expr = var_name
|
12
|
+
as_alias(var_name)
|
13
|
+
@return_value = (expr != var_name.to_s) ? "#{expr} as #{var_name}" : expr
|
14
|
+
end
|
15
|
+
|
16
|
+
def return_value
|
17
|
+
@return_value
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.new_arg_from_clause(clause)
|
21
|
+
Argument.new(clause.clause_list, clause.return_value, clause.as_alias? && clause.var_name)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.new_arg_from_string(string, clause_list)
|
25
|
+
Argument.new(clause_list, string.to_s, string)
|
26
|
+
end
|
27
|
+
|
28
|
+
class EvalContext
|
29
|
+
include Context
|
30
|
+
include Comparable
|
31
|
+
include MathOperator
|
32
|
+
include MathFunctions
|
33
|
+
include PredicateMethods
|
34
|
+
include Aggregate
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Cypher
|
3
|
+
|
4
|
+
# Responsible for order of the clauses
|
5
|
+
# Does expect a #clause method when included
|
6
|
+
module Clause
|
7
|
+
|
8
|
+
ORDER = [:start, :match, :create, :where, :with, :foreach, :set, :delete, :return, :order_by, :skip, :limit]
|
9
|
+
NAME = {:start => 'START', :create => 'CREATE', :match => 'MATCH', :where => "WHERE", :with => 'WITH',
|
10
|
+
:return => 'RETURN', :order_by => 'ORDER BY', :skip => 'SKIP', :limit => 'LIMIT', :set => 'SET',
|
11
|
+
:delete => 'DELETE', :foreach => 'FOREACH'}
|
12
|
+
|
13
|
+
attr_accessor :clause_type, :clause_list, :eval_context, :expr, :insert_order
|
14
|
+
|
15
|
+
def initialize(clause_list, clause_type, eval_context = Context::Empty)
|
16
|
+
@clause_type = clause_type
|
17
|
+
@clause_list = clause_list
|
18
|
+
if eval_context.is_a?(Class)
|
19
|
+
@eval_context = eval_context.new(self)
|
20
|
+
else
|
21
|
+
@eval_context = eval_context
|
22
|
+
end
|
23
|
+
self.insert_order = 0
|
24
|
+
clause_list.insert(self)
|
25
|
+
end
|
26
|
+
|
27
|
+
def <=>(other)
|
28
|
+
clause_position == other.clause_position ? insert_order <=> other.insert_order : clause_position <=> other.clause_position
|
29
|
+
end
|
30
|
+
|
31
|
+
def clause_position
|
32
|
+
valid_clause?
|
33
|
+
ORDER.find_index(clause_type)
|
34
|
+
end
|
35
|
+
|
36
|
+
def valid_clause?
|
37
|
+
raise "Unknown clause_type '#{clause_type}' on #{self}" unless ORDER.include?(clause_type)
|
38
|
+
end
|
39
|
+
|
40
|
+
def separator
|
41
|
+
','
|
42
|
+
end
|
43
|
+
|
44
|
+
def match_value=(mv)
|
45
|
+
@match_value = mv
|
46
|
+
end
|
47
|
+
|
48
|
+
def match_value
|
49
|
+
@match_value || expr || var_name
|
50
|
+
end
|
51
|
+
|
52
|
+
# Used in return clause to generate the last part of the return cypher clause string
|
53
|
+
def return_value
|
54
|
+
var_name
|
55
|
+
end
|
56
|
+
|
57
|
+
def prefix
|
58
|
+
NAME[clause_type]
|
59
|
+
end
|
60
|
+
|
61
|
+
def create_clause_args_for(args)
|
62
|
+
args.map do |arg|
|
63
|
+
case arg
|
64
|
+
when Neo4j::Cypher::ReturnItem::EvalContext, Neo4j::Cypher::Property::EvalContext
|
65
|
+
Argument.new_arg_from_clause(arg.clause)
|
66
|
+
when String, Symbol
|
67
|
+
Argument.new_arg_from_string(arg, clause_list)
|
68
|
+
else
|
69
|
+
arg.clause
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Cypher
|
3
|
+
|
4
|
+
class ClauseList
|
5
|
+
attr_accessor :variables
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
def initialize(variables = [])
|
9
|
+
@variables = variables
|
10
|
+
@clause_list = []
|
11
|
+
@insert_order = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def empty?
|
15
|
+
!first
|
16
|
+
end
|
17
|
+
|
18
|
+
def include?(clause_type)
|
19
|
+
@clause_list.find { |c| c.clause_type == clause_type }
|
20
|
+
end
|
21
|
+
|
22
|
+
def each
|
23
|
+
@clause_list.each { |c| yield c }
|
24
|
+
end
|
25
|
+
|
26
|
+
def push
|
27
|
+
raise "Only support stack of depth 2" if @old_clause_list
|
28
|
+
@old_clause_list = @clause_list
|
29
|
+
@clause_list = []
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def pop
|
34
|
+
@clause_list = @old_clause_list
|
35
|
+
@clause_list.sort!
|
36
|
+
@old_clause_list = nil
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def insert(clause)
|
41
|
+
ctype = clause.clause_type
|
42
|
+
|
43
|
+
if Clause::ORDER.include?(ctype)
|
44
|
+
# which list should we add the cluase to, the root or the sub list ?
|
45
|
+
# ALl the start and return clauses should move to the clause_list
|
46
|
+
c = (@old_clause_list && (ctype == :start || ctype == :return)) ? @old_clause_list : @clause_list
|
47
|
+
c << clause
|
48
|
+
@insert_order += 1
|
49
|
+
clause.insert_order = @insert_order
|
50
|
+
c.sort!
|
51
|
+
end
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def last
|
56
|
+
@clause_list.last
|
57
|
+
end
|
58
|
+
|
59
|
+
def delete(clause_or_context)
|
60
|
+
c = clause_or_context.respond_to?(:clause) ? clause_or_context.clause : clause_or_context
|
61
|
+
@clause_list.delete(c)
|
62
|
+
end
|
63
|
+
|
64
|
+
#def debug
|
65
|
+
# puts "ClauseList id: #{object_id}, vars: #{variables.size}"
|
66
|
+
# @clause_list.each_with_index { |c, i| puts " #{i} #{c.clause_type.inspect}, #{c.class} id: #{c.object_id} order #{c.insert_order}" }
|
67
|
+
#end
|
68
|
+
|
69
|
+
def create_variable(var)
|
70
|
+
raise "Already included #{var}" if @variables.include?(var)
|
71
|
+
@variables << var
|
72
|
+
"v#{@variables.size}".to_sym
|
73
|
+
end
|
74
|
+
|
75
|
+
def group_by_clause
|
76
|
+
prev_clause = nil
|
77
|
+
inject([]) do |memo, clause|
|
78
|
+
memo << [] if clause.clause_type != prev_clause
|
79
|
+
prev_clause = clause.clause_type
|
80
|
+
memo.last << clause
|
81
|
+
memo
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def join_group(list)
|
86
|
+
list.map { |c| c.to_cypher }.join(list.first.separator)
|
87
|
+
end
|
88
|
+
|
89
|
+
def to_cypher
|
90
|
+
# Sub lists, like in with clause should not have a clause prefix like WHERE or MATCH
|
91
|
+
group_by_clause.map { |list| "#{prefix(list)}#{join_group(list)}" }.join(' ')
|
92
|
+
end
|
93
|
+
|
94
|
+
def prefix(list)
|
95
|
+
@old_clause_list && ![:set, :delete, :create].include?(list.first.clause_type) ? '' : "#{list.first.prefix} "
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
@@ -0,0 +1,411 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Cypher
|
3
|
+
module Context
|
4
|
+
# @return [Neo4j::Cypher:Clause]
|
5
|
+
attr_accessor :clause
|
6
|
+
|
7
|
+
# @param [Neo4j::Cypher:Clause] clause the clause for this eval context
|
8
|
+
def initialize(clause)
|
9
|
+
@clause = clause
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [Array<Neo4j::Cypher:Clause>] the sorted clause list
|
14
|
+
def clause_list
|
15
|
+
@clause.clause_list
|
16
|
+
end
|
17
|
+
|
18
|
+
# Used for eval context for a clause which does not allow any more method chaining.
|
19
|
+
class Empty
|
20
|
+
include Context
|
21
|
+
end
|
22
|
+
|
23
|
+
module Alias
|
24
|
+
# Typically used in a WITH statement for a count.as(:stuff) or node(42).as(:foo)
|
25
|
+
def as(name)
|
26
|
+
clause.as_alias(name)
|
27
|
+
self
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module MathFunctions
|
32
|
+
def abs(value=nil)
|
33
|
+
_add_math_func(:abs, value)
|
34
|
+
end
|
35
|
+
|
36
|
+
def sqrt(value=nil)
|
37
|
+
_add_math_func(:sqrt, value)
|
38
|
+
end
|
39
|
+
|
40
|
+
def round(value=nil)
|
41
|
+
_add_math_func(:round, value)
|
42
|
+
end
|
43
|
+
|
44
|
+
def sign(value=nil)
|
45
|
+
_add_math_func(:sign, value)
|
46
|
+
end
|
47
|
+
|
48
|
+
# @private
|
49
|
+
def _add_math_func(name, value)
|
50
|
+
value ||= clause.to_cypher
|
51
|
+
clause_list.delete(clause)
|
52
|
+
ReturnItem.new(clause_list, "#{name}(#{value})").eval_context
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
module MathOperator
|
57
|
+
def -(other)
|
58
|
+
Operator.new(clause_list, clause, other, '-').eval_context
|
59
|
+
end
|
60
|
+
|
61
|
+
def +(other)
|
62
|
+
Operator.new(clause_list, clause, other, '+').eval_context
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
module Comparable
|
67
|
+
def <(other)
|
68
|
+
Operator.new(clause_list, clause, other, '<').eval_context
|
69
|
+
end
|
70
|
+
|
71
|
+
def <=(other)
|
72
|
+
Operator.new(clause_list, clause, other, '<=').eval_context
|
73
|
+
end
|
74
|
+
|
75
|
+
def =~(other)
|
76
|
+
Operator.new(clause_list, clause, other, '=~').eval_context
|
77
|
+
end
|
78
|
+
|
79
|
+
def >(other)
|
80
|
+
Operator.new(clause_list, clause, other, '>').eval_context
|
81
|
+
end
|
82
|
+
|
83
|
+
def >=(other)
|
84
|
+
Operator.new(clause_list, clause, other, '>=').eval_context
|
85
|
+
end
|
86
|
+
|
87
|
+
## Only in 1.9
|
88
|
+
if RUBY_VERSION > "1.9.0"
|
89
|
+
eval %{
|
90
|
+
def !=(other)
|
91
|
+
Operator.new(clause_list, clause, other, "<>").eval_context
|
92
|
+
end }
|
93
|
+
end
|
94
|
+
|
95
|
+
def ==(other)
|
96
|
+
Operator.new(clause_list, clause, other, "=").eval_context
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
module PredicateMethods
|
101
|
+
def all?(&block)
|
102
|
+
self.respond_to?(:iterable)
|
103
|
+
Predicate.new(clause_list, :op => 'all', :clause => :where, :input => input, :iterable => iterable, :predicate_block => block).eval_context
|
104
|
+
end
|
105
|
+
|
106
|
+
def extract(&block)
|
107
|
+
Predicate.new(clause_list, :op => 'extract', :clause => :return_item, :input => input, :iterable => iterable, :predicate_block => block).eval_context
|
108
|
+
end
|
109
|
+
|
110
|
+
def filter(&block)
|
111
|
+
Predicate.new(clause_list, :op => 'filter', :clause => :return_item, :input => input, :iterable => iterable, :predicate_block => block).eval_context
|
112
|
+
end
|
113
|
+
|
114
|
+
def any?(&block)
|
115
|
+
Predicate.new(clause_list, :op => 'any', :clause => :where, :input => input, :iterable => iterable, :predicate_block => block).eval_context
|
116
|
+
end
|
117
|
+
|
118
|
+
def none?(&block)
|
119
|
+
Predicate.new(clause_list, :op => 'none', :clause => :where, :input => input, :iterable => iterable, :predicate_block => block).eval_context
|
120
|
+
end
|
121
|
+
|
122
|
+
def single?(&block)
|
123
|
+
Predicate.new(clause_list, :op => 'single', :clause => :where, :input => input, :iterable => iterable, :predicate_block => block).eval_context
|
124
|
+
end
|
125
|
+
|
126
|
+
def foreach(&block)
|
127
|
+
Predicate.new(clause_list, :op => '', :clause => :foreach, :input => input, :iterable => iterable, :predicate_block => block, :separator => ' FOREACH ').eval_context
|
128
|
+
input.eval_context
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
module Returnable
|
133
|
+
# Specifies a return statement.
|
134
|
+
# Notice that this is not needed, since the last value of the DSL block will be converted into one or more
|
135
|
+
# return statements.
|
136
|
+
# @param [Symbol, #var_name] returns a list of variables we want to return
|
137
|
+
# @return [ReturnItem]
|
138
|
+
def ret(*returns, &block)
|
139
|
+
options = returns.last.is_a?(Hash) ? returns.pop : {}
|
140
|
+
returns = [self] if returns.empty? # return self unless not specified what to return
|
141
|
+
returns = [RootClause::EvalContext.new(self).instance_exec(self, &block)].flatten if block
|
142
|
+
r = Return.new(clause_list, returns, options, &block).eval_context
|
143
|
+
(self.is_a?(RootClause::EvalContext)) ? r : self
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
module Sortable
|
149
|
+
def _sort_args(prop)
|
150
|
+
return self if prop.nil?
|
151
|
+
prop.is_a?(Symbol) ? Property.new(clause, prop).eval_context : prop
|
152
|
+
end
|
153
|
+
|
154
|
+
def asc(*props)
|
155
|
+
@return_item ||= ReturnItem.new(clause_list, self).eval_context
|
156
|
+
@return_item.asc(_sort_args(props.first))
|
157
|
+
end
|
158
|
+
|
159
|
+
def desc(*props)
|
160
|
+
@return_item ||= ReturnItem.new(clause_list, self).eval_context
|
161
|
+
@return_item.desc(_sort_args(props.first))
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
module ReturnOrder
|
167
|
+
def _sort_args(props)
|
168
|
+
return [self] if props.empty?
|
169
|
+
props.map { |p| p.is_a?(Symbol) ? Property.new(clause, p).eval_context : p }
|
170
|
+
end
|
171
|
+
|
172
|
+
# Specifies an <tt>ORDER BY</tt> cypher query
|
173
|
+
# @param [Property] props the properties which should be sorted
|
174
|
+
# @return self
|
175
|
+
def asc(*props)
|
176
|
+
@order_by ||= OrderBy.new(clause_list, self)
|
177
|
+
clause_list.delete(props.first)
|
178
|
+
@order_by.asc(_sort_args(props))
|
179
|
+
self
|
180
|
+
end
|
181
|
+
|
182
|
+
# Specifies an <tt>ORDER BY</tt> cypher query
|
183
|
+
# @param [Property] props the properties which should be sorted
|
184
|
+
# @return self
|
185
|
+
def desc(*props)
|
186
|
+
@order_by ||= OrderBy.new(clause_list, self)
|
187
|
+
clause_list.delete(props.first)
|
188
|
+
@order_by.desc(_sort_args(props))
|
189
|
+
self
|
190
|
+
end
|
191
|
+
|
192
|
+
# Creates a <tt>SKIP</tt> cypher clause
|
193
|
+
# @param [Fixnum] val the number of entries to skip
|
194
|
+
# @return self
|
195
|
+
def skip(val)
|
196
|
+
Skip.new(clause_list, val, self)
|
197
|
+
self
|
198
|
+
end
|
199
|
+
|
200
|
+
# Creates a <tt>LIMIT</tt> cypher clause
|
201
|
+
# @param [Fixnum] val the number of entries to limit
|
202
|
+
# @return self
|
203
|
+
def limit(val)
|
204
|
+
Limit.new(clause_list, val, self)
|
205
|
+
self
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
|
210
|
+
module Aggregate
|
211
|
+
def distinct
|
212
|
+
ReturnItem.new(clause_list, "distinct(#{clause.return_value})").eval_context
|
213
|
+
end
|
214
|
+
|
215
|
+
def count
|
216
|
+
ReturnItem.new(clause_list, "count(#{clause.return_value})").eval_context
|
217
|
+
end
|
218
|
+
|
219
|
+
def sum
|
220
|
+
ReturnItem.new(clause_list, "sum(#{clause.return_value})").eval_context
|
221
|
+
end
|
222
|
+
|
223
|
+
def avg
|
224
|
+
ReturnItem.new(clause_list, "avg(#{clause.return_value})").eval_context
|
225
|
+
end
|
226
|
+
|
227
|
+
def min
|
228
|
+
ReturnItem.new(clause_list, "min(#{clause.return_value})").eval_context
|
229
|
+
end
|
230
|
+
|
231
|
+
def max
|
232
|
+
ReturnItem.new(clause_list, "max(#{clause.return_value})").eval_context
|
233
|
+
end
|
234
|
+
|
235
|
+
def collect
|
236
|
+
ReturnItem.new(clause_list, "collect(#{clause.return_value})").eval_context
|
237
|
+
end
|
238
|
+
|
239
|
+
|
240
|
+
def last
|
241
|
+
ReturnItem.new(clause_list, "last(#{clause.return_value})").eval_context
|
242
|
+
end
|
243
|
+
|
244
|
+
def tail
|
245
|
+
ReturnItem.new(clause_list, "tail(#{clause.return_value})").eval_context
|
246
|
+
end
|
247
|
+
|
248
|
+
def head
|
249
|
+
ReturnItem.new(clause_list, "head(#{clause.return_value})").eval_context
|
250
|
+
end
|
251
|
+
|
252
|
+
end
|
253
|
+
|
254
|
+
module Variable
|
255
|
+
def where(&block)
|
256
|
+
x = block.call(self)
|
257
|
+
clause_list.delete(x)
|
258
|
+
Operator.new(clause_list, x.clause, nil, "").unary!
|
259
|
+
self
|
260
|
+
end
|
261
|
+
|
262
|
+
def where_not(&block)
|
263
|
+
x = block.call(self)
|
264
|
+
clause_list.delete(x)
|
265
|
+
Operator.new(clause_list, x.clause, nil, "not").unary!
|
266
|
+
self
|
267
|
+
end
|
268
|
+
|
269
|
+
def [](prop_name)
|
270
|
+
Property.new(clause, prop_name).eval_context
|
271
|
+
end
|
272
|
+
|
273
|
+
# generates a <tt>ID</tt> cypher fragment.
|
274
|
+
def neo_id
|
275
|
+
Property.new(clause, 'ID').to_function!
|
276
|
+
end
|
277
|
+
|
278
|
+
# generates a <tt>has</tt> cypher fragment.
|
279
|
+
def property?(p)
|
280
|
+
p = Property.new(clause, p)
|
281
|
+
Operator.new(clause_list, p, nil, "has").unary!
|
282
|
+
end
|
283
|
+
|
284
|
+
def []=(p, value)
|
285
|
+
left = Property.new(clause, p).eval_context
|
286
|
+
Operator.new(clause_list, left, value, "=", :set)
|
287
|
+
self
|
288
|
+
end
|
289
|
+
|
290
|
+
def del
|
291
|
+
Delete.new(clause_list, clause)
|
292
|
+
self
|
293
|
+
end
|
294
|
+
|
295
|
+
# Can be used instead of [_classname] == klass
|
296
|
+
def is_a?(klass)
|
297
|
+
return super if klass.class != Class || !klass.respond_to?(:_load_wrapper)
|
298
|
+
self[:_classname] == klass.to_s
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
module Matchable
|
303
|
+
|
304
|
+
def with(*args, &cypher_dsl)
|
305
|
+
With.new(clause_list, :where, self, *args, &cypher_dsl)
|
306
|
+
self
|
307
|
+
end
|
308
|
+
|
309
|
+
def with_match(*args, &cypher_dsl)
|
310
|
+
With.new(clause_list, :match, self, *args, &cypher_dsl)
|
311
|
+
self
|
312
|
+
end
|
313
|
+
|
314
|
+
def create_path(*args, &cypher_dsl)
|
315
|
+
CreatePath.new(clause_list, self, *args, &cypher_dsl)
|
316
|
+
self
|
317
|
+
end
|
318
|
+
|
319
|
+
def create_unique_path(*args, &cypher_dsl)
|
320
|
+
CreatePath.new(clause_list, self, *args, &cypher_dsl).unique!
|
321
|
+
self
|
322
|
+
end
|
323
|
+
|
324
|
+
|
325
|
+
# This operator means related to, without regard to type or direction.
|
326
|
+
# @param [Symbol, #var_name] other either a node (Symbol, #var_name)
|
327
|
+
# @return [MatchRelLeft, MatchNode]
|
328
|
+
def <=>(other)
|
329
|
+
MatchStart.new_match_node(clause, other, :both).eval_context
|
330
|
+
end
|
331
|
+
|
332
|
+
# This operator means outgoing related to
|
333
|
+
# @param [Symbol, #var_name, String] other the relationship
|
334
|
+
# @return [MatchRelLeft, MatchNode]
|
335
|
+
def >(other)
|
336
|
+
MatchStart.new(clause).new_match_rel(other).eval_context
|
337
|
+
end
|
338
|
+
|
339
|
+
# This operator means any direction related to
|
340
|
+
# @param (see #>)
|
341
|
+
# @return [MatchRelLeft, MatchNode]
|
342
|
+
def -(other)
|
343
|
+
MatchStart.new(clause).new_match_rel(other).eval_context
|
344
|
+
end
|
345
|
+
|
346
|
+
# This operator means incoming related to
|
347
|
+
# @param (see #>)
|
348
|
+
# @return [MatchRelLeft, MatchNode]
|
349
|
+
def <(other)
|
350
|
+
MatchStart.new(clause).new_match_rel(other).eval_context
|
351
|
+
end
|
352
|
+
|
353
|
+
# Outgoing relationship to other node
|
354
|
+
# @param [Symbol, #var_name] other either a node (Symbol, #var_name)
|
355
|
+
# @return [MatchRelLeft, MatchNode]
|
356
|
+
def >>(other)
|
357
|
+
MatchStart.new_match_node(clause, other, :outgoing).eval_context
|
358
|
+
end
|
359
|
+
|
360
|
+
# Incoming relationship to other node
|
361
|
+
# @param [Symbol, #var_name] other either a node (Symbol, #var_name)
|
362
|
+
# @return [MatchRelLeft, MatchNode]
|
363
|
+
def <<(other)
|
364
|
+
MatchStart.new_match_node(clause, other, :incoming).eval_context
|
365
|
+
end
|
366
|
+
|
367
|
+
def outgoing(*rel_types)
|
368
|
+
node = _get_or_create_node(rel_types)
|
369
|
+
MatchStart.new(clause).new_match_rels(rel_types).eval_context > node
|
370
|
+
node.eval_context
|
371
|
+
end
|
372
|
+
|
373
|
+
def _get_or_create_node(rel_types)
|
374
|
+
rel_types.last.kind_of?(Matchable) ? rel_types.pop.clause : NodeVar.new(clause.clause_list)
|
375
|
+
end
|
376
|
+
|
377
|
+
def outgoing?(*rel_types)
|
378
|
+
node = _get_or_create_node(rel_types)
|
379
|
+
MatchStart.new(clause).new_match_rels?(rel_types).eval_context > node
|
380
|
+
node.eval_context
|
381
|
+
end
|
382
|
+
|
383
|
+
def incoming(*rel_types)
|
384
|
+
node = _get_or_create_node(rel_types)
|
385
|
+
MatchStart.new(clause).new_match_rels(rel_types).eval_context < node
|
386
|
+
node.eval_context
|
387
|
+
end
|
388
|
+
|
389
|
+
def incoming?(*rel_types)
|
390
|
+
node = _get_or_create_node(rel_types)
|
391
|
+
MatchStart.new(clause).new_match_rels?(rel_types).eval_context < node
|
392
|
+
node.eval_context
|
393
|
+
end
|
394
|
+
|
395
|
+
def both(*rel_types)
|
396
|
+
node = _get_or_create_node(rel_types)
|
397
|
+
MatchStart.new(clause).new_match_rels(rel_types).eval_context - node
|
398
|
+
node.eval_context
|
399
|
+
end
|
400
|
+
|
401
|
+
def both?(*rel_types)
|
402
|
+
node = _get_or_create_node(rel_types)
|
403
|
+
MatchStart.new(clause).new_match_rels?(rel_types).eval_context - node
|
404
|
+
node.eval_context
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|