neo4j-cypher 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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, &params[: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