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
@@ -0,0 +1,65 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Cypher
|
3
|
+
|
4
|
+
# Represents an unbound node variable used in match statements
|
5
|
+
class NodeVar
|
6
|
+
include Clause
|
7
|
+
include Referenceable
|
8
|
+
|
9
|
+
def initialize(clause_list, var_name = nil)
|
10
|
+
super(clause_list, :node_var, EvalContext)
|
11
|
+
@var_name = var_name
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.as_var(clause_list, something)
|
15
|
+
if something.is_a?(Symbol) || something.is_a?(String)
|
16
|
+
NodeVar.new(clause_list, something)
|
17
|
+
elsif something.respond_to?(:clause)
|
18
|
+
something.clause
|
19
|
+
else
|
20
|
+
something
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def expr
|
25
|
+
var_name
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [String] a cypher string for this node variable
|
29
|
+
def to_cypher
|
30
|
+
var_name
|
31
|
+
end
|
32
|
+
|
33
|
+
def return_value
|
34
|
+
to_cypher
|
35
|
+
end
|
36
|
+
|
37
|
+
class EvalContext
|
38
|
+
include Context
|
39
|
+
include Variable
|
40
|
+
include Matchable
|
41
|
+
include Returnable
|
42
|
+
include Aggregate
|
43
|
+
include Alias
|
44
|
+
|
45
|
+
|
46
|
+
def initialize(clause)
|
47
|
+
super
|
48
|
+
end
|
49
|
+
|
50
|
+
def new(props = nil)
|
51
|
+
clause.clause_list.delete(clause)
|
52
|
+
Create.new(clause_list, props).eval_context
|
53
|
+
end
|
54
|
+
|
55
|
+
def [](p)
|
56
|
+
property = Property.new(clause, p)
|
57
|
+
property.match_value = clause.var_name
|
58
|
+
property.eval_context
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Cypher
|
3
|
+
|
4
|
+
class Operand
|
5
|
+
attr_reader :obj
|
6
|
+
|
7
|
+
def initialize(obj)
|
8
|
+
@obj = obj.respond_to?(:clause) ? obj.clause : obj
|
9
|
+
end
|
10
|
+
|
11
|
+
def regexp?
|
12
|
+
@obj.kind_of?(Regexp)
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
if @obj.is_a?(String)
|
17
|
+
%Q["#{@obj}"]
|
18
|
+
elsif @obj.is_a?(Operator)
|
19
|
+
"(#{@obj.to_s})"
|
20
|
+
elsif @obj.is_a?(MatchStart)
|
21
|
+
"(#{@obj.to_cypher})"
|
22
|
+
elsif @obj.respond_to?(:expr) && @obj.expr
|
23
|
+
@obj.expr
|
24
|
+
elsif @obj.respond_to?(:source)
|
25
|
+
"'#{@obj.source}'"
|
26
|
+
elsif @obj.respond_to?(:return_value)
|
27
|
+
@obj.return_value.to_s
|
28
|
+
else
|
29
|
+
@obj.to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Operator
|
36
|
+
attr_reader :left_operand, :right_operand, :op, :neg, :eval_context
|
37
|
+
include Clause
|
38
|
+
include Referenceable
|
39
|
+
|
40
|
+
def initialize(clause_list, left_operand, right_operand, op, clause_type = :where, post_fix = nil, &dsl)
|
41
|
+
super(clause_list, clause_type, EvalContext)
|
42
|
+
right_operand = Regexp.new(right_operand) if op == '=~' && right_operand.is_a?(String)
|
43
|
+
@left_operand = Operand.new(left_operand)
|
44
|
+
raise "No Leftoperatnd #{left_operand.class}" unless @left_operand.obj
|
45
|
+
@right_operand = Operand.new(right_operand) if right_operand
|
46
|
+
@op = (@right_operand && @right_operand.regexp?) ? '=~' : op
|
47
|
+
@post_fix = post_fix
|
48
|
+
@valid = true
|
49
|
+
|
50
|
+
# since we handle it our self in to_cypher method
|
51
|
+
clause_list.delete(left_operand) if left_operand.kind_of?(Clause)
|
52
|
+
clause_list.delete(right_operand) if right_operand.kind_of?(Clause)
|
53
|
+
|
54
|
+
@neg = nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def separator
|
58
|
+
" and "
|
59
|
+
end
|
60
|
+
|
61
|
+
def match_value
|
62
|
+
@left_operand.obj.match_value || expr
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def var_name
|
67
|
+
@left_operand.obj.var_name
|
68
|
+
end
|
69
|
+
|
70
|
+
def return_value
|
71
|
+
(@right_operand || @unary) ? @left_operand.obj.var_name : to_cypher
|
72
|
+
end
|
73
|
+
|
74
|
+
def not
|
75
|
+
@neg = "not"
|
76
|
+
end
|
77
|
+
|
78
|
+
def unary!
|
79
|
+
@unary = true # TODO needed ?
|
80
|
+
eval_context
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_s
|
84
|
+
to_cypher
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_cypher
|
88
|
+
if @right_operand
|
89
|
+
neg ? "#{neg}(#{@left_operand.to_s} #{op} #{@right_operand.to_s})" : "#{@left_operand.to_s} #{op} #{@right_operand.to_s}"
|
90
|
+
else
|
91
|
+
left_p, right_p = @left_operand.to_s[0..0] == '(' ? ['', ''] : ['(', ')']
|
92
|
+
# binary operator
|
93
|
+
neg ? "#{neg}#{op}(#{@left_operand.to_s}#{@post_fix})" : "#{op}#{left_p}#{@left_operand.to_s}#{@post_fix}#{right_p}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
class EvalContext
|
99
|
+
include Context
|
100
|
+
include MathFunctions
|
101
|
+
|
102
|
+
def &(other)
|
103
|
+
Operator.new(clause.clause_list, clause, other.clause, "and").eval_context
|
104
|
+
end
|
105
|
+
|
106
|
+
def |(other)
|
107
|
+
Operator.new(clause.clause_list, clause, other.clause, "or").eval_context
|
108
|
+
end
|
109
|
+
|
110
|
+
def not
|
111
|
+
clause.not
|
112
|
+
self
|
113
|
+
end
|
114
|
+
|
115
|
+
# Only in 1.9
|
116
|
+
if RUBY_VERSION > "1.9.0"
|
117
|
+
eval %{
|
118
|
+
def !
|
119
|
+
clause.not
|
120
|
+
self
|
121
|
+
end
|
122
|
+
}
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Cypher
|
3
|
+
class Predicate
|
4
|
+
include Clause
|
5
|
+
include Referenceable
|
6
|
+
attr_accessor :params
|
7
|
+
|
8
|
+
def initialize(clause_list, params)
|
9
|
+
super(clause_list, params[:clause])
|
10
|
+
@identifier = :x
|
11
|
+
@separator = params[:separator] || ','
|
12
|
+
params[:input].referenced! if params[:input].respond_to?(:referenced!)
|
13
|
+
|
14
|
+
clause_list.push
|
15
|
+
|
16
|
+
var = NodeVar.as_var(clause_list, @identifier)
|
17
|
+
|
18
|
+
input = params[:input]
|
19
|
+
|
20
|
+
# TODO refactor please
|
21
|
+
if input.kind_of?(Property)
|
22
|
+
eval_prop = Property.new(var)
|
23
|
+
eval_prop.expr = @identifier
|
24
|
+
yield_param = eval_prop.eval_context
|
25
|
+
args = ""
|
26
|
+
else
|
27
|
+
yield_param = var.eval_context
|
28
|
+
args = "(#{input.var_name})"
|
29
|
+
end
|
30
|
+
|
31
|
+
result = RootClause::EvalContext.new(self).instance_exec(yield_param, ¶ms[:predicate_block])
|
32
|
+
|
33
|
+
result = case params[:clause]
|
34
|
+
when :return_item
|
35
|
+
block_result = result.clause.to_cypher
|
36
|
+
"#{params[:op]}(#@identifier in #{params[:iterable]}#{args} : #{block_result})"
|
37
|
+
when :foreach
|
38
|
+
block_result = clause_list.to_cypher
|
39
|
+
"#{params[:op]}(#@identifier in #{params[:iterable]}#{args} : #{block_result})"
|
40
|
+
else
|
41
|
+
block_result = clause_list.to_cypher
|
42
|
+
"#{params[:op]}(#@identifier in #{params[:iterable]}#{args} WHERE #{block_result})"
|
43
|
+
end
|
44
|
+
|
45
|
+
clause_list.pop
|
46
|
+
@result = result
|
47
|
+
end
|
48
|
+
|
49
|
+
def return_value
|
50
|
+
to_cypher
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
def separator
|
55
|
+
@separator
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_cypher
|
59
|
+
@result
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Cypher
|
3
|
+
|
4
|
+
# A property is returned from a Variable by using the [] operator.
|
5
|
+
#
|
6
|
+
# It has a number of useful method like
|
7
|
+
# <tt>count</tt>, <tt>sum</tt>, <tt>avg</tt>, <tt>min</tt>, <tt>max</tt>, <tt>collect</tt>, <tt>head</tt>, <tt>last</tt>, <tt>tail</tt>,
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# n=node(2, 3, 4); n[:name].collect
|
11
|
+
# # same as START n0=node(2,3,4) RETURN collect(n0.property)
|
12
|
+
class Property
|
13
|
+
include Referenceable
|
14
|
+
include Clause
|
15
|
+
|
16
|
+
def initialize(var, prop_name = nil)
|
17
|
+
super(var.clause_list, :property, EvalContext)
|
18
|
+
@var = var
|
19
|
+
self.var_name = var.var_name
|
20
|
+
@prop_name = prop_name
|
21
|
+
@expr = prop_name ? "#{var.var_name}.#{prop_name}" : var.var_name.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
# @private
|
26
|
+
def to_function!(prop_name = nil)
|
27
|
+
@expr = "#{prop_name || @prop_name}(#{var_name})"
|
28
|
+
eval_context
|
29
|
+
end
|
30
|
+
|
31
|
+
def return_value
|
32
|
+
to_cypher
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def match_value
|
37
|
+
@var.match_value
|
38
|
+
end
|
39
|
+
|
40
|
+
def unary_operator(op, clause_type = :where, post_fix = nil)
|
41
|
+
# TODO DELETE THIS ?
|
42
|
+
Operator.new(clause_list, self, nil, op, clause_type, post_fix).unary!
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def to_cypher
|
47
|
+
@expr
|
48
|
+
end
|
49
|
+
|
50
|
+
class EvalContext
|
51
|
+
include Context
|
52
|
+
include Alias
|
53
|
+
include Comparable
|
54
|
+
include MathOperator
|
55
|
+
include MathFunctions
|
56
|
+
include PredicateMethods
|
57
|
+
include Aggregate
|
58
|
+
|
59
|
+
def asc
|
60
|
+
ReturnItem.new(clause_list, self).eval_context.asc
|
61
|
+
end
|
62
|
+
|
63
|
+
def desc
|
64
|
+
ReturnItem.new(clause_list, self).eval_context.desc
|
65
|
+
end
|
66
|
+
|
67
|
+
def where(&block)
|
68
|
+
x = block.call(self)
|
69
|
+
clause_list.delete(x)
|
70
|
+
Operator.new(clause_list, x.clause, nil, "").unary!
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
def where_not(&block)
|
75
|
+
x = block.call(self)
|
76
|
+
clause_list.delete(x)
|
77
|
+
Operator.new(clause_list, x.clause, nil, "not").unary!
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
# required by the Predicate Methods Module
|
82
|
+
# @see PredicateMethods
|
83
|
+
# @private
|
84
|
+
def iterable
|
85
|
+
clause.return_value
|
86
|
+
end
|
87
|
+
|
88
|
+
def input
|
89
|
+
clause
|
90
|
+
end
|
91
|
+
|
92
|
+
# @private
|
93
|
+
def in?(values)
|
94
|
+
clause.unary_operator("", :where, " IN [#{values.map { |x| %Q["#{x}"] }.join(',')}]")
|
95
|
+
end
|
96
|
+
|
97
|
+
def length
|
98
|
+
clause.to_function!('length')
|
99
|
+
self
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Cypher
|
3
|
+
|
4
|
+
class RelVar
|
5
|
+
include Clause
|
6
|
+
include ToPropString
|
7
|
+
include Referenceable
|
8
|
+
|
9
|
+
def initialize(clause_list, expr, props = nil)
|
10
|
+
super(clause_list, :rel_var, EvalContext)
|
11
|
+
|
12
|
+
case expr
|
13
|
+
when String
|
14
|
+
@match_value = expr.empty? ? '?' : expr.to_s
|
15
|
+
guess = expr.is_a?(String) && /([[:alpha:]_]*)/.match(expr)[1]
|
16
|
+
self.var_name = guess.to_sym if guess && !guess.empty?
|
17
|
+
when Symbol
|
18
|
+
@match_value = ":`#{expr}`"
|
19
|
+
else
|
20
|
+
raise "Illegal arg for rel #{expr.class}"
|
21
|
+
end
|
22
|
+
|
23
|
+
@match_value = "#@match_value #{to_prop_string(props)}" if props
|
24
|
+
end
|
25
|
+
|
26
|
+
def rel_type
|
27
|
+
@match_value.include?(':') ? @match_value.split(':').last : @match_value.sub('?', '')
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.join(clause_list, rel_types)
|
31
|
+
rel_string = rel_types.map { |r| _rel_to_string(clause_list, r) }.join('|')
|
32
|
+
|
33
|
+
if rel_string.empty?
|
34
|
+
RelVar.new(clause_list, "")
|
35
|
+
else
|
36
|
+
RelVar.new(clause_list, ":#{rel_string}")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self._rel_to_string(clause_list, rel_or_symbol)
|
41
|
+
case rel_or_symbol
|
42
|
+
when String, Symbol
|
43
|
+
RelVar.new(clause_list, rel_or_symbol).rel_type
|
44
|
+
when Neo4j::Cypher::RelVar::EvalContext
|
45
|
+
rel_or_symbol.clause.rel_type
|
46
|
+
else
|
47
|
+
raise "Unknown type of relationship, got #{rel_or_symbol.class}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def referenced!
|
52
|
+
eval_context.as(var_name) unless referenced?
|
53
|
+
super
|
54
|
+
end
|
55
|
+
|
56
|
+
def return_value
|
57
|
+
var_name
|
58
|
+
end
|
59
|
+
|
60
|
+
def optionally!
|
61
|
+
if @match_value.include?('?')
|
62
|
+
# We are done
|
63
|
+
elsif @match_value.include?(':')
|
64
|
+
@match_value.sub!(/:/, "?:")
|
65
|
+
else
|
66
|
+
@match_value += '?'
|
67
|
+
end
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
class EvalContext
|
72
|
+
include Context
|
73
|
+
include Variable
|
74
|
+
include Returnable
|
75
|
+
include Aggregate
|
76
|
+
include Alias
|
77
|
+
|
78
|
+
def rel_type
|
79
|
+
Property.new(clause, 'type').to_function!
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
def where(&block)
|
84
|
+
x = block.call(self)
|
85
|
+
clause_list.delete(x)
|
86
|
+
Operator.new(clause_list, x.clause, nil, "").unary!
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
def where_not(&block)
|
91
|
+
x = block.call(self)
|
92
|
+
clause_list.delete(x)
|
93
|
+
Operator.new(clause_list, x.clause, nil, "not").unary!
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
# generates a <tt>is null</tt> cypher fragment.
|
98
|
+
def null
|
99
|
+
clause.referenced!
|
100
|
+
Operator.new(clause_list, self, nil, '', :where, " is null").unary!
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
def [](p)
|
105
|
+
# TODO
|
106
|
+
clause.referenced!
|
107
|
+
property = super
|
108
|
+
property.clause.match_value = clause.expr
|
109
|
+
property
|
110
|
+
end
|
111
|
+
|
112
|
+
def as(name) # TODO DRY
|
113
|
+
super
|
114
|
+
super.tap do
|
115
|
+
if clause.match_value == '?'
|
116
|
+
clause.match_value = "#{clause.var_name}?"
|
117
|
+
elsif clause.match_value.include?(':') || clause.match_value.include?('?')
|
118
|
+
clause.match_value = clause.match_value.sub(/[^:\?]*/, clause.var_name.to_s)
|
119
|
+
else
|
120
|
+
clause.match_value = clause.var_name.to_s
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
end
|