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 ADDED
@@ -0,0 +1,15 @@
1
+ source :gemcutter
2
+
3
+ gemspec
4
+
5
+ group 'development' do
6
+ gem 'pry'
7
+ gem 'simplecov'
8
+ end
9
+
10
+ group 'test' do
11
+ gem "rake", ">= 0.8.7"
12
+ gem "rspec"
13
+ gem "its" # its(:with, :arguments) { should be_possible }
14
+ end
15
+
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
@@ -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