neo4j-cypher 1.0.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|